summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugenia Grieff <egrieff@gitlab.com>2019-09-04 09:52:06 +0100
committerEugenia Grieff <egrieff@gitlab.com>2019-09-04 09:52:06 +0100
commita3b4d920ba7791739300a9dcde0d18897b13afcb (patch)
tree52433c6cb061d1999ec8e40546718ea182593ba2
parent9039a937661ea7190d1615b9b0141504afbb75dc (diff)
parentfb9e8646bdb9f3f1382a86da890ff509f1b4549c (diff)
downloadgitlab-ce-60372-milestone-link-prevent-delete-issue-after-move-it-to-another-project-2.tar.gz
Merge branch 'master' into 60372-milestone-link-prevent-delete-issue-after-move-it-to-another-project-260372-milestone-link-prevent-delete-issue-after-move-it-to-another-project-2
-rw-r--r--.gitignore3
-rw-r--r--.gitlab-ci.yml1
-rw-r--r--.gitlab/ci/docs.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/ee-specific-checks.gitlab-ci.yml22
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml38
-rw-r--r--.gitlab/ci/pages.gitlab-ci.yml4
-rw-r--r--.gitlab/ci/qa.gitlab-ci.yml36
-rw-r--r--.gitlab/ci/reports.gitlab-ci.yml4
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml16
-rw-r--r--.overcommit.yml.example28
-rw-r--r--.rubocop.yml2
-rw-r--r--.rubocop_todo.yml5
-rw-r--r--CHANGELOG.md77
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_ELASTICSEARCH_INDEXER_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile8
-rw-r--r--Gemfile.lock22
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_math.js146
-rw-r--r--app/assets/javascripts/boards/components/sidebar/remove_issue.vue8
-rw-r--r--app/assets/javascripts/boards/models/issue.js4
-rw-r--r--app/assets/javascripts/branches/divergence_graph.js5
-rw-r--r--app/assets/javascripts/clusters/stores/clusters_store.js2
-rw-r--r--app/assets/javascripts/droplab/drop_lab.js4
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue20
-rw-r--r--app/assets/javascripts/flash.js21
-rw-r--r--app/assets/javascripts/groups/components/app.vue7
-rw-r--r--app/assets/javascripts/groups/service/groups_service.js21
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/actions.vue6
-rw-r--r--app/assets/javascripts/ide/components/error_message.vue2
-rw-r--r--app/assets/javascripts/ide/components/file_row_extra.vue2
-rw-r--r--app/assets/javascripts/ide/components/panes/right.vue3
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue4
-rw-r--r--app/assets/javascripts/issue_show/components/edit_actions.vue2
-rw-r--r--app/assets/javascripts/issue_show/services/index.js4
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue6
-rw-r--r--app/assets/javascripts/jobs/components/job_log_json.vue10
-rw-r--r--app/assets/javascripts/jobs/store/utils.js40
-rw-r--r--app/assets/javascripts/monitoring/components/charts/area.vue304
-rw-r--r--app/assets/javascripts/pages/profiles/two_factor_auths/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue7
-rw-r--r--app/assets/javascripts/project_find_file.js2
-rw-r--r--app/assets/javascripts/releases/components/release_block.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/constants.js8
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js6
-rw-r--r--app/assets/javascripts/vue_shared/components/changed_file_icon.vue4
-rw-r--r--app/assets/stylesheets/framework/flash.scss47
-rw-r--r--app/assets/stylesheets/framework/forms.scss24
-rw-r--r--app/assets/stylesheets/framework/layout.scss9
-rw-r--r--app/assets/stylesheets/framework/typography.scss12
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/pages/search.scss3
-rw-r--r--app/controllers/admin/groups_controller.rb6
-rw-r--r--app/controllers/application_controller.rb6
-rw-r--r--app/controllers/boards/lists_controller.rb24
-rw-r--r--app/controllers/concerns/issuable_actions.rb30
-rw-r--r--app/controllers/concerns/lfs_request.rb2
-rw-r--r--app/controllers/concerns/notes_actions.rb2
-rw-r--r--app/controllers/concerns/uploads_actions.rb4
-rw-r--r--app/controllers/concerns/with_performance_bar.rb16
-rw-r--r--app/controllers/groups/runners_controller.rb6
-rw-r--r--app/controllers/projects/jobs_controller.rb15
-rw-r--r--app/controllers/projects/lfs_api_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb16
-rw-r--r--app/controllers/sessions_controller.rb50
-rw-r--r--app/controllers/uploads_controller.rb6
-rw-r--r--app/finders/members_finder.rb35
-rw-r--r--app/helpers/application_settings_helper.rb5
-rw-r--r--app/helpers/avatars_helper.rb9
-rw-r--r--app/helpers/emails_helper.rb4
-rw-r--r--app/helpers/labels_helper.rb2
-rw-r--r--app/helpers/performance_bar_helper.rb4
-rw-r--r--app/helpers/projects_helper.rb2
-rw-r--r--app/helpers/search_helper.rb41
-rw-r--r--app/mailers/emails/issues.rb4
-rw-r--r--app/mailers/emails/notes.rb25
-rw-r--r--app/models/application_setting.rb41
-rw-r--r--app/models/application_setting_implementation.rb28
-rw-r--r--app/models/award_emoji.rb4
-rw-r--r--app/models/blob_viewer/base.rb2
-rw-r--r--app/models/board.rb1
-rw-r--r--app/models/broadcast_message.rb2
-rw-r--r--app/models/ci/build.rb17
-rw-r--r--app/models/ci/build_runner_session.rb2
-rw-r--r--app/models/ci/pipeline.rb32
-rw-r--r--app/models/ci/runner.rb3
-rw-r--r--app/models/clusters/applications/cert_manager.rb33
-rw-r--r--app/models/clusters/applications/ingress.rb2
-rw-r--r--app/models/clusters/applications/jupyter.rb2
-rw-r--r--app/models/clusters/applications/knative.rb6
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/models/clusters/cluster.rb4
-rw-r--r--app/models/concerns/cacheable_attributes.rb2
-rw-r--r--app/models/concerns/has_status.rb2
-rw-r--r--app/models/concerns/ignorable_column.rb30
-rw-r--r--app/models/concerns/issuable.rb5
-rw-r--r--app/models/concerns/protected_ref_access.rb6
-rw-r--r--app/models/concerns/routable.rb26
-rw-r--r--app/models/concerns/taskable.rb4
-rw-r--r--app/models/deploy_key.rb3
-rw-r--r--app/models/deploy_token.rb2
-rw-r--r--app/models/diff_viewer/base.rb2
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/gpg_key.rb4
-rw-r--r--app/models/group.rb8
-rw-r--r--app/models/instance_configuration.rb4
-rw-r--r--app/models/issue.rb2
-rw-r--r--app/models/label.rb8
-rw-r--r--app/models/list.rb54
-rw-r--r--app/models/list_user_preference.rb10
-rw-r--r--app/models/member.rb2
-rw-r--r--app/models/members/group_member.rb2
-rw-r--r--app/models/members/project_member.rb2
-rw-r--r--app/models/merge_request.rb4
-rw-r--r--app/models/merge_request_diff.rb1
-rw-r--r--app/models/milestone.rb8
-rw-r--r--app/models/milestone_release.rb17
-rw-r--r--app/models/namespace/aggregation_schedule.rb2
-rw-r--r--app/models/note.rb9
-rw-r--r--app/models/notification_reason.rb6
-rw-r--r--app/models/notification_setting.rb4
-rw-r--r--app/models/pages_domain.rb2
-rw-r--r--app/models/project.rb12
-rw-r--r--app/models/project_services/buildkite_service.rb2
-rw-r--r--app/models/project_services/chat_message/base_message.rb2
-rw-r--r--app/models/project_services/chat_message/push_message.rb8
-rw-r--r--app/models/project_services/chat_notification_service.rb19
-rw-r--r--app/models/project_services/jira_service.rb7
-rw-r--r--app/models/project_services/microsoft_teams_service.rb2
-rw-r--r--app/models/project_services/pivotaltracker_service.rb2
-rw-r--r--app/models/project_services/pushover_service.rb2
-rw-r--r--app/models/project_services/slash_commands_service.rb4
-rw-r--r--app/models/release.rb7
-rw-r--r--app/models/remote_mirror.rb1
-rw-r--r--app/models/repository.rb22
-rw-r--r--app/models/service.rb8
-rw-r--r--app/models/system_note_metadata.rb2
-rw-r--r--app/models/todo.rb1
-rw-r--r--app/models/user.rb20
-rw-r--r--app/models/user_status.rb2
-rw-r--r--app/policies/issue_policy.rb9
-rw-r--r--app/policies/merge_request_policy.rb6
-rw-r--r--app/presenters/blob_presenter.rb18
-rw-r--r--app/presenters/blobs/unfold_presenter.rb8
-rw-r--r--app/presenters/ci/pipeline_presenter.rb12
-rw-r--r--app/presenters/clusterable_presenter.rb5
-rw-r--r--app/serializers/deployment_entity.rb2
-rw-r--r--app/serializers/entity_date_helper.rb12
-rw-r--r--app/serializers/merge_request_noteable_entity.rb53
-rw-r--r--app/serializers/merge_request_poll_widget_entity.rb10
-rw-r--r--app/serializers/merge_request_serializer.rb2
-rw-r--r--app/serializers/merge_request_widget_entity.rb12
-rw-r--r--app/serializers/pipeline_entity.rb16
-rw-r--r--app/services/application_settings/update_service.rb15
-rw-r--r--app/services/auth/container_registry_authentication_service.rb10
-rw-r--r--app/services/base_service.rb4
-rw-r--r--app/services/boards/lists/list_service.rb2
-rw-r--r--app/services/boards/lists/update_service.rb56
-rw-r--r--app/services/chat_names/authorize_user_service.rb10
-rw-r--r--app/services/ci/create_pipeline_service.rb3
-rw-r--r--app/services/clusters/applications/check_installation_progress_service.rb35
-rw-r--r--app/services/clusters/applications/check_progress_service.rb50
-rw-r--r--app/services/clusters/applications/check_uninstall_progress_service.rb33
-rw-r--r--app/services/clusters/create_service.rb2
-rw-r--r--app/services/clusters/gcp/finalize_creation_service.rb8
-rw-r--r--app/services/clusters/gcp/kubernetes.rb16
-rw-r--r--app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb47
-rw-r--r--app/services/clusters/gcp/kubernetes/create_or_update_service_account_service.rb141
-rw-r--r--app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb42
-rw-r--r--app/services/clusters/kubernetes/create_or_update_namespace_service.rb45
-rw-r--r--app/services/clusters/kubernetes/create_or_update_service_account_service.rb139
-rw-r--r--app/services/clusters/kubernetes/fetch_kubernetes_token_service.rb40
-rw-r--r--app/services/clusters/kubernetes/kubernetes.rb14
-rw-r--r--app/services/create_snippet_service.rb2
-rw-r--r--app/services/event_create_service.rb13
-rw-r--r--app/services/groups/create_service.rb4
-rw-r--r--app/services/notification_service.rb17
-rw-r--r--app/services/projects/create_service.rb27
-rw-r--r--app/services/quick_actions/interpret_service.rb2
-rw-r--r--app/services/releases/concerns.rb21
-rw-r--r--app/services/releases/create_service.rb4
-rw-r--r--app/services/releases/update_service.rb3
-rw-r--r--app/services/system_hooks_service.rb2
-rw-r--r--app/services/system_note_service.rb10
-rw-r--r--app/services/todo_service.rb6
-rw-r--r--app/services/update_snippet_service.rb2
-rw-r--r--app/uploaders/personal_file_uploader.rb4
-rw-r--r--app/views/admin/application_settings/_email.html.haml2
-rw-r--r--app/views/admin/application_settings/_spam.html.haml13
-rw-r--r--app/views/admin/application_settings/preferences.html.haml2
-rw-r--r--app/views/admin/application_settings/reporting.html.haml4
-rw-r--r--app/views/admin/background_jobs/show.html.haml12
-rw-r--r--app/views/admin/dashboard/index.html.haml316
-rw-r--r--app/views/admin/groups/index.html.haml32
-rw-r--r--app/views/admin/health_check/show.html.haml74
-rw-r--r--app/views/admin/jobs/index.html.haml31
-rw-r--r--app/views/admin/logs/show.html.haml46
-rw-r--r--app/views/admin/projects/index.html.haml73
-rw-r--r--app/views/admin/requests_profiles/index.html.haml42
-rw-r--r--app/views/admin/runners/index.html.haml244
-rw-r--r--app/views/admin/system_info/show.html.haml68
-rw-r--r--app/views/admin/users/index.html.haml140
-rw-r--r--app/views/clusters/clusters/user/_form.html.haml1
-rw-r--r--app/views/clusters/platforms/kubernetes/_form.html.haml2
-rw-r--r--app/views/dashboard/activity.html.haml11
-rw-r--r--app/views/dashboard/projects/index.html.haml16
-rw-r--r--app/views/dashboard/projects/starred.html.haml14
-rw-r--r--app/views/devise/sessions/_new_base.html.haml2
-rw-r--r--app/views/groups/_group_admin_settings.html.haml2
-rw-r--r--app/views/groups/labels/index.html.haml34
-rw-r--r--app/views/groups/milestones/new.html.haml10
-rw-r--r--app/views/groups/settings/_permissions.html.haml2
-rw-r--r--app/views/groups/show.html.haml3
-rw-r--r--app/views/instance_statistics/cohorts/index.html.haml26
-rw-r--r--app/views/instance_statistics/conversational_development_index/index.html.haml1
-rw-r--r--app/views/layouts/_flash.html.haml10
-rw-r--r--app/views/layouts/_page.html.haml2
-rw-r--r--app/views/layouts/_search.html.haml5
-rw-r--r--app/views/layouts/devise.html.haml3
-rw-r--r--app/views/layouts/devise_empty.html.haml3
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml2
-rw-r--r--app/views/notify/new_issue_email.text.erb10
-rw-r--r--app/views/notify/new_merge_request_email.text.erb4
-rw-r--r--app/views/peek/_bar.html.haml4
-rw-r--r--app/views/projects/_merge_request_merge_checks_settings.html.haml2
-rw-r--r--app/views/projects/_merge_request_merge_method_settings.html.haml6
-rw-r--r--app/views/projects/activity.html.haml6
-rw-r--r--app/views/projects/blame/show.html.haml78
-rw-r--r--app/views/projects/blob/edit.html.haml50
-rw-r--r--app/views/projects/blob/show.html.haml19
-rw-r--r--app/views/projects/branches/index.html.haml108
-rw-r--r--app/views/projects/commit/_commit_box.html.haml2
-rw-r--r--app/views/projects/commit/show.html.haml1
-rw-r--r--app/views/projects/commits/show.html.haml50
-rw-r--r--app/views/projects/compare/index.html.haml26
-rw-r--r--app/views/projects/compare/show.html.haml40
-rw-r--r--app/views/projects/cycle_analytics/show.html.haml3
-rw-r--r--app/views/projects/deployments/_deployment.html.haml3
-rw-r--r--app/views/projects/empty.html.haml3
-rw-r--r--app/views/projects/environments/edit.html.haml10
-rw-r--r--app/views/projects/environments/folder.html.haml4
-rw-r--r--app/views/projects/environments/index.html.haml4
-rw-r--r--app/views/projects/environments/metrics.html.haml3
-rw-r--r--app/views/projects/environments/new.html.haml10
-rw-r--r--app/views/projects/environments/show.html.haml120
-rw-r--r--app/views/projects/environments/terminal.html.haml26
-rw-r--r--app/views/projects/graphs/charts.html.haml5
-rw-r--r--app/views/projects/graphs/show.html.haml3
-rw-r--r--app/views/projects/imports/show.html.haml1
-rw-r--r--app/views/projects/issues/index.html.haml22
-rw-r--r--app/views/projects/jobs/index.html.haml24
-rw-r--r--app/views/projects/jobs/show.html.haml16
-rw-r--r--app/views/projects/jobs/terminal.html.haml3
-rw-r--r--app/views/projects/labels/edit.html.haml10
-rw-r--r--app/views/projects/labels/index.html.haml78
-rw-r--r--app/views/projects/labels/new.html.haml10
-rw-r--r--app/views/projects/merge_requests/index.html.haml27
-rw-r--r--app/views/projects/merge_requests/show.html.haml13
-rw-r--r--app/views/projects/milestones/_deprecation_message.html.haml7
-rw-r--r--app/views/projects/milestones/_form.html.haml2
-rw-r--r--app/views/projects/milestones/edit.html.haml12
-rw-r--r--app/views/projects/milestones/index.html.haml36
-rw-r--r--app/views/projects/milestones/new.html.haml10
-rw-r--r--app/views/projects/milestones/show.html.haml111
-rw-r--r--app/views/projects/mirrors/_mirror_repos.html.haml2
-rw-r--r--app/views/projects/network/_head.html.haml13
-rw-r--r--app/views/projects/pages/_access.html.haml4
-rw-r--r--app/views/projects/pipeline_schedules/index.html.haml30
-rw-r--r--app/views/projects/pipelines/charts.html.haml12
-rw-r--r--app/views/projects/pipelines/index.html.haml24
-rw-r--r--app/views/projects/pipelines/show.html.haml3
-rw-r--r--app/views/projects/project_templates/_built_in_templates.html.haml4
-rw-r--r--app/views/projects/releases/index.html.haml4
-rw-r--r--app/views/projects/serverless/functions/index.html.haml3
-rw-r--r--app/views/projects/serverless/functions/show.html.haml3
-rw-r--r--app/views/projects/services/_form.html.haml2
-rw-r--r--app/views/projects/services/_index.html.haml12
-rw-r--r--app/views/projects/services/edit.html.haml6
-rw-r--r--app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml28
-rw-r--r--app/views/projects/services/mattermost_slash_commands/_help.html.haml8
-rw-r--r--app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml2
-rw-r--r--app/views/projects/services/slack_slash_commands/_help.html.haml44
-rw-r--r--app/views/projects/settings/ci_cd/_autodevops_form.html.haml18
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml4
-rw-r--r--app/views/projects/show.html.haml3
-rw-r--r--app/views/projects/tags/index.html.haml3
-rw-r--r--app/views/projects/tags/releases/edit.html.haml31
-rw-r--r--app/views/projects/tags/show.html.haml74
-rw-r--r--app/views/projects/tree/show.html.haml6
-rw-r--r--app/views/projects/wikis/pages.html.haml44
-rw-r--r--app/views/shared/empty_states/_priority_labels.html.haml4
-rw-r--r--app/views/shared/empty_states/_profile_tabs.html.haml2
-rw-r--r--app/views/shared/issuable/_close_reopen_button.html.haml2
-rw-r--r--app/views/shared/issuable/_form.html.haml2
-rw-r--r--app/views/shared/issuable/_nav.html.haml2
-rw-r--r--app/views/users/show.html.haml2
-rwxr-xr-xbin/rspec-stackprof2
-rw-r--r--changelogs/unreleased/28643-access-request-emails-limit-to-ten-owners.yml5
-rw-r--r--changelogs/unreleased/36383-improve-search-result-labels.yml5
-rw-r--r--changelogs/unreleased/36765-flash-notification.yml5
-rw-r--r--changelogs/unreleased/40096-allow-ci-token-to-delete-from-registry.yml5
-rw-r--r--changelogs/unreleased/51372-remove-milestone-tabs-deprecation-message.yml5
-rw-r--r--changelogs/unreleased/56130-operations-environments-shows-incorrect-deployment-date-for-manual-.yml6
-rw-r--r--changelogs/unreleased/56295-some-avatars-not-visible-in-commit-trailers.yml5
-rw-r--r--changelogs/unreleased/60561-quick-action-label-first-for-issue.yml5
-rw-r--r--changelogs/unreleased/62055-find-file-links-encoding.yml5
-rw-r--r--changelogs/unreleased/62284-follow-up-from-resolve-api-to-get-all-project-group-members-returns-duplicates.yml5
-rw-r--r--changelogs/unreleased/62402-milestone-release-be.yml5
-rw-r--r--changelogs/unreleased/62591-fix-milestone-due-date-today-wording.yml5
-rw-r--r--changelogs/unreleased/62673-clean-note-app-tests.yml5
-rw-r--r--changelogs/unreleased/63262-notes-are-persisted-with-the-user-s-locale.yml5
-rw-r--r--changelogs/unreleased/64251-branch-name-set-cache.yml5
-rw-r--r--changelogs/unreleased/65063-Embeded-metrics-inconsistent-styles.yml5
-rw-r--r--changelogs/unreleased/65152-unfolded-lines-perf-improvement.yml5
-rw-r--r--changelogs/unreleased/65251-default-clusters-namespace_per_environment-column-to-true.yml5
-rw-r--r--changelogs/unreleased/65304-add-pages-first-deployment-message.yml5
-rw-r--r--changelogs/unreleased/65389-wrong-format-on-ms-teams-integration-push-events-with-multi-line-commit-messages.yml5
-rw-r--r--changelogs/unreleased/66066-dark-theme-style-for-expansion-on-mr-diffs.yml5
-rw-r--r--changelogs/unreleased/66264-moved-issue-reference.yml5
-rw-r--r--changelogs/unreleased/66454-utils-parser.yml5
-rw-r--r--changelogs/unreleased/66524-issue-due-notification-emails-are-threaded-incorrectly.yml5
-rw-r--r--changelogs/unreleased/66715-delete-search-animation.yml5
-rw-r--r--changelogs/unreleased/ab-admin-page-user-active-count.yml5
-rw-r--r--changelogs/unreleased/ab-remove-support-bot-column.yml5
-rw-r--r--changelogs/unreleased/ab-routable-nplus1.yml5
-rw-r--r--changelogs/unreleased/add-notification-reason-to-note-emails.yml5
-rw-r--r--changelogs/unreleased/add-warning-note-to-project-container-registry-setting.yml6
-rw-r--r--changelogs/unreleased/ce-60465-prevent-comments-on-private-mrs.yml3
-rw-r--r--changelogs/unreleased/ce-slack-close-command.yml5
-rw-r--r--changelogs/unreleased/ce-xanf-move-auto-merge-failed-to-jest.yml5
-rw-r--r--changelogs/unreleased/cert_manager_v0_9.yml5
-rw-r--r--changelogs/unreleased/change-prioritized-labels-empty-state-message.yml5
-rw-r--r--changelogs/unreleased/change-role-system-hook.yml5
-rw-r--r--changelogs/unreleased/cluster-form-ca-cert-larger.yml5
-rw-r--r--changelogs/unreleased/cluster_deployments.yml5
-rw-r--r--changelogs/unreleased/fe-fix-issuable-sidebar-icon-of-notification-disabled.yml5
-rw-r--r--changelogs/unreleased/fix-create-milestone-btn-success.yml5
-rw-r--r--changelogs/unreleased/fix-dropdown-closing.yml5
-rw-r--r--changelogs/unreleased/fix-nil-deployable-exception-on-job-controller-show.yml5
-rw-r--r--changelogs/unreleased/fix-peek-on-puma.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-13698-override-params.yml5
-rw-r--r--changelogs/unreleased/handle-invalid-mirror-url.yml5
-rw-r--r--changelogs/unreleased/id-optimize-sql-requests-mr-show.yml5
-rw-r--r--changelogs/unreleased/improve-chatops-help.yml5
-rw-r--r--changelogs/unreleased/issue_10770.yml5
-rw-r--r--changelogs/unreleased/issue_40630.yml5
-rw-r--r--changelogs/unreleased/je-add-cluster-domain-warning.yml5
-rw-r--r--changelogs/unreleased/new-project-milestone-primary-button.yml5
-rw-r--r--changelogs/unreleased/refactor-showStagedIcon.yml5
-rw-r--r--changelogs/unreleased/remove-vue-resource-from-group-service.yml5
-rw-r--r--changelogs/unreleased/remove-vue-resource-from-issue.yml5
-rw-r--r--changelogs/unreleased/remove-vue-resource-from-remove-issue.yml5
-rw-r--r--changelogs/unreleased/runner-chart-repo-use-new-location.yml5
-rw-r--r--changelogs/unreleased/security-59549-add-capcha-for-failed-logins.yml5
-rw-r--r--changelogs/unreleased/security-61974-limit-issue-comment-size-2.yml5
-rw-r--r--changelogs/unreleased/security-61974-limit-issue-comment-size.yml5
-rw-r--r--changelogs/unreleased/security-64711-fix-commit-todos.yml5
-rw-r--r--changelogs/unreleased/security-ci-metrics-permissions.yml6
-rw-r--r--changelogs/unreleased/security-enable-image-proxy.yml5
-rw-r--r--changelogs/unreleased/security-epic-notes-api-reveals-historical-info-ce-master.yml5
-rw-r--r--changelogs/unreleased/security-exposed-default-branch.yml5
-rw-r--r--changelogs/unreleased/security-fix-html-injection-for-label-description-ce-master.yml5
-rw-r--r--changelogs/unreleased/security-fix-markdown-xss.yml5
-rw-r--r--changelogs/unreleased/security-fix_jira_ssrf_vulnerability.yml5
-rw-r--r--changelogs/unreleased/security-gitaly-1-61-0.yml5
-rw-r--r--changelogs/unreleased/security-group-runners-permissions.yml5
-rw-r--r--changelogs/unreleased/security-hide_merge_request_ids_on_emails.yml5
-rw-r--r--changelogs/unreleased/security-id-filter-timeline-activities-for-guests.yml5
-rw-r--r--changelogs/unreleased/security-katex-dos-master.yml5
-rw-r--r--changelogs/unreleased/security-mr-head-pipeline-leak.yml5
-rw-r--r--changelogs/unreleased/security-personal-snippets.yml5
-rw-r--r--changelogs/unreleased/security-project-import-bypass.yml5
-rw-r--r--changelogs/unreleased/security-sarcila-fix-weak-session-management.yml6
-rw-r--r--changelogs/unreleased/security-ssrf-kubernetes-dns.yml5
-rw-r--r--changelogs/unreleased/sh-add-delete-confirmation.yml5
-rw-r--r--changelogs/unreleased/sh-fix-ci-lint-500-error.yml5
-rw-r--r--changelogs/unreleased/sh-fix-nplusone-issues.yml5
-rw-r--r--changelogs/unreleased/sh-fix-piwik-template.yml5
-rw-r--r--changelogs/unreleased/sh-support-content-for-snippets-api.yml5
-rw-r--r--changelogs/unreleased/sh-suppress-diverging-count-commits-request.yml5
-rw-r--r--changelogs/unreleased/sh-upgrade-mermaid-8-2-4.yml5
-rw-r--r--changelogs/unreleased/swagger-ui-ci-page-template.yml5
-rw-r--r--changelogs/unreleased/tc-cleanup-issue-created-text-mail.yml5
-rw-r--r--changelogs/unreleased/update-workhorse.yml5
-rw-r--r--config/dependency_decisions.yml7
-rw-r--r--config/gitlab.yml.example1
-rw-r--r--config/initializers/1_settings.rb2
-rw-r--r--config/initializers/asset_proxy_settings.rb6
-rw-r--r--config/initializers/fill_shards.rb4
-rw-r--r--config/initializers/peek.rb1
-rw-r--r--config/initializers/rest-client-hostname_override.rb49
-rw-r--r--config/initializers/warden.rb1
-rw-r--r--config/routes.rb1
-rw-r--r--config/routes/uploads.rb4
-rw-r--r--db/fixtures/development/17_cycle_analytics.rb6
-rw-r--r--db/fixtures/development/98_gitlab_instance_administration_project.rb3
-rw-r--r--db/fixtures/production/998_gitlab_instance_administration_project.rb3
-rw-r--r--db/migrate/20190219201635_add_asset_proxy_settings.rb16
-rw-r--r--db/migrate/20190719122333_add_login_recaptcha_protection_enabled_to_application_settings.rb12
-rw-r--r--db/migrate/20190722144316_create_milestone_releases_table.rb20
-rw-r--r--db/migrate/20190816151221_add_active_jobs_limit_to_plans.rb17
-rw-r--r--db/migrate/20190819131155_add_cluster_status_index_to_deployments.rb17
-rw-r--r--db/migrate/20190822175441_rename_epics_state_to_state_id.rb17
-rw-r--r--db/migrate/20190822181528_create_list_user_preferences.rb16
-rw-r--r--db/migrate/20190823055948_change_clusters_namespace_per_environment_default.rb12
-rw-r--r--db/migrate/20190826090628_remove_redundant_deployments_index.rb18
-rw-r--r--db/migrate/20190826100605_add_group_column_to_events.rb9
-rw-r--r--db/migrate/20190828110802_add_not_null_constraints_to_prometheus_metrics_y_label_and_unit.rb8
-rw-r--r--db/migrate/20190902131045_replace_indexes_for_counting_active_users.rb23
-rw-r--r--db/post_migrate/20190822185441_cleanup_epics_state_id_rename.rb17
-rw-r--r--db/post_migrate/20190902160015_remove_support_bot_column_from_users.rb19
-rw-r--r--db/schema.rb47
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/group_linking.gifbin1504079 -> 1328162 bytes
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/manual_permissions.gifbin1703084 -> 1029427 bytes
-rw-r--r--doc/administration/geo/replication/updating_the_geo_nodes.md17
-rw-r--r--doc/administration/gitaly/index.md2
-rw-r--r--doc/administration/logs.md13
-rw-r--r--doc/administration/raketasks/uploads/sanitize.md2
-rw-r--r--doc/administration/troubleshooting/kubernetes_cheat_sheet.md10
-rw-r--r--doc/api/applications.md4
-rw-r--r--doc/api/deploy_keys.md13
-rw-r--r--doc/api/epics.md4
-rw-r--r--doc/api/issues.md4
-rw-r--r--doc/api/merge_request_approvals.md272
-rw-r--r--doc/api/merge_requests.md4
-rw-r--r--doc/api/notes.md16
-rw-r--r--doc/api/releases/index.md178
-rw-r--r--doc/api/settings.md83
-rw-r--r--doc/api/snippets.md10
-rw-r--r--doc/ci/caching/index.md2
-rw-r--r--doc/ci/ci_cd_for_external_repos/github_integration.md51
-rw-r--r--doc/ci/ci_cd_for_external_repos/img/github_repo_list.pngbin14282 -> 0 bytes
-rw-r--r--doc/ci/docker/using_docker_build.md37
-rw-r--r--doc/ci/environments.md5
-rw-r--r--doc/ci/large_repositories/index.md2
-rw-r--r--doc/ci/multi_project_pipelines.md12
-rw-r--r--doc/ci/variables/README.md2
-rw-r--r--doc/ci/yaml/README.md146
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/architecture.md10
-rw-r--r--doc/development/code_review.md2
-rw-r--r--doc/development/contributing/issue_workflow.md4
-rw-r--r--doc/development/contributing/style_guides.md5
-rw-r--r--doc/development/dangerbot.md116
-rw-r--r--doc/development/database_review.md3
-rw-r--r--doc/development/documentation/index.md51
-rw-r--r--doc/development/documentation/structure.md2
-rw-r--r--doc/development/git_object_deduplication.md2
-rw-r--r--doc/development/gotchas.md4
-rw-r--r--doc/development/prometheus_metrics.md23
-rw-r--r--doc/development/python_guide/index.md8
-rw-r--r--doc/development/testing_guide/best_practices.md2
-rw-r--r--doc/development/testing_guide/end_to_end/index.md2
-rw-r--r--doc/development/testing_guide/index.md4
-rw-r--r--doc/development/testing_guide/review_apps.md4
-rw-r--r--doc/development/ux_guide/animation.md4
-rw-r--r--doc/development/ux_guide/illustrations.md4
-rw-r--r--doc/development/what_requires_downtime.md12
-rw-r--r--doc/install/azure/index.md3
-rw-r--r--doc/integration/elasticsearch.md308
-rw-r--r--doc/integration/slash_commands.md1
-rw-r--r--doc/legal/corporate_contributor_license_agreement.md2
-rw-r--r--doc/migrate_ci_to_ce/README.md4
-rw-r--r--doc/push_rules/push_rules.md6
-rw-r--r--doc/raketasks/backup_restore.md6
-rw-r--r--doc/security/README.md1
-rw-r--r--doc/security/asset_proxy.md28
-rw-r--r--doc/security/information_exclusivity.md2
-rw-r--r--doc/system_hooks/system_hooks.md40
-rw-r--r--doc/topics/autodevops/index.md75
-rw-r--r--doc/topics/git/partial_clone.md2
-rw-r--r--doc/university/process/README.md4
-rw-r--r--doc/university/training/gitlab_flow.md4
-rw-r--r--doc/university/training/topics/git_intro.md2
-rw-r--r--doc/university/training/user_training.md2
-rw-r--r--doc/update/patch_versions.md6
-rw-r--r--doc/update/upgrading_from_ce_to_ee.md3
-rw-r--r--doc/update/upgrading_from_source.md17
-rw-r--r--doc/update/upgrading_postgresql_using_slony.md2
-rw-r--r--doc/user/admin_area/license.md2
-rw-r--r--doc/user/admin_area/settings/account_and_limit_settings.md19
-rw-r--r--doc/user/admin_area/settings/continuous_integration.md2
-rw-r--r--doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md2
-rw-r--r--doc/user/application_security/container_scanning/index.md4
-rw-r--r--doc/user/application_security/dependency_scanning/index.md4
-rw-r--r--doc/user/application_security/index.md4
-rw-r--r--doc/user/application_security/license_compliance/img/license_compliance_add_license.pngbin24247 -> 0 bytes
-rw-r--r--doc/user/application_security/license_compliance/img/license_compliance_add_license_v12_3.pngbin0 -> 14046 bytes
-rw-r--r--doc/user/application_security/license_compliance/img/license_compliance_pipeline_tab.pngbin12115 -> 0 bytes
-rw-r--r--doc/user/application_security/license_compliance/img/license_compliance_pipeline_tab_v12_3.pngbin0 -> 16435 bytes
-rw-r--r--doc/user/application_security/license_compliance/img/license_compliance_search.pngbin28237 -> 0 bytes
-rw-r--r--doc/user/application_security/license_compliance/img/license_compliance_search_v12_3.pngbin0 -> 26074 bytes
-rw-r--r--doc/user/application_security/license_compliance/img/license_compliance_settings.pngbin44790 -> 0 bytes
-rw-r--r--doc/user/application_security/license_compliance/img/license_compliance_settings_v12_3.pngbin0 -> 14766 bytes
-rw-r--r--doc/user/application_security/license_compliance/index.md8
-rw-r--r--doc/user/application_security/sast/index.md15
-rw-r--r--doc/user/application_security/security_dashboard/img/group_security_dashboard.pngbin68332 -> 0 bytes
-rw-r--r--doc/user/application_security/security_dashboard/img/group_security_dashboard_v12_3.pngbin0 -> 60530 bytes
-rw-r--r--doc/user/application_security/security_dashboard/index.md16
-rw-r--r--doc/user/clusters/applications.md2
-rw-r--r--doc/user/clusters/img/jupyter-git-extension.gifbin2120084 -> 495240 bytes
-rw-r--r--doc/user/discussions/index.md2
-rw-r--r--doc/user/gitlab_com/index.md8
-rw-r--r--doc/user/group/epics/index.md13
-rw-r--r--doc/user/group/index.md13
-rw-r--r--doc/user/instance/clusters/index.md2
-rw-r--r--doc/user/markdown.md4
-rw-r--r--doc/user/project/code_owners.md2
-rw-r--r--doc/user/project/import/tfvc.md2
-rw-r--r--doc/user/project/integrations/bamboo.md4
-rw-r--r--doc/user/project/integrations/img/grafana_live_embed.pngbin0 -> 44603 bytes
-rw-r--r--doc/user/project/integrations/mock_ci.md2
-rw-r--r--doc/user/project/integrations/prometheus.md21
-rw-r--r--doc/user/project/integrations/webhooks.md33
-rw-r--r--doc/user/project/issues/issue_data_and_actions.md2
-rw-r--r--doc/user/project/issues/sorting_issue_lists.md4
-rw-r--r--doc/user/project/members/index.md11
-rw-r--r--doc/user/project/merge_requests/allow_collaboration.md3
-rw-r--r--doc/user/project/merge_requests/code_quality.md5
-rw-r--r--doc/user/project/merge_requests/img/approvals_premium_project_edit_v12_3.pngbin24542 -> 21908 bytes
-rw-r--r--doc/user/project/merge_requests/img/code_quality.gifbin2617453 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/img/code_quality.pngbin0 -> 94062 bytes
-rw-r--r--doc/user/project/merge_requests/index.md2
-rw-r--r--doc/user/project/merge_requests/merge_request_approvals.md2
-rw-r--r--doc/user/project/merge_requests/work_in_progress_merge_requests.md4
-rw-r--r--doc/user/project/operations/feature_flags.md10
-rw-r--r--doc/user/project/operations/tracing.md6
-rw-r--r--doc/user/project/packages/maven_repository.md3
-rw-r--r--doc/user/project/packages/npm_registry.md3
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md2
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/index.md2
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md4
-rw-r--r--doc/user/project/pages/getting_started_part_one.md2
-rw-r--r--doc/user/project/pages/index.md10
-rw-r--r--doc/user/project/pipelines/settings.md6
-rw-r--r--doc/user/project/quick_actions.md137
-rw-r--r--doc/user/project/settings/index.md2
-rw-r--r--doc/user/reserved_names.md42
-rw-r--r--doc/user/search/advanced_search_syntax.md1
-rw-r--r--doc/workflow/git_annex.md12
-rw-r--r--doc/workflow/releases.md8
-rw-r--r--doc/workflow/time_tracking.md2
-rw-r--r--doc/workflow/timezone.md2
-rw-r--r--lib/api/discussions.rb2
-rw-r--r--lib/api/entities.rb16
-rw-r--r--lib/api/helpers/internal_helpers.rb8
-rw-r--r--lib/api/helpers/notes_helpers.rb6
-rw-r--r--lib/api/internal.rb23
-rw-r--r--lib/api/notes.rb2
-rw-r--r--lib/api/project_snippets.rb14
-rw-r--r--lib/api/release/links.rb4
-rw-r--r--lib/api/releases.rb10
-rw-r--r--lib/api/settings.rb11
-rw-r--r--lib/api/validations/types/comma_separated_to_array.rb22
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb16
-rw-r--r--lib/banzai/filter/asset_proxy_filter.rb62
-rw-r--r--lib/banzai/filter/commit_trailers_filter.rb3
-rw-r--r--lib/banzai/filter/external_link_filter.rb18
-rw-r--r--lib/banzai/filter/image_link_filter.rb3
-rw-r--r--lib/banzai/filter/issuable_state_filter.rb7
-rw-r--r--lib/banzai/filter/label_reference_filter.rb18
-rw-r--r--lib/banzai/filter/milestone_reference_filter.rb12
-rw-r--r--lib/banzai/filter/relative_link_filter.rb11
-rw-r--r--lib/banzai/filter/video_link_filter.rb15
-rw-r--r--lib/banzai/pipeline/ascii_doc_pipeline.rb5
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb3
-rw-r--r--lib/banzai/pipeline/markup_pipeline.rb5
-rw-r--r--lib/banzai/pipeline/single_line_pipeline.rb3
-rw-r--r--lib/gitlab/anonymous_session.rb39
-rw-r--r--lib/gitlab/auth.rb3
-rw-r--r--lib/gitlab/authorized_keys.rb30
-rw-r--r--lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb2
-rw-r--r--lib/gitlab/ci/config/external/file/base.rb8
-rw-r--r--lib/gitlab/ci/pipeline/chain/limit/job_activity.rb21
-rw-r--r--lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml5
-rw-r--r--lib/gitlab/ci/templates/Pages/SwaggerUI.gitlab-ci.yml29
-rw-r--r--lib/gitlab/correlation_id.rb40
-rw-r--r--lib/gitlab/database.rb4
-rw-r--r--lib/gitlab/database_importers/self_monitoring/project/create_service.rb40
-rw-r--r--lib/gitlab/diff/suggestion.rb2
-rw-r--r--lib/gitlab/gitaly_client.rb8
-rw-r--r--lib/gitlab/internal_post_receive/response.rb51
-rw-r--r--lib/gitlab/jira/http_client.rb66
-rw-r--r--lib/gitlab/markdown_cache.rb2
-rw-r--r--lib/gitlab/metrics/samplers/ruby_sampler.rb2
-rw-r--r--lib/gitlab/optimistic_locking.rb3
-rw-r--r--lib/gitlab/path_regex.rb2
-rw-r--r--lib/gitlab/performance_bar.rb6
-rw-r--r--lib/gitlab/performance_bar/redis_adapter_when_peek_enabled.rb4
-rw-r--r--lib/gitlab/performance_bar/with_top_level_warnings.rb19
-rw-r--r--lib/gitlab/recaptcha.rb6
-rw-r--r--lib/gitlab/redis/shared_state.rb1
-rw-r--r--lib/gitlab/regex.rb4
-rw-r--r--lib/gitlab/repository_cache_adapter.rb53
-rw-r--r--lib/gitlab/repository_set_cache.rb67
-rw-r--r--lib/gitlab/rugged_instrumentation.rb8
-rw-r--r--lib/gitlab/sanitizers/exif.rb7
-rw-r--r--lib/gitlab/shell.rb86
-rw-r--r--lib/gitlab/sidekiq_middleware/metrics.rb2
-rw-r--r--lib/gitlab/slash_commands/application_help.rb7
-rw-r--r--lib/gitlab/slash_commands/command.rb3
-rw-r--r--lib/gitlab/slash_commands/help.rb4
-rw-r--r--lib/gitlab/slash_commands/issue_close.rb44
-rw-r--r--lib/gitlab/slash_commands/presenters/access.rb25
-rw-r--r--lib/gitlab/slash_commands/presenters/help.rb70
-rw-r--r--lib/gitlab/slash_commands/presenters/issue_base.rb8
-rw-r--r--lib/gitlab/slash_commands/presenters/issue_close.rb51
-rw-r--r--lib/gitlab/slash_commands/presenters/issue_new.rb10
-rw-r--r--lib/gitlab/tracing.rb2
-rw-r--r--lib/gitlab/visibility_level_checker.rb88
-rw-r--r--lib/peek/views/active_record.rb20
-rw-r--r--lib/peek/views/detailed_view.rb45
-rw-r--r--lib/peek/views/gitaly.rb20
-rw-r--r--lib/peek/views/redis_detailed.rb6
-rw-r--r--lib/peek/views/rugged.rb2
-rw-r--r--lib/system_check/app/authorized_keys_permission_check.rb41
-rw-r--r--lib/system_check/incoming_email/foreman_configured_check.rb25
-rw-r--r--lib/system_check/incoming_email_check.rb2
-rw-r--r--lib/system_check/rake_task/app_task.rb3
-rw-r--r--lib/tasks/gitlab/uploads/sanitize.rake6
-rw-r--r--locale/gitlab.pot257
-rw-r--r--package.json10
-rw-r--r--qa/qa.rb5
-rw-r--r--qa/qa/page/admin/menu.rb11
-rw-r--r--qa/qa/page/component/web_ide/alert.rb27
-rw-r--r--qa/qa/page/dashboard/projects.rb2
-rw-r--r--qa/qa/page/file/show.rb2
-rw-r--r--qa/qa/page/merge_request/show.rb2
-rw-r--r--qa/qa/page/profile/menu.rb2
-rw-r--r--qa/qa/page/project/commit/show.rb5
-rw-r--r--qa/qa/page/project/issue/index.rb10
-rw-r--r--qa/qa/page/project/issue/show.rb6
-rw-r--r--qa/qa/page/project/menu.rb2
-rw-r--r--qa/qa/page/project/new.rb2
-rw-r--r--qa/qa/page/project/operations/kubernetes/show.rb2
-rw-r--r--qa/qa/page/project/pipeline/show.rb2
-rw-r--r--qa/qa/page/project/settings/main.rb2
-rw-r--r--qa/qa/page/project/settings/mirroring_repositories.rb2
-rw-r--r--qa/qa/page/project/settings/protected_branches.rb2
-rw-r--r--qa/qa/page/project/show.rb2
-rw-r--r--qa/qa/page/project/web_ide/edit.rb9
-rw-r--r--qa/qa/page/settings/common.rb2
-rw-r--r--qa/qa/resource/project.rb1
-rw-r--r--qa/qa/resource/repository/commit.rb66
-rw-r--r--qa/qa/resource/runner.rb36
-rw-r--r--qa/qa/runtime/browser.rb2
-rw-r--r--qa/qa/runtime/env.rb2
-rw-r--r--qa/qa/scenario/test/sanity/selectors.rb2
-rw-r--r--qa/qa/service/runner.rb27
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb57
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb2
-rw-r--r--rubocop/cop/inject_enterprise_edition_module.rb2
-rwxr-xr-xscripts/frontend/check_dependencies.sh11
-rwxr-xr-xscripts/review_apps/review-apps.sh2
-rw-r--r--spec/controllers/boards/lists_controller_spec.rb44
-rw-r--r--spec/controllers/groups/runners_controller_spec.rb205
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb2
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb29
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb1
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb112
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb311
-rw-r--r--spec/controllers/projects/services_controller_spec.rb5
-rw-r--r--spec/controllers/sent_notifications_controller_spec.rb2
-rw-r--r--spec/controllers/sessions_controller_spec.rb108
-rw-r--r--spec/controllers/uploads_controller_spec.rb18
-rw-r--r--spec/db/schema_spec.rb2
-rw-r--r--spec/factories/group_members.rb4
-rw-r--r--spec/factories/milestone_releases.rb14
-rw-r--r--spec/factories/project_members.rb4
-rw-r--r--spec/factories/users.rb8
-rw-r--r--spec/features/admin/admin_disables_git_access_protocol_spec.rb2
-rw-r--r--spec/features/admin/admin_disables_two_factor_spec.rb2
-rw-r--r--spec/features/admin/admin_settings_spec.rb2
-rw-r--r--spec/features/admin/admin_uses_repository_checks_spec.rb2
-rw-r--r--spec/features/boards/add_issues_modal_spec.rb2
-rw-r--r--spec/features/boards/boards_spec.rb2
-rw-r--r--spec/features/boards/issue_ordering_spec.rb2
-rw-r--r--spec/features/boards/keyboard_shortcut_spec.rb2
-rw-r--r--spec/features/boards/modal_filter_spec.rb2
-rw-r--r--spec/features/boards/multiple_boards_spec.rb2
-rw-r--r--spec/features/boards/new_issue_spec.rb2
-rw-r--r--spec/features/boards/reload_boards_on_browser_back_spec.rb2
-rw-r--r--spec/features/boards/sidebar_spec.rb2
-rw-r--r--spec/features/boards/sub_group_project_spec.rb2
-rw-r--r--spec/features/commits/user_uses_quick_actions_spec.rb2
-rw-r--r--spec/features/dashboard/todos/target_state_spec.rb2
-rw-r--r--spec/features/global_search_spec.rb8
-rw-r--r--spec/features/groups/board_sidebar_spec.rb2
-rw-r--r--spec/features/groups/board_spec.rb2
-rw-r--r--spec/features/groups/clusters/user_spec.rb2
-rw-r--r--spec/features/groups/milestone_spec.rb2
-rw-r--r--spec/features/groups/user_browse_projects_group_page_spec.rb2
-rw-r--r--spec/features/instance_statistics/cohorts_spec.rb2
-rw-r--r--spec/features/instance_statistics/instance_statistics_spec.rb2
-rw-r--r--spec/features/issuables/issuable_list_spec.rb2
-rw-r--r--spec/features/issuables/markdown_references/internal_references_spec.rb2
-rw-r--r--spec/features/issuables/markdown_references/jira_spec.rb2
-rw-r--r--spec/features/issuables/user_sees_sidebar_spec.rb2
-rw-r--r--spec/features/issues/bulk_assignment_labels_spec.rb2
-rw-r--r--spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb2
-rw-r--r--spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_author_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_emoji_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_hint_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_milestone_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/search_bar_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/visual_tokens_spec.rb2
-rw-r--r--spec/features/issues/form_spec.rb2
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb2
-rw-r--r--spec/features/issues/group_label_sidebar_spec.rb2
-rw-r--r--spec/features/issues/issue_detail_spec.rb2
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb2
-rw-r--r--spec/features/issues/keyboard_shortcut_spec.rb2
-rw-r--r--spec/features/issues/markdown_toolbar_spec.rb2
-rw-r--r--spec/features/issues/move_spec.rb2
-rw-r--r--spec/features/issues/resource_label_events_spec.rb2
-rw-r--r--spec/features/issues/spam_issues_spec.rb2
-rw-r--r--spec/features/issues/todo_spec.rb2
-rw-r--r--spec/features/issues/update_issues_spec.rb2
-rw-r--r--spec/features/issues/user_comments_on_issue_spec.rb17
-rw-r--r--spec/features/issues/user_creates_branch_and_merge_request_spec.rb2
-rw-r--r--spec/features/issues/user_creates_confidential_merge_request_spec.rb2
-rw-r--r--spec/features/issues/user_sees_breadcrumb_links_spec.rb2
-rw-r--r--spec/features/issues/user_uses_quick_actions_spec.rb2
-rw-r--r--spec/features/markdown/math_spec.rb6
-rw-r--r--spec/features/merge_request/user_assigns_themselves_spec.rb2
-rw-r--r--spec/features/merge_request/user_awards_emoji_spec.rb2
-rw-r--r--spec/features/merge_request/user_creates_mr_spec.rb2
-rw-r--r--spec/features/merge_request/user_customizes_merge_commit_message_spec.rb2
-rw-r--r--spec/features/merge_request/user_locks_discussion_spec.rb2
-rw-r--r--spec/features/merge_request/user_merges_immediately_spec.rb2
-rw-r--r--spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb2
-rw-r--r--spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb2
-rw-r--r--spec/features/merge_request/user_posts_diff_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_posts_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_resolves_conflicts_spec.rb2
-rw-r--r--spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb2
-rw-r--r--spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_breadcrumb_links_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_closing_issues_message_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_deleted_target_branch_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_deployment_widget_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_diff_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_discussions_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_empty_state_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_pipelines_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_system_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_versions_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_wip_help_message_spec.rb2
-rw-r--r--spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb2
-rw-r--r--spec/features/merge_request/user_toggles_whitespace_changes_spec.rb2
-rw-r--r--spec/features/merge_request/user_uses_quick_actions_spec.rb2
-rw-r--r--spec/features/merge_requests/filters_generic_behavior_spec.rb2
-rw-r--r--spec/features/merge_requests/user_filters_by_assignees_spec.rb2
-rw-r--r--spec/features/merge_requests/user_filters_by_labels_spec.rb2
-rw-r--r--spec/features/merge_requests/user_filters_by_milestones_spec.rb2
-rw-r--r--spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb2
-rw-r--r--spec/features/merge_requests/user_filters_by_target_branch_spec.rb2
-rw-r--r--spec/features/merge_requests/user_lists_merge_requests_spec.rb2
-rw-r--r--spec/features/merge_requests/user_mass_updates_spec.rb2
-rw-r--r--spec/features/milestone_spec.rb2
-rw-r--r--spec/features/milestones/user_creates_milestone_spec.rb2
-rw-r--r--spec/features/milestones/user_deletes_milestone_spec.rb2
-rw-r--r--spec/features/milestones/user_edits_milestone_spec.rb2
-rw-r--r--spec/features/milestones/user_promotes_milestone_spec.rb2
-rw-r--r--spec/features/milestones/user_sees_breadcrumb_links_spec.rb2
-rw-r--r--spec/features/milestones/user_views_milestone_spec.rb2
-rw-r--r--spec/features/milestones/user_views_milestones_spec.rb2
-rw-r--r--spec/features/profiles/account_spec.rb2
-rw-r--r--spec/features/profiles/active_sessions_spec.rb2
-rw-r--r--spec/features/profiles/chat_names_spec.rb2
-rw-r--r--spec/features/profiles/emails_spec.rb2
-rw-r--r--spec/features/profiles/gpg_keys_spec.rb2
-rw-r--r--spec/features/profiles/keys_spec.rb2
-rw-r--r--spec/features/profiles/user_edit_profile_spec.rb17
-rw-r--r--spec/features/projects/clusters/user_spec.rb2
-rw-r--r--spec/features/projects/commit/mini_pipeline_graph_spec.rb2
-rw-r--r--spec/features/projects/files/user_searches_for_files_spec.rb6
-rw-r--r--spec/features/projects/jobs/user_browses_job_spec.rb2
-rw-r--r--spec/features/projects/jobs_spec.rb9
-rw-r--r--spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb2
-rw-r--r--spec/features/projects/pages_spec.rb6
-rw-r--r--spec/features/projects/settings/lfs_settings_spec.rb2
-rw-r--r--spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb2
-rw-r--r--spec/features/projects/show/developer_views_empty_project_instructions_spec.rb2
-rw-r--r--spec/features/projects/snippets/create_snippet_spec.rb2
-rw-r--r--spec/features/projects/tags/user_edits_tags_spec.rb2
-rw-r--r--spec/features/read_only_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_code_spec.rb57
-rw-r--r--spec/features/search/user_searches_for_comments_spec.rb24
-rw-r--r--spec/features/search/user_searches_for_commits_spec.rb14
-rw-r--r--spec/features/search/user_searches_for_issues_spec.rb24
-rw-r--r--spec/features/search/user_searches_for_merge_requests_spec.rb16
-rw-r--r--spec/features/search/user_searches_for_milestones_spec.rb16
-rw-r--r--spec/features/search/user_searches_for_projects_spec.rb6
-rw-r--r--spec/features/search/user_searches_for_users_spec.rb92
-rw-r--r--spec/features/search/user_searches_for_wiki_pages_spec.rb7
-rw-r--r--spec/features/search/user_uses_header_search_field_spec.rb6
-rw-r--r--spec/features/security/group/internal_access_spec.rb2
-rw-r--r--spec/features/security/group/private_access_spec.rb2
-rw-r--r--spec/features/security/group/public_access_spec.rb2
-rw-r--r--spec/features/security/project/internal_access_spec.rb4
-rw-r--r--spec/features/security/project/private_access_spec.rb4
-rw-r--r--spec/features/security/project/public_access_spec.rb4
-rw-r--r--spec/features/snippets/explore_spec.rb2
-rw-r--r--spec/features/snippets/internal_snippet_spec.rb2
-rw-r--r--spec/features/snippets/public_snippets_spec.rb2
-rw-r--r--spec/features/snippets/search_snippets_spec.rb16
-rw-r--r--spec/features/snippets/user_creates_snippet_spec.rb2
-rw-r--r--spec/features/snippets/user_deletes_snippet_spec.rb2
-rw-r--r--spec/features/snippets/user_edits_snippet_spec.rb2
-rw-r--r--spec/features/snippets/user_snippets_spec.rb2
-rw-r--r--spec/features/uploads/user_uploads_avatar_to_group_spec.rb2
-rw-r--r--spec/features/uploads/user_uploads_avatar_to_profile_spec.rb2
-rw-r--r--spec/features/uploads/user_uploads_file_to_note_spec.rb2
-rw-r--r--spec/features/user_can_display_performance_bar_spec.rb2
-rw-r--r--spec/features/user_sees_revert_modal_spec.rb2
-rw-r--r--spec/features/users/login_spec.rb8
-rw-r--r--spec/finders/members_finder_spec.rb44
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_noteable.json28
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_poll_widget.json8
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_widget.json3
-rw-r--r--spec/fixtures/api/schemas/pipeline.json8
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/release.json1
-rw-r--r--spec/frontend/branches/divergence_graph_spec.js14
-rw-r--r--spec/frontend/clusters/components/application_row_spec.js2
-rw-r--r--spec/frontend/clusters/stores/clusters_store_spec.js2
-rw-r--r--spec/frontend/jobs/store/utils_spec.js60
-rw-r--r--spec/frontend/mocks/ce/lib/utils/axios_utils.js3
-rw-r--r--spec/frontend/mocks/mocks_helper_spec.js7
-rw-r--r--spec/frontend/mocks/node/jquery.js4
-rw-r--r--spec/frontend/mocks_spec.js15
-rw-r--r--spec/frontend/notes/components/note_app_spec.js31
-rw-r--r--spec/frontend/project_find_file_spec.js77
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js55
-rw-r--r--spec/frontend/vue_shared/components/changed_file_icon_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/file_icon_spec.js75
-rw-r--r--spec/helpers/avatars_helper_spec.rb44
-rw-r--r--spec/helpers/commits_helper_spec.rb2
-rw-r--r--spec/helpers/emails_helper_spec.rb56
-rw-r--r--spec/helpers/form_helper_spec.rb2
-rw-r--r--spec/helpers/import_helper_spec.rb2
-rw-r--r--spec/helpers/labels_helper_spec.rb10
-rw-r--r--spec/helpers/page_layout_helper_spec.rb2
-rw-r--r--spec/helpers/profiles_helper_spec.rb2
-rw-r--r--spec/helpers/projects_helper_spec.rb38
-rw-r--r--spec/helpers/search_helper_spec.rb43
-rw-r--r--spec/helpers/users_helper_spec.rb2
-rw-r--r--spec/initializers/asset_proxy_setting_spec.rb13
-rw-r--r--spec/initializers/rest-client-hostname_override_spec.rb147
-rw-r--r--spec/javascripts/boards/issue_spec.js6
-rw-r--r--spec/javascripts/environments/environment_item_spec.js5
-rw-r--r--spec/javascripts/flash_spec.js12
-rw-r--r--spec/javascripts/groups/components/app_spec.js3
-rw-r--r--spec/javascripts/groups/service/groups_service_spec.js17
-rw-r--r--spec/javascripts/issue_show/components/edit_actions_spec.js2
-rw-r--r--spec/javascripts/monitoring/charts/area_spec.js265
-rw-r--r--spec/javascripts/monitoring/components/dashboard_spec.js4
-rw-r--r--spec/javascripts/releases/components/release_block_spec.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js47
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js4
-rw-r--r--spec/javascripts/vue_shared/components/file_icon_spec.js92
-rw-r--r--spec/lib/banzai/filter/asset_proxy_filter_spec.rb95
-rw-r--r--spec/lib/banzai/filter/blockquote_fence_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/commit_trailers_filter_spec.rb21
-rw-r--r--spec/lib/banzai/filter/external_link_filter_spec.rb12
-rw-r--r--spec/lib/banzai/filter/front_matter_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/image_link_filter_spec.rb7
-rw-r--r--spec/lib/banzai/filter/issuable_state_filter_spec.rb8
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb5
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb8
-rw-r--r--spec/lib/banzai/filter/project_reference_filter_spec.rb16
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb83
-rw-r--r--spec/lib/banzai/filter/video_link_filter_spec.rb22
-rw-r--r--spec/lib/banzai/pipeline/description_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/email_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/full_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/gfm_pipeline_spec.rb46
-rw-r--r--spec/lib/banzai/pipeline/wiki_pipeline_spec.rb2
-rw-r--r--spec/lib/gitlab/anonymous_session_spec.rb78
-rw-r--r--spec/lib/gitlab/auth_spec.rb3
-rw-r--r--spec/lib/gitlab/authorized_keys_spec.rb132
-rw-r--r--spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/reset_merge_status_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/project_created_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/project_moved_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/config/external/file/base_spec.rb6
-rw-r--r--spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb2
-rw-r--r--spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb18
-rw-r--r--spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/favicon_spec.rb10
-rw-r--r--spec/lib/gitlab/file_markdown_link_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/file_type_detection_spec.rb2
-rw-r--r--spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb2
-rw-r--r--spec/lib/gitlab/gpg/commit_spec.rb2
-rw-r--r--spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb2
-rw-r--r--spec/lib/gitlab/gpg_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml9
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb21
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/lib/gitlab/internal_post_receive/response_spec.rb121
-rw-r--r--spec/lib/gitlab/kubernetes/helm/install_command_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/pod_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/kube_client_spec.rb53
-rw-r--r--spec/lib/gitlab/middleware/go_spec.rb2
-rw-r--r--spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb29
-rw-r--r--spec/lib/gitlab/performance_bar_spec.rb26
-rw-r--r--spec/lib/gitlab/prometheus/metric_group_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_cache_adapter_spec.rb7
-rw-r--r--spec/lib/gitlab/repository_set_cache_spec.rb75
-rw-r--r--spec/lib/gitlab/sanitizers/exif_spec.rb18
-rw-r--r--spec/lib/gitlab/shell_spec.rb485
-rw-r--r--spec/lib/gitlab/sidekiq_config_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/application_help_spec.rb3
-rw-r--r--spec/lib/gitlab/slash_commands/command_spec.rb6
-rw-r--r--spec/lib/gitlab/slash_commands/issue_close_spec.rb80
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/access_spec.rb6
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_close_spec.rb27
-rw-r--r--spec/lib/gitlab/tracing_spec.rb2
-rw-r--r--spec/lib/gitlab/visibility_level_checker_spec.rb82
-rw-r--r--spec/lib/gitlab_spec.rb8
-rw-r--r--spec/lib/peek/views/detailed_view_spec.rb81
-rw-r--r--spec/lib/peek/views/redis_detailed_spec.rb8
-rw-r--r--spec/lib/peek/views/rugged_spec.rb2
-rw-r--r--spec/lib/system_check/app/authorized_keys_permission_check_spec.rb67
-rw-r--r--spec/mailers/abuse_report_mailer_spec.rb2
-rw-r--r--spec/mailers/email_rejection_mailer_spec.rb2
-rw-r--r--spec/mailers/notify_spec.rb16
-rw-r--r--spec/mailers/repository_check_mailer_spec.rb2
-rw-r--r--spec/models/abuse_report_spec.rb2
-rw-r--r--spec/models/active_session_spec.rb2
-rw-r--r--spec/models/appearance_spec.rb2
-rw-r--r--spec/models/application_setting_spec.rb65
-rw-r--r--spec/models/blob_spec.rb2
-rw-r--r--spec/models/board_spec.rb2
-rw-r--r--spec/models/ci/pipeline_spec.rb64
-rw-r--r--spec/models/clusters/applications/cert_manager_spec.rb29
-rw-r--r--spec/models/clusters/applications/helm_spec.rb2
-rw-r--r--spec/models/clusters/applications/ingress_spec.rb2
-rw-r--r--spec/models/clusters/applications/jupyter_spec.rb2
-rw-r--r--spec/models/clusters/applications/knative_spec.rb2
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb2
-rw-r--r--spec/models/clusters/applications/runner_spec.rb2
-rw-r--r--spec/models/concerns/blocks_json_serialization_spec.rb2
-rw-r--r--spec/models/concerns/deployable_spec.rb2
-rw-r--r--spec/models/concerns/deployment_platform_spec.rb2
-rw-r--r--spec/models/concerns/ignorable_column_spec.rb44
-rw-r--r--spec/models/concerns/issuable_spec.rb26
-rw-r--r--spec/models/concerns/routable_spec.rb27
-rw-r--r--spec/models/concerns/triggerable_hooks_spec.rb2
-rw-r--r--spec/models/conversational_development_index/metric_spec.rb2
-rw-r--r--spec/models/gpg_key_spec.rb2
-rw-r--r--spec/models/gpg_key_subkey_spec.rb2
-rw-r--r--spec/models/gpg_signature_spec.rb2
-rw-r--r--spec/models/group_spec.rb19
-rw-r--r--spec/models/hooks/system_hook_spec.rb23
-rw-r--r--spec/models/hooks/web_hook_log_spec.rb2
-rw-r--r--spec/models/label_spec.rb7
-rw-r--r--spec/models/lfs_download_object_spec.rb2
-rw-r--r--spec/models/lfs_file_lock_spec.rb2
-rw-r--r--spec/models/list_spec.rb81
-rw-r--r--spec/models/list_user_preference_spec.rb22
-rw-r--r--spec/models/merge_request_diff_commit_spec.rb2
-rw-r--r--spec/models/merge_request_diff_file_spec.rb2
-rw-r--r--spec/models/merge_request_spec.rb34
-rw-r--r--spec/models/milestone_release_spec.rb36
-rw-r--r--spec/models/milestone_spec.rb20
-rw-r--r--spec/models/note_diff_file_spec.rb2
-rw-r--r--spec/models/note_spec.rb1
-rw-r--r--spec/models/notification_setting_spec.rb2
-rw-r--r--spec/models/project_deploy_token_spec.rb2
-rw-r--r--spec/models/project_import_state_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/push_message_spec.rb26
-rw-r--r--spec/models/project_services/discord_service_spec.rb33
-rw-r--r--spec/models/project_spec.rb20
-rw-r--r--spec/models/project_statistics_spec.rb2
-rw-r--r--spec/models/redirect_route_spec.rb2
-rw-r--r--spec/models/release_spec.rb17
-rw-r--r--spec/models/remote_mirror_spec.rb9
-rw-r--r--spec/models/repository_spec.rb52
-rw-r--r--spec/models/resource_label_event_spec.rb2
-rw-r--r--spec/models/timelog_spec.rb2
-rw-r--r--spec/models/upload_spec.rb2
-rw-r--r--spec/models/user_agent_detail_spec.rb2
-rw-r--r--spec/models/user_callout_spec.rb2
-rw-r--r--spec/models/user_spec.rb41
-rw-r--r--spec/policies/issue_policy_spec.rb28
-rw-r--r--spec/policies/merge_request_policy_spec.rb89
-rw-r--r--spec/presenters/blob_presenter_spec.rb14
-rw-r--r--spec/presenters/ci/pipeline_presenter_spec.rb34
-rw-r--r--spec/presenters/clusterable_presenter_spec.rb9
-rw-r--r--spec/rails_helper.rb1
-rw-r--r--spec/requests/api/internal_spec.rb64
-rw-r--r--spec/requests/api/pages_domains_spec.rb2
-rw-r--r--spec/requests/api/project_snippets_spec.rb69
-rw-r--r--spec/requests/api/projects_spec.rb2
-rw-r--r--spec/requests/api/settings_spec.rb28
-rw-r--r--spec/requests/api/snippets_spec.rb65
-rw-r--r--spec/rubocop/cop/inject_enterprise_edition_module_spec.rb19
-rw-r--r--spec/serializers/deployment_entity_spec.rb9
-rw-r--r--spec/serializers/entity_date_helper_spec.rb22
-rw-r--r--spec/serializers/merge_request_serializer_spec.rb8
-rw-r--r--spec/serializers/pipeline_entity_spec.rb6
-rw-r--r--spec/services/application_settings/update_service_spec.rb33
-rw-r--r--spec/services/auth/container_registry_authentication_service_spec.rb12
-rw-r--r--spec/services/boards/lists/list_service_spec.rb6
-rw-r--r--spec/services/boards/lists/update_service_spec.rb89
-rw-r--r--spec/services/chat_names/authorize_user_service_spec.rb21
-rw-r--r--spec/services/clusters/applications/check_installation_progress_service_spec.rb16
-rw-r--r--spec/services/clusters/applications/check_uninstall_progress_service_spec.rb10
-rw-r--r--spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb (renamed from spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb)10
-rw-r--r--spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb (renamed from spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb)10
-rw-r--r--spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb (renamed from spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb)2
-rw-r--r--spec/services/create_snippet_service_spec.rb13
-rw-r--r--spec/services/groups/transfer_service_spec.rb2
-rw-r--r--spec/services/issues/close_service_spec.rb40
-rw-r--r--spec/services/metrics/dashboard/project_dashboard_service_spec.rb2
-rw-r--r--spec/services/milestones/destroy_service_spec.rb14
-rw-r--r--spec/services/notification_service_spec.rb156
-rw-r--r--spec/services/projects/create_service_spec.rb68
-rw-r--r--spec/services/releases/create_service_spec.rb62
-rw-r--r--spec/services/releases/destroy_service_spec.rb10
-rw-r--r--spec/services/releases/update_service_spec.rb37
-rw-r--r--spec/services/system_hooks_service_spec.rb10
-rw-r--r--spec/services/system_note_service_spec.rb7
-rw-r--r--spec/services/todo_service_spec.rb121
-rw-r--r--spec/services/update_merge_request_metrics_service_spec.rb2
-rw-r--r--spec/services/update_snippet_service_spec.rb15
-rw-r--r--spec/spec_helper.rb6
-rw-r--r--spec/support/features/discussion_comments_shared_example.rb2
-rw-r--r--spec/support/helpers/capybara_helpers.rb4
-rw-r--r--spec/support/helpers/project_forks_helper.rb2
-rw-r--r--spec/support/helpers/rails_helpers.rb7
-rw-r--r--spec/support/helpers/search_helpers.rb19
-rw-r--r--spec/support/helpers/stub_configuration.rb4
-rw-r--r--spec/support/helpers/test_env.rb1
-rw-r--r--spec/support/helpers/wait_for_requests.rb4
-rw-r--r--spec/support/matchers/be_url.rb22
-rw-r--r--spec/support/services/clusters/create_service_shared.rb68
-rw-r--r--spec/support/shared_examples/chat_slash_commands_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/controllers/issuables_requiring_filter_shared_examples.rb (renamed from spec/support/issuables_requiring_filter_shared_examples.rb)0
-rw-r--r--spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/lib/banzai/filters/reference_filter_shared_examples.rb23
-rw-r--r--spec/support/shared_examples/models/active_record_enum_shared_examples.rb (renamed from spec/support/active_record_enum.rb)0
-rw-r--r--spec/support/shared_examples/models/concern/issuable_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/services/notification_service_shared_examples.rb44
-rw-r--r--spec/uploaders/gitlab_uploader_spec.rb2
-rw-r--r--spec/uploaders/object_storage_spec.rb2
-rw-r--r--spec/uploaders/records_uploads_spec.rb2
-rw-r--r--spec/uploaders/uploader_helper_spec.rb2
-rw-r--r--spec/views/devise/shared/_signin_box.html.haml_spec.rb3
-rw-r--r--spec/views/groups/edit.html.haml_spec.rb2
-rw-r--r--spec/views/help/index.html.haml_spec.rb2
-rw-r--r--spec/views/help/instance_configuration.html.haml_spec.rb2
-rw-r--r--spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb8
-rw-r--r--spec/views/projects/settings/operations/show.html.haml_spec.rb1
-rw-r--r--spec/views/search/_results.html.haml_spec.rb33
-rw-r--r--spec/workers/repository_remove_remote_worker_spec.rb2
-rw-r--r--spec/workers/repository_update_remote_mirror_worker_spec.rb2
-rw-r--r--spec/workers/upload_checksum_worker_spec.rb2
-rw-r--r--vendor/licenses.csv1
-rw-r--r--yarn.lock36
1090 files changed, 11660 insertions, 5758 deletions
diff --git a/.gitignore b/.gitignore
index 104c6930050..3ffe4263c4f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -81,4 +81,5 @@ package-lock.json
/junit_*.xml
/coverage-frontend/
jsdoc/
-**/tmp/rubocop_cache/** \ No newline at end of file
+**/tmp/rubocop_cache/**
+.overcommit.yml
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 27992024265..5b5527284d3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -41,3 +41,4 @@ include:
- local: .gitlab/ci/setup.gitlab-ci.yml
- local: .gitlab/ci/test-metadata.gitlab-ci.yml
- local: .gitlab/ci/yaml.gitlab-ci.yml
+ - local: .gitlab/ci/ee-specific-checks.gitlab-ci.yml
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml
index e77c773824f..3f29adddf73 100644
--- a/.gitlab/ci/docs.gitlab-ci.yml
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -74,7 +74,7 @@ docs lint:
script:
- scripts/lint-doc.sh
# Lint Markdown
- - markdownlint --config .markdownlint.json doc/**/*.md
+ - markdownlint --config .markdownlint.json 'doc/**/*.md'
# Prepare docs for build
- mv doc/ /tmp/gitlab-docs/content/$DOCS_GITLAB_REPO_SUFFIX
- cd /tmp/gitlab-docs
diff --git a/.gitlab/ci/ee-specific-checks.gitlab-ci.yml b/.gitlab/ci/ee-specific-checks.gitlab-ci.yml
new file mode 100644
index 00000000000..babb89b4606
--- /dev/null
+++ b/.gitlab/ci/ee-specific-checks.gitlab-ci.yml
@@ -0,0 +1,22 @@
+.ee-specific-check:
+ extends: .default-tags
+ dependencies: []
+ only:
+ - branches@gitlab-org/gitlab-ee
+ except:
+ - master
+ - tags
+ - /[\d-]+-stable(-ee)?/
+ - /[\d-]+-auto-deploy-\d{7}/
+ - /^security-/
+ - /\bce\-to\-ee\b/
+
+ee-files-location-check:
+ extends: .ee-specific-check
+ script:
+ - scripts/ee-files-location-check
+
+ee-specific-lines-check:
+ extends: .ee-specific-check
+ script:
+ - scripts/ee-specific-lines-check
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
index df38cb4ff8e..0720ea3e056 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -233,41 +233,3 @@ qa-frontend-node:latest:
extends: .qa-frontend-node
image: node:latest
allow_failure: true
-
-lint:javascript:report:
- extends:
- - .default-tags
- - .default-retry
- - .default-cache
- - .except-docs
- variables:
- SETUP_DB: "false"
- stage: post-test
- dependencies: []
- script:
- - date
- - yarn run eslint-report || true # ignore exit code
- artifacts:
- name: eslint-report
- expire_in: 31d
- paths:
- - eslint-report.html
-
-jsdoc:
- extends:
- - .default-tags
- - .default-retry
- - .default-cache
- - .except-docs
- variables:
- SETUP_DB: "false"
- stage: post-test
- dependencies: ["compile-assets", "compile-assets pull-cache"]
- script:
- - date
- - yarn run jsdoc || true # ignore exit code
- artifacts:
- name: jsdoc
- expire_in: 31d
- paths:
- - jsdoc/
diff --git a/.gitlab/ci/pages.gitlab-ci.yml b/.gitlab/ci/pages.gitlab-ci.yml
index 3247d7c4bce..2de09753cca 100644
--- a/.gitlab/ci/pages.gitlab-ci.yml
+++ b/.gitlab/ci/pages.gitlab-ci.yml
@@ -9,17 +9,15 @@ pages:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
stage: pages
- dependencies: ["coverage", "karma", "gitlab:assets:compile", "lint:javascript:report", "jsdoc"]
+ dependencies: ["coverage", "karma", "gitlab:assets:compile"]
script:
- mv public/ .public/
- mkdir public/
- mv coverage/ public/coverage-ruby/ || true
- mv coverage-javascript/ public/coverage-javascript/ || true
- - mv eslint-report.html public/ || true
- mv webpack-report/ public/webpack-report/ || true
- cp .public/assets/application-*.css public/application.css || true
- cp .public/assets/application-*.css.gz public/application.css.gz || true
- - mv jsdoc/ public/jsdoc/ || true
artifacts:
paths:
- public
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
index ac2a70dda0b..8628e1e0a14 100644
--- a/.gitlab/ci/qa.gitlab-ci.yml
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -1,7 +1,6 @@
.package-and-qa-base:
image: ruby:2.6-alpine
stage: qa
- needs: ["build-qa-image", "gitlab:assets:compile pull-cache"]
dependencies: []
variables:
GIT_DEPTH: "1"
@@ -11,24 +10,31 @@
- install_gitlab_gem
- ./scripts/trigger-build omnibus
only:
- - branches@gitlab-org/gitlab-ce
- - branches@gitlab-org/gitlab-ee
- except:
- - master
+ refs:
+ - branches@gitlab-org/gitlab-ce
+ - branches@gitlab-org/gitlab-ee
package-and-qa-manual:
- extends:
- - .package-and-qa-base
- - .except-docs-qa
- when: manual
+ extends: .package-and-qa-base
except:
- - master
- - /(^docs[\/-].+|.+-docs$)/
- - /(^qa[\/-].*|.*-qa$)
+ refs:
+ - master
+ when: manual
+ needs: ["build-qa-image", "gitlab:assets:compile pull-cache"]
+
+package-and-qa-manual:master:
+ extends: .package-and-qa-base
+ only:
+ refs:
+ - master
+ when: manual
+ needs: ["build-qa-image", "gitlab:assets:compile"]
package-and-qa:
extends: .package-and-qa-base
- allow_failure: true
only:
- - /(^qa[\/-].*|.*-qa$)/@gitlab-org/gitlab-ce
- - /(^qa[\/-].*|.*-qa$)/@gitlab-org/gitlab-ee
+ refs:
+ - /(^qa[\/-].*|.*-qa$)/@gitlab-org/gitlab-ce
+ - /(^qa[\/-].*|.*-qa$)/@gitlab-org/gitlab-ee
+ needs: ["build-qa-image", "gitlab:assets:compile pull-cache"]
+ allow_failure: true
diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml
index efe33049939..5622cd232ca 100644
--- a/.gitlab/ci/reports.gitlab-ci.yml
+++ b/.gitlab/ci/reports.gitlab-ci.yml
@@ -26,7 +26,9 @@ dependency_scanning:
extends: .reports
dast:
- extends: .reports
+ extends:
+ - .reports
+ - .review-only
stage: qa
dependencies: ["review-deploy"]
before_script:
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index 3415f1b6ab4..c4a81a021a9 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -22,7 +22,9 @@
- source scripts/utils.sh
.review-docker:
- extends: .review-base
+ extends:
+ - .default-tags
+ - .default-retry
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine
services:
- docker:19.03.0-dind
@@ -36,7 +38,13 @@
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab/${CI_PROJECT_NAME}-qa:${CI_COMMIT_REF_SLUG}"
build-qa-image:
- extends: .review-docker
+ extends:
+ - .review-docker
+ - .except-docs
+ only:
+ refs:
+ - branches@gitlab-org/gitlab-ce
+ - branches@gitlab-org/gitlab-ee
stage: test
script:
- time docker build --cache-from ${LATEST_QA_IMAGE} --tag ${QA_IMAGE} --file ./qa/Dockerfile ./
@@ -124,7 +132,9 @@ review-stop:
artifacts: {}
.review-qa-base:
- extends: .review-docker
+ extends:
+ - .review-docker
+ - .review-only
retry: 2
stage: qa
variables:
diff --git a/.overcommit.yml.example b/.overcommit.yml.example
new file mode 100644
index 00000000000..25823b9a8b3
--- /dev/null
+++ b/.overcommit.yml.example
@@ -0,0 +1,28 @@
+# Use this file to configure the Overcommit hooks you wish to use. This will
+# extend the default configuration defined in:
+# https://github.com/sds/overcommit/blob/master/config/default.yml
+#
+# At the topmost level of this YAML file is a key representing type of hook
+# being run (e.g. pre-commit, commit-msg, etc.). Within each type you can
+# customize each hook, such as whether to only run it on certain files (via
+# `include`), whether to only display output if it fails (via `quiet`), etc.
+#
+# For a complete list of hooks, see:
+# https://github.com/sds/overcommit/tree/master/lib/overcommit/hook
+#
+# For a complete list of options that you can use to customize hooks, see:
+# https://github.com/sds/overcommit#configuration
+#
+# Uncomment the following lines to make the configuration take effect.
+
+PreCommit:
+ RuboCop:
+ enabled: true
+# on_warn: fail # Treat all warnings as failures
+#
+#PostCheckout:
+# ALL: # Special hook name that customizes all hooks of this type
+# quiet: true # Change all post-checkout hooks to only display output on failure
+#
+# IndexTags:
+# enabled: true # Generate a tags file with `ctags` each time HEAD changes
diff --git a/.rubocop.yml b/.rubocop.yml
index a20924c21b7..f24cbb6ce92 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -264,8 +264,6 @@ RSpec/EnvAssignment:
Exclude:
- 'spec/**/fast_spec_helper.rb'
- 'ee/spec/**/fast_spec_helper.rb'
- - 'spec/**/rails_helper.rb'
- - 'ee/spec/**/rails_helper.rb'
- 'spec/**/spec_helper.rb'
- 'ee/spec/**/spec_helper.rb'
RSpec/BeSuccessMatcher:
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 3898206e3b5..be147d72f71 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -570,11 +570,6 @@ Style/EmptyMethod:
Style/Encoding:
Enabled: false
-# Offense count: 2
-Style/EvalWithLocation:
- Exclude:
- - 'app/models/service.rb'
-
# Offense count: 203
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ffca09a92e7..a432b091c7e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,55 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 12.2.4
+
+### Fixed (7 changes)
+
+- Add syntax highlighting for line expansion. !31821
+- Fix issuable sidebar icon on notification disabled. !32134
+- Upgrade Mermaid to v8.2.4. !32186
+- Fix Piwik not working. !32234
+- Fix snippets API not working with visibility level. !32286
+- Fix upload URLs in Markdown for users without access to project repository. !32448
+- Update Mermaid to v8.2.6. !32502
+
+### Performance (1 change)
+
+- Fix N+1 Gitaly calls in /api/v4/projects/:id/issues. !32171
+
+
+## 12.2.3
+
+### Security (22 changes)
+
+- Ensure only authorised users can create notes on Merge Requests and Issues.
+- Gitaly: ignore git redirects.
+- Add :login_recaptcha_protection_enabled setting to prevent bots from brute-force attacks.
+- Speed up regexp in namespace format by failing fast after reaching maximum namespace depth.
+- Limit the size of issuable description and comments.
+- Send TODOs for comments on commits correctly.
+- Restrict MergeRequests#test_reports to authenticated users with read-access on Builds.
+- Added image proxy to mitigate potential stealing of IP addresses.
+- Filter out old system notes for epics in notes api endpoint response.
+- Avoid exposing unaccessible repo data upon GFM post processing.
+- Fix HTML injection for label description.
+- Make sure HTML text is always escaped when replacing label/milestone references.
+- Prevent DNS rebind on JIRA service integration.
+- Use admin_group authorization in Groups::RunnersController.
+- Prevent disclosure of merge request ID via email.
+- Show cross-referenced MR-id in issues' activities only to authorized users.
+- Enforce max chars and max render time in markdown math.
+- Check permissions before responding in MergeController#pipeline_status.
+- Remove EXIF from users/personal snippet uploads.
+- Fix project import restricted visibility bypass via API.
+- Fix weak session management by clearing password reset tokens after login (username/email) are updated.
+- Fix SSRF via DNS rebinding in Kubernetes Integration.
+
+
+## 12.2.2
+
+- Unreleased due to QA failure.
+
## 12.2.1
### Fixed (3 changes)
@@ -591,6 +640,34 @@ entry.
- Removes EE differences for app/views/admin/users/show.html.haml.
+## 12.0.7
+
+### Security (22 changes)
+
+- Ensure only authorised users can create notes on Merge Requests and Issues.
+- Add :login_recaptcha_protection_enabled setting to prevent bots from brute-force attacks.
+- Queries for Upload should be scoped by model.
+- Speed up regexp in namespace format by failing fast after reaching maximum namespace depth.
+- Limit the size of issuable description and comments.
+- Send TODOs for comments on commits correctly.
+- Restrict MergeRequests#test_reports to authenticated users with read-access on Builds.
+- Added image proxy to mitigate potential stealing of IP addresses.
+- Filter out old system notes for epics in notes api endpoint response.
+- Avoid exposing unaccessible repo data upon GFM post processing.
+- Fix HTML injection for label description.
+- Make sure HTML text is always escaped when replacing label/milestone references.
+- Prevent DNS rebind on JIRA service integration.
+- Use admin_group authorization in Groups::RunnersController.
+- Prevent disclosure of merge request ID via email.
+- Show cross-referenced MR-id in issues' activities only to authorized users.
+- Enforce max chars and max render time in markdown math.
+- Check permissions before responding in MergeController#pipeline_status.
+- Remove EXIF from users/personal snippet uploads.
+- Fix project import restricted visibility bypass via API.
+- Fix weak session management by clearing password reset tokens after login (username/email) are updated.
+- Fix SSRF via DNS rebinding in Kubernetes Integration.
+
+
## 12.0.6
- No changes.
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 4d5fde5bd16..91951fd8ad7 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.60.0
+1.61.0
diff --git a/GITLAB_ELASTICSEARCH_INDEXER_VERSION b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
index 26aaba0e866..f0bb29e7638 100644
--- a/GITLAB_ELASTICSEARCH_INDEXER_VERSION
+++ b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
@@ -1 +1 @@
-1.2.0
+1.3.0
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index ccfb75e5120..3c40359d3dc 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-9.4.1
+9.4.2
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index e5c15102d9b..7f6758ef97b 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-8.9.0
+8.10.0
diff --git a/Gemfile b/Gemfile
index 6c7ef7264c3..bc573434063 100644
--- a/Gemfile
+++ b/Gemfile
@@ -171,7 +171,7 @@ gem 'acts-as-taggable-on', '~> 6.0'
gem 'sidekiq', '~> 5.2.7'
gem 'sidekiq-cron', '~> 1.0'
gem 'redis-namespace', '~> 1.6.0'
-gem 'gitlab-sidekiq-fetcher', '0.5.1', require: 'sidekiq-reliable-fetch'
+gem 'gitlab-sidekiq-fetcher', '0.5.2', require: 'sidekiq-reliable-fetch'
# Cron Parser
gem 'fugit', '~> 1.2.1'
@@ -215,7 +215,8 @@ gem 'discordrb-webhooks-blackst0ne', '~> 3.3', require: false
gem 'hipchat', '~> 1.5.0'
# Jira integration
-gem 'jira-ruby', '~> 1.4'
+gem 'jira-ruby', '~> 1.7'
+gem 'atlassian-jwt', '~> 0.2.0'
# Flowdock integration
gem 'flowdock', '~> 0.7'
@@ -295,7 +296,8 @@ gem 'gettext', '~> 3.2.2', require: false, group: :development
gem 'batch-loader', '~> 1.4.0'
# Perf bar
-gem 'peek', '~> 1.0.1'
+# https://gitlab.com/gitlab-org/gitlab-ee/issues/13996
+gem 'gitlab-peek', '~> 0.0.1', require: 'peek'
# Snowplow events tracking
gem 'snowplow-tracker', '~> 0.6.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index dac68eac5b0..f0b3d722326 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -74,6 +74,8 @@ GEM
asciidoctor-plantuml (0.0.9)
asciidoctor (>= 1.5.6, < 3.0.0)
ast (2.4.0)
+ atlassian-jwt (0.2.0)
+ jwt (~> 2.1.0)
attr_encrypted (3.1.0)
encryptor (~> 3.0.0)
attr_required (1.0.1)
@@ -147,8 +149,6 @@ GEM
adamantium (~> 0.2.0)
equalizer (~> 0.0.9)
concurrent-ruby (1.1.5)
- concurrent-ruby-ext (1.1.5)
- concurrent-ruby (= 1.1.5)
connection_pool (2.2.2)
contracts (0.11.0)
crack (0.4.3)
@@ -319,7 +319,9 @@ GEM
opentracing (~> 0.4)
redis (> 3.0.0, < 5.0.0)
gitlab-markup (1.7.0)
- gitlab-sidekiq-fetcher (0.5.1)
+ gitlab-peek (0.0.1)
+ railties (>= 4.0.0)
+ gitlab-sidekiq-fetcher (0.5.2)
sidekiq (~> 5)
gitlab-styles (2.8.0)
rubocop (~> 0.69.0)
@@ -444,8 +446,9 @@ GEM
opentracing (~> 0.3)
thrift
jaro_winkler (1.5.3)
- jira-ruby (1.4.1)
+ jira-ruby (1.7.1)
activesupport
+ atlassian-jwt
multipart-post
oauth (~> 0.5, >= 0.5.0)
js_regex (3.1.1)
@@ -632,10 +635,6 @@ GEM
parser (2.6.3.0)
ast (~> 2.4.0)
parslet (1.8.2)
- peek (1.0.1)
- concurrent-ruby (>= 0.9.0)
- concurrent-ruby-ext (>= 0.9.0)
- railties (>= 4.0.0)
pg (1.1.4)
po_to_json (1.0.1)
json (>= 1.6.0)
@@ -1029,6 +1028,7 @@ DEPENDENCIES
asciidoctor (~> 2.0.10)
asciidoctor-include-ext (~> 0.3.1)
asciidoctor-plantuml (= 0.0.9)
+ atlassian-jwt (~> 0.2.0)
attr_encrypted (~> 3.1.0)
awesome_print
babosa (~> 1.0.2)
@@ -1097,7 +1097,8 @@ DEPENDENCIES
github-markup (~> 1.7.0)
gitlab-labkit (~> 0.5)
gitlab-markup (~> 1.7.0)
- gitlab-sidekiq-fetcher (= 0.5.1)
+ gitlab-peek (~> 0.0.1)
+ gitlab-sidekiq-fetcher (= 0.5.2)
gitlab-styles (~> 2.7)
gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.2)
@@ -1124,7 +1125,7 @@ DEPENDENCIES
icalendar
influxdb (~> 0.2)
invisible_captcha (~> 0.12.1)
- jira-ruby (~> 1.4)
+ jira-ruby (~> 1.7)
js_regex (~> 3.1)
json-schema (~> 2.8.0)
jwt (~> 2.1.0)
@@ -1167,7 +1168,6 @@ DEPENDENCIES
omniauth_crowd (~> 2.2.0)
omniauth_openid_connect (~> 0.3.1)
org-ruby (~> 0.9.12)
- peek (~> 1.0.1)
pg (~> 1.1)
premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.9.8)
diff --git a/app/assets/javascripts/behaviors/markdown/render_math.js b/app/assets/javascripts/behaviors/markdown/render_math.js
index a68936d79e2..b5dbdbb7e86 100644
--- a/app/assets/javascripts/behaviors/markdown/render_math.js
+++ b/app/assets/javascripts/behaviors/markdown/render_math.js
@@ -1,6 +1,5 @@
-import $ from 'jquery';
-import { __ } from '~/locale';
import flash from '~/flash';
+import { s__, sprintf } from '~/locale';
// Renders math using KaTeX in any element with the
// `js-render-math` class
@@ -10,21 +9,131 @@ import flash from '~/flash';
// <code class="js-render-math"></div>
//
-// Loop over all math elements and render math
-function renderWithKaTeX(elements, katex) {
- elements.each(function katexElementsLoop() {
- const mathNode = $('<span></span>');
- const $this = $(this);
-
- const display = $this.attr('data-math-style') === 'display';
- try {
- katex.render($this.text(), mathNode.get(0), { displayMode: display, throwOnError: false });
- mathNode.insertAfter($this);
- $this.remove();
- } catch (err) {
- throw err;
+const MAX_MATH_CHARS = 1000;
+const MAX_RENDER_TIME_MS = 2000;
+
+// These messages might be used with inline errors in the future. Keep them around. For now, we will
+// display a single error message using flash().
+
+// const CHAR_LIMIT_EXCEEDED_MSG = sprintf(
+// s__(
+// 'math|The following math is too long. For performance reasons, math blocks are limited to %{maxChars} characters. Try splitting up this block, or include an image instead.',
+// ),
+// { maxChars: MAX_MATH_CHARS },
+// );
+// const RENDER_TIME_EXCEEDED_MSG = s__(
+// "math|The math in this entry is taking too long to render. Any math below this point won't be shown. Consider splitting it among multiple entries.",
+// );
+
+const RENDER_FLASH_MSG = sprintf(
+ s__(
+ 'math|The math in this entry is taking too long to render and may not be displayed as expected. For performance reasons, math blocks are also limited to %{maxChars} characters. Consider splitting up large formulae, splitting math blocks among multiple entries, or using an image instead.',
+ ),
+ { maxChars: MAX_MATH_CHARS },
+);
+
+// Wait for the browser to reflow the layout. Reflowing SVG takes time.
+// This has to wrap the inner function, otherwise IE/Edge throw "invalid calling object".
+const waitForReflow = fn => {
+ window.requestAnimationFrame(fn);
+};
+
+/**
+ * Renders math blocks sequentially while protecting against DoS attacks. Math blocks have a maximum character limit of MAX_MATH_CHARS. If rendering math takes longer than MAX_RENDER_TIME_MS, all subsequent math blocks are skipped and an error message is shown.
+ */
+class SafeMathRenderer {
+ /*
+ How this works:
+
+ The performance bottleneck in rendering math is in the browser trying to reflow the generated SVG.
+ During this time, the JS is blocked and the page becomes unresponsive.
+ We want to render math blocks one by one until a certain time is exceeded, after which we stop
+ rendering subsequent math blocks, to protect against DoS. However, browsers do reflowing in an
+ asynchronous task, so we can't time it synchronously.
+
+ SafeMathRenderer essentially does the following:
+ 1. Replaces all math blocks with placeholders so that they're not mistakenly rendered twice.
+ 2. Places each placeholder element in a queue.
+ 3. Renders the element at the head of the queue and waits for reflow.
+ 4. After reflow, gets the elapsed time since step 3 and repeats step 3 until the queue is empty.
+ */
+ queue = [];
+ totalMS = 0;
+
+ constructor(elements, katex) {
+ this.elements = elements;
+ this.katex = katex;
+
+ this.renderElement = this.renderElement.bind(this);
+ this.render = this.render.bind(this);
+ }
+
+ renderElement() {
+ if (!this.queue.length) {
+ return;
}
- });
+
+ const el = this.queue.shift();
+ const text = el.textContent;
+
+ el.removeAttribute('style');
+
+ if (this.totalMS >= MAX_RENDER_TIME_MS || text.length > MAX_MATH_CHARS) {
+ if (!this.flashShown) {
+ flash(RENDER_FLASH_MSG);
+ this.flashShown = true;
+ }
+
+ // Show unrendered math code
+ const codeElement = document.createElement('pre');
+ codeElement.className = 'code';
+ codeElement.textContent = el.textContent;
+ el.parentNode.replaceChild(codeElement, el);
+
+ // Render the next math
+ this.renderElement();
+ } else {
+ this.startTime = Date.now();
+
+ try {
+ el.innerHTML = this.katex.renderToString(text, {
+ displayMode: el.getAttribute('data-math-style') === 'display',
+ throwOnError: true,
+ maxSize: 20,
+ maxExpand: 20,
+ });
+ } catch (e) {
+ // Don't show a flash for now because it would override an existing flash message
+ el.textContent = s__('math|There was an error rendering this math block');
+ // el.style.color = '#d00';
+ el.className = 'katex-error';
+ }
+
+ // Give the browser time to reflow the svg
+ waitForReflow(() => {
+ const deltaTime = Date.now() - this.startTime;
+ this.totalMS += deltaTime;
+
+ this.renderElement();
+ });
+ }
+ }
+
+ render() {
+ // Replace math blocks with a placeholder so they aren't rendered twice
+ this.elements.forEach(el => {
+ const placeholder = document.createElement('span');
+ placeholder.style.display = 'none';
+ placeholder.setAttribute('data-math-style', el.getAttribute('data-math-style'));
+ placeholder.textContent = el.textContent;
+ el.parentNode.replaceChild(placeholder, el);
+ this.queue.push(placeholder);
+ });
+
+ // If we wait for the browser thread to settle down a bit, math rendering becomes 5-10x faster
+ // and less prone to timeouts.
+ setTimeout(this.renderElement, 400);
+ }
}
export default function renderMath($els) {
@@ -34,7 +143,8 @@ export default function renderMath($els) {
import(/* webpackChunkName: 'katex' */ 'katex/dist/katex.min.css'),
])
.then(([katex]) => {
- renderWithKaTeX($els, katex);
+ const renderer = new SafeMathRenderer($els.get(), katex);
+ renderer.render();
})
- .catch(() => flash(__('An error occurred while rendering KaTeX')));
+ .catch(() => {});
}
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.vue b/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
index b84722244d1..71e5d8058da 100644
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
@@ -1,10 +1,10 @@
<script>
-import Vue from 'vue';
+import axios from '~/lib/utils/axios_utils';
import Flash from '../../../flash';
import { __ } from '../../../locale';
import boardsStore from '../../stores/boards_store';
-export default Vue.extend({
+export default {
props: {
issue: {
type: Object,
@@ -35,7 +35,7 @@ export default Vue.extend({
}
// Post the remove data
- Vue.http.patch(this.updateUrl, data).catch(() => {
+ axios.patch(this.updateUrl, data).catch(() => {
Flash(__('Failed to remove issue from board, please try again.'));
lists.forEach(list => {
@@ -71,7 +71,7 @@ export default Vue.extend({
return req;
},
},
-});
+};
</script>
<template>
<div class="block list">
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index 9069b35db9a..086340105b7 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -3,7 +3,7 @@
/* global ListMilestone */
/* global ListAssignee */
-import Vue from 'vue';
+import axios from '~/lib/utils/axios_utils';
import './label';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import IssueProject from './project';
@@ -133,7 +133,7 @@ class ListIssue {
}
const projectPath = this.project ? this.project.path : '';
- return Vue.http.patch(`${this.path}.json`, data).then(({ body = {} } = {}) => {
+ return axios.patch(`${this.path}.json`, data).then(({ data: body = {} } = {}) => {
/**
* Since post implementation of Scoped labels, server can reject
* same key-ed labels. To keep the UI and server Model consistent,
diff --git a/app/assets/javascripts/branches/divergence_graph.js b/app/assets/javascripts/branches/divergence_graph.js
index 7dbaf984acf..303735a1807 100644
--- a/app/assets/javascripts/branches/divergence_graph.js
+++ b/app/assets/javascripts/branches/divergence_graph.js
@@ -25,6 +25,11 @@ export default endpoint => {
const names = [...document.querySelectorAll('.js-branch-item')].map(
({ dataset }) => dataset.name,
);
+
+ if (names.length === 0) {
+ return true;
+ }
+
return axios
.get(endpoint, {
params: { names },
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index ada5a49e246..772f16cab4e 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -55,7 +55,7 @@ export default class ClusterStore {
...applicationInitialState,
title: s__('ClusterIntegration|GitLab Runner'),
version: null,
- chartRepo: 'https://gitlab.com/charts/gitlab-runner',
+ chartRepo: 'https://gitlab.com/gitlab-org/charts/gitlab-runner',
updateAvailable: null,
updateSuccessful: false,
updateFailed: false,
diff --git a/app/assets/javascripts/droplab/drop_lab.js b/app/assets/javascripts/droplab/drop_lab.js
index 1339e28d8b8..33c05404493 100644
--- a/app/assets/javascripts/droplab/drop_lab.js
+++ b/app/assets/javascripts/droplab/drop_lab.js
@@ -60,7 +60,7 @@ class DropLab {
addEvents() {
this.eventWrapper.documentClicked = this.documentClicked.bind(this);
- document.addEventListener('click', this.eventWrapper.documentClicked);
+ document.addEventListener('mousedown', this.eventWrapper.documentClicked);
}
documentClicked(e) {
@@ -74,7 +74,7 @@ class DropLab {
}
removeEvents() {
- document.removeEventListener('click', this.eventWrapper.documentClicked);
+ document.removeEventListener('mousedown', this.eventWrapper.documentClicked);
}
changeHookList(trigger, list, plugins, config) {
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index 95e1e8af9b3..1d4a6e64f9d 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -111,12 +111,7 @@ export default {
* @returns {Boolean|Undefined}
*/
canShowDate() {
- return (
- this.model &&
- this.model.last_deployment &&
- this.model.last_deployment.deployable &&
- this.model.last_deployment.deployable !== undefined
- );
+ return this.model && this.model.last_deployment && this.model.last_deployment.deployed_at;
},
/**
@@ -124,14 +119,9 @@ export default {
*
* @returns {String}
*/
- createdDate() {
- if (
- this.model &&
- this.model.last_deployment &&
- this.model.last_deployment.deployable &&
- this.model.last_deployment.deployable.created_at
- ) {
- return timeagoInstance.format(this.model.last_deployment.deployable.created_at);
+ deployedDate() {
+ if (this.canShowDate) {
+ return timeagoInstance.format(this.model.last_deployment.deployed_at);
}
return '';
},
@@ -547,7 +537,7 @@ export default {
<div v-if="!model.isFolder" class="table-section section-10" role="gridcell">
<div role="rowheader" class="table-mobile-header">{{ s__('Environments|Updated') }}</div>
<span v-if="canShowDate" class="environment-created-date-timeago table-mobile-content">
- {{ createdDate }}
+ {{ deployedDate }}
</span>
</div>
diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js
index c2397842125..660f0f0ba3e 100644
--- a/app/assets/javascripts/flash.js
+++ b/app/assets/javascripts/flash.js
@@ -1,4 +1,5 @@
import _ from 'underscore';
+import { spriteIcon } from './lib/utils/common_utils';
const hideFlash = (flashEl, fadeTransition = true) => {
if (fadeTransition) {
@@ -35,16 +36,11 @@ const createAction = config => `
</a>
`;
-const createFlashEl = (message, type, isFixedLayout = false) => `
- <div
- class="flash-${type}"
- >
- <div
- class="flash-text ${
- isFixedLayout ? 'container-fluid container-limited limit-container-width' : ''
- }"
- >
+const createFlashEl = (message, type) => `
+ <div class="flash-content flash-${type} rounded">
+ <div class="flash-text">
${_.escape(message)}
+ ${spriteIcon('close', 'close-icon')}
</div>
</div>
`;
@@ -76,15 +72,10 @@ const createFlash = function createFlash(
addBodyClass = false,
) {
const flashContainer = parent.querySelector('.flash-container');
- const navigation = parent.querySelector('.content');
if (!flashContainer) return null;
- const isFixedLayout = navigation
- ? navigation.parentNode.classList.contains('container-limited')
- : true;
-
- flashContainer.innerHTML = createFlashEl(message, type, isFixedLayout);
+ flashContainer.innerHTML = createFlashEl(message, type);
const flashEl = flashContainer.querySelector(`.flash-${type}`);
removeFlashClickListener(flashEl, fadeTransition);
diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue
index aa50fd8ff62..8d2dac47ff2 100644
--- a/app/assets/javascripts/groups/components/app.vue
+++ b/app/assets/javascripts/groups/components/app.vue
@@ -95,10 +95,8 @@ export default {
if (updatePagination) {
this.updatePagination(res.headers);
}
-
- return res;
+ return res.data;
})
- .then(res => res.json())
.catch(() => {
this.isLoading = false;
$.scrollTo(0);
@@ -190,11 +188,10 @@ export default {
this.targetGroup.isBeingRemoved = true;
this.service
.leaveGroup(this.targetGroup.leavePath)
- .then(res => res.json())
.then(res => {
$.scrollTo(0);
this.store.removeGroup(this.targetGroup, this.targetParentGroup);
- Flash(res.notice, 'notice');
+ Flash(res.data.notice, 'notice');
})
.catch(err => {
let message = COMMON_STR.FAILURE;
diff --git a/app/assets/javascripts/groups/service/groups_service.js b/app/assets/javascripts/groups/service/groups_service.js
index b79ba291463..790b581a7c0 100644
--- a/app/assets/javascripts/groups/service/groups_service.js
+++ b/app/assets/javascripts/groups/service/groups_service.js
@@ -1,40 +1,39 @@
-import Vue from 'vue';
-import '../../vue_shared/vue_resource_interceptor';
+import axios from '~/lib/utils/axios_utils';
export default class GroupsService {
constructor(endpoint) {
- this.groups = Vue.resource(endpoint);
+ this.endpoint = endpoint;
}
getGroups(parentId, page, filterGroups, sort, archived) {
- const data = {};
+ const params = {};
if (parentId) {
- data.parent_id = parentId;
+ params.parent_id = parentId;
} else {
// Do not send the following param for sub groups
if (page) {
- data.page = page;
+ params.page = page;
}
if (filterGroups) {
- data.filter = filterGroups;
+ params.filter = filterGroups;
}
if (sort) {
- data.sort = sort;
+ params.sort = sort;
}
if (archived) {
- data.archived = archived;
+ params.archived = archived;
}
}
- return this.groups.get(data);
+ return axios.get(this.endpoint, { params });
}
// eslint-disable-next-line class-methods-use-this
leaveGroup(endpoint) {
- return Vue.http.delete(endpoint);
+ return axios.delete(endpoint);
}
}
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
index 8b356ee6e97..549324831e9 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
@@ -69,7 +69,11 @@ export default {
:disabled="currentBranch && !currentBranch.can_push"
:title="$options.currentBranchPermissionsTooltip"
>
- <span class="ide-radio-label" v-html="commitToCurrentBranchText"> </span>
+ <span
+ class="ide-radio-label"
+ data-qa-selector="commit_to_current_branch_radio"
+ v-html="commitToCurrentBranchText"
+ ></span>
</radio-group>
<radio-group
:value="$options.commitToNewBranch"
diff --git a/app/assets/javascripts/ide/components/error_message.vue b/app/assets/javascripts/ide/components/error_message.vue
index 22113692968..500f6737839 100644
--- a/app/assets/javascripts/ide/components/error_message.vue
+++ b/app/assets/javascripts/ide/components/error_message.vue
@@ -44,7 +44,7 @@ export default {
<template>
<div class="flash-container flash-container-page" @click="clickFlash">
- <div class="flash-alert">
+ <div class="flash-alert" data-qa-selector="flash_alert">
<span v-html="message.text"> </span>
<button
v-if="message.action"
diff --git a/app/assets/javascripts/ide/components/file_row_extra.vue b/app/assets/javascripts/ide/components/file_row_extra.vue
index 7254c50a568..48be97c8952 100644
--- a/app/assets/javascripts/ide/components/file_row_extra.vue
+++ b/app/assets/javascripts/ide/components/file_row_extra.vue
@@ -86,7 +86,7 @@ export default {
v-else-if="showChangedFileIcon"
:file="file"
:show-tooltip="true"
- :show-staged-icon="true"
+ :show-staged-icon="false"
/>
<new-dropdown
:type="file.type"
diff --git a/app/assets/javascripts/ide/components/panes/right.vue b/app/assets/javascripts/ide/components/panes/right.vue
index 2e6bd85feec..200391282e7 100644
--- a/app/assets/javascripts/ide/components/panes/right.vue
+++ b/app/assets/javascripts/ide/components/panes/right.vue
@@ -89,7 +89,7 @@ export default {
</script>
<template>
- <div class="multi-file-commit-panel ide-right-sidebar">
+ <div class="multi-file-commit-panel ide-right-sidebar" data-qa-selector="ide_right_sidebar">
<resizable-panel
v-show="isOpen"
:collapsible="false"
@@ -120,6 +120,7 @@ export default {
}"
data-container="body"
data-placement="left"
+ :data-qa-selector="`${tab.title.toLowerCase()}_tab_button`"
class="ide-sidebar-link is-right"
type="button"
@click="clickTab($event, tab)"
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index 9ca38d6bbfa..88975c2cc73 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -300,9 +300,9 @@ export default {
this.closeRecaptcha();
},
- deleteIssuable() {
+ deleteIssuable(payload) {
this.service
- .deleteIssuable()
+ .deleteIssuable(payload)
.then(res => res.data)
.then(data => {
// Stop the poll so we don't get 404's with the issuable not existing
diff --git a/app/assets/javascripts/issue_show/components/edit_actions.vue b/app/assets/javascripts/issue_show/components/edit_actions.vue
index eb51a074f84..ce867f16acf 100644
--- a/app/assets/javascripts/issue_show/components/edit_actions.vue
+++ b/app/assets/javascripts/issue_show/components/edit_actions.vue
@@ -55,7 +55,7 @@ export default {
if (window.confirm(confirmMessage)) {
this.deleteLoading = true;
- eventHub.$emit('delete.issuable');
+ eventHub.$emit('delete.issuable', { destroy_confirm: true });
}
},
},
diff --git a/app/assets/javascripts/issue_show/services/index.js b/app/assets/javascripts/issue_show/services/index.js
index 9546eb22c27..3c8334bee50 100644
--- a/app/assets/javascripts/issue_show/services/index.js
+++ b/app/assets/javascripts/issue_show/services/index.js
@@ -10,8 +10,8 @@ export default class Service {
return axios.get(this.realtimeEndpoint);
}
- deleteIssuable() {
- return axios.delete(this.endpoint);
+ deleteIssuable(payload) {
+ return axios.delete(this.endpoint, { params: payload });
}
updateIssuable(data) {
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index 8da87f424c4..ad1072366f3 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -12,7 +12,6 @@ import createStore from '../store';
import EmptyState from './empty_state.vue';
import EnvironmentsBlock from './environments_block.vue';
import ErasedBlock from './erased_block.vue';
-import Log from './job_log.vue';
import LogTopBar from './job_log_controllers.vue';
import StuckBlock from './stuck_block.vue';
import UnmetPrerequisitesBlock from './unmet_prerequisites_block.vue';
@@ -30,7 +29,10 @@ export default {
EnvironmentsBlock,
ErasedBlock,
Icon,
- Log,
+ Log: () =>
+ gon && gon.features && gon.features.jobLogJson
+ ? import('./job_log_json.vue')
+ : import('./job_log.vue'),
LogTopBar,
StuckBlock,
UnmetPrerequisitesBlock,
diff --git a/app/assets/javascripts/jobs/components/job_log_json.vue b/app/assets/javascripts/jobs/components/job_log_json.vue
new file mode 100644
index 00000000000..2198b20eb8f
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/job_log_json.vue
@@ -0,0 +1,10 @@
+<script>
+export default {
+ name: 'JobLogJSON',
+};
+</script>
+<template>
+ <pre>
+ {{ __('This feature is in development. Please disable the `job_log_json` feature flag') }}
+ </pre>
+</template>
diff --git a/app/assets/javascripts/jobs/store/utils.js b/app/assets/javascripts/jobs/store/utils.js
new file mode 100644
index 00000000000..de7de92ed2e
--- /dev/null
+++ b/app/assets/javascripts/jobs/store/utils.js
@@ -0,0 +1,40 @@
+/**
+ * Parses the job log content into a structure usable by the template
+ *
+ * For collaspible lines (section_header = true):
+ * - creates a new array to hold the lines that are collpasible,
+ * - adds a isClosed property to handle toggle
+ * - adds a isHeader property to handle template logic
+ * For each line:
+ * - adds the index as lineNumber
+ *
+ * @param {Array} lines
+ * @returns {Array}
+ */
+export default (lines = []) =>
+ lines.reduce((acc, line, index) => {
+ if (line.section_header) {
+ acc.push({
+ isClosed: true,
+ isHeader: true,
+ line: {
+ ...line,
+ lineNumber: index,
+ },
+
+ lines: [],
+ });
+ } else if (acc.length && acc[acc.length - 1].isHeader) {
+ acc[acc.length - 1].lines.push({
+ ...line,
+ lineNumber: index,
+ });
+ } else {
+ acc.push({
+ ...line,
+ lineNumber: index,
+ });
+ }
+
+ return acc;
+ }, []);
diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue
deleted file mode 100644
index cac10474d06..00000000000
--- a/app/assets/javascripts/monitoring/components/charts/area.vue
+++ /dev/null
@@ -1,304 +0,0 @@
-<script>
-import { __ } from '~/locale';
-import { GlLink } from '@gitlab/ui';
-import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
-import dateFormat from 'dateformat';
-import { debounceByAnimationFrame, roundOffFloat } from '~/lib/utils/common_utils';
-import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
-import Icon from '~/vue_shared/components/icon.vue';
-import { chartHeight, graphTypes, lineTypes } from '../../constants';
-import { makeDataSeries } from '~/helpers/monitor_helper';
-import { graphDataValidatorForValues } from '../../utils';
-
-let debouncedResize;
-
-// TODO: Remove this component in favor of the more general time_series.vue
-// Please port all changes here to time_series.vue as well.
-
-export default {
- components: {
- GlAreaChart,
- GlChartSeriesLabel,
- GlLink,
- Icon,
- },
- inheritAttrs: false,
- props: {
- graphData: {
- type: Object,
- required: true,
- validator: graphDataValidatorForValues.bind(null, false),
- },
- containerWidth: {
- type: Number,
- required: true,
- },
- deploymentData: {
- type: Array,
- required: false,
- default: () => [],
- },
- projectPath: {
- type: String,
- required: false,
- default: () => '',
- },
- showBorder: {
- type: Boolean,
- required: false,
- default: () => false,
- },
- singleEmbed: {
- type: Boolean,
- required: false,
- default: false,
- },
- thresholds: {
- type: Array,
- required: false,
- default: () => [],
- },
- },
- data() {
- return {
- tooltip: {
- title: '',
- content: [],
- commitUrl: '',
- isDeployment: false,
- sha: '',
- },
- width: 0,
- height: chartHeight,
- svgs: {},
- primaryColor: null,
- };
- },
- computed: {
- chartData() {
- // Transforms & supplements query data to render appropriate labels & styles
- // Input: [{ queryAttributes1 }, { queryAttributes2 }]
- // Output: [{ seriesAttributes1 }, { seriesAttributes2 }]
- return this.graphData.queries.reduce((acc, query) => {
- const { appearance } = query;
- const lineType =
- appearance && appearance.line && appearance.line.type
- ? appearance.line.type
- : lineTypes.default;
- const lineWidth =
- appearance && appearance.line && appearance.line.width
- ? appearance.line.width
- : undefined;
-
- const series = makeDataSeries(query.result, {
- name: this.formatLegendLabel(query),
- lineStyle: {
- type: lineType,
- width: lineWidth,
- },
- areaStyle: {
- opacity:
- appearance && appearance.area && typeof appearance.area.opacity === 'number'
- ? appearance.area.opacity
- : undefined,
- },
- });
-
- return acc.concat(series);
- }, []);
- },
- chartOptions() {
- return {
- xAxis: {
- name: __('Time'),
- type: 'time',
- axisLabel: {
- formatter: date => dateFormat(date, 'h:MM TT'),
- },
- axisPointer: {
- snap: true,
- },
- },
- yAxis: {
- name: this.yAxisLabel,
- axisLabel: {
- formatter: num => roundOffFloat(num, 3).toString(),
- },
- },
- series: this.scatterSeries,
- dataZoom: [this.dataZoomConfig],
- };
- },
- dataZoomConfig() {
- const handleIcon = this.svgs['scroll-handle'];
-
- return handleIcon ? { handleIcon } : {};
- },
- earliestDatapoint() {
- return this.chartData.reduce((acc, series) => {
- const { data } = series;
- const { length } = data;
- if (!length) {
- return acc;
- }
-
- const [first] = data[0];
- const [last] = data[length - 1];
- const seriesEarliest = first < last ? first : last;
-
- return seriesEarliest < acc || acc === null ? seriesEarliest : acc;
- }, null);
- },
- isMultiSeries() {
- return this.tooltip.content.length > 1;
- },
- recentDeployments() {
- return this.deploymentData.reduce((acc, deployment) => {
- if (deployment.created_at >= this.earliestDatapoint) {
- acc.push({
- id: deployment.id,
- createdAt: deployment.created_at,
- sha: deployment.sha,
- commitUrl: `${this.projectPath}/commit/${deployment.sha}`,
- tag: deployment.tag,
- tagUrl: deployment.tag ? `${this.tagsPath}/${deployment.ref.name}` : null,
- ref: deployment.ref.name,
- showDeploymentFlag: false,
- });
- }
-
- return acc;
- }, []);
- },
- scatterSeries() {
- return {
- type: graphTypes.deploymentData,
- data: this.recentDeployments.map(deployment => [deployment.createdAt, 0]),
- symbol: this.svgs.rocket,
- symbolSize: 14,
- itemStyle: {
- color: this.primaryColor,
- },
- };
- },
- yAxisLabel() {
- return `${this.graphData.y_label}`;
- },
- },
- watch: {
- containerWidth: 'onResize',
- },
- beforeDestroy() {
- window.removeEventListener('resize', debouncedResize);
- },
- created() {
- debouncedResize = debounceByAnimationFrame(this.onResize);
- window.addEventListener('resize', debouncedResize);
- this.setSvg('rocket');
- this.setSvg('scroll-handle');
- },
- methods: {
- formatLegendLabel(query) {
- return `${query.label}`;
- },
- formatTooltipText(params) {
- this.tooltip.title = dateFormat(params.value, 'dd mmm yyyy, h:MMTT');
- this.tooltip.content = [];
- params.seriesData.forEach(seriesData => {
- this.tooltip.isDeployment = seriesData.componentSubType === graphTypes.deploymentData;
- if (this.tooltip.isDeployment) {
- const [deploy] = this.recentDeployments.filter(
- deployment => deployment.createdAt === seriesData.value[0],
- );
- this.tooltip.sha = deploy.sha.substring(0, 8);
- this.tooltip.commitUrl = deploy.commitUrl;
- } else {
- const { seriesName, color } = seriesData;
- // seriesData.value contains the chart's [x, y] value pair
- // seriesData.value[1] is threfore the chart y value
- const value = seriesData.value[1].toFixed(3);
-
- this.tooltip.content.push({
- name: seriesName,
- value,
- color,
- });
- }
- });
- },
- setSvg(name) {
- getSvgIconPathContent(name)
- .then(path => {
- if (path) {
- this.$set(this.svgs, name, `path://${path}`);
- }
- })
- .catch(() => {});
- },
- onChartUpdated(chart) {
- [this.primaryColor] = chart.getOption().color;
- },
- onResize() {
- if (!this.$refs.areaChart) return;
- const { width } = this.$refs.areaChart.$el.getBoundingClientRect();
- this.width = width;
- },
- },
-};
-</script>
-
-<template>
- <div
- class="prometheus-graph col-12"
- :class="[showBorder ? 'p-2' : 'p-0', { 'col-lg-6': !singleEmbed }]"
- >
- <div :class="{ 'prometheus-graph-embed w-100 p-3': showBorder }">
- <div class="prometheus-graph-header">
- <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5>
- <div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div>
- </div>
- <gl-area-chart
- ref="areaChart"
- v-bind="$attrs"
- :data="chartData"
- :option="chartOptions"
- :format-tooltip-text="formatTooltipText"
- :thresholds="thresholds"
- :width="width"
- :height="height"
- @updated="onChartUpdated"
- >
- <template v-if="tooltip.isDeployment">
- <template slot="tooltipTitle">
- {{ __('Deployed') }}
- </template>
- <div slot="tooltipContent" class="d-flex align-items-center">
- <icon name="commit" class="mr-2" />
- <gl-link :href="tooltip.commitUrl">{{ tooltip.sha }}</gl-link>
- </div>
- </template>
- <template v-else>
- <template slot="tooltipTitle">
- <div class="text-nowrap">
- {{ tooltip.title }}
- </div>
- </template>
- <template slot="tooltipContent">
- <div
- v-for="(content, key) in tooltip.content"
- :key="key"
- class="d-flex justify-content-between"
- >
- <gl-chart-series-label :color="isMultiSeries ? content.color : ''">
- {{ content.name }}
- </gl-chart-series-label>
- <div class="prepend-left-32">
- {{ content.value }}
- </div>
- </div>
- </template>
- </template>
- </gl-area-chart>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
index 820f0f7f12d..0d377eb9c68 100644
--- a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
+++ b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
@@ -5,9 +5,10 @@ import { parseBoolean } from '~/lib/utils/common_utils';
document.addEventListener('DOMContentLoaded', () => {
const twoFactorNode = document.querySelector('.js-two-factor-auth');
const skippable = parseBoolean(twoFactorNode.dataset.twoFactorSkippable);
+
if (skippable) {
const button = `<a class="btn btn-sm btn-warning float-right" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`;
- const flashAlert = document.querySelector('.flash-alert .container-fluid');
+ const flashAlert = document.querySelector('.flash-alert');
if (flashAlert) flashAlert.insertAdjacentHTML('beforeend', button);
}
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index a223a8f5b08..ea867d30ce8 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -144,6 +144,10 @@ export default {
visibilityLevelDescription() {
return visibilityLevelDescriptions[this.visibilityLevel];
},
+
+ showContainerRegistryPublicNote() {
+ return this.visibilityLevel === visibilityOptions.PUBLIC;
+ },
},
watch: {
@@ -286,6 +290,9 @@ export default {
label="Container registry"
help-text="Every project can have its own space to store its Docker images"
>
+ <div v-if="showContainerRegistryPublicNote" class="text-muted">
+ {{ __('Note: the container registry is always visible when a project is public') }}
+ </div>
<project-feature-toggle
v-model="containerRegistryEnabled"
:disabled-input="!repositoryEnabled"
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
index 60d3d83a4b2..765cb868f80 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/project_find_file.js
@@ -113,7 +113,7 @@ export default class ProjectFindFile {
if (searchText) {
matches = fuzzaldrinPlus.match(filePath, searchText);
}
- blobItemUrl = this.options.blobUrlTemplate + '/' + filePath;
+ blobItemUrl = this.options.blobUrlTemplate + '/' + encodeURIComponent(filePath);
html = ProjectFindFile.makeHtml(filePath, matches, blobItemUrl);
results.push(this.element.find('.tree-table > tbody').append(html));
}
diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue
index 7580c2d0ad0..88b6b4732b1 100644
--- a/app/assets/javascripts/releases/components/release_block.vue
+++ b/app/assets/javascripts/releases/components/release_block.vue
@@ -53,7 +53,7 @@ export default {
};
</script>
<template>
- <div class="card">
+ <div :id="release.tag_name" class="card">
<div class="card-body">
<h2 class="card-title mt-0">
{{ release.name }}
diff --git a/app/assets/javascripts/vue_merge_request_widget/constants.js b/app/assets/javascripts/vue_merge_request_widget/constants.js
index 3e65bdf0cb0..6f6d145815e 100644
--- a/app/assets/javascripts/vue_merge_request_widget/constants.js
+++ b/app/assets/javascripts/vue_merge_request_widget/constants.js
@@ -5,11 +5,7 @@ export const WARNING_MESSAGE_CLASS = 'warning_message';
export const DANGER_MESSAGE_CLASS = 'danger_message';
export const MWPS_MERGE_STRATEGY = 'merge_when_pipeline_succeeds';
-export const ATMTWPS_MERGE_STRATEGY = 'add_to_merge_train_when_pipeline_succeeds';
+export const MTWPS_MERGE_STRATEGY = 'add_to_merge_train_when_pipeline_succeeds';
export const MT_MERGE_STRATEGY = 'merge_train';
-export const AUTO_MERGE_STRATEGIES = [
- MWPS_MERGE_STRATEGY,
- ATMTWPS_MERGE_STRATEGY,
- MT_MERGE_STRATEGY,
-];
+export const AUTO_MERGE_STRATEGIES = [MWPS_MERGE_STRATEGY, MTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY];
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index 7843409f4a7..699d41494bf 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -3,7 +3,7 @@ import _ from 'underscore';
import getStateKey from 'ee_else_ce/vue_merge_request_widget/stores/get_state_key';
import { stateKey } from './state_maps';
import { formatDate } from '../../lib/utils/datetime_utility';
-import { ATMTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY, MWPS_MERGE_STRATEGY } from '../constants';
+import { MTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY, MWPS_MERGE_STRATEGY } from '../constants';
export default class MergeRequestStore {
constructor(data) {
@@ -217,8 +217,8 @@ export default class MergeRequestStore {
}
static getPreferredAutoMergeStrategy(availableAutoMergeStrategies) {
- if (_.includes(availableAutoMergeStrategies, ATMTWPS_MERGE_STRATEGY)) {
- return ATMTWPS_MERGE_STRATEGY;
+ if (_.includes(availableAutoMergeStrategies, MTWPS_MERGE_STRATEGY)) {
+ return MTWPS_MERGE_STRATEGY;
} else if (_.includes(availableAutoMergeStrategies, MT_MERGE_STRATEGY)) {
return MT_MERGE_STRATEGY;
} else if (_.includes(availableAutoMergeStrategies, MWPS_MERGE_STRATEGY)) {
diff --git a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
index beb2ac09992..a97538d813a 100644
--- a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
@@ -24,7 +24,7 @@ export default {
showStagedIcon: {
type: Boolean,
required: false,
- default: false,
+ default: true,
},
size: {
type: Number,
@@ -41,7 +41,7 @@ export default {
changedIcon() {
// False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
- const suffix = !this.file.changed && this.file.staged && !this.showStagedIcon ? '-solid' : '';
+ const suffix = !this.file.changed && this.file.staged && this.showStagedIcon ? '-solid' : '';
return `${getCommitIconMap(this.file).icon}${suffix}`;
},
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index 96f6d02a68f..af05d069f97 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -1,3 +1,5 @@
+$notification-box-shadow-color: rgba(0, 0, 0, 0.25);
+
.flash-container {
cursor: pointer;
margin: 0;
@@ -6,12 +8,32 @@
position: relative;
z-index: 1;
+ &.sticky {
+ position: sticky;
+ position: -webkit-sticky;
+ top: $flash-container-top;
+ z-index: 200;
+
+ .flash-content {
+ box-shadow: 0 2px 4px 0 $notification-box-shadow-color;
+ }
+ }
+
+ .close-icon {
+ width: 16px;
+ height: 16px;
+ position: absolute;
+ right: $gl-padding;
+ top: $gl-padding;
+ }
+
.flash-notice,
.flash-alert,
.flash-success,
.flash-warning {
border-radius: $border-radius-default;
color: $white-light;
+ padding-right: $gl-padding * 2;
.container-fluid,
.container-fluid.container-limited {
@@ -97,3 +119,28 @@
}
}
}
+
+.gl-browser-ie .flash-container {
+ position: fixed;
+ max-width: $limited-layout-width;
+ left: 50%;
+
+ .flash-alert {
+ position: relative;
+ left: -50%;
+ }
+}
+
+.with-system-header .flash-container {
+ top: $flash-container-top + $system-header-height;
+}
+
+.with-performance-bar {
+ .flash-container {
+ top: $flash-container-top + $performance-bar-height;
+ }
+
+ &.with-system-header .flash-container {
+ top: $flash-container-top + $performance-bar-height + $system-header-height;
+ }
+}
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 821e6691fe4..69ef116043a 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -245,27 +245,3 @@ label {
.input-group-text {
max-height: $input-height;
}
-
-.gl-form-checkbox {
- align-items: baseline;
- margin-right: 1rem;
- margin-bottom: 0.25rem;
-
- .form-check-input {
- margin-right: 0;
- }
-
- .form-check-label {
- padding-left: $gl-padding-8;
- }
-
- &.form-check-inline .form-check-input {
- align-self: flex-start;
- height: 1.5 * $gl-font-size;
- }
-
- .form-check-input:disabled,
- .form-check-input:disabled ~ .form-check-label {
- cursor: not-allowed;
- }
-}
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index 97cb9d90ff0..7205324e86f 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -29,6 +29,15 @@ body {
}
}
+.container-fluid {
+ &.limit-container-width {
+ .flash-container.sticky {
+ max-width: $limited-layout-width;
+ margin: 0 auto;
+ }
+ }
+}
+
.content-wrapper {
margin-top: $header-height;
padding-bottom: 100px;
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index afcc7f8a1db..33caac4d725 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -384,8 +384,18 @@
@extend .fa-exclamation-circle;
}
}
-}
+ .prometheus-graph-embed {
+ h3.popover-header {
+ /** Override <h3> .popover-header
+ * as embed metrics do not follow the same
+ * style as default md <h3> (which are deeply nested)
+ */
+ margin: 0;
+ font-size: $gl-font-size-small;
+ }
+ }
+}
/**
* Headers
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 7a3fd2adfbb..15a779dde1d 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -377,6 +377,7 @@ $performance-bar-height: 35px;
$system-header-height: 16px;
$system-footer-height: $system-header-height;
$flash-height: 52px;
+$flash-container-top: 48px;
$context-header-height: 60px;
$breadcrumb-min-height: 48px;
$home-panel-title-row-height: 64px;
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 74380ec995a..2d2f0c531c7 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -45,8 +45,7 @@ input[type='checkbox']:hover {
border: 0;
border-radius: $border-radius-default;
transition: border-color ease-in-out $default-transition-duration,
- background-color ease-in-out $default-transition-duration,
- width ease-in-out $default-transition-duration;
+ background-color ease-in-out $default-transition-duration;
@include media-breakpoint-up(xl) {
width: $search-input-xl-width;
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 6317fa7c8d1..32a36da56fe 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -14,7 +14,11 @@ class Admin::GroupsController < Admin::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def show
- @group = Group.with_statistics.joins(:route).group('routes.path').find_by_full_path(params[:id])
+ # Group.with_statistics doesn't behave nicely when including other relations.
+ # Group.find_by_full_path includes the routes relation to avoid a common N+1
+ # (at the expense of this action: there are two queries here to find and retrieve
+ # the Group with statistics).
+ @group = Group.with_statistics.find(group&.id)
@members = present_members(
@group.members.order("access_level DESC").page(params[:members_page]))
@requesters = present_members(
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index af6644b8fcc..2f7ac41781a 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -47,8 +47,8 @@ class ApplicationController < ActionController::Base
# Adds `no-store` to the DEFAULT_CACHE_CONTROL, to prevent security
# concerns due to caching private data.
- DEFAULT_GITLAB_CACHE_CONTROL = "#{ActionDispatch::Http::Cache::Response::DEFAULT_CACHE_CONTROL}, no-store".freeze
- DEFAULT_GITLAB_CONTROL_NO_CACHE = "#{DEFAULT_GITLAB_CACHE_CONTROL}, no-cache".freeze
+ DEFAULT_GITLAB_CACHE_CONTROL = "#{ActionDispatch::Http::Cache::Response::DEFAULT_CACHE_CONTROL}, no-store"
+ DEFAULT_GITLAB_CONTROL_NO_CACHE = "#{DEFAULT_GITLAB_CACHE_CONTROL}, no-cache"
rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception)
@@ -143,7 +143,7 @@ class ApplicationController < ActionController::Base
payload[:username] = logged_user.try(:username)
end
- if response.status == 422 && response.body.present? && response.content_type == 'application/json'.freeze
+ if response.status == 422 && response.body.present? && response.content_type == 'application/json'
payload[:response] = response.body
end
diff --git a/app/controllers/boards/lists_controller.rb b/app/controllers/boards/lists_controller.rb
index ccd02144671..08b4748d7e1 100644
--- a/app/controllers/boards/lists_controller.rb
+++ b/app/controllers/boards/lists_controller.rb
@@ -4,7 +4,7 @@ module Boards
class ListsController < Boards::ApplicationController
include BoardsResponses
- before_action :authorize_admin_list, only: [:create, :update, :destroy, :generate]
+ before_action :authorize_admin_list, only: [:create, :destroy, :generate]
before_action :authorize_read_list, only: [:index]
skip_before_action :authenticate_user!, only: [:index]
@@ -15,7 +15,7 @@ module Boards
end
def create
- list = Boards::Lists::CreateService.new(board.parent, current_user, list_params).execute(board)
+ list = Boards::Lists::CreateService.new(board.parent, current_user, create_list_params).execute(board)
if list.valid?
render json: serialize_as_json(list)
@@ -26,12 +26,13 @@ module Boards
def update
list = board.lists.movable.find(params[:id])
- service = Boards::Lists::MoveService.new(board_parent, current_user, move_params)
+ service = Boards::Lists::UpdateService.new(board_parent, current_user, update_list_params)
+ result = service.execute(list)
- if service.execute(list)
+ if result[:status] == :success
head :ok
else
- head :unprocessable_entity
+ head result[:http_status]
end
end
@@ -50,7 +51,8 @@ module Boards
service = Boards::Lists::GenerateService.new(board_parent, current_user)
if service.execute(board)
- render json: serialize_as_json(board.lists.movable)
+ lists = board.lists.movable.preload_associations(current_user)
+ render json: serialize_as_json(lists)
else
head :unprocessable_entity
end
@@ -62,12 +64,12 @@ module Boards
%i[label_id]
end
- def list_params
+ def create_list_params
params.require(:list).permit(list_creation_attrs)
end
- def move_params
- params.require(:list).permit(:position)
+ def update_list_params
+ params.require(:list).permit(:collapsed, :position)
end
def serialize_as_json(resource)
@@ -78,7 +80,9 @@ module Boards
{
only: [:id, :list_type, :position],
methods: [:title],
- label: true
+ label: true,
+ collapsed: true,
+ current_user: current_user
}
end
end
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index b86e4451a7e..e537c11096c 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -6,6 +6,7 @@ module IssuableActions
included do
before_action :authorize_destroy_issuable!, only: :destroy
+ before_action :check_destroy_confirmation!, only: :destroy
before_action :authorize_admin_issuable!, only: :bulk_update
before_action only: :show do
push_frontend_feature_flag(:scoped_labels, default_enabled: true)
@@ -91,6 +92,33 @@ module IssuableActions
end
end
+ def check_destroy_confirmation!
+ return true if params[:destroy_confirm]
+
+ error_message = "Destroy confirmation not provided for #{issuable.human_class_name}"
+ exception = RuntimeError.new(error_message)
+ Gitlab::Sentry.track_acceptable_exception(
+ exception,
+ extra: {
+ project_path: issuable.project.full_path,
+ issuable_type: issuable.class.name,
+ issuable_id: issuable.id
+ }
+ )
+
+ index_path = polymorphic_path([parent, issuable.class])
+
+ respond_to do |format|
+ format.html do
+ flash[:notice] = error_message
+ redirect_to index_path
+ end
+ format.json do
+ render json: { errors: error_message }, status: :unprocessable_entity
+ end
+ end
+ end
+
def bulk_update
result = Issuable::BulkUpdateService.new(current_user, bulk_update_params).execute(resource_name)
quantity = result[:count]
@@ -110,7 +138,7 @@ module IssuableActions
end
notes = prepare_notes_for_rendering(notes)
- notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
+ notes = notes.select { |n| n.visible_for?(current_user) }
discussions = Discussion.build_collection(notes, issuable)
diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb
index f7137a04437..bff0715f192 100644
--- a/app/controllers/concerns/lfs_request.rb
+++ b/app/controllers/concerns/lfs_request.rb
@@ -12,7 +12,7 @@
module LfsRequest
extend ActiveSupport::Concern
- CONTENT_TYPE = 'application/vnd.git-lfs+json'.freeze
+ CONTENT_TYPE = 'application/vnd.git-lfs+json'
included do
before_action :require_lfs_enabled!
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index 4b7899d469b..fbae4c53c31 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -29,7 +29,7 @@ module NotesActions
end
notes = prepare_notes_for_rendering(notes)
- notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
+ notes = notes.select { |n| n.visible_for?(current_user) }
notes_json[:notes] =
if use_note_serializer?
diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb
index f5d35379e10..60a68cec3c3 100644
--- a/app/controllers/concerns/uploads_actions.rb
+++ b/app/controllers/concerns/uploads_actions.rb
@@ -127,4 +127,8 @@ module UploadsActions
def model
strong_memoize(:model) { find_model }
end
+
+ def workhorse_authorize_request?
+ action_name == 'authorize'
+ end
end
diff --git a/app/controllers/concerns/with_performance_bar.rb b/app/controllers/concerns/with_performance_bar.rb
index 4e0ae3c59eb..93ded59900d 100644
--- a/app/controllers/concerns/with_performance_bar.rb
+++ b/app/controllers/concerns/with_performance_bar.rb
@@ -3,15 +3,25 @@
module WithPerformanceBar
extend ActiveSupport::Concern
- def peek_enabled?
- return false unless Gitlab::PerformanceBar.enabled?(current_user)
+ included do
+ before_action :set_peek_enabled_for_current_request
+ end
+
+ private
+ def set_peek_enabled_for_current_request
Gitlab::SafeRequestStore.fetch(:peek_enabled) { cookie_or_default_value }
end
- private
+ # Needed for Peek's routing to work;
+ # Peek::ResultsController#restrict_non_access calls this method.
+ def peek_enabled?
+ Gitlab::PerformanceBar.enabled_for_request?
+ end
def cookie_or_default_value
+ return false unless Gitlab::PerformanceBar.enabled_for_user?(current_user)
+
if cookies[:perf_bar_enabled].present?
cookies[:perf_bar_enabled] == 'true'
else
diff --git a/app/controllers/groups/runners_controller.rb b/app/controllers/groups/runners_controller.rb
index f8e32451b02..af2b2cbd1fd 100644
--- a/app/controllers/groups/runners_controller.rb
+++ b/app/controllers/groups/runners_controller.rb
@@ -3,7 +3,7 @@
class Groups::RunnersController < Groups::ApplicationController
# Proper policies should be implemented per
# https://gitlab.com/gitlab-org/gitlab-ce/issues/45894
- before_action :authorize_admin_pipeline!
+ before_action :authorize_admin_group!
before_action :runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
@@ -50,10 +50,6 @@ class Groups::RunnersController < Groups::ApplicationController
@runner ||= @group.runners.find(params[:id])
end
- def authorize_admin_pipeline!
- return render_404 unless can?(current_user, :admin_pipeline, group)
- end
-
def runner_params
params.require(:runner).permit(Ci::Runner::FORM_EDITABLE)
end
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index adbc0159358..06d7579aff4 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -11,6 +11,9 @@ class Projects::JobsController < Projects::ApplicationController
before_action :authorize_erase_build!, only: [:erase]
before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_websocket_authorize]
before_action :verify_api_request!, only: :terminal_websocket_authorize
+ before_action only: [:trace] do
+ push_frontend_feature_flag(:job_log_json)
+ end
layout 'project'
@@ -64,6 +67,14 @@ class Projects::JobsController < Projects::ApplicationController
# rubocop: enable CodeReuse/ActiveRecord
def trace
+ if Feature.enabled?(:job_log_json, @project)
+ json_trace
+ else
+ html_trace
+ end
+ end
+
+ def html_trace
build.trace.read do |stream|
respond_to do |format|
format.json do
@@ -84,6 +95,10 @@ class Projects::JobsController < Projects::ApplicationController
end
end
+ def json_trace
+ # will be implemented with https://gitlab.com/gitlab-org/gitlab-ce/issues/66454
+ end
+
def retry
return respond_422 unless @build.retryable?
diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb
index 42c415757f9..c16736a756a 100644
--- a/app/controllers/projects/lfs_api_controller.rb
+++ b/app/controllers/projects/lfs_api_controller.rb
@@ -3,7 +3,7 @@
class Projects::LfsApiController < Projects::GitHttpClientController
include LfsRequest
- LFS_TRANSFER_CONTENT_TYPE = 'application/octet-stream'.freeze
+ LFS_TRANSFER_CONTENT_TYPE = 'application/octet-stream'
skip_before_action :lfs_check_access!, only: [:deprecated]
before_action :lfs_check_batch_operation!, only: [:batch]
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index f4cc0a5851b..ea1dd7d19d5 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -12,6 +12,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
skip_before_action :merge_request, only: [:index, :bulk_update]
before_action :whitelist_query_limiting, only: [:assign_related_issues, :update]
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
+ before_action :authorize_test_reports!, only: [:test_reports]
before_action :set_issuables_index, only: [:index]
before_action :authenticate_user!, only: [:assign_related_issues]
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
@@ -46,6 +47,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@noteable = @merge_request
@commits_count = @merge_request.commits_count
@issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar')
+ @current_user_data = UserSerializer.new(project: @project).represent(current_user, {}, MergeRequestUserEntity).to_json
set_pipeline_variables
@@ -188,7 +190,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
def pipeline_status
render json: PipelineSerializer
.new(project: @project, current_user: @current_user)
- .represent_status(@merge_request.head_pipeline)
+ .represent_status(head_pipeline)
end
def ci_environments_status
@@ -238,6 +240,13 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
private
+ def head_pipeline
+ strong_memoize(:head_pipeline) do
+ pipeline = @merge_request.head_pipeline
+ pipeline if can?(current_user, :read_pipeline, pipeline)
+ end
+ end
+
def ci_environments_status_on_merge_result?
params[:environment_target] == 'merge_commit'
end
@@ -336,4 +345,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
render json: { status_reason: 'Unknown error' }, status: :internal_server_error
end
end
+
+ def authorize_test_reports!
+ # MergeRequest#actual_head_pipeline is the pipeline accessed in MergeRequest#compare_reports.
+ return render_404 unless can?(current_user, :read_build, merge_request.actual_head_pipeline)
+ end
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 1880bead3ee..a6dd811ab8b 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -21,10 +21,13 @@ class SessionsController < Devise::SessionsController
prepend_before_action :ensure_password_authentication_enabled!, if: -> { action_name == 'create' && password_based_login? }
before_action :auto_sign_in_with_provider, only: [:new]
+ before_action :store_unauthenticated_sessions, only: [:new]
+ before_action :save_failed_login, if: :action_new_and_failed_login?
before_action :load_recaptcha
- after_action :log_failed_login, if: -> { action_name == 'new' && failed_login? }
- helper_method :captcha_enabled?
+ after_action :log_failed_login, if: :action_new_and_failed_login?
+
+ helper_method :captcha_enabled?, :captcha_on_login_required?
# protect_from_forgery is already prepended in ApplicationController but
# authenticate_with_two_factor which signs in the user is prepended before
@@ -37,7 +40,8 @@ class SessionsController < Devise::SessionsController
# token mismatch.
protect_from_forgery with: :exception, prepend: true
- CAPTCHA_HEADER = 'X-GitLab-Show-Login-Captcha'.freeze
+ CAPTCHA_HEADER = 'X-GitLab-Show-Login-Captcha'
+ MAX_FAILED_LOGIN_ATTEMPTS = 5
def new
set_minimum_password_length
@@ -81,10 +85,14 @@ class SessionsController < Devise::SessionsController
request.headers[CAPTCHA_HEADER] && Gitlab::Recaptcha.enabled?
end
+ def captcha_on_login_required?
+ Gitlab::Recaptcha.enabled_on_login? && unverified_anonymous_user?
+ end
+
# From https://github.com/plataformatec/devise/wiki/How-To:-Use-Recaptcha-with-Devise#devisepasswordscontroller
def check_captcha
return unless user_params[:password].present?
- return unless captcha_enabled?
+ return unless captcha_enabled? || captcha_on_login_required?
return unless Gitlab::Recaptcha.load_configurations!
if verify_recaptcha
@@ -103,14 +111,14 @@ class SessionsController < Devise::SessionsController
def increment_failed_login_captcha_counter
Gitlab::Metrics.counter(
:failed_login_captcha_total,
- 'Number of failed CAPTCHA attempts for logins'.freeze
+ 'Number of failed CAPTCHA attempts for logins'
).increment
end
def increment_successful_login_captcha_counter
Gitlab::Metrics.counter(
:successful_login_captcha_total,
- 'Number of successful CAPTCHA attempts for logins'.freeze
+ 'Number of successful CAPTCHA attempts for logins'
).increment
end
@@ -126,10 +134,28 @@ class SessionsController < Devise::SessionsController
Gitlab::AppLogger.info("Failed Login: username=#{user_params[:login]} ip=#{request.remote_ip}")
end
+ def action_new_and_failed_login?
+ action_name == 'new' && failed_login?
+ end
+
+ def save_failed_login
+ session[:failed_login_attempts] ||= 0
+ session[:failed_login_attempts] += 1
+ end
+
def failed_login?
(options = request.env["warden.options"]) && options[:action] == "unauthenticated"
end
+ # storing sessions per IP lets us check if there are associated multiple
+ # anonymous sessions with one IP and prevent situations when there are
+ # multiple attempts of logging in
+ def store_unauthenticated_sessions
+ return if current_user
+
+ Gitlab::AnonymousSession.new(request.remote_ip, session_id: request.session.id).store_session_id_per_ip
+ end
+
# Handle an "initial setup" state, where there's only one user, it's an admin,
# and they require a password change.
# rubocop: disable CodeReuse/ActiveRecord
@@ -240,6 +266,18 @@ class SessionsController < Devise::SessionsController
@ldap_servers ||= Gitlab::Auth::LDAP::Config.available_servers
end
+ def unverified_anonymous_user?
+ exceeded_failed_login_attempts? || exceeded_anonymous_sessions?
+ end
+
+ def exceeded_failed_login_attempts?
+ session.fetch(:failed_login_attempts, 0) > MAX_FAILED_LOGIN_ATTEMPTS
+ end
+
+ def exceeded_anonymous_sessions?
+ Gitlab::AnonymousSession.new(request.remote_ip).stored_sessions >= MAX_FAILED_LOGIN_ATTEMPTS
+ end
+
def authentication_method
if user_params[:otp_attempt]
"two-factor"
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index 94bd18f70d4..2adfeab182e 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -2,6 +2,7 @@
class UploadsController < ApplicationController
include UploadsActions
+ include WorkhorseRequest
UnknownUploadModelError = Class.new(StandardError)
@@ -21,7 +22,8 @@ class UploadsController < ApplicationController
before_action :upload_mount_satisfied?
before_action :find_model
before_action :authorize_access!, only: [:show]
- before_action :authorize_create_access!, only: [:create]
+ before_action :authorize_create_access!, only: [:create, :authorize]
+ before_action :verify_workhorse_api!, only: [:authorize]
def uploader_class
PersonalFileUploader
@@ -72,7 +74,7 @@ class UploadsController < ApplicationController
end
def render_unauthorized
- if current_user
+ if current_user || workhorse_authorize_request?
render_404
else
authenticate_user!
diff --git a/app/finders/members_finder.rb b/app/finders/members_finder.rb
index f730b015c0a..e8c7f9622a9 100644
--- a/app/finders/members_finder.rb
+++ b/app/finders/members_finder.rb
@@ -60,15 +60,32 @@ class MembersFinder
# We're interested in a list of members without duplicates by user_id.
# We prefer project members over group members, project members should go first.
<<~SQL
- SELECT DISTINCT ON (user_id, invite_email) member_union.*
- FROM (#{union.to_sql}) AS member_union
- ORDER BY user_id,
- invite_email,
- CASE
- WHEN type = 'ProjectMember' THEN 1
- WHEN type = 'GroupMember' THEN 2
- ELSE 3
- END
+ SELECT DISTINCT ON (user_id, invite_email) #{member_columns}
+ FROM (#{union.to_sql}) AS #{member_union_table}
+ LEFT JOIN users on users.id = member_union.user_id
+ LEFT JOIN project_authorizations on project_authorizations.user_id = users.id
+ AND
+ project_authorizations.project_id = #{project.id}
+ ORDER BY user_id,
+ invite_email,
+ CASE
+ WHEN type = 'ProjectMember' THEN 1
+ WHEN type = 'GroupMember' THEN 2
+ ELSE 3
+ END
SQL
end
+
+ def member_union_table
+ 'member_union'
+ end
+
+ def member_columns
+ Member.column_names.map do |column_name|
+ # fallback to members.access_level when project_authorizations.access_level is missing
+ next "COALESCE(#{ProjectAuthorization.table_name}.access_level, #{member_union_table}.access_level) access_level" if column_name == 'access_level'
+
+ "#{member_union_table}.#{column_name}"
+ end.join(',')
+ end
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 0ab19f1d2d2..84021d0da56 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -164,6 +164,10 @@ module ApplicationSettingsHelper
:allow_local_requests_from_system_hooks,
:dns_rebinding_protection_enabled,
:archive_builds_in_human_readable,
+ :asset_proxy_enabled,
+ :asset_proxy_secret_key,
+ :asset_proxy_url,
+ :asset_proxy_whitelist,
:authorized_keys_enabled,
:auto_devops_enabled,
:auto_devops_domain,
@@ -231,6 +235,7 @@ module ApplicationSettingsHelper
:recaptcha_enabled,
:recaptcha_private_key,
:recaptcha_site_key,
+ :login_recaptcha_protection_enabled,
:receive_max_input_size,
:repository_checks_enabled,
:repository_storages,
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
index 81ff359556d..b7f7e617825 100644
--- a/app/helpers/avatars_helper.rb
+++ b/app/helpers/avatars_helper.rb
@@ -56,13 +56,13 @@ module AvatarsHelper
}))
end
- def user_avatar_url_for(options = {})
+ def user_avatar_url_for(only_path: true, **options)
if options[:url]
options[:url]
elsif options[:user]
- avatar_icon_for_user(options[:user], options[:size])
+ avatar_icon_for_user(options[:user], options[:size], only_path: only_path)
else
- avatar_icon_for_email(options[:user_email], options[:size])
+ avatar_icon_for_email(options[:user_email], options[:size], only_path: only_path)
end
end
@@ -75,6 +75,7 @@ module AvatarsHelper
has_tooltip = options[:has_tooltip].nil? ? true : options[:has_tooltip]
data_attributes = options[:data] || {}
css_class = %W[avatar s#{avatar_size}].push(*options[:css_class])
+ alt_text = user_name ? "#{user_name}'s avatar" : "default avatar"
if has_tooltip
css_class.push('has-tooltip')
@@ -88,7 +89,7 @@ module AvatarsHelper
end
image_options = {
- alt: "#{user_name}'s avatar",
+ alt: alt_text,
src: avatar_url,
data: data_attributes,
class: css_class,
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index 36122d3a22a..23596769738 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -90,6 +90,8 @@ module EmailsHelper
when MergeRequest
merge_request = MergeRequest.find(closed_via[:id]).present
+ return "" unless Ability.allowed?(@recipient, :read_merge_request, merge_request)
+
case format
when :html
merge_request_link = link_to(merge_request.to_reference, merge_request.web_url)
@@ -102,6 +104,8 @@ module EmailsHelper
# Technically speaking this should be Commit but per
# https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15610#note_163812339
# we can't deserialize Commit without custom serializer for ActiveJob
+ return "" unless Ability.allowed?(@recipient, :download_code, @project)
+
_("via %{closed_via}") % { closed_via: closed_via }
else
""
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 2ed016beea4..c5a3507637e 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -71,7 +71,7 @@ module LabelsHelper
end
def label_tooltip_title(label)
- label.description
+ Sanitize.clean(label.description)
end
def suggested_colors
diff --git a/app/helpers/performance_bar_helper.rb b/app/helpers/performance_bar_helper.rb
index 7518cec160c..b225e4206a9 100644
--- a/app/helpers/performance_bar_helper.rb
+++ b/app/helpers/performance_bar_helper.rb
@@ -1,9 +1,7 @@
# frozen_string_literal: true
module PerformanceBarHelper
- # This is a hack since using `alias_method :performance_bar_enabled?, :peek_enabled?`
- # in WithPerformanceBar breaks tests (but works in the browser).
def performance_bar_enabled?
- peek_enabled?
+ Gitlab::PerformanceBar.enabled_for_request?
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 33bf2d57fae..14f947a03a3 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -448,7 +448,7 @@ module ProjectsHelper
def git_user_email
if current_user
- current_user.email
+ current_user.commit_email
else
"your@email.com"
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 91c83380b62..2e2d324ab62 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -30,7 +30,46 @@ module SearchHelper
to = collection.offset_value + collection.to_a.size
count = collection.total_count
- s_("SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\"") % { from: from, to: to, count: count, scope: scope.humanize(capitalize: false), term: term }
+ search_entries_info_template(collection) % {
+ from: from,
+ to: to,
+ count: count,
+ scope: search_entries_info_label(scope, count),
+ term: term
+ }
+ end
+
+ def search_entries_info_label(scope, count)
+ case scope
+ when 'blobs', 'snippet_blobs', 'wiki_blobs'
+ ns_('SearchResults|result', 'SearchResults|results', count)
+ when 'commits'
+ ns_('SearchResults|commit', 'SearchResults|commits', count)
+ when 'issues'
+ ns_('SearchResults|issue', 'SearchResults|issues', count)
+ when 'merge_requests'
+ ns_('SearchResults|merge request', 'SearchResults|merge requests', count)
+ when 'milestones'
+ ns_('SearchResults|milestone', 'SearchResults|milestones', count)
+ when 'notes'
+ ns_('SearchResults|comment', 'SearchResults|comments', count)
+ when 'projects'
+ ns_('SearchResults|project', 'SearchResults|projects', count)
+ when 'snippet_titles'
+ ns_('SearchResults|snippet', 'SearchResults|snippets', count)
+ when 'users'
+ ns_('SearchResults|user', 'SearchResults|users', count)
+ else
+ raise "Unrecognized search scope '#{scope}'"
+ end
+ end
+
+ def search_entries_info_template(collection)
+ if collection.total_pages > 1
+ s_("SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\"")
+ else
+ s_("SearchResults|Showing %{count} %{scope} for \"%{term}\"")
+ end
end
def find_project_for_result_blob(projects, result)
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index f3a3203f7ad..47d15836da0 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -11,7 +11,7 @@ module Emails
def issue_due_email(recipient_id, issue_id, reason = nil)
setup_issue_mail(issue_id, recipient_id)
- mail_new_thread(@issue, issue_thread_options(@issue.author_id, recipient_id, reason))
+ mail_answer_thread(@issue, issue_thread_options(@issue.author_id, recipient_id, reason))
end
def new_mention_in_issue_email(recipient_id, issue_id, updated_by_user_id, reason = nil)
@@ -34,6 +34,8 @@ module Emails
setup_issue_mail(issue_id, recipient_id, closed_via: closed_via)
@updated_by = User.find(updated_by_user_id)
+ @recipient = User.find(recipient_id)
+
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason))
end
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index 04db1980b99..8b93ead0ee6 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -2,44 +2,44 @@
module Emails
module Notes
- def note_commit_email(recipient_id, note_id)
+ def note_commit_email(recipient_id, note_id, reason = nil)
setup_note_mail(note_id, recipient_id)
@commit = @note.noteable
@target_url = project_commit_url(*note_target_url_options)
- mail_answer_note_thread(@commit, @note, note_thread_options(recipient_id))
+ mail_answer_note_thread(@commit, @note, note_thread_options(recipient_id, reason))
end
- def note_issue_email(recipient_id, note_id)
+ def note_issue_email(recipient_id, note_id, reason = nil)
setup_note_mail(note_id, recipient_id)
@issue = @note.noteable
@target_url = project_issue_url(*note_target_url_options)
- mail_answer_note_thread(@issue, @note, note_thread_options(recipient_id))
+ mail_answer_note_thread(@issue, @note, note_thread_options(recipient_id, reason))
end
- def note_merge_request_email(recipient_id, note_id)
+ def note_merge_request_email(recipient_id, note_id, reason = nil)
setup_note_mail(note_id, recipient_id)
@merge_request = @note.noteable
@target_url = project_merge_request_url(*note_target_url_options)
- mail_answer_note_thread(@merge_request, @note, note_thread_options(recipient_id))
+ mail_answer_note_thread(@merge_request, @note, note_thread_options(recipient_id, reason))
end
- def note_project_snippet_email(recipient_id, note_id)
+ def note_project_snippet_email(recipient_id, note_id, reason = nil)
setup_note_mail(note_id, recipient_id)
@snippet = @note.noteable
@target_url = project_snippet_url(*note_target_url_options)
- mail_answer_note_thread(@snippet, @note, note_thread_options(recipient_id))
+ mail_answer_note_thread(@snippet, @note, note_thread_options(recipient_id, reason))
end
- def note_personal_snippet_email(recipient_id, note_id)
+ def note_personal_snippet_email(recipient_id, note_id, reason = nil)
setup_note_mail(note_id, recipient_id)
@snippet = @note.noteable
@target_url = snippet_url(@note.noteable)
- mail_answer_note_thread(@snippet, @note, note_thread_options(recipient_id))
+ mail_answer_note_thread(@snippet, @note, note_thread_options(recipient_id, reason))
end
private
@@ -48,11 +48,12 @@ module Emails
[@project || @group, @note.noteable, anchor: "note_#{@note.id}"]
end
- def note_thread_options(recipient_id)
+ def note_thread_options(recipient_id, reason)
{
from: sender(@note.author_id),
to: recipient(recipient_id, @project&.group || @group),
- subject: subject("#{@note.noteable.title} (#{@note.noteable.reference_link_text})")
+ subject: subject("#{@note.noteable.title} (#{@note.noteable.reference_link_text})"),
+ 'X-GitLab-NotificationReason' => reason
}
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 2a99c6e5c59..e39d655325f 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -4,7 +4,6 @@ class ApplicationSetting < ApplicationRecord
include CacheableAttributes
include CacheMarkdownField
include TokenAuthenticatable
- include IgnorableColumn
include ChronicDurationAttribute
add_authentication_token_field :runners_registration_token, encrypted: -> { Feature.enabled?(:application_settings_tokens_optional_encryption, default_enabled: true) ? :optional : :required }
@@ -18,19 +17,28 @@ class ApplicationSetting < ApplicationRecord
# fix a lot of tests using allow_any_instance_of
include ApplicationSettingImplementation
+ attr_encrypted :asset_proxy_secret_key,
+ mode: :per_attribute_iv,
+ insecure_mode: true,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-cbc'
+
serialize :restricted_visibility_levels # rubocop:disable Cop/ActiveRecordSerialize
serialize :import_sources # rubocop:disable Cop/ActiveRecordSerialize
serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :domain_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :domain_blacklist, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize
+ serialize :asset_proxy_whitelist, Array # rubocop:disable Cop/ActiveRecordSerialize
- ignore_column :koding_url
- ignore_column :koding_enabled
- ignore_column :sentry_enabled
- ignore_column :sentry_dsn
- ignore_column :clientside_sentry_enabled
- ignore_column :clientside_sentry_dsn
+ self.ignored_columns += %i[
+ clientside_sentry_dsn
+ clientside_sentry_enabled
+ koding_enabled
+ koding_url
+ sentry_dsn
+ sentry_enabled
+ ]
cache_markdown_field :sign_in_text
cache_markdown_field :help_page_text
@@ -75,11 +83,11 @@ class ApplicationSetting < ApplicationRecord
validates :recaptcha_site_key,
presence: true,
- if: :recaptcha_enabled
+ if: :recaptcha_or_login_protection_enabled
validates :recaptcha_private_key,
presence: true,
- if: :recaptcha_enabled
+ if: :recaptcha_or_login_protection_enabled
validates :akismet_api_key,
presence: true,
@@ -192,6 +200,17 @@ class ApplicationSetting < ApplicationRecord
allow_nil: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than: 65536 }
+ validates :asset_proxy_url,
+ presence: true,
+ allow_blank: false,
+ url: true,
+ if: :asset_proxy_enabled?
+
+ validates :asset_proxy_secret_key,
+ presence: true,
+ allow_blank: false,
+ if: :asset_proxy_enabled?
+
SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end
@@ -292,4 +311,8 @@ class ApplicationSetting < ApplicationRecord
def self.cache_backend
Gitlab::ThreadMemoryCache.cache_backend
end
+
+ def recaptcha_or_login_protection_enabled
+ recaptcha_enabled || login_recaptcha_protection_enabled
+ end
end
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index 55ac1e129cf..f402c0e2775 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -23,8 +23,9 @@ module ApplicationSettingImplementation
akismet_enabled: false,
allow_local_requests_from_web_hooks_and_services: false,
allow_local_requests_from_system_hooks: true,
- dns_rebinding_protection_enabled: true,
+ asset_proxy_enabled: false,
authorized_keys_enabled: true, # TODO default to false if the instance is configured to use AuthorizedKeysCommand
+ commit_email_hostname: default_commit_email_hostname,
container_registry_token_expire_delay: 5,
default_artifacts_expire_in: '30 days',
default_branch_protection: Settings.gitlab['default_branch_protection'],
@@ -33,7 +34,9 @@ module ApplicationSettingImplementation
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_projects_limit: Settings.gitlab['default_projects_limit'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
+ diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
disabled_oauth_sign_in_sources: [],
+ dns_rebinding_protection_enabled: true,
domain_whitelist: Settings.gitlab['domain_whitelist'],
dsa_key_restriction: 0,
ecdsa_key_restriction: 0,
@@ -52,9 +55,11 @@ module ApplicationSettingImplementation
housekeeping_gc_period: 200,
housekeeping_incremental_repack_period: 10,
import_sources: Settings.gitlab['import_sources'],
+ local_markdown_version: 0,
max_artifacts_size: Settings.artifacts['max_size'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
mirror_available: true,
+ outbound_local_requests_whitelist: [],
password_authentication_enabled_for_git: true,
password_authentication_enabled_for_web: Settings.gitlab['signin_enabled'],
performance_bar_allowed_group_id: nil,
@@ -63,7 +68,10 @@ module ApplicationSettingImplementation
plantuml_url: nil,
polling_interval_multiplier: 1,
project_export_enabled: true,
+ protected_ci_variables: false,
+ raw_blob_request_limit: 300,
recaptcha_enabled: false,
+ login_recaptcha_protection_enabled: false,
repository_checks_enabled: true,
repository_storages: ['default'],
require_two_factor_authentication: false,
@@ -95,16 +103,10 @@ module ApplicationSettingImplementation
user_default_internal_regex: nil,
user_show_add_ssh_key_message: true,
usage_stats_set_by_user_id: nil,
- diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
- commit_email_hostname: default_commit_email_hostname,
snowplow_collector_hostname: nil,
snowplow_cookie_domain: nil,
snowplow_enabled: false,
- snowplow_site_id: nil,
- protected_ci_variables: false,
- local_markdown_version: 0,
- outbound_local_requests_whitelist: [],
- raw_blob_request_limit: 300
+ snowplow_site_id: nil
}
end
@@ -198,6 +200,15 @@ module ApplicationSettingImplementation
end
end
+ def asset_proxy_whitelist=(values)
+ values = domain_strings_to_array(values) if values.is_a?(String)
+
+ # make sure we always whitelist the running host
+ values << Gitlab.config.gitlab.host unless values.include?(Gitlab.config.gitlab.host)
+
+ self[:asset_proxy_whitelist] = values
+ end
+
def repository_storages
Array(read_attribute(:repository_storages))
end
@@ -306,6 +317,7 @@ module ApplicationSettingImplementation
values
.split(DOMAIN_LIST_SEPARATOR)
+ .map(&:strip)
.reject(&:empty?)
.uniq
end
diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb
index 0ab302a0f3e..24fcb97db6e 100644
--- a/app/models/award_emoji.rb
+++ b/app/models/award_emoji.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
class AwardEmoji < ApplicationRecord
- DOWNVOTE_NAME = "thumbsdown".freeze
- UPVOTE_NAME = "thumbsup".freeze
+ DOWNVOTE_NAME = "thumbsdown"
+ UPVOTE_NAME = "thumbsup"
include Participable
include GhostUser
diff --git a/app/models/blob_viewer/base.rb b/app/models/blob_viewer/base.rb
index df6b9bb2f0b..1c3a6599f36 100644
--- a/app/models/blob_viewer/base.rb
+++ b/app/models/blob_viewer/base.rb
@@ -2,7 +2,7 @@
module BlobViewer
class Base
- PARTIAL_PATH_PREFIX = 'projects/blob/viewers'.freeze
+ PARTIAL_PATH_PREFIX = 'projects/blob/viewers'
class_attribute :partial_name, :loading_partial_name, :type, :extensions, :file_types, :load_async, :binary, :switcher_icon, :switcher_title, :collapse_limit, :size_limit
diff --git a/app/models/board.rb b/app/models/board.rb
index 50b6ca9b70f..b5d07f1b282 100644
--- a/app/models/board.rb
+++ b/app/models/board.rb
@@ -19,6 +19,7 @@ class Board < ApplicationRecord
def parent
@parent ||= group || project
end
+ alias_method :resource_parent, :parent
def group_board?
group_id.present?
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index da4584228ce..1338a585c9e 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -16,7 +16,7 @@ class BroadcastMessage < ApplicationRecord
default_value_for :color, '#E75E40'
default_value_for :font, '#FFFFFF'
- CACHE_KEY = 'broadcast_message_current_json'.freeze
+ CACHE_KEY = 'broadcast_message_current_json'
after_commit :flush_redis_cache
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 7930bef5cf2..d558f66154e 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -11,19 +11,20 @@ module Ci
include ObjectStorage::BackgroundMove
include Presentable
include Importable
- include IgnorableColumn
include Gitlab::Utils::StrongMemoize
include Deployable
include HasRef
BuildArchivedError = Class.new(StandardError)
- ignore_column :commands
- ignore_column :artifacts_file
- ignore_column :artifacts_metadata
- ignore_column :artifacts_file_store
- ignore_column :artifacts_metadata_store
- ignore_column :artifacts_size
+ self.ignored_columns += %i[
+ artifacts_file
+ artifacts_file_store
+ artifacts_metadata
+ artifacts_metadata_store
+ artifacts_size
+ commands
+ ]
belongs_to :project, inverse_of: :builds
belongs_to :runner
@@ -444,7 +445,7 @@ module Ci
end
end
- CI_REGISTRY_USER = 'gitlab-ci-token'.freeze
+ CI_REGISTRY_USER = 'gitlab-ci-token'
def persisted_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
diff --git a/app/models/ci/build_runner_session.rb b/app/models/ci/build_runner_session.rb
index 997bf298025..8075c15bbaf 100644
--- a/app/models/ci/build_runner_session.rb
+++ b/app/models/ci/build_runner_session.rb
@@ -6,7 +6,7 @@ module Ci
class BuildRunnerSession < ApplicationRecord
extend Gitlab::Ci::Model
- TERMINAL_SUBPROTOCOL = 'terminal.gitlab.com'.freeze
+ TERMINAL_SUBPROTOCOL = 'terminal.gitlab.com'
self.table_name = 'ci_builds_runner_session'
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 0a943a33bbb..2b6f10ef79f 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -203,6 +203,7 @@ module Ci
scope :for_sha, -> (sha) { where(sha: sha) }
scope :for_source_sha, -> (source_sha) { where(source_sha: source_sha) }
scope :for_sha_or_source_sha, -> (sha) { for_sha(sha).or(for_source_sha(sha)) }
+ scope :created_after, -> (time) { where('ci_pipelines.created_at > ?', time) }
scope :triggered_by_merge_request, -> (merge_request) do
where(source: :merge_request_event, merge_request: merge_request)
@@ -459,8 +460,8 @@ module Ci
canceled? && auto_canceled_by_id?
end
- def cancel_running
- retry_optimistic_lock(cancelable_statuses) do |cancelable|
+ def cancel_running(retries: nil)
+ retry_optimistic_lock(cancelable_statuses, retries) do |cancelable|
cancelable.find_each do |job|
yield(job) if block_given?
job.cancel
@@ -468,10 +469,10 @@ module Ci
end
end
- def auto_cancel_running(pipeline)
+ def auto_cancel_running(pipeline, retries: nil)
update(auto_canceled_by: pipeline)
- cancel_running do |job|
+ cancel_running(retries: retries) do |job|
job.auto_canceled_by = pipeline
end
end
@@ -669,6 +670,7 @@ module Ci
variables.append(key: 'CI_COMMIT_REF_PROTECTED', value: (!!protected_ref?).to_s)
if merge_request_event? && merge_request
+ variables.append(key: 'CI_MERGE_REQUEST_EVENT_TYPE', value: merge_request_event_type.to_s)
variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', value: source_sha.to_s)
variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA', value: target_sha.to_s)
variables.concat(merge_request.predefined_variables)
@@ -771,10 +773,18 @@ module Ci
triggered_by_merge_request? && target_sha.present?
end
+ def merge_train_pipeline?
+ merge_request_pipeline? && merge_train_ref?
+ end
+
def merge_request_ref?
MergeRequest.merge_request_ref?(ref)
end
+ def merge_train_ref?
+ MergeRequest.merge_train_ref?(ref)
+ end
+
def matches_sha_or_source_sha?(sha)
self.sha == sha || self.source_sha == sha
end
@@ -803,6 +813,20 @@ module Ci
errors ? errors.full_messages.to_sentence : ""
end
+ def merge_request_event_type
+ return unless merge_request_event?
+
+ strong_memoize(:merge_request_event_type) do
+ if detached_merge_request_pipeline?
+ :detached
+ elsif merge_request_pipeline?
+ :merged_result
+ elsif merge_train_pipeline?
+ :merge_train
+ end
+ end
+ end
+
private
def ci_yaml_from_repo
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 1c1c7a5ae7a..e0e905ebfa8 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -4,7 +4,6 @@ module Ci
class Runner < ApplicationRecord
extend Gitlab::Ci::Model
include Gitlab::SQL::Pattern
- include IgnorableColumn
include RedisCacheable
include ChronicDurationAttribute
include FromUnion
@@ -36,7 +35,7 @@ module Ci
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze
- ignore_column :is_shared
+ self.ignored_columns = %i[is_shared]
has_many :builds
has_many :runner_projects, inverse_of: :runner, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
diff --git a/app/models/clusters/applications/cert_manager.rb b/app/models/clusters/applications/cert_manager.rb
index 6bd7473c8ff..27d4180e5b9 100644
--- a/app/models/clusters/applications/cert_manager.rb
+++ b/app/models/clusters/applications/cert_manager.rb
@@ -3,7 +3,8 @@
module Clusters
module Applications
class CertManager < ApplicationRecord
- VERSION = 'v0.5.2'.freeze
+ VERSION = 'v0.9.1'
+ CRD_VERSION = '0.9'
self.table_name = 'clusters_applications_cert_managers'
@@ -21,16 +22,22 @@ module Clusters
validates :email, presence: true
def chart
- 'stable/cert-manager'
+ 'certmanager/cert-manager'
+ end
+
+ def repository
+ 'https://charts.jetstack.io'
end
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: 'certmanager',
+ repository: repository,
version: VERSION,
rbac: cluster.platform_kubernetes_rbac?,
chart: chart,
files: files.merge(cluster_issuer_file),
+ preinstall: pre_install_script,
postinstall: post_install_script
)
end
@@ -46,16 +53,30 @@ module Clusters
private
+ def pre_install_script
+ [
+ apply_file("https://raw.githubusercontent.com/jetstack/cert-manager/release-#{CRD_VERSION}/deploy/manifests/00-crds.yaml"),
+ "kubectl label --overwrite namespace #{Gitlab::Kubernetes::Helm::NAMESPACE} certmanager.k8s.io/disable-validation=true"
+ ]
+ end
+
def post_install_script
- ["kubectl create -f /data/helm/certmanager/config/cluster_issuer.yaml"]
+ [retry_command(apply_file('/data/helm/certmanager/config/cluster_issuer.yaml'))]
+ end
+
+ def retry_command(command)
+ "for i in $(seq 1 30); do #{command} && break; sleep 1s; echo \"Retrying ($i)...\"; done"
end
def post_delete_script
[
delete_private_key,
delete_crd('certificates.certmanager.k8s.io'),
+ delete_crd('certificaterequests.certmanager.k8s.io'),
+ delete_crd('challenges.certmanager.k8s.io'),
delete_crd('clusterissuers.certmanager.k8s.io'),
- delete_crd('issuers.certmanager.k8s.io')
+ delete_crd('issuers.certmanager.k8s.io'),
+ delete_crd('orders.certmanager.k8s.io')
].compact
end
@@ -75,6 +96,10 @@ module Clusters
Gitlab::Kubernetes::KubectlCmd.delete("crd", definition, "--ignore-not-found")
end
+ def apply_file(filename)
+ Gitlab::Kubernetes::KubectlCmd.apply_file(filename)
+ end
+
def cluster_issuer_file
{
'cluster_issuer.yaml': cluster_issuer_yaml_content
diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb
index 1430b82c2f2..50def3ba38c 100644
--- a/app/models/clusters/applications/ingress.rb
+++ b/app/models/clusters/applications/ingress.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Ingress < ApplicationRecord
- VERSION = '1.1.2'.freeze
+ VERSION = '1.1.2'
self.table_name = 'clusters_applications_ingress'
diff --git a/app/models/clusters/applications/jupyter.rb b/app/models/clusters/applications/jupyter.rb
index 9ede0615fa3..fb74d96efe3 100644
--- a/app/models/clusters/applications/jupyter.rb
+++ b/app/models/clusters/applications/jupyter.rb
@@ -5,7 +5,7 @@ require 'securerandom'
module Clusters
module Applications
class Jupyter < ApplicationRecord
- VERSION = '0.9-174bbd5'.freeze
+ VERSION = '0.9-174bbd5'
self.table_name = 'clusters_applications_jupyter'
diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb
index 244fe738396..a9b9374622d 100644
--- a/app/models/clusters/applications/knative.rb
+++ b/app/models/clusters/applications/knative.rb
@@ -3,9 +3,9 @@
module Clusters
module Applications
class Knative < ApplicationRecord
- VERSION = '0.6.0'.freeze
- REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts'.freeze
- METRICS_CONFIG = 'https://storage.googleapis.com/triggermesh-charts/istio-metrics.yaml'.freeze
+ VERSION = '0.6.0'
+ REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts'
+ METRICS_CONFIG = 'https://storage.googleapis.com/triggermesh-charts/istio-metrics.yaml'
FETCH_IP_ADDRESS_DELAY = 30.seconds
API_RESOURCES_PATH = 'config/knative/api_resources.yml'
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index 329250255fd..2d6af8f4f0b 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ApplicationRecord
- VERSION = '0.8.0'.freeze
+ VERSION = '0.8.0'
self.table_name = 'clusters_applications_runners'
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 97d39491b73..444e1a82c97 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -20,8 +20,8 @@ module Clusters
Applications::Runner.application_name => Applications::Runner,
Applications::Prometheus.application_name => Applications::Prometheus
}.merge(PROJECT_ONLY_APPLICATIONS).freeze
- DEFAULT_ENVIRONMENT = '*'.freeze
- KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'.freeze
+ DEFAULT_ENVIRONMENT = '*'
+ KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'
belongs_to :user
diff --git a/app/models/concerns/cacheable_attributes.rb b/app/models/concerns/cacheable_attributes.rb
index 0c800621a55..d459af23a2f 100644
--- a/app/models/concerns/cacheable_attributes.rb
+++ b/app/models/concerns/cacheable_attributes.rb
@@ -11,7 +11,7 @@ module CacheableAttributes
class_methods do
def cache_key
- "#{name}:#{Gitlab::VERSION}:#{Rails.version}".freeze
+ "#{name}:#{Gitlab::VERSION}:#{Rails.version}"
end
# Can be overridden
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 71ebb586c13..cf88076ac74 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -3,7 +3,7 @@
module HasStatus
extend ActiveSupport::Concern
- DEFAULT_STATUS = 'created'.freeze
+ DEFAULT_STATUS = 'created'
BLOCKED_STATUS = %w[manual scheduled].freeze
AVAILABLE_STATUSES = %w[created preparing pending running success failed canceled skipped manual scheduled].freeze
STARTED_STATUSES = %w[running success failed skipped manual scheduled].freeze
diff --git a/app/models/concerns/ignorable_column.rb b/app/models/concerns/ignorable_column.rb
deleted file mode 100644
index 3bec44dc79b..00000000000
--- a/app/models/concerns/ignorable_column.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-# Module that can be included into a model to make it easier to ignore database
-# columns.
-#
-# Example:
-#
-# class User < ApplicationRecord
-# include IgnorableColumn
-#
-# ignore_column :updated_at
-# end
-#
-module IgnorableColumn
- extend ActiveSupport::Concern
-
- class_methods do
- def columns
- super.reject { |column| ignored_columns.include?(column.name) }
- end
-
- def ignored_columns
- @ignored_columns ||= Set.new
- end
-
- def ignore_column(*names)
- ignored_columns.merge(names.map(&:to_s))
- end
- end
-end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index db46d7afbb9..a998d9b7e1b 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -73,6 +73,7 @@ module Issuable
validates :author, presence: true
validates :title, presence: true, length: { maximum: 255 }
+ validates :description, length: { maximum: Gitlab::Database::MAX_TEXT_SIZE_LIMIT }, allow_blank: true
validate :milestone_is_valid
scope :authored, ->(user) { where(author_id: user) }
@@ -276,6 +277,10 @@ module Issuable
end
end
+ def resource_parent
+ project
+ end
+
def milestone_available?
project_id == milestone&.project_id || project.ancestors_upto.compact.include?(milestone&.group)
end
diff --git a/app/models/concerns/protected_ref_access.rb b/app/models/concerns/protected_ref_access.rb
index 583751ea6ac..208937f2aff 100644
--- a/app/models/concerns/protected_ref_access.rb
+++ b/app/models/concerns/protected_ref_access.rb
@@ -4,9 +4,9 @@ module ProtectedRefAccess
extend ActiveSupport::Concern
HUMAN_ACCESS_LEVELS = {
- Gitlab::Access::MAINTAINER => "Maintainers".freeze,
- Gitlab::Access::DEVELOPER => "Developers + Maintainers".freeze,
- Gitlab::Access::NO_ACCESS => "No one".freeze
+ Gitlab::Access::MAINTAINER => "Maintainers",
+ Gitlab::Access::DEVELOPER => "Developers + Maintainers",
+ Gitlab::Access::NO_ACCESS => "No one"
}.freeze
class_methods do
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index 116e8967651..07d22641a0a 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -33,8 +33,17 @@ module Routable
#
# Returns a single object, or nil.
def find_by_full_path(path, follow_redirects: false)
- order_sql = Arel.sql("(CASE WHEN routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)")
- found = where_full_path_in([path]).reorder(order_sql).take
+ increment_counter(:routable_find_by_full_path, 'Number of calls to Routable.find_by_full_path')
+
+ if Feature.enabled?(:routable_two_step_lookup)
+ # Case sensitive match first (it's cheaper and the usual case)
+ # If we didn't have an exact match, we perform a case insensitive search
+ found = includes(:route).find_by(routes: { path: path }) || where_full_path_in([path]).take
+ else
+ order_sql = Arel.sql("(CASE WHEN routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)")
+ found = where_full_path_in([path]).reorder(order_sql).take
+ end
+
return found if found
if follow_redirects
@@ -52,11 +61,22 @@ module Routable
def where_full_path_in(paths)
return none if paths.empty?
+ increment_counter(:routable_where_full_path_in, 'Number of calls to Routable.where_full_path_in')
+
wheres = paths.map do |path|
"(LOWER(routes.path) = LOWER(#{connection.quote(path)}))"
end
- joins(:route).where(wheres.join(' OR '))
+ includes(:route).where(wheres.join(' OR ')).references(:routes)
+ end
+
+ # Temporary instrumentation of method calls
+ def increment_counter(counter, description)
+ @counters[counter] ||= Gitlab::Metrics.counter(counter, description)
+
+ @counters[counter].increment
+ rescue
+ # ignore the error
end
end
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index 8b536a123fc..98842242eb6 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -9,8 +9,8 @@ require 'task_list/filter'
#
# Used by MergeRequest and Issue
module Taskable
- COMPLETED = 'completed'.freeze
- INCOMPLETE = 'incomplete'.freeze
+ COMPLETED = 'completed'
+ INCOMPLETE = 'incomplete'
COMPLETE_PATTERN = /(\[[xX]\])/.freeze
INCOMPLETE_PATTERN = /(\[[\s]\])/.freeze
ITEM_PATTERN = %r{
diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb
index 0bd90bd28e3..22ab326a0ab 100644
--- a/app/models/deploy_key.rb
+++ b/app/models/deploy_key.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
class DeployKey < Key
- include IgnorableColumn
include FromUnion
has_many :deploy_keys_projects, inverse_of: :deploy_key, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
@@ -11,7 +10,7 @@ class DeployKey < Key
scope :are_public, -> { where(public: true) }
scope :with_projects, -> { includes(deploy_keys_projects: { project: [:route, :namespace] }) }
- ignore_column :can_push
+ self.ignored_columns += %i[can_push]
accepts_nested_attributes_for :deploy_keys_projects
diff --git a/app/models/deploy_token.rb b/app/models/deploy_token.rb
index 85f5a2040c0..20e1d802178 100644
--- a/app/models/deploy_token.rb
+++ b/app/models/deploy_token.rb
@@ -8,7 +8,7 @@ class DeployToken < ApplicationRecord
add_authentication_token_field :token, encrypted: :optional
AVAILABLE_SCOPES = %i(read_repository read_registry).freeze
- GITLAB_DEPLOY_TOKEN_NAME = 'gitlab-deploy-token'.freeze
+ GITLAB_DEPLOY_TOKEN_NAME = 'gitlab-deploy-token'
default_value_for(:expires_at) { Forever.date }
diff --git a/app/models/diff_viewer/base.rb b/app/models/diff_viewer/base.rb
index 527ee33b83b..22c8fe73563 100644
--- a/app/models/diff_viewer/base.rb
+++ b/app/models/diff_viewer/base.rb
@@ -2,7 +2,7 @@
module DiffViewer
class Base
- PARTIAL_PATH_PREFIX = 'projects/diffs/viewers'.freeze
+ PARTIAL_PATH_PREFIX = 'projects/diffs/viewers'
class_attribute :partial_name, :type, :extensions, :file_types, :binary, :switcher_icon, :switcher_title
diff --git a/app/models/event.rb b/app/models/event.rb
index 738080eb584..52d54be39a9 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -2,7 +2,6 @@
class Event < ApplicationRecord
include Sortable
- include IgnorableColumn
include FromUnion
default_scope { reorder(nil) }
@@ -52,6 +51,7 @@ class Event < ApplicationRecord
belongs_to :author, class_name: "User"
belongs_to :project
+ belongs_to :group
belongs_to :target, -> {
# If the association for "target" defines an "author" association we want to
diff --git a/app/models/gpg_key.rb b/app/models/gpg_key.rb
index 116beac5c2a..995baf8565c 100644
--- a/app/models/gpg_key.rb
+++ b/app/models/gpg_key.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
class GpgKey < ApplicationRecord
- KEY_PREFIX = '-----BEGIN PGP PUBLIC KEY BLOCK-----'.freeze
- KEY_SUFFIX = '-----END PGP PUBLIC KEY BLOCK-----'.freeze
+ KEY_PREFIX = '-----BEGIN PGP PUBLIC KEY BLOCK-----'
+ KEY_SUFFIX = '-----END PGP PUBLIC KEY BLOCK-----'
include ShaAttribute
diff --git a/app/models/group.rb b/app/models/group.rb
index 6c868b1d1f0..abe93cf3c84 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -15,6 +15,8 @@ class Group < Namespace
include WithUploads
include Gitlab::Utils::StrongMemoize
+ ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10
+
has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent
alias_method :members, :group_members
has_many :users, through: :group_members
@@ -365,6 +367,8 @@ class Group < Namespace
end
def max_member_access_for_user(user)
+ return GroupMember::NO_ACCESS unless user
+
return GroupMember::OWNER if user.admin?
members_with_parents
@@ -427,6 +431,10 @@ class Group < Namespace
super || ::Gitlab::Access::OWNER_SUBGROUP_ACCESS
end
+ def access_request_approvers_to_be_notified
+ members.owners.order_recent_sign_in.limit(ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT)
+ end
+
private
def update_two_factor_requirement
diff --git a/app/models/instance_configuration.rb b/app/models/instance_configuration.rb
index a9b1962f24c..f401c23e453 100644
--- a/app/models/instance_configuration.rb
+++ b/app/models/instance_configuration.rb
@@ -4,8 +4,8 @@ require 'resolv'
class InstanceConfiguration
SSH_ALGORITHMS = %w(DSA ECDSA ED25519 RSA).freeze
- SSH_ALGORITHMS_PATH = '/etc/ssh/'.freeze
- CACHE_KEY = 'instance_configuration'.freeze
+ SSH_ALGORITHMS_PATH = '/etc/ssh/'
+ CACHE_KEY = 'instance_configuration'
EXPIRATION_TIME = 24.hours
def settings
diff --git a/app/models/issue.rb b/app/models/issue.rb
index caea8eadd18..75d4fc8c1c5 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -178,7 +178,7 @@ class Issue < ApplicationRecord
end
def moved?
- !moved_to.nil?
+ !moved_to_id.nil?
end
def can_move?(user, to_project = nil)
diff --git a/app/models/label.rb b/app/models/label.rb
index d9455b36242..dc9f0a3d1a9 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -199,7 +199,11 @@ class Label < ApplicationRecord
end
def title=(value)
- write_attribute(:title, sanitize_title(value)) if value.present?
+ write_attribute(:title, sanitize_value(value)) if value.present?
+ end
+
+ def description=(value)
+ write_attribute(:description, sanitize_value(value)) if value.present?
end
##
@@ -260,7 +264,7 @@ class Label < ApplicationRecord
end
end
- def sanitize_title(value)
+ def sanitize_value(value)
CGI.unescapeHTML(Sanitize.clean(value.to_s))
end
diff --git a/app/models/list.rb b/app/models/list.rb
index ccadd39bda2..ae7085f05a7 100644
--- a/app/models/list.rb
+++ b/app/models/list.rb
@@ -1,9 +1,11 @@
# frozen_string_literal: true
class List < ApplicationRecord
+ include Importable
+
belongs_to :board
belongs_to :label
- include Importable
+ has_many :list_user_preferences
enum list_type: { backlog: 0, label: 1, closed: 2, assignee: 3, milestone: 4 }
@@ -16,9 +18,24 @@ class List < ApplicationRecord
scope :destroyable, -> { where(list_type: list_types.slice(*destroyable_types).values) }
scope :movable, -> { where(list_type: list_types.slice(*movable_types).values) }
- scope :preload_associations, -> { preload(:board, :label) }
+
+ scope :preload_associations, -> (user) do
+ preload(:board, label: :priorities)
+ .with_preferences_for(user)
+ end
+
scope :ordered, -> { order(:list_type, :position) }
+ # Loads list with preferences for given user
+ # if preferences exists for user or not
+ scope :with_preferences_for, -> (user) do
+ return unless user
+
+ includes(:list_user_preferences).where(list_user_preferences: { user_id: [user.id, nil] })
+ end
+
+ alias_method :preferences, :list_user_preferences
+
class << self
def destroyable_types
[:label]
@@ -29,6 +46,31 @@ class List < ApplicationRecord
end
end
+ def preferences_for(user)
+ return preferences.build unless user
+
+ if preferences.loaded?
+ preloaded_preferences_for(user)
+ else
+ preferences.find_or_initialize_by(user: user)
+ end
+ end
+
+ def preloaded_preferences_for(user)
+ user_preferences =
+ preferences.find do |preference|
+ preference.user_id == user.id
+ end
+
+ user_preferences || preferences.build(user: user)
+ end
+
+ def update_preferences_for(user, preferences = {})
+ return unless user
+
+ preferences_for(user).update(preferences)
+ end
+
def destroyable?
self.class.destroyable_types.include?(list_type&.to_sym)
end
@@ -43,6 +85,14 @@ class List < ApplicationRecord
def as_json(options = {})
super(options).tap do |json|
+ json[:collapsed] = false
+
+ if options.key?(:collapsed)
+ preferences = preferences_for(options[:current_user])
+
+ json[:collapsed] = preferences.collapsed?
+ end
+
if options.key?(:label)
json[:label] = label.as_json(
project: board.project,
diff --git a/app/models/list_user_preference.rb b/app/models/list_user_preference.rb
new file mode 100644
index 00000000000..fe1cc7d5425
--- /dev/null
+++ b/app/models/list_user_preference.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class ListUserPreference < ApplicationRecord
+ belongs_to :user
+ belongs_to :list
+
+ validates :user, presence: true
+ validates :list, presence: true
+ validates :user_id, uniqueness: { scope: :list_id, message: "should have only one list preference per user" }
+end
diff --git a/app/models/member.rb b/app/models/member.rb
index dbae1076670..6457fe9ef0c 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -399,7 +399,7 @@ class Member < ApplicationRecord
end
def post_update_hook
- # override in sub class
+ system_hook_service.execute_hooks_for(self, :update)
end
def post_destroy_hook
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index 3d6f397e599..ed5832ff989 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -3,7 +3,7 @@
class GroupMember < Member
include FromUnion
- SOURCE_TYPE = 'Namespace'.freeze
+ SOURCE_TYPE = 'Namespace'
belongs_to :group, foreign_key: 'source_id'
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index c64e2669b6a..2bb5806cd21 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class ProjectMember < Member
- SOURCE_TYPE = 'Project'.freeze
+ SOURCE_TYPE = 'Project'
belongs_to :project, foreign_key: 'source_id'
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index bfd636fa62a..28e450f9b30 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1142,6 +1142,10 @@ class MergeRequest < ApplicationRecord
ref.start_with?("refs/#{Repository::REF_MERGE_REQUEST}/")
end
+ def self.merge_train_ref?(ref)
+ %r{\Arefs/#{Repository::REF_MERGE_REQUEST}/\d+/train\z}.match?(ref)
+ end
+
def in_locked_state
begin
lock_mr
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 2c9dbf2585c..2402fa8e38f 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -4,7 +4,6 @@ class MergeRequestDiff < ApplicationRecord
include Sortable
include Importable
include ManualInverseAssociation
- include IgnorableColumn
include EachBatch
include Gitlab::Utils::StrongMemoize
include ObjectStorage::BackgroundMove
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 68cbf75b5a2..915978d37b8 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -25,6 +25,12 @@ class Milestone < ApplicationRecord
belongs_to :project
belongs_to :group
+ # A one-to-one relationship is set up here as part of a MVC: https://gitlab.com/gitlab-org/gitlab-ce/issues/62402
+ # However, on the long term, we will want a many-to-many relationship between Release and Milestone.
+ # The "has_one through" allows us today to set up this one-to-one relationship while setting up the architecture for the long-term (ie intermediate table).
+ has_one :milestone_release
+ has_one :release, through: :milestone_release
+
has_internal_id :iid, scope: :project, init: ->(s) { s&.project&.milestones&.maximum(:iid) }
has_internal_id :iid, scope: :group, init: ->(s) { s&.group&.milestones&.maximum(:iid) }
@@ -60,6 +66,7 @@ class Milestone < ApplicationRecord
validate :milestone_type_check
validate :start_date_should_be_less_than_due_date, if: proc { |m| m.start_date.present? && m.due_date.present? }
validate :dates_within_4_digits
+ validates_associated :milestone_release, message: -> (_, obj) { obj[:value].errors.full_messages.join(",") }
strip_attributes :title
@@ -254,6 +261,7 @@ class Milestone < ApplicationRecord
def parent
group || project
end
+ alias_method :resource_parent, :parent
def group_milestone?
group_id.present?
diff --git a/app/models/milestone_release.rb b/app/models/milestone_release.rb
new file mode 100644
index 00000000000..c8743a8cad8
--- /dev/null
+++ b/app/models/milestone_release.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class MilestoneRelease < ApplicationRecord
+ belongs_to :milestone
+ belongs_to :release
+
+ validates :milestone_id, uniqueness: { scope: [:release_id] }
+ validate :same_project_between_milestone_and_release
+
+ private
+
+ def same_project_between_milestone_and_release
+ return if milestone&.project_id == release&.project_id
+
+ errors.add(:base, 'does not have the same project as the milestone')
+ end
+end
diff --git a/app/models/namespace/aggregation_schedule.rb b/app/models/namespace/aggregation_schedule.rb
index 61a7eb4b576..ed61c807519 100644
--- a/app/models/namespace/aggregation_schedule.rb
+++ b/app/models/namespace/aggregation_schedule.rb
@@ -7,7 +7,7 @@ class Namespace::AggregationSchedule < ApplicationRecord
self.primary_key = :namespace_id
DEFAULT_LEASE_TIMEOUT = 1.5.hours.to_i
- REDIS_SHARED_KEY = 'gitlab:update_namespace_statistics_delay'.freeze
+ REDIS_SHARED_KEY = 'gitlab:update_namespace_statistics_delay'
belongs_to :namespace
diff --git a/app/models/note.rb b/app/models/note.rb
index a12d1eb7243..0d024b0a25c 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -14,7 +14,6 @@ class Note < ApplicationRecord
include CacheMarkdownField
include AfterCommitQueue
include ResolvableNote
- include IgnorableColumn
include Editable
include Gitlab::SQL::Pattern
include ThrottledTouch
@@ -34,7 +33,7 @@ class Note < ApplicationRecord
end
end
- ignore_column :original_discussion_id
+ self.ignored_columns += %i[original_discussion_id]
cache_markdown_field :note, pipeline: :note, issuable_state_filter_enabled: true
@@ -89,6 +88,7 @@ class Note < ApplicationRecord
delegate :title, to: :noteable, allow_nil: true
validates :note, presence: true
+ validates :note, length: { maximum: Gitlab::Database::MAX_TEXT_SIZE_LIMIT }
validates :project, presence: true, if: :for_project_noteable?
# Attachments are deprecated and are handled by Markdown uploader
@@ -331,6 +331,10 @@ class Note < ApplicationRecord
cross_reference? && !all_referenced_mentionables_allowed?(user)
end
+ def visible_for?(user)
+ !cross_reference_not_visible_for?(user)
+ end
+
def award_emoji?
can_be_award_emoji? && contains_emoji_only?
end
@@ -473,6 +477,7 @@ class Note < ApplicationRecord
def parent
project
end
+ alias_method :resource_parent, :parent
private
diff --git a/app/models/notification_reason.rb b/app/models/notification_reason.rb
index 0a13487574f..6856d397413 100644
--- a/app/models/notification_reason.rb
+++ b/app/models/notification_reason.rb
@@ -3,9 +3,9 @@
# Holds reasons for a notification to have been sent as well as a priority list to select which reason to use
# above the rest
class NotificationReason
- OWN_ACTIVITY = 'own_activity'.freeze
- ASSIGNED = 'assigned'.freeze
- MENTIONED = 'mentioned'.freeze
+ OWN_ACTIVITY = 'own_activity'
+ ASSIGNED = 'assigned'
+ MENTIONED = 'mentioned'
# Priority list for selecting which reason to return in the notification
REASON_PRIORITY = [
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index 8306b11a7b6..637c017a342 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -1,9 +1,7 @@
# frozen_string_literal: true
class NotificationSetting < ApplicationRecord
- include IgnorableColumn
-
- ignore_column :events
+ self.ignored_columns += %i[events]
enum level: { global: 3, watch: 2, participating: 1, mention: 4, disabled: 0, custom: 5 }
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index 27c122d3559..12ce717efd7 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class PagesDomain < ApplicationRecord
- VERIFICATION_KEY = 'gitlab-pages-verification-code'.freeze
+ VERIFICATION_KEY = 'gitlab-pages-verification-code'
VERIFICATION_THRESHOLD = 3.days.freeze
SSL_RENEWAL_THRESHOLD = 30.days.freeze
diff --git a/app/models/project.rb b/app/models/project.rb
index c67c5c7bc8c..17b52d0578e 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -37,8 +37,8 @@ class Project < ApplicationRecord
BoardLimitExceeded = Class.new(StandardError)
- STATISTICS_ATTRIBUTE = 'repositories_count'.freeze
- UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze
+ STATISTICS_ATTRIBUTE = 'repositories_count'
+ UNKNOWN_IMPORT_URL = 'http://unknown.git'
# Hashed Storage versions handle rolling out new storage to project and dependents models:
# nil: legacy
# 1: repository
@@ -55,12 +55,16 @@ class Project < ApplicationRecord
VALID_MIRROR_PORTS = [22, 80, 443].freeze
VALID_MIRROR_PROTOCOLS = %w(http https ssh git).freeze
+ ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10
+
SORTING_PREFERENCE_FIELD = :projects_sort
cache_markdown_field :description, pipeline: :description
delegate :feature_available?, :builds_enabled?, :wiki_enabled?,
:merge_requests_enabled?, :issues_enabled?, :pages_enabled?, :public_pages?,
+ :merge_requests_access_level, :issues_access_level, :wiki_access_level,
+ :snippets_access_level, :builds_access_level, :repository_access_level,
to: :project_feature, allow_nil: true
delegate :base_dir, :disk_path, :ensure_storage_path_exists, to: :storage
@@ -2191,6 +2195,10 @@ class Project < ApplicationRecord
pool_repository.present?
end
+ def access_request_approvers_to_be_notified
+ members.maintainers.order_recent_sign_in.limit(ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT)
+ end
+
private
def merge_requests_allowing_collaboration(source_branch = nil)
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index 43edfde851c..d058904dd9e 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -5,7 +5,7 @@ require "addressable/uri"
class BuildkiteService < CiService
include ReactiveService
- ENDPOINT = "https://buildkite.com".freeze
+ ENDPOINT = "https://buildkite.com"
prop_accessor :project_url, :token
boolean_accessor :enable_ssl_verification
diff --git a/app/models/project_services/chat_message/base_message.rb b/app/models/project_services/chat_message/base_message.rb
index 8c68ddc40f2..6542112ba32 100644
--- a/app/models/project_services/chat_message/base_message.rb
+++ b/app/models/project_services/chat_message/base_message.rb
@@ -10,6 +10,7 @@ module ChatMessage
attr_reader :user_avatar
attr_reader :project_name
attr_reader :project_url
+ attr_reader :commit_message_html
def initialize(params)
@markdown = params[:markdown] || false
@@ -18,6 +19,7 @@ module ChatMessage
@user_full_name = params.dig(:user, :name) || params[:user_full_name]
@user_name = params.dig(:user, :username) || params[:user_name]
@user_avatar = params.dig(:user, :avatar_url) || params[:user_avatar]
+ @commit_message_html = params[:commit_message_html] || false
end
def user_combined_name
diff --git a/app/models/project_services/chat_message/push_message.rb b/app/models/project_services/chat_message/push_message.rb
index 5dd0414b7e6..8163fca33a2 100644
--- a/app/models/project_services/chat_message/push_message.rb
+++ b/app/models/project_services/chat_message/push_message.rb
@@ -52,7 +52,8 @@ module ChatMessage
end
def commit_messages
- commits.map { |commit| compose_commit_message(commit) }.join("\n\n")
+ linebreak_chars = commit_message_html ? "<br/>\n<br/>\n" : "\n\n"
+ commits.map { |commit| compose_commit_message(commit) }.join(linebreak_chars)
end
def commit_message_attachments
@@ -63,6 +64,11 @@ module ChatMessage
author = commit[:author][:name]
id = Commit.truncate_sha(commit[:id])
message = commit[:message]
+
+ if commit_message_html
+ message = message.gsub(Gitlab::Regex.breakline_regex, "<br/>\n")
+ end
+
url = commit[:url]
"[#{id}](#{url}): #{message} - #{author}"
diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb
index 7c9ecc6b821..cb75c89136e 100644
--- a/app/models/project_services/chat_notification_service.rb
+++ b/app/models/project_services/chat_notification_service.rb
@@ -5,17 +5,25 @@
class ChatNotificationService < Service
include ChatMessage
+ SUPPORTED_EVENTS = %w[
+ push issue confidential_issue merge_request note confidential_note
+ tag_push pipeline wiki_page deployment
+ ].freeze
+
+ EVENT_CHANNEL = proc { |event| "#{event}_channel" }
+
default_value_for :category, 'chat'
prop_accessor :webhook, :username, :channel
+
+ # Custom serialized properties initialization
+ prop_accessor(*SUPPORTED_EVENTS.map { |event| EVENT_CHANNEL[event] })
+
boolean_accessor :notify_only_broken_pipelines, :notify_only_default_branch
validates :webhook, presence: true, public_url: true, if: :activated?
def initialize_properties
- # Custom serialized properties initialization
- self.supported_events.each { |event| self.class.prop_accessor(event_channel_name(event)) }
-
if properties.nil?
self.properties = {}
self.notify_only_broken_pipelines = true
@@ -32,8 +40,7 @@ class ChatNotificationService < Service
end
def self.supported_events
- %w[push issue confidential_issue merge_request note confidential_note tag_push
- pipeline wiki_page deployment]
+ SUPPORTED_EVENTS
end
def fields
@@ -139,7 +146,7 @@ class ChatNotificationService < Service
end
def event_channel_name(event)
- "#{event}_channel"
+ EVENT_CHANNEL[event]
end
def project_name
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index d08fcd8954d..0728c83005e 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -64,7 +64,12 @@ class JiraService < IssueTrackerService
end
def client
- @client ||= JIRA::Client.new(options)
+ @client ||= begin
+ JIRA::Client.new(options).tap do |client|
+ # Replaces JIRA default http client with our implementation
+ client.request_client = Gitlab::Jira::HttpClient.new(client.options)
+ end
+ end
end
def help
diff --git a/app/models/project_services/microsoft_teams_service.rb b/app/models/project_services/microsoft_teams_service.rb
index c22a6dc26f6..2334b3f7f66 100644
--- a/app/models/project_services/microsoft_teams_service.rb
+++ b/app/models/project_services/microsoft_teams_service.rb
@@ -58,6 +58,6 @@ class MicrosoftTeamsService < ChatNotificationService
end
def custom_data(data)
- super(data).merge(markdown: true)
+ super(data).merge(markdown: true, commit_message_html: true)
end
end
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index c15993bdc06..d3fff100964 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class PivotaltrackerService < Service
- API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits'.freeze
+ API_ENDPOINT = 'https://www.pivotaltracker.com/services/v5/source_commits'
prop_accessor :token, :restrict_to_branch
validates :token, presence: true, if: :activated?
diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb
index 0d35bab7f80..7324890551c 100644
--- a/app/models/project_services/pushover_service.rb
+++ b/app/models/project_services/pushover_service.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class PushoverService < Service
- BASE_URI = 'https://api.pushover.net/1'.freeze
+ BASE_URI = 'https://api.pushover.net/1'
prop_accessor :api_key, :user_key, :device, :priority, :sound
validates :api_key, :user_key, :priority, presence: true, if: :activated?
diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb
index cb16ad75d14..5bfd06476f0 100644
--- a/app/models/project_services/slash_commands_service.rb
+++ b/app/models/project_services/slash_commands_service.rb
@@ -35,7 +35,9 @@ class SlashCommandsService < Service
chat_user = find_chat_user(params)
if chat_user&.user
- return Gitlab::SlashCommands::Presenters::Access.new.access_denied unless chat_user.user.can?(:use_slash_commands)
+ unless chat_user.user.can?(:use_slash_commands)
+ return Gitlab::SlashCommands::Presenters::Access.new.access_denied(project)
+ end
Gitlab::SlashCommands::Command.new(project, chat_user, params).execute
else
diff --git a/app/models/release.rb b/app/models/release.rb
index 459a7c29ad0..b2e65974aa0 100644
--- a/app/models/release.rb
+++ b/app/models/release.rb
@@ -12,6 +12,12 @@ class Release < ApplicationRecord
has_many :links, class_name: 'Releases::Link'
+ # A one-to-one relationship is set up here as part of a MVC: https://gitlab.com/gitlab-org/gitlab-ce/issues/62402
+ # However, on the long term, we will want a many-to-many relationship between Release and Milestone.
+ # The "has_one through" allows us today to set up this one-to-one relationship while setting up the architecture for the long-term (ie intermediate table).
+ has_one :milestone_release
+ has_one :milestone, through: :milestone_release
+
default_value_for :released_at, allows_nil: false do
Time.zone.now
end
@@ -20,6 +26,7 @@ class Release < ApplicationRecord
validates :description, :project, :tag, presence: true
validates :name, presence: true, on: :create
+ validates_associated :milestone_release, message: -> (_, obj) { obj[:value].errors.full_messages.join(",") }
scope :sorted, -> { order(released_at: :desc) }
diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb
index c9ee0653d86..41e63986286 100644
--- a/app/models/remote_mirror.rb
+++ b/app/models/remote_mirror.rb
@@ -200,6 +200,7 @@ class RemoteMirror < ApplicationRecord
result.password = '*****' if result.password
result.user = '*****' if result.user && result.user != 'git' # tokens or other data may be saved as user
result.to_s
+ rescue URI::Error
end
def ensure_remote!
diff --git a/app/models/repository.rb b/app/models/repository.rb
index b957b9b0bdd..5cb4b56a114 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -3,9 +3,9 @@
require 'securerandom'
class Repository
- REF_MERGE_REQUEST = 'merge-requests'.freeze
- REF_KEEP_AROUND = 'keep-around'.freeze
- REF_ENVIRONMENTS = 'environments'.freeze
+ REF_MERGE_REQUEST = 'merge-requests'
+ REF_KEEP_AROUND = 'keep-around'
+ REF_ENVIRONMENTS = 'environments'
ARCHIVE_CACHE_TIME = 60 # Cache archives referred to by a (mutable) ref for 1 minute
ARCHIVE_CACHE_TIME_IMMUTABLE = 3600 # Cache archives referred to by an immutable reference for 1 hour
@@ -239,13 +239,13 @@ class Repository
def branch_exists?(branch_name)
return false unless raw_repository
- branch_names_include?(branch_name)
+ branch_names.include?(branch_name)
end
def tag_exists?(tag_name)
return false unless raw_repository
- tag_names_include?(tag_name)
+ tag_names.include?(tag_name)
end
def ref_exists?(ref)
@@ -456,6 +456,10 @@ class Repository
def after_import
expire_content_cache
+ # This call is stubbed in tests due to being an expensive operation
+ # It can be reenabled for specific tests via:
+ #
+ # allow(DetectRepositoryLanguagesWorker).to receive(:perform_async).and_call_original
DetectRepositoryLanguagesWorker.perform_async(project.id)
end
@@ -565,10 +569,10 @@ class Repository
end
delegate :branch_names, to: :raw_repository
- cache_method_as_redis_set :branch_names, fallback: []
+ cache_method :branch_names, fallback: []
delegate :tag_names, to: :raw_repository
- cache_method_as_redis_set :tag_names, fallback: []
+ cache_method :tag_names, fallback: []
delegate :branch_count, :tag_count, :has_visible_content?, to: :raw_repository
cache_method :branch_count, fallback: 0
@@ -1130,10 +1134,6 @@ class Repository
@cache ||= Gitlab::RepositoryCache.new(self)
end
- def redis_set_cache
- @redis_set_cache ||= Gitlab::RepositorySetCache.new(self)
- end
-
def request_store_cache
@request_store_cache ||= Gitlab::RepositoryCache.new(self, backend: Gitlab::SafeRequestStore)
end
diff --git a/app/models/service.rb b/app/models/service.rb
index f6d8fb1fb46..431c5881460 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -174,7 +174,7 @@ class Service < ApplicationRecord
# Also keep track of updated properties in a similar way as ActiveModel::Dirty
def self.prop_accessor(*args)
args.each do |arg|
- class_eval %{
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
unless method_defined?(arg)
def #{arg}
properties['#{arg}']
@@ -198,7 +198,7 @@ class Service < ApplicationRecord
def #{arg}_was
updated_properties['#{arg}']
end
- }
+ RUBY
end
end
@@ -209,12 +209,12 @@ class Service < ApplicationRecord
self.prop_accessor(*args)
args.each do |arg|
- class_eval %{
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
def #{arg}?
# '!!' is used because nil or empty string is converted to nil
!!ActiveRecord::Type::Boolean.new.cast(#{arg})
end
- }
+ RUBY
end
end
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index 9a2640db9ca..a19755d286a 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -9,7 +9,7 @@ class SystemNoteMetadata < ApplicationRecord
TYPES_WITH_CROSS_REFERENCES = %w[
commit cross_reference
close duplicate
- moved
+ moved merge
].freeze
ICON_TYPES = %w[
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 1ec04189482..f7f30aed832 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -146,6 +146,7 @@ class Todo < ApplicationRecord
def parent
project
end
+ alias_method :resource_parent, :parent
def unmergeable?
action == UNMERGEABLE
diff --git a/app/models/user.rb b/app/models/user.rb
index 6107aaa7fca..67d730e2fa3 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -13,7 +13,6 @@ class User < ApplicationRecord
include Sortable
include CaseSensitivity
include TokenAuthenticatable
- include IgnorableColumn
include FeatureGate
include CreatedAtFilterable
include BulkMemberAccessLoad
@@ -24,9 +23,11 @@ class User < ApplicationRecord
DEFAULT_NOTIFICATION_LEVEL = :participating
- ignore_column :external_email
- ignore_column :email_provider
- ignore_column :authentication_token
+ self.ignored_columns += %i[
+ authentication_token
+ email_provider
+ external_email
+ ]
add_authentication_token_field :incoming_email_token, token_generator: -> { SecureRandom.hex.to_i(16).to_s(36) }
add_authentication_token_field :feed_token
@@ -58,7 +59,7 @@ class User < ApplicationRecord
:validatable, :omniauthable, :confirmable, :registerable
BLOCKED_MESSAGE = "Your account has been blocked. Please contact your GitLab " \
- "administrator if you think this is an error.".freeze
+ "administrator if you think this is an error."
# Override Devise::Models::Trackable#update_tracked_fields!
# to limit database writes to at most once every hour
@@ -493,7 +494,7 @@ class User < ApplicationRecord
def by_login(login)
return unless login
- if login.include?('@'.freeze)
+ if login.include?('@')
unscoped.iwhere(email: login).take
else
unscoped.iwhere(username: login).take
@@ -645,6 +646,13 @@ class User < ApplicationRecord
end
end
+ # will_save_change_to_attribute? is used by Devise to check if it is necessary
+ # to clear any existing reset_password_tokens before updating an authentication_key
+ # and login in our case is a virtual attribute to allow login by username or email.
+ def will_save_change_to_login?
+ will_save_change_to_username? || will_save_change_to_email?
+ end
+
def unique_email
if !emails.exists?(email: email) && Email.exists?(email: email)
errors.add(:email, _('has already been taken'))
diff --git a/app/models/user_status.rb b/app/models/user_status.rb
index 6ced4f56823..016b89bae81 100644
--- a/app/models/user_status.rb
+++ b/app/models/user_status.rb
@@ -5,7 +5,7 @@ class UserStatus < ApplicationRecord
self.primary_key = :user_id
- DEFAULT_EMOJI = 'speech_balloon'.freeze
+ DEFAULT_EMOJI = 'speech_balloon'
belongs_to :user
diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb
index dd8c5d49cf4..fa252af55e4 100644
--- a/app/policies/issue_policy.rb
+++ b/app/policies/issue_policy.rb
@@ -5,6 +5,8 @@ class IssuePolicy < IssuablePolicy
# Make sure to sync this class checks with issue.rb to avoid security problems.
# Check commit 002ad215818450d2cbbc5fa065850a953dc7ada8 for more information.
+ extend ProjectPolicy::ClassMethods
+
desc "User can read confidential issues"
condition(:can_read_confidential) do
@user && IssueCollection.new([@subject]).visible_to(@user).any?
@@ -14,13 +16,12 @@ class IssuePolicy < IssuablePolicy
condition(:confidential, scope: :subject) { @subject.confidential? }
rule { confidential & ~can_read_confidential }.policy do
- prevent :read_issue
+ prevent(*create_read_update_admin_destroy(:issue))
prevent :read_issue_iid
- prevent :update_issue
- prevent :admin_issue
- prevent :create_note
end
+ rule { ~can?(:read_issue) }.prevent :create_note
+
rule { locked }.policy do
prevent :reopen_issue
end
diff --git a/app/policies/merge_request_policy.rb b/app/policies/merge_request_policy.rb
index a3692857ff4..5ad7bdabdff 100644
--- a/app/policies/merge_request_policy.rb
+++ b/app/policies/merge_request_policy.rb
@@ -4,4 +4,10 @@ class MergeRequestPolicy < IssuablePolicy
rule { locked }.policy do
prevent :reopen_merge_request
end
+
+ # Only users who can read the merge request can comment.
+ # Although :read_merge_request is computed in the policy context,
+ # it would not be safe to prevent :create_note there, since
+ # note permissions are shared, and this would apply too broadly.
+ rule { ~can?(:read_merge_request) }.prevent :create_note
end
diff --git a/app/presenters/blob_presenter.rb b/app/presenters/blob_presenter.rb
index 2cf3278d240..3a71d2b87f3 100644
--- a/app/presenters/blob_presenter.rb
+++ b/app/presenters/blob_presenter.rb
@@ -3,12 +3,12 @@
class BlobPresenter < Gitlab::View::Presenter::Delegated
presents :blob
- def highlight(plain: nil)
+ def highlight(to: nil, plain: nil)
load_all_blob_data
Gitlab::Highlight.highlight(
blob.path,
- blob.data,
+ limited_blob_data(to: to),
language: blob.language_from_gitattributes,
plain: plain
)
@@ -23,4 +23,18 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
def load_all_blob_data
blob.load_all_data! if blob.respond_to?(:load_all_data!)
end
+
+ def limited_blob_data(to: nil)
+ return blob.data if to.blank?
+
+ # Even though we don't need all the lines at the start of the file (e.g
+ # viewing the middle part of a file), they still need to be highlighted
+ # to ensure that the succeeding lines can be formatted correctly (e.g.
+ # multi-line comments).
+ all_lines[0..to - 1].join
+ end
+
+ def all_lines
+ @all_lines ||= blob.data.lines
+ end
end
diff --git a/app/presenters/blobs/unfold_presenter.rb b/app/presenters/blobs/unfold_presenter.rb
index a256dd05a4d..487c6fe0757 100644
--- a/app/presenters/blobs/unfold_presenter.rb
+++ b/app/presenters/blobs/unfold_presenter.rb
@@ -26,8 +26,6 @@ module Blobs
# so we can accurately show the rest of the diff when unfolding.
load_all_blob_data
- @all_lines = blob.data.lines
-
handle_full_or_end!
end
@@ -46,7 +44,7 @@ module Blobs
def lines
strong_memoize(:lines) do
- limit(highlight.lines).map(&:html_safe)
+ limit(highlight(to: to).lines).map(&:html_safe)
end
end
@@ -76,7 +74,7 @@ module Blobs
def all_lines_size
strong_memoize(:all_lines_size) do
- @all_lines.size
+ all_lines.size
end
end
@@ -101,7 +99,7 @@ module Blobs
def limited_blob_lines
strong_memoize(:limited_blob_lines) do
- limit(@all_lines)
+ limit(all_lines)
end
end
diff --git a/app/presenters/ci/pipeline_presenter.rb b/app/presenters/ci/pipeline_presenter.rb
index 358473d0a74..a96f97988b2 100644
--- a/app/presenters/ci/pipeline_presenter.rb
+++ b/app/presenters/ci/pipeline_presenter.rb
@@ -34,6 +34,18 @@ module Ci
end
end
+ NAMES = {
+ merge_train: s_('Pipeline|Merge train pipeline'),
+ merged_result: s_('Pipeline|Merged result pipeline'),
+ detached: s_('Pipeline|Detached merge request pipeline')
+ }.freeze
+
+ def name
+ # Currently, `merge_request_event_type` is the only source to name pipelines
+ # but this could be extended with the other types in the future.
+ NAMES.fetch(pipeline.merge_request_event_type, s_('Pipeline|Pipeline'))
+ end
+
def ref_text
if pipeline.detached_merge_request_pipeline?
_("for %{link_to_merge_request} with %{link_to_merge_request_source_branch}").html_safe % { link_to_merge_request: link_to_merge_request, link_to_merge_request_source_branch: link_to_merge_request_source_branch }
diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb
index fff6d23efdf..d1bf0344b66 100644
--- a/app/presenters/clusterable_presenter.rb
+++ b/app/presenters/clusterable_presenter.rb
@@ -53,6 +53,11 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
raise NotImplementedError
end
+ # Will be overidden in EE
+ def environments_cluster_path(cluster)
+ nil
+ end
+
def empty_state_help_text
nil
end
diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb
index 8b967459173..94a827658f0 100644
--- a/app/serializers/deployment_entity.rb
+++ b/app/serializers/deployment_entity.rb
@@ -23,7 +23,7 @@ class DeploymentEntity < Grape::Entity
expose :last?
expose :deployed_by, as: :user, using: UserEntity
- expose :deployable do |deployment, opts|
+ expose :deployable, if: -> (deployment) { deployment.deployable.present? } do |deployment, opts|
deployment.deployable.yield_self do |deployable|
if include_details?
JobEntity.represent(deployable, opts)
diff --git a/app/serializers/entity_date_helper.rb b/app/serializers/entity_date_helper.rb
index f515abe5917..d8f267d7183 100644
--- a/app/serializers/entity_date_helper.rb
+++ b/app/serializers/entity_date_helper.rb
@@ -46,12 +46,14 @@ module EntityDateHelper
# If start date is provided and elapsed, with no due date, it returns "# days elapsed"
def remaining_days_in_words(due_date, start_date = nil)
if due_date&.past?
- content_tag(:strong, 'Past due')
+ content_tag(:strong, _('Past due'))
+ elsif due_date&.today?
+ content_tag(:strong, _('Today'))
elsif start_date&.future?
- content_tag(:strong, 'Upcoming')
+ content_tag(:strong, _('Upcoming'))
elsif due_date
is_upcoming = (due_date - Date.today).to_i > 0
- time_ago = time_ago_in_words(due_date)
+ time_ago = distance_of_time_in_words(due_date, Date.today)
# https://gitlab.com/gitlab-org/gitlab-ce/issues/49440
#
@@ -59,8 +61,8 @@ module EntityDateHelper
# of the string instead of piecewise translations.
content = time_ago
.gsub(/\d+/) { |match| "<strong>#{match}</strong>" }
- .remove("about ")
- remaining_or_ago = is_upcoming ? _("remaining") : _("ago")
+ .remove('about ')
+ remaining_or_ago = is_upcoming ? _('remaining') : _('ago')
"#{content} #{remaining_or_ago}".html_safe
elsif start_date&.past?
diff --git a/app/serializers/merge_request_noteable_entity.rb b/app/serializers/merge_request_noteable_entity.rb
new file mode 100644
index 00000000000..e22be6880bb
--- /dev/null
+++ b/app/serializers/merge_request_noteable_entity.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+class MergeRequestNoteableEntity < Grape::Entity
+ include RequestAwareEntity
+
+ # Currently this attr is exposed to be used in app/assets/javascripts/notes/stores/getters.js
+ # in order to determine whether a noteable is an issue or an MR
+ expose :merge_params
+
+ expose :state
+ expose :source_branch
+ expose :target_branch
+ expose :diff_head_sha
+
+ expose :create_note_path do |merge_request|
+ project_notes_path(merge_request.project, target_type: 'merge_request', target_id: merge_request.id)
+ end
+
+ expose :preview_note_path do |merge_request|
+ preview_markdown_path(merge_request.project, target_type: 'MergeRequest', target_id: merge_request.iid)
+ end
+
+ expose :supports_suggestion?, as: :can_receive_suggestion
+
+ expose :create_issue_to_resolve_discussions_path do |merge_request|
+ presenter(merge_request).create_issue_to_resolve_discussions_path
+ end
+
+ expose :new_blob_path do |merge_request|
+ if presenter(merge_request).can_push_to_source_branch?
+ project_new_blob_path(merge_request.source_project, merge_request.source_branch)
+ end
+ end
+
+ expose :current_user do
+ expose :can_create_note do |merge_request|
+ can?(current_user, :create_note, merge_request)
+ end
+
+ expose :can_update do |merge_request|
+ can?(current_user, :update_merge_request, merge_request)
+ end
+ end
+
+ private
+
+ delegate :current_user, to: :request
+
+ def presenter(merge_request)
+ @presenters ||= {}
+ @presenters[merge_request] ||= MergeRequestPresenter.new(merge_request, current_user: current_user) # rubocop: disable CodeReuse/Presenter
+ end
+end
diff --git a/app/serializers/merge_request_poll_widget_entity.rb b/app/serializers/merge_request_poll_widget_entity.rb
index 65132b4b215..cd33ffa702a 100644
--- a/app/serializers/merge_request_poll_widget_entity.rb
+++ b/app/serializers/merge_request_poll_widget_entity.rb
@@ -65,8 +65,6 @@ class MergeRequestPollWidgetEntity < IssuableEntity
end
end
- expose :supports_suggestion?, as: :can_receive_suggestion
-
expose :create_issue_to_resolve_discussions_path do |merge_request|
presenter(merge_request).create_issue_to_resolve_discussions_path
end
@@ -84,17 +82,9 @@ class MergeRequestPollWidgetEntity < IssuableEntity
presenter(merge_request).can_cherry_pick_on_current_merge_request?
end
- expose :can_create_note do |merge_request|
- can?(current_user, :create_note, merge_request)
- end
-
expose :can_create_issue do |merge_request|
can?(current_user, :create_issue, merge_request.project)
end
-
- expose :can_update do |merge_request|
- can?(current_user, :update_merge_request, merge_request)
- end
end
expose :can_push_to_source_branch do |merge_request|
diff --git a/app/serializers/merge_request_serializer.rb b/app/serializers/merge_request_serializer.rb
index bd2e682a122..aa67cd1f39e 100644
--- a/app/serializers/merge_request_serializer.rb
+++ b/app/serializers/merge_request_serializer.rb
@@ -13,6 +13,8 @@ class MergeRequestSerializer < BaseSerializer
MergeRequestSidebarExtrasEntity
when 'basic'
MergeRequestBasicEntity
+ when 'noteable'
+ MergeRequestNoteableEntity
else
# fallback to widget for old poll requests without `serializer` set
MergeRequestWidgetEntity
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index c8088608cb0..2f2c42a7387 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -3,10 +3,6 @@
class MergeRequestWidgetEntity < Grape::Entity
include RequestAwareEntity
- # Currently this attr is exposed to be used in app/assets/javascripts/notes/stores/getters.js
- # in order to determine whether a noteable is an issue or an MR
- expose :merge_params
-
expose :source_project_full_path do |merge_request|
merge_request.source_project&.full_path
end
@@ -35,18 +31,10 @@ class MergeRequestWidgetEntity < Grape::Entity
cached_widget_project_json_merge_request_path(merge_request.target_project, merge_request, format: :json)
end
- expose :create_note_path do |merge_request|
- project_notes_path(merge_request.project, target_type: 'merge_request', target_id: merge_request.id)
- end
-
expose :commit_change_content_path do |merge_request|
commit_change_content_project_merge_request_path(merge_request.project, merge_request)
end
- expose :preview_note_path do |merge_request|
- preview_markdown_path(merge_request.project, target_type: 'MergeRequest', target_id: merge_request.iid)
- end
-
expose :conflicts_docs_path do |merge_request|
help_page_path('user/project/merge_requests/resolve_conflicts.md')
end
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index 9ef93b2387f..94e8b174f0f 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -2,6 +2,9 @@
class PipelineEntity < Grape::Entity
include RequestAwareEntity
+ include Gitlab::Utils::StrongMemoize
+
+ delegate :name, :failure_reason, to: :presented_pipeline
expose :id
expose :user, using: UserEntity
@@ -36,6 +39,7 @@ class PipelineEntity < Grape::Entity
expose :ordered_stages, as: :stages, using: StageEntity
expose :duration
expose :finished_at
+ expose :name
end
expose :merge_request, if: -> (*) { has_presentable_merge_request? }, with: MergeRequestForPipelineEntity do |pipeline|
@@ -59,13 +63,11 @@ class PipelineEntity < Grape::Entity
end
expose :commit, using: CommitEntity
+ expose :merge_request_event_type, if: -> (pipeline, _) { pipeline.merge_request_event? }
expose :source_sha, if: -> (pipeline, _) { pipeline.merge_request_pipeline? }
expose :target_sha, if: -> (pipeline, _) { pipeline.merge_request_pipeline? }
expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? }
-
- expose :failure_reason, if: -> (pipeline, _) { pipeline.failure_reason? } do |pipeline|
- pipeline.present.failure_reason
- end
+ expose :failure_reason, if: -> (pipeline, _) { pipeline.failure_reason? }
expose :retry_path, if: -> (*) { can_retry? } do |pipeline|
retry_project_pipeline_path(pipeline.project, pipeline)
@@ -97,4 +99,10 @@ class PipelineEntity < Grape::Entity
def detailed_status
pipeline.detailed_status(request.current_user)
end
+
+ def presented_pipeline
+ strong_memoize(:presented_pipeline) do
+ pipeline.present
+ end
+ end
end
diff --git a/app/services/application_settings/update_service.rb b/app/services/application_settings/update_service.rb
index 8115585b7a8..e06a87c4763 100644
--- a/app/services/application_settings/update_service.rb
+++ b/app/services/application_settings/update_service.rb
@@ -6,6 +6,8 @@ module ApplicationSettings
attr_reader :params, :application_setting
+ MARKDOWN_CACHE_INVALIDATING_PARAMS = %w(asset_proxy_enabled asset_proxy_url asset_proxy_secret_key asset_proxy_whitelist).freeze
+
def execute
validate_classification_label(application_setting, :external_authorization_service_default_label) unless bypass_external_auth?
@@ -25,7 +27,13 @@ module ApplicationSettings
params[:usage_stats_set_by_user_id] = current_user.id
end
- @application_setting.update(@params)
+ @application_setting.assign_attributes(params)
+
+ if invalidate_markdown_cache?
+ @application_setting[:local_markdown_version] = @application_setting.local_markdown_version + 1
+ end
+
+ @application_setting.save
end
private
@@ -41,6 +49,11 @@ module ApplicationSettings
@application_setting.add_to_outbound_local_requests_whitelist(values_array)
end
+ def invalidate_markdown_cache?
+ !params.key?(:local_markdown_version) &&
+ (@application_setting.changes.keys & MARKDOWN_CACHE_INVALIDATING_PARAMS).any?
+ end
+
def update_terms(terms)
return unless terms.present?
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index 0a069320936..9e7319c1d9b 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -124,13 +124,21 @@ module Auth
build_can_pull?(requested_project) || user_can_pull?(requested_project) || deploy_token_can_pull?(requested_project)
when 'push'
build_can_push?(requested_project) || user_can_push?(requested_project)
- when '*', 'delete'
+ when 'delete'
+ build_can_delete?(requested_project) || user_can_admin?(requested_project)
+ when '*'
user_can_admin?(requested_project)
else
false
end
end
+ def build_can_delete?(requested_project)
+ # Build can delete only from the project from which it originates
+ has_authentication_ability?(:build_destroy_container_image) &&
+ requested_project == project
+ end
+
def registry
Gitlab.config.registry
end
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index 3e968c8f707..c39edd5c114 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -44,6 +44,10 @@ class BaseService
model.errors.add(:visibility_level, "#{level_name} has been restricted by your GitLab administrator")
end
+ def visibility_level
+ params[:visibility].is_a?(String) ? Gitlab::VisibilityLevel.level_value(params[:visibility]) : params[:visibility_level]
+ end
+
private
def error(message, http_status = nil)
diff --git a/app/services/boards/lists/list_service.rb b/app/services/boards/lists/list_service.rb
index 5cf5f14a55b..1f20ec8df9e 100644
--- a/app/services/boards/lists/list_service.rb
+++ b/app/services/boards/lists/list_service.rb
@@ -6,7 +6,7 @@ module Boards
def execute(board)
board.lists.create(list_type: :backlog) unless board.lists.backlog.exists?
- board.lists.preload_associations
+ board.lists.preload_associations(current_user)
end
end
end
diff --git a/app/services/boards/lists/update_service.rb b/app/services/boards/lists/update_service.rb
new file mode 100644
index 00000000000..2ddeb6f0bd8
--- /dev/null
+++ b/app/services/boards/lists/update_service.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module Boards
+ module Lists
+ class UpdateService < Boards::BaseService
+ def execute(list)
+ return not_authorized if preferences? && !can_read?(list)
+ return not_authorized if position? && !can_admin?(list)
+
+ if update_preferences(list) || update_position(list)
+ success(list: list)
+ else
+ error(list.errors.messages, 422)
+ end
+ end
+
+ def update_preferences(list)
+ return unless preferences?
+
+ list.update_preferences_for(current_user, preferences)
+ end
+
+ def update_position(list)
+ return unless position?
+
+ move_service = Boards::Lists::MoveService.new(parent, current_user, params)
+
+ move_service.execute(list)
+ end
+
+ def preferences
+ { collapsed: Gitlab::Utils.to_boolean(params[:collapsed]) }
+ end
+
+ def not_authorized
+ error("Not authorized", 403)
+ end
+
+ def preferences?
+ params.has_key?(:collapsed)
+ end
+
+ def position?
+ params.has_key?(:position)
+ end
+
+ def can_read?(list)
+ Ability.allowed?(current_user, :read_list, parent)
+ end
+
+ def can_admin?(list)
+ Ability.allowed?(current_user, :admin_list, parent)
+ end
+ end
+ end
+end
diff --git a/app/services/chat_names/authorize_user_service.rb b/app/services/chat_names/authorize_user_service.rb
index 78b53cb3637..f7780488923 100644
--- a/app/services/chat_names/authorize_user_service.rb
+++ b/app/services/chat_names/authorize_user_service.rb
@@ -24,16 +24,16 @@ module ChatNames
end
def chat_name_token
- Gitlab::ChatNameToken.new
+ @chat_name_token ||= Gitlab::ChatNameToken.new
end
def chat_name_params
{
- service_id: @service.id,
- team_id: @params[:team_id],
+ service_id: @service.id,
+ team_id: @params[:team_id],
team_domain: @params[:team_domain],
- chat_id: @params[:user_id],
- chat_name: @params[:user_name]
+ chat_id: @params[:user_id],
+ chat_name: @params[:user_name]
}
end
end
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index cdcc4b15bea..29317f1176e 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -15,7 +15,8 @@ module Ci
Gitlab::Ci::Pipeline::Chain::Limit::Size,
Gitlab::Ci::Pipeline::Chain::Populate,
Gitlab::Ci::Pipeline::Chain::Create,
- Gitlab::Ci::Pipeline::Chain::Limit::Activity].freeze
+ Gitlab::Ci::Pipeline::Chain::Limit::Activity,
+ Gitlab::Ci::Pipeline::Chain::Limit::JobActivity].freeze
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, **options, &block)
@pipeline = Ci::Pipeline.new
diff --git a/app/services/clusters/applications/check_installation_progress_service.rb b/app/services/clusters/applications/check_installation_progress_service.rb
index 3c6803d24e6..65d08966802 100644
--- a/app/services/clusters/applications/check_installation_progress_service.rb
+++ b/app/services/clusters/applications/check_installation_progress_service.rb
@@ -2,24 +2,7 @@
module Clusters
module Applications
- class CheckInstallationProgressService < BaseHelmService
- def execute
- return unless operation_in_progress?
-
- case installation_phase
- when Gitlab::Kubernetes::Pod::SUCCEEDED
- on_success
- when Gitlab::Kubernetes::Pod::FAILED
- on_failed
- else
- check_timeout
- end
- rescue Kubeclient::HttpError => e
- log_error(e)
-
- app.make_errored!("Kubernetes error: #{e.error_code}")
- end
-
+ class CheckInstallationProgressService < CheckProgressService
private
def operation_in_progress?
@@ -32,10 +15,6 @@ module Clusters
remove_installation_pod
end
- def on_failed
- app.make_errored!("Operation failed. Check pod logs for #{pod_name} for more details.")
- end
-
def check_timeout
if timed_out?
begin
@@ -54,18 +33,6 @@ module Clusters
def timed_out?
Time.now.utc - app.updated_at.utc > ClusterWaitForAppInstallationWorker::TIMEOUT
end
-
- def remove_installation_pod
- helm_api.delete_pod!(pod_name)
- end
-
- def installation_phase
- helm_api.status(pod_name)
- end
-
- def installation_errors
- helm_api.log(pod_name)
- end
end
end
end
diff --git a/app/services/clusters/applications/check_progress_service.rb b/app/services/clusters/applications/check_progress_service.rb
new file mode 100644
index 00000000000..4a07b955f8e
--- /dev/null
+++ b/app/services/clusters/applications/check_progress_service.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Applications
+ class CheckProgressService < BaseHelmService
+ def execute
+ return unless operation_in_progress?
+
+ case pod_phase
+ when Gitlab::Kubernetes::Pod::SUCCEEDED
+ on_success
+ when Gitlab::Kubernetes::Pod::FAILED
+ on_failed
+ else
+ check_timeout
+ end
+ rescue Kubeclient::HttpError => e
+ log_error(e)
+
+ app.make_errored!(_('Kubernetes error: %{error_code}') % { error_code: e.error_code })
+ end
+
+ private
+
+ def operation_in_progress?
+ raise NotImplementedError
+ end
+
+ def on_success
+ raise NotImplementedError
+ end
+
+ def pod_name
+ raise NotImplementedError
+ end
+
+ def on_failed
+ app.make_errored!(_('Operation failed. Check pod logs for %{pod_name} for more details.') % { pod_name: pod_name })
+ end
+
+ def timed_out?
+ raise NotImplementedError
+ end
+
+ def pod_phase
+ helm_api.status(pod_name)
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/applications/check_uninstall_progress_service.rb b/app/services/clusters/applications/check_uninstall_progress_service.rb
index e51d84ef052..6a618d61c4f 100644
--- a/app/services/clusters/applications/check_uninstall_progress_service.rb
+++ b/app/services/clusters/applications/check_uninstall_progress_service.rb
@@ -2,26 +2,13 @@
module Clusters
module Applications
- class CheckUninstallProgressService < BaseHelmService
- def execute
- return unless app.uninstalling?
-
- case installation_phase
- when Gitlab::Kubernetes::Pod::SUCCEEDED
- on_success
- when Gitlab::Kubernetes::Pod::FAILED
- on_failed
- else
- check_timeout
- end
- rescue Kubeclient::HttpError => e
- log_error(e)
+ class CheckUninstallProgressService < CheckProgressService
+ private
- app.make_errored!(_('Kubernetes error: %{error_code}') % { error_code: e.error_code })
+ def operation_in_progress?
+ app.uninstalling?
end
- private
-
def on_success
app.post_uninstall
app.destroy!
@@ -31,10 +18,6 @@ module Clusters
remove_installation_pod
end
- def on_failed
- app.make_errored!(_('Operation failed. Check pod logs for %{pod_name} for more details.') % { pod_name: pod_name })
- end
-
def check_timeout
if timed_out?
app.make_errored!(_('Operation timed out. Check pod logs for %{pod_name} for more details.') % { pod_name: pod_name })
@@ -50,14 +33,6 @@ module Clusters
def timed_out?
Time.now.utc - app.updated_at.utc > WaitForUninstallAppWorker::TIMEOUT
end
-
- def remove_installation_pod
- helm_api.delete_pod!(pod_name)
- end
-
- def installation_phase
- helm_api.status(pod_name)
- end
end
end
end
diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb
index e5a5b73321a..bbbeb4b30e4 100644
--- a/app/services/clusters/create_service.rb
+++ b/app/services/clusters/create_service.rb
@@ -37,7 +37,7 @@ module Clusters
end
def global_params
- { user: current_user, namespace_per_environment: Feature.enabled?(:kubernetes_namespace_per_environment, default_enabled: true) }
+ { user: current_user }
end
def clusterable_params
diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb
index 2f3c1df7651..c5cde831964 100644
--- a/app/services/clusters/gcp/finalize_creation_service.rb
+++ b/app/services/clusters/gcp/finalize_creation_service.rb
@@ -26,7 +26,7 @@ module Clusters
private
def create_gitlab_service_account!
- Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService.gitlab_creator(
+ Clusters::Kubernetes::CreateOrUpdateServiceAccountService.gitlab_creator(
kube_client,
rbac: create_rbac_cluster?
).execute
@@ -49,10 +49,10 @@ module Clusters
end
def request_kubernetes_token
- Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new(
+ Clusters::Kubernetes::FetchKubernetesTokenService.new(
kube_client,
- Clusters::Gcp::Kubernetes::GITLAB_ADMIN_TOKEN_NAME,
- Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE
+ Clusters::Kubernetes::GITLAB_ADMIN_TOKEN_NAME,
+ Clusters::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE
).execute
end
diff --git a/app/services/clusters/gcp/kubernetes.rb b/app/services/clusters/gcp/kubernetes.rb
deleted file mode 100644
index 85711764785..00000000000
--- a/app/services/clusters/gcp/kubernetes.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Gcp
- module Kubernetes
- GITLAB_SERVICE_ACCOUNT_NAME = 'gitlab'
- GITLAB_SERVICE_ACCOUNT_NAMESPACE = 'default'
- GITLAB_ADMIN_TOKEN_NAME = 'gitlab-token'
- GITLAB_CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin'
- GITLAB_CLUSTER_ROLE_NAME = 'cluster-admin'
- PROJECT_CLUSTER_ROLE_NAME = 'edit'
- GITLAB_KNATIVE_SERVING_ROLE_NAME = 'gitlab-knative-serving-role'
- GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME = 'gitlab-knative-serving-rolebinding'
- end
- end
-end
diff --git a/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb b/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb
deleted file mode 100644
index c45dac7b273..00000000000
--- a/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Gcp
- module Kubernetes
- class CreateOrUpdateNamespaceService
- def initialize(cluster:, kubernetes_namespace:)
- @cluster = cluster
- @kubernetes_namespace = kubernetes_namespace
- @platform = cluster.platform
- end
-
- def execute
- create_project_service_account
- configure_kubernetes_token
-
- kubernetes_namespace.save!
- end
-
- private
-
- attr_reader :cluster, :kubernetes_namespace, :platform
-
- def create_project_service_account
- Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService.namespace_creator(
- platform.kubeclient,
- service_account_name: kubernetes_namespace.service_account_name,
- service_account_namespace: kubernetes_namespace.namespace,
- rbac: platform.rbac?
- ).execute
- end
-
- def configure_kubernetes_token
- kubernetes_namespace.service_account_token = fetch_service_account_token
- end
-
- def fetch_service_account_token
- Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new(
- platform.kubeclient,
- kubernetes_namespace.token_name,
- kubernetes_namespace.namespace
- ).execute
- end
- end
- end
- end
-end
diff --git a/app/services/clusters/gcp/kubernetes/create_or_update_service_account_service.rb b/app/services/clusters/gcp/kubernetes/create_or_update_service_account_service.rb
deleted file mode 100644
index 7c5450dbcd6..00000000000
--- a/app/services/clusters/gcp/kubernetes/create_or_update_service_account_service.rb
+++ /dev/null
@@ -1,141 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Gcp
- module Kubernetes
- class CreateOrUpdateServiceAccountService
- def initialize(kubeclient, service_account_name:, service_account_namespace:, token_name:, rbac:, namespace_creator: false, role_binding_name: nil)
- @kubeclient = kubeclient
- @service_account_name = service_account_name
- @service_account_namespace = service_account_namespace
- @token_name = token_name
- @rbac = rbac
- @namespace_creator = namespace_creator
- @role_binding_name = role_binding_name
- end
-
- def self.gitlab_creator(kubeclient, rbac:)
- self.new(
- kubeclient,
- service_account_name: Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAME,
- service_account_namespace: Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE,
- token_name: Clusters::Gcp::Kubernetes::GITLAB_ADMIN_TOKEN_NAME,
- rbac: rbac
- )
- end
-
- def self.namespace_creator(kubeclient, service_account_name:, service_account_namespace:, rbac:)
- self.new(
- kubeclient,
- service_account_name: service_account_name,
- service_account_namespace: service_account_namespace,
- token_name: "#{service_account_namespace}-token",
- rbac: rbac,
- namespace_creator: true,
- role_binding_name: "gitlab-#{service_account_namespace}"
- )
- end
-
- def execute
- ensure_project_namespace_exists if namespace_creator
-
- kubeclient.create_or_update_service_account(service_account_resource)
- kubeclient.create_or_update_secret(service_account_token_resource)
-
- return unless rbac
-
- create_role_or_cluster_role_binding
-
- return unless namespace_creator
-
- create_or_update_knative_serving_role
- create_or_update_knative_serving_role_binding
- end
-
- private
-
- attr_reader :kubeclient, :service_account_name, :service_account_namespace, :token_name, :rbac, :namespace_creator, :role_binding_name
-
- def ensure_project_namespace_exists
- Gitlab::Kubernetes::Namespace.new(
- service_account_namespace,
- kubeclient
- ).ensure_exists!
- end
-
- def create_role_or_cluster_role_binding
- if namespace_creator
- kubeclient.create_or_update_role_binding(role_binding_resource)
- else
- kubeclient.create_or_update_cluster_role_binding(cluster_role_binding_resource)
- end
- end
-
- def create_or_update_knative_serving_role
- kubeclient.update_role(knative_serving_role_resource)
- end
-
- def create_or_update_knative_serving_role_binding
- kubeclient.update_role_binding(knative_serving_role_binding_resource)
- end
-
- def service_account_resource
- Gitlab::Kubernetes::ServiceAccount.new(
- service_account_name,
- service_account_namespace
- ).generate
- end
-
- def service_account_token_resource
- Gitlab::Kubernetes::ServiceAccountToken.new(
- token_name,
- service_account_name,
- service_account_namespace
- ).generate
- end
-
- def cluster_role_binding_resource
- subjects = [{ kind: 'ServiceAccount', name: service_account_name, namespace: service_account_namespace }]
-
- Gitlab::Kubernetes::ClusterRoleBinding.new(
- Clusters::Gcp::Kubernetes::GITLAB_CLUSTER_ROLE_BINDING_NAME,
- Clusters::Gcp::Kubernetes::GITLAB_CLUSTER_ROLE_NAME,
- subjects
- ).generate
- end
-
- def role_binding_resource
- Gitlab::Kubernetes::RoleBinding.new(
- name: role_binding_name,
- role_name: Clusters::Gcp::Kubernetes::PROJECT_CLUSTER_ROLE_NAME,
- role_kind: :ClusterRole,
- namespace: service_account_namespace,
- service_account_name: service_account_name
- ).generate
- end
-
- def knative_serving_role_resource
- Gitlab::Kubernetes::Role.new(
- name: Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME,
- namespace: service_account_namespace,
- rules: [{
- apiGroups: %w(serving.knative.dev),
- resources: %w(configurations configurationgenerations routes revisions revisionuids autoscalers services),
- verbs: %w(get list create update delete patch watch)
- }]
- ).generate
- end
-
- def knative_serving_role_binding_resource
- Gitlab::Kubernetes::RoleBinding.new(
- name: Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME,
- role_name: Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME,
- role_kind: :Role,
- namespace: service_account_namespace,
- service_account_name: service_account_name
- ).generate
- end
- end
- end
- end
-end
diff --git a/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb b/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb
deleted file mode 100644
index 5d9bdc52d37..00000000000
--- a/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Gcp
- module Kubernetes
- class FetchKubernetesTokenService
- DEFAULT_TOKEN_RETRY_DELAY = 5.seconds
- TOKEN_RETRY_LIMIT = 5
-
- attr_reader :kubeclient, :service_account_token_name, :namespace
-
- def initialize(kubeclient, service_account_token_name, namespace, token_retry_delay: DEFAULT_TOKEN_RETRY_DELAY)
- @kubeclient = kubeclient
- @service_account_token_name = service_account_token_name
- @namespace = namespace
- @token_retry_delay = token_retry_delay
- end
-
- def execute
- # Kubernetes will create the Secret and set the token asynchronously
- # so it is necessary to retry
- # https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/#token-controller
- TOKEN_RETRY_LIMIT.times do
- token_base64 = get_secret&.dig('data', 'token')
- return Base64.decode64(token_base64) if token_base64
-
- sleep @token_retry_delay
- end
-
- nil
- end
-
- private
-
- def get_secret
- kubeclient.get_secret(service_account_token_name, namespace).as_json
- rescue Kubeclient::ResourceNotFoundError
- end
- end
- end
- end
-end
diff --git a/app/services/clusters/kubernetes/create_or_update_namespace_service.rb b/app/services/clusters/kubernetes/create_or_update_namespace_service.rb
new file mode 100644
index 00000000000..15be8446cc0
--- /dev/null
+++ b/app/services/clusters/kubernetes/create_or_update_namespace_service.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Kubernetes
+ class CreateOrUpdateNamespaceService
+ def initialize(cluster:, kubernetes_namespace:)
+ @cluster = cluster
+ @kubernetes_namespace = kubernetes_namespace
+ @platform = cluster.platform
+ end
+
+ def execute
+ create_project_service_account
+ configure_kubernetes_token
+
+ kubernetes_namespace.save!
+ end
+
+ private
+
+ attr_reader :cluster, :kubernetes_namespace, :platform
+
+ def create_project_service_account
+ Clusters::Kubernetes::CreateOrUpdateServiceAccountService.namespace_creator(
+ platform.kubeclient,
+ service_account_name: kubernetes_namespace.service_account_name,
+ service_account_namespace: kubernetes_namespace.namespace,
+ rbac: platform.rbac?
+ ).execute
+ end
+
+ def configure_kubernetes_token
+ kubernetes_namespace.service_account_token = fetch_service_account_token
+ end
+
+ def fetch_service_account_token
+ Clusters::Kubernetes::FetchKubernetesTokenService.new(
+ platform.kubeclient,
+ kubernetes_namespace.token_name,
+ kubernetes_namespace.namespace
+ ).execute
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/kubernetes/create_or_update_service_account_service.rb b/app/services/clusters/kubernetes/create_or_update_service_account_service.rb
new file mode 100644
index 00000000000..8b8ad924b64
--- /dev/null
+++ b/app/services/clusters/kubernetes/create_or_update_service_account_service.rb
@@ -0,0 +1,139 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Kubernetes
+ class CreateOrUpdateServiceAccountService
+ def initialize(kubeclient, service_account_name:, service_account_namespace:, token_name:, rbac:, namespace_creator: false, role_binding_name: nil)
+ @kubeclient = kubeclient
+ @service_account_name = service_account_name
+ @service_account_namespace = service_account_namespace
+ @token_name = token_name
+ @rbac = rbac
+ @namespace_creator = namespace_creator
+ @role_binding_name = role_binding_name
+ end
+
+ def self.gitlab_creator(kubeclient, rbac:)
+ self.new(
+ kubeclient,
+ service_account_name: Clusters::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAME,
+ service_account_namespace: Clusters::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE,
+ token_name: Clusters::Kubernetes::GITLAB_ADMIN_TOKEN_NAME,
+ rbac: rbac
+ )
+ end
+
+ def self.namespace_creator(kubeclient, service_account_name:, service_account_namespace:, rbac:)
+ self.new(
+ kubeclient,
+ service_account_name: service_account_name,
+ service_account_namespace: service_account_namespace,
+ token_name: "#{service_account_namespace}-token",
+ rbac: rbac,
+ namespace_creator: true,
+ role_binding_name: "gitlab-#{service_account_namespace}"
+ )
+ end
+
+ def execute
+ ensure_project_namespace_exists if namespace_creator
+
+ kubeclient.create_or_update_service_account(service_account_resource)
+ kubeclient.create_or_update_secret(service_account_token_resource)
+
+ return unless rbac
+
+ create_role_or_cluster_role_binding
+
+ return unless namespace_creator
+
+ create_or_update_knative_serving_role
+ create_or_update_knative_serving_role_binding
+ end
+
+ private
+
+ attr_reader :kubeclient, :service_account_name, :service_account_namespace, :token_name, :rbac, :namespace_creator, :role_binding_name
+
+ def ensure_project_namespace_exists
+ Gitlab::Kubernetes::Namespace.new(
+ service_account_namespace,
+ kubeclient
+ ).ensure_exists!
+ end
+
+ def create_role_or_cluster_role_binding
+ if namespace_creator
+ kubeclient.create_or_update_role_binding(role_binding_resource)
+ else
+ kubeclient.create_or_update_cluster_role_binding(cluster_role_binding_resource)
+ end
+ end
+
+ def create_or_update_knative_serving_role
+ kubeclient.update_role(knative_serving_role_resource)
+ end
+
+ def create_or_update_knative_serving_role_binding
+ kubeclient.update_role_binding(knative_serving_role_binding_resource)
+ end
+
+ def service_account_resource
+ Gitlab::Kubernetes::ServiceAccount.new(
+ service_account_name,
+ service_account_namespace
+ ).generate
+ end
+
+ def service_account_token_resource
+ Gitlab::Kubernetes::ServiceAccountToken.new(
+ token_name,
+ service_account_name,
+ service_account_namespace
+ ).generate
+ end
+
+ def cluster_role_binding_resource
+ subjects = [{ kind: 'ServiceAccount', name: service_account_name, namespace: service_account_namespace }]
+
+ Gitlab::Kubernetes::ClusterRoleBinding.new(
+ Clusters::Kubernetes::GITLAB_CLUSTER_ROLE_BINDING_NAME,
+ Clusters::Kubernetes::GITLAB_CLUSTER_ROLE_NAME,
+ subjects
+ ).generate
+ end
+
+ def role_binding_resource
+ Gitlab::Kubernetes::RoleBinding.new(
+ name: role_binding_name,
+ role_name: Clusters::Kubernetes::PROJECT_CLUSTER_ROLE_NAME,
+ role_kind: :ClusterRole,
+ namespace: service_account_namespace,
+ service_account_name: service_account_name
+ ).generate
+ end
+
+ def knative_serving_role_resource
+ Gitlab::Kubernetes::Role.new(
+ name: Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME,
+ namespace: service_account_namespace,
+ rules: [{
+ apiGroups: %w(serving.knative.dev),
+ resources: %w(configurations configurationgenerations routes revisions revisionuids autoscalers services),
+ verbs: %w(get list create update delete patch watch)
+ }]
+ ).generate
+ end
+
+ def knative_serving_role_binding_resource
+ Gitlab::Kubernetes::RoleBinding.new(
+ name: Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME,
+ role_name: Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME,
+ role_kind: :Role,
+ namespace: service_account_namespace,
+ service_account_name: service_account_name
+ ).generate
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/kubernetes/fetch_kubernetes_token_service.rb b/app/services/clusters/kubernetes/fetch_kubernetes_token_service.rb
new file mode 100644
index 00000000000..aaf437abfad
--- /dev/null
+++ b/app/services/clusters/kubernetes/fetch_kubernetes_token_service.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Kubernetes
+ class FetchKubernetesTokenService
+ DEFAULT_TOKEN_RETRY_DELAY = 5.seconds
+ TOKEN_RETRY_LIMIT = 5
+
+ attr_reader :kubeclient, :service_account_token_name, :namespace
+
+ def initialize(kubeclient, service_account_token_name, namespace, token_retry_delay: DEFAULT_TOKEN_RETRY_DELAY)
+ @kubeclient = kubeclient
+ @service_account_token_name = service_account_token_name
+ @namespace = namespace
+ @token_retry_delay = token_retry_delay
+ end
+
+ def execute
+ # Kubernetes will create the Secret and set the token asynchronously
+ # so it is necessary to retry
+ # https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/#token-controller
+ TOKEN_RETRY_LIMIT.times do
+ token_base64 = get_secret&.dig('data', 'token')
+ return Base64.decode64(token_base64) if token_base64
+
+ sleep @token_retry_delay
+ end
+
+ nil
+ end
+
+ private
+
+ def get_secret
+ kubeclient.get_secret(service_account_token_name, namespace).as_json
+ rescue Kubeclient::ResourceNotFoundError
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/kubernetes/kubernetes.rb b/app/services/clusters/kubernetes/kubernetes.rb
new file mode 100644
index 00000000000..7d5d0c2c1d6
--- /dev/null
+++ b/app/services/clusters/kubernetes/kubernetes.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Kubernetes
+ GITLAB_SERVICE_ACCOUNT_NAME = 'gitlab'
+ GITLAB_SERVICE_ACCOUNT_NAMESPACE = 'default'
+ GITLAB_ADMIN_TOKEN_NAME = 'gitlab-token'
+ GITLAB_CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin'
+ GITLAB_CLUSTER_ROLE_NAME = 'cluster-admin'
+ PROJECT_CLUSTER_ROLE_NAME = 'edit'
+ GITLAB_KNATIVE_SERVING_ROLE_NAME = 'gitlab-knative-serving-role'
+ GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME = 'gitlab-knative-serving-rolebinding'
+ end
+end
diff --git a/app/services/create_snippet_service.rb b/app/services/create_snippet_service.rb
index 6e5bf823cc7..0aa76df35ba 100644
--- a/app/services/create_snippet_service.rb
+++ b/app/services/create_snippet_service.rb
@@ -12,7 +12,7 @@ class CreateSnippetService < BaseService
PersonalSnippet.new(params)
end
- unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level])
+ unless Gitlab::VisibilityLevel.allowed_for?(current_user, snippet.visibility_level)
deny_visibility_level(snippet)
return snippet
end
diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb
index e7464fd9d5f..39266a6c961 100644
--- a/app/services/event_create_service.rb
+++ b/app/services/event_create_service.rb
@@ -95,16 +95,23 @@ class EventCreateService
private
def create_record_event(record, current_user, status)
- create_event(record.project, current_user, status, target_id: record.id, target_type: record.class.name)
+ create_event(record.resource_parent, current_user, status, target_id: record.id, target_type: record.class.name)
end
- def create_event(project, current_user, status, attributes = {})
+ def create_event(resource_parent, current_user, status, attributes = {})
attributes.reverse_merge!(
- project: project,
action: status,
author_id: current_user.id
)
+ resource_parent_attr = case resource_parent
+ when Project
+ :project
+ when Group
+ :group
+ end
+ attributes[resource_parent_attr] = resource_parent if resource_parent_attr
+
Event.create!(attributes)
end
end
diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb
index e78e5d5fc2c..1dd22d7a3ae 100644
--- a/app/services/groups/create_service.rb
+++ b/app/services/groups/create_service.rb
@@ -68,9 +68,5 @@ module Groups
true
end
-
- def visibility_level
- params[:visibility].present? ? Gitlab::VisibilityLevel.level_value(params[:visibility]) : params[:visibility_level]
- end
end
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 83710ffce2f..a4243b7bc5e 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -285,7 +285,7 @@ class NotificationService
recipients = NotificationRecipientService.build_new_note_recipients(note)
recipients.each do |recipient|
- mailer.send(notify_method, recipient.user.id, note.id).deliver_later
+ mailer.send(notify_method, recipient.user.id, note.id, recipient.reason).deliver_later
end
end
@@ -293,11 +293,16 @@ class NotificationService
def new_access_request(member)
return true unless member.notifiable?(:subscription)
- recipients = member.source.members.active_without_invites_and_requests.owners_and_maintainers
- if fallback_to_group_owners_maintainers?(recipients, member)
- recipients = member.source.group.members.active_without_invites_and_requests.owners_and_maintainers
+ source = member.source
+
+ recipients = source.access_request_approvers_to_be_notified
+
+ if fallback_to_group_access_request_approvers?(recipients, source)
+ recipients = source.group.access_request_approvers_to_be_notified
end
+ return true if recipients.empty?
+
recipients.each { |recipient| deliver_access_request_email(recipient, member) }
end
@@ -611,9 +616,9 @@ class NotificationService
mailer.member_access_requested_email(member.real_source_type, member.id, recipient.user.id).deliver_later
end
- def fallback_to_group_owners_maintainers?(recipients, member)
+ def fallback_to_group_access_request_approvers?(recipients, source)
return false if recipients.present?
- member.source.respond_to?(:group) && member.source.group
+ source.respond_to?(:group) && source.group
end
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 89dc4375c63..942a45286b2 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -5,9 +5,11 @@ module Projects
include ValidatesClassificationLabel
def initialize(user, params)
- @current_user, @params = user, params.dup
- @skip_wiki = @params.delete(:skip_wiki)
+ @current_user, @params = user, params.dup
+ @skip_wiki = @params.delete(:skip_wiki)
@initialize_with_readme = Gitlab::Utils.to_boolean(@params.delete(:initialize_with_readme))
+ @import_data = @params.delete(:import_data)
+ @relations_block = @params.delete(:relations_block)
end
def execute
@@ -15,14 +17,11 @@ module Projects
return ::Projects::CreateFromTemplateService.new(current_user, params).execute
end
- import_data = params.delete(:import_data)
- relations_block = params.delete(:relations_block)
-
@project = Project.new(params)
# Make sure that the user is allowed to use the specified visibility level
- unless Gitlab::VisibilityLevel.allowed_for?(current_user, @project.visibility_level)
- deny_visibility_level(@project)
+ if project_visibility.restricted?
+ deny_visibility_level(@project, project_visibility.visibility_level)
return @project
end
@@ -44,7 +43,7 @@ module Projects
@project.namespace_id = current_user.namespace_id
end
- relations_block&.call(@project)
+ @relations_block&.call(@project)
yield(@project) if block_given?
validate_classification_label(@project, :external_authorization_classification_label)
@@ -54,7 +53,7 @@ module Projects
@project.creator = current_user
- save_project_and_import_data(import_data)
+ save_project_and_import_data
after_create_actions if @project.persisted?
@@ -129,9 +128,9 @@ module Projects
!@project.feature_available?(:wiki, current_user) || @skip_wiki
end
- def save_project_and_import_data(import_data)
+ def save_project_and_import_data
Project.transaction do
- @project.create_or_update_import_data(data: import_data[:data], credentials: import_data[:credentials]) if import_data
+ @project.create_or_update_import_data(data: @import_data[:data], credentials: @import_data[:credentials]) if @import_data
if @project.save
unless @project.gitlab_project_import?
@@ -192,5 +191,11 @@ module Projects
fail(error: @project.errors.full_messages.join(', '))
end
end
+
+ def project_visibility
+ @project_visibility ||= Gitlab::VisibilityLevelChecker
+ .new(current_user, @project, project_params: { import_data: @import_data })
+ .level_restricted?
+ end
end
end
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index 7f944e25887..e0924608a6d 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -5,8 +5,8 @@ module QuickActions
include Gitlab::Utils::StrongMemoize
include Gitlab::QuickActions::Dsl
include Gitlab::QuickActions::IssueActions
- include Gitlab::QuickActions::IssueAndMergeRequestActions
include Gitlab::QuickActions::IssuableActions
+ include Gitlab::QuickActions::IssueAndMergeRequestActions
include Gitlab::QuickActions::MergeRequestActions
include Gitlab::QuickActions::CommitActions
include Gitlab::QuickActions::CommonActions
diff --git a/app/services/releases/concerns.rb b/app/services/releases/concerns.rb
index 618d96717b8..b5412e97284 100644
--- a/app/services/releases/concerns.rb
+++ b/app/services/releases/concerns.rb
@@ -47,6 +47,27 @@ module Releases
project.repository
end
end
+
+ def milestone
+ return unless params[:milestone]
+
+ strong_memoize(:milestone) do
+ MilestonesFinder.new(
+ project: project,
+ current_user: current_user,
+ project_ids: Array(project.id),
+ title: params[:milestone]
+ ).execute.first
+ end
+ end
+
+ def inexistent_milestone?
+ params[:milestone] && !params[:milestone].empty? && !milestone
+ end
+
+ def param_for_milestone_title_provided?
+ params[:milestone].present? || params[:milestone]&.empty?
+ end
end
end
end
diff --git a/app/services/releases/create_service.rb b/app/services/releases/create_service.rb
index 5b13ac631ba..c91d43084d3 100644
--- a/app/services/releases/create_service.rb
+++ b/app/services/releases/create_service.rb
@@ -7,6 +7,7 @@ module Releases
def execute
return error('Access Denied', 403) unless allowed?
return error('Release already exists', 409) if release
+ return error('Milestone does not exist', 400) if inexistent_milestone?
tag = ensure_tag
@@ -59,7 +60,8 @@ module Releases
tag: tag.name,
sha: tag.dereferenced_target.sha,
released_at: released_at,
- links_attributes: params.dig(:assets, 'links') || []
+ links_attributes: params.dig(:assets, 'links') || [],
+ milestone: milestone
)
end
end
diff --git a/app/services/releases/update_service.rb b/app/services/releases/update_service.rb
index fabfa398c59..70acc68f747 100644
--- a/app/services/releases/update_service.rb
+++ b/app/services/releases/update_service.rb
@@ -9,6 +9,9 @@ module Releases
return error('Release does not exist', 404) unless release
return error('Access Denied', 403) unless allowed?
return error('params is empty', 400) if empty_params?
+ return error('Milestone does not exist', 400) if inexistent_milestone?
+
+ params[:milestone] = milestone if param_for_milestone_title_provided?
if release.update(params)
success(tag: existing_tag, release: release)
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index 858e04f43b2..34260d12a62 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -74,9 +74,11 @@ class SystemHooksService
when ProjectMember
return "user_add_to_team" if event == :create
return "user_remove_from_team" if event == :destroy
+ return "user_update_for_team" if event == :update
when GroupMember
return 'user_add_to_group' if event == :create
return 'user_remove_from_group' if event == :destroy
+ return 'user_update_for_group' if event == :update
else
"#{model.class.name.downcase}_#{event}"
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index ee7223d6349..1b48b20e28b 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -67,7 +67,7 @@ module SystemNoteService
create_note(NoteSummary.new(noteable, project, author, body, action: 'assignee'))
end
- # Called when the assignees of an Issue is changed or removed
+ # Called when the assignees of an issuable is changed or removed
#
# issuable - Issuable object (responds to assignees)
# project - Project owning noteable
@@ -88,10 +88,12 @@ module SystemNoteService
def change_issuable_assignees(issuable, project, author, old_assignees)
unassigned_users = old_assignees - issuable.assignees
added_users = issuable.assignees.to_a - old_assignees
-
text_parts = []
- text_parts << "assigned to #{added_users.map(&:to_reference).to_sentence}" if added_users.any?
- text_parts << "unassigned #{unassigned_users.map(&:to_reference).to_sentence}" if unassigned_users.any?
+
+ Gitlab::I18n.with_default_locale do
+ text_parts << "assigned to #{added_users.map(&:to_reference).to_sentence}" if added_users.any?
+ text_parts << "unassigned #{unassigned_users.map(&:to_reference).to_sentence}" if unassigned_users.any?
+ end
body = text_parts.join(' and ')
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 0ea230a44a1..b1256df35d6 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -314,11 +314,9 @@ class TodoService
end
def reject_users_without_access(users, parent, target)
- if target.is_a?(Note) && target.for_issuable?
- target = target.noteable
- end
+ target = target.noteable if target.is_a?(Note)
- if target.is_a?(Issuable)
+ if target.respond_to?(:to_ability_name)
select_users(users, :"read_#{target.to_ability_name}", target)
else
select_users(users, :read_project, parent)
diff --git a/app/services/update_snippet_service.rb b/app/services/update_snippet_service.rb
index 2969c360de5..a294812ef9e 100644
--- a/app/services/update_snippet_service.rb
+++ b/app/services/update_snippet_service.rb
@@ -12,7 +12,7 @@ class UpdateSnippetService < BaseService
def execute
# check that user is allowed to set specified visibility_level
- new_visibility = params[:visibility_level]
+ new_visibility = visibility_level
if new_visibility && new_visibility.to_i != snippet.visibility_level
unless Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
diff --git a/app/uploaders/personal_file_uploader.rb b/app/uploaders/personal_file_uploader.rb
index 1ac69601d18..3efdd0aa1d9 100644
--- a/app/uploaders/personal_file_uploader.rb
+++ b/app/uploaders/personal_file_uploader.rb
@@ -6,6 +6,10 @@ class PersonalFileUploader < FileUploader
options.storage_path
end
+ def self.workhorse_local_upload_path
+ File.join(options.storage_path, 'uploads', TMP_UPLOAD_PATH)
+ end
+
def self.base_dir(model, _store = nil)
# base_dir is the path seen by the user when rendering Markdown, so
# it should be the same for both local and object storage. It is
diff --git a/app/views/admin/application_settings/_email.html.haml b/app/views/admin/application_settings/_email.html.haml
index bd60ff0b99c..3dd72909805 100644
--- a/app/views/admin/application_settings/_email.html.haml
+++ b/app/views/admin/application_settings/_email.html.haml
@@ -25,4 +25,4 @@
= render_if_exists 'admin/application_settings/email_additional_text_setting', form: f
- = f.submit _('Save changes'), class: "btn btn-success"
+ = f.submit _('Save changes'), class: "btn btn-success", data: { qa_selector: 'save_changes_button' }
diff --git a/app/views/admin/application_settings/_spam.html.haml b/app/views/admin/application_settings/_spam.html.haml
index d24e46b2815..f0a19075115 100644
--- a/app/views/admin/application_settings/_spam.html.haml
+++ b/app/views/admin/application_settings/_spam.html.haml
@@ -7,11 +7,15 @@
= f.check_box :recaptcha_enabled, class: 'form-check-input'
= f.label :recaptcha_enabled, class: 'form-check-label' do
Enable reCAPTCHA
- - recaptcha_v2_link_url = 'https://developers.google.com/recaptcha/docs/versions'
- - recaptcha_v2_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: recaptcha_v2_link_url }
%span.form-text.text-muted#recaptcha_help_block
- = _('Helps prevent bots from creating accounts. We currently only support %{recaptcha_v2_link_start}reCAPTCHA v2%{recaptcha_v2_link_end}').html_safe % { recaptcha_v2_link_start: recaptcha_v2_link_start, recaptcha_v2_link_end: '</a>'.html_safe }
-
+ = _('Helps prevent bots from creating accounts.')
+ .form-group
+ .form-check
+ = f.check_box :login_recaptcha_protection_enabled, class: 'form-check-input'
+ = f.label :login_recaptcha_protection_enabled, class: 'form-check-label' do
+ Enable reCAPTCHA for login
+ %span.form-text.text-muted#recaptcha_help_block
+ = _('Helps prevent bots from brute-force attacks.')
.form-group
= f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'label-bold'
= f.text_field :recaptcha_site_key, class: 'form-control'
@@ -21,6 +25,7 @@
.form-group
= f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'label-bold'
+ .form-group
= f.text_field :recaptcha_private_key, class: 'form-control'
.form-group
diff --git a/app/views/admin/application_settings/preferences.html.haml b/app/views/admin/application_settings/preferences.html.haml
index c468d69d7b8..0ad76e56d0b 100644
--- a/app/views/admin/application_settings/preferences.html.haml
+++ b/app/views/admin/application_settings/preferences.html.haml
@@ -2,7 +2,7 @@
- page_title _("Preferences")
- @content_class = "limit-container-width" unless fluid_layout
-%section.settings.as-email.no-animate#js-email-settings{ class: ('expanded' if expanded_by_default?) }
+%section.settings.as-email.no-animate#js-email-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'email_section' } }
.settings-header
%h4
= _('Email')
diff --git a/app/views/admin/application_settings/reporting.html.haml b/app/views/admin/application_settings/reporting.html.haml
index 46e3d1c4570..c60e44b3864 100644
--- a/app/views/admin/application_settings/reporting.html.haml
+++ b/app/views/admin/application_settings/reporting.html.haml
@@ -9,7 +9,9 @@
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded_by_default? ? _('Collapse') : _('Expand')
%p
- = _('Enable reCAPTCHA or Akismet and set IP limits.')
+ - recaptcha_v2_link_url = 'https://developers.google.com/recaptcha/docs/versions'
+ - recaptcha_v2_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: recaptcha_v2_link_url }
+ = _('Enable reCAPTCHA or Akismet and set IP limits. For reCAPTCHA, we currently only support %{recaptcha_v2_link_start}v2%{recaptcha_v2_link_end}').html_safe % { recaptcha_v2_link_start: recaptcha_v2_link_start, recaptcha_v2_link_end: '</a>'.html_safe }
.settings-content
= render 'spam'
diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml
index a0a00ac5d96..1001a69b787 100644
--- a/app/views/admin/background_jobs/show.html.haml
+++ b/app/views/admin/background_jobs/show.html.haml
@@ -1,10 +1,8 @@
-- @no_container = true
- page_title "Background Jobs"
-%div{ class: container_class }
- %h3.page-title Background Jobs
- %p.light GitLab uses #{link_to "sidekiq", "http://sidekiq.org/"} library for async job processing
+%h3.page-title Background Jobs
+%p.light GitLab uses #{link_to "sidekiq", "http://sidekiq.org/"} library for async job processing
- %hr
- .card
- %iframe{ src: sidekiq_path, width: '100%', height: 970, style: "border: 0" }
+%hr
+.card
+ %iframe{ src: sidekiq_path, width: '100%', height: 970, style: "border: 0" }
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index c29ecb43fe6..8aca61efe7b 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -1,167 +1,165 @@
-- @no_container = true
- breadcrumb_title "Dashboard"
-%div{ class: container_class }
- = render_if_exists 'admin/licenses/breakdown', license: @license
+= render_if_exists 'admin/licenses/breakdown', license: @license
- .admin-dashboard.prepend-top-default
- .row
- .col-sm-4
- .info-well.dark-well
- .well-segment.well-centered
- = link_to admin_projects_path do
- %h3.text-center
- Projects:
- = approximate_count_with_delimiters(@counts, Project)
- %hr
- = link_to('New project', new_project_path, class: "btn btn-success")
- .col-sm-4
- .info-well.dark-well
- .well-segment.well-centered
- = link_to admin_users_path do
- %h3.text-center
- Users:
- = approximate_count_with_delimiters(@counts, User)
- %hr
- .btn-group.d-flex{ role: 'group' }
- = link_to 'New user', new_admin_user_path, class: "btn btn-success"
- = render_if_exists 'admin/dashboard/users_statistics'
- .col-sm-4
- .info-well.dark-well
- .well-segment.well-centered
- = link_to admin_groups_path do
- %h3.text-center
- Groups:
- = approximate_count_with_delimiters(@counts, Group)
- %hr
- = link_to 'New group', new_admin_group_path, class: "btn btn-success"
- .row
- .col-md-4
- .info-well
- .well-segment.admin-well.admin-well-statistics
- %h4 Statistics
- %p
- Forks
- %span.light.float-right
- = approximate_fork_count_with_delimiters(@counts)
- %p
- Issues
- %span.light.float-right
- = approximate_count_with_delimiters(@counts, Issue)
- %p
- Merge Requests
- %span.light.float-right
- = approximate_count_with_delimiters(@counts, MergeRequest)
- %p
- Notes
- %span.light.float-right
- = approximate_count_with_delimiters(@counts, Note)
- %p
- Snippets
- %span.light.float-right
- = approximate_count_with_delimiters(@counts, Snippet)
- %p
- SSH Keys
- %span.light.float-right
- = approximate_count_with_delimiters(@counts, Key)
- %p
- Milestones
- %span.light.float-right
- = approximate_count_with_delimiters(@counts, Milestone)
- %p
- Active Users
- %span.light.float-right
- = number_with_delimiter(User.active.count)
- .col-md-4
- .info-well
- .well-segment.admin-well.admin-well-features
- %h4 Features
- = feature_entry(_('Sign up'), href: admin_application_settings_path(anchor: 'js-signup-settings'))
- = feature_entry(_('LDAP'), enabled: Gitlab.config.ldap.enabled)
- = feature_entry(_('Gravatar'), href: admin_application_settings_path(anchor: 'js-account-settings'), enabled: gravatar_enabled?)
- = feature_entry(_('OmniAuth'), href: admin_application_settings_path(anchor: 'js-signin-settings'), enabled: Gitlab::Auth.omniauth_enabled?)
- = feature_entry(_('Reply by email'), enabled: Gitlab::IncomingEmail.enabled?)
+.admin-dashboard.prepend-top-default
+ .row
+ .col-sm-4
+ .info-well.dark-well
+ .well-segment.well-centered
+ = link_to admin_projects_path do
+ %h3.text-center
+ Projects:
+ = approximate_count_with_delimiters(@counts, Project)
+ %hr
+ = link_to('New project', new_project_path, class: "btn btn-success")
+ .col-sm-4
+ .info-well.dark-well
+ .well-segment.well-centered
+ = link_to admin_users_path do
+ %h3.text-center
+ Users:
+ = approximate_count_with_delimiters(@counts, User)
+ %hr
+ .btn-group.d-flex{ role: 'group' }
+ = link_to 'New user', new_admin_user_path, class: "btn btn-success"
+ = render_if_exists 'admin/dashboard/users_statistics'
+ .col-sm-4
+ .info-well.dark-well
+ .well-segment.well-centered
+ = link_to admin_groups_path do
+ %h3.text-center
+ Groups:
+ = approximate_count_with_delimiters(@counts, Group)
+ %hr
+ = link_to 'New group', new_admin_group_path, class: "btn btn-success"
+ .row
+ .col-md-4
+ .info-well
+ .well-segment.admin-well.admin-well-statistics
+ %h4 Statistics
+ %p
+ Forks
+ %span.light.float-right
+ = approximate_fork_count_with_delimiters(@counts)
+ %p
+ Issues
+ %span.light.float-right
+ = approximate_count_with_delimiters(@counts, Issue)
+ %p
+ Merge Requests
+ %span.light.float-right
+ = approximate_count_with_delimiters(@counts, MergeRequest)
+ %p
+ Notes
+ %span.light.float-right
+ = approximate_count_with_delimiters(@counts, Note)
+ %p
+ Snippets
+ %span.light.float-right
+ = approximate_count_with_delimiters(@counts, Snippet)
+ %p
+ SSH Keys
+ %span.light.float-right
+ = approximate_count_with_delimiters(@counts, Key)
+ %p
+ Milestones
+ %span.light.float-right
+ = approximate_count_with_delimiters(@counts, Milestone)
+ %p
+ Active Users
+ %span.light.float-right
+ = number_with_delimiter(User.active.count)
+ .col-md-4
+ .info-well
+ .well-segment.admin-well.admin-well-features
+ %h4 Features
+ = feature_entry(_('Sign up'), href: admin_application_settings_path(anchor: 'js-signup-settings'))
+ = feature_entry(_('LDAP'), enabled: Gitlab.config.ldap.enabled)
+ = feature_entry(_('Gravatar'), href: admin_application_settings_path(anchor: 'js-account-settings'), enabled: gravatar_enabled?)
+ = feature_entry(_('OmniAuth'), href: admin_application_settings_path(anchor: 'js-signin-settings'), enabled: Gitlab::Auth.omniauth_enabled?)
+ = feature_entry(_('Reply by email'), enabled: Gitlab::IncomingEmail.enabled?)
- = render_if_exists 'admin/dashboard/elastic_and_geo'
+ = render_if_exists 'admin/dashboard/elastic_and_geo'
- = feature_entry(_('Container Registry'), href: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'), enabled: Gitlab.config.registry.enabled)
- = feature_entry(_('Gitlab Pages'), href: help_instance_configuration_url, enabled: Gitlab.config.pages.enabled)
- = feature_entry(_('Shared Runners'), href: admin_runners_path, enabled: Gitlab.config.gitlab_ci.shared_runners_enabled)
- .col-md-4
- .info-well
- .well-segment.admin-well
- %h4
- Components
- - if Gitlab::CurrentSettings.version_check_enabled
- .float-right
- = version_status_badge
- %p
- %a{ href: admin_application_settings_path }
- GitLab
+ = feature_entry(_('Container Registry'), href: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'), enabled: Gitlab.config.registry.enabled)
+ = feature_entry(_('Gitlab Pages'), href: help_instance_configuration_url, enabled: Gitlab.config.pages.enabled)
+ = feature_entry(_('Shared Runners'), href: admin_runners_path, enabled: Gitlab.config.gitlab_ci.shared_runners_enabled)
+ .col-md-4
+ .info-well
+ .well-segment.admin-well
+ %h4
+ Components
+ - if Gitlab::CurrentSettings.version_check_enabled
+ .float-right
+ = version_status_badge
+ %p
+ %a{ href: admin_application_settings_path }
+ GitLab
+ %span.float-right
+ = Gitlab::VERSION
+ = "(#{Gitlab.revision})"
+ %p
+ GitLab Shell
+ %span.float-right
+ = Gitlab::Shell.new.version
+ %p
+ GitLab Workhorse
+ %span.float-right
+ = gitlab_workhorse_version
+ %p
+ GitLab API
+ %span.float-right
+ = API::API::version
+ - if Gitlab.config.pages.enabled
+ %p
+ GitLab Pages
%span.float-right
- = Gitlab::VERSION
- = "(#{Gitlab.revision})"
- %p
- GitLab Shell
- %span.float-right
- = Gitlab::Shell.new.version
- %p
- GitLab Workhorse
- %span.float-right
- = gitlab_workhorse_version
- %p
- GitLab API
- %span.float-right
- = API::API::version
- - if Gitlab.config.pages.enabled
- %p
- GitLab Pages
- %span.float-right
- = Gitlab::Pages::VERSION
+ = Gitlab::Pages::VERSION
- = render_if_exists 'admin/dashboard/geo'
+ = render_if_exists 'admin/dashboard/geo'
- %p
- Ruby
- %span.float-right
- #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
- %p
- Rails
- %span.float-right
- #{Rails::VERSION::STRING}
- %p
- = Gitlab::Database.human_adapter_name
- %span.float-right
- = Gitlab::Database.version
- %p
- = link_to "Gitaly Servers", admin_gitaly_servers_path
- .row
- .col-md-4
- .info-well
- .well-segment.admin-well
- %h4 Latest projects
- - @projects.each do |project|
- %p
- = link_to project.full_name, admin_project_path(project), class: 'str-truncated-60'
- %span.light.float-right
- #{time_ago_with_tooltip(project.created_at)}
- .col-md-4
- .info-well
- .well-segment.admin-well
- %h4 Latest users
- - @users.each do |user|
- %p
- = link_to [:admin, user], class: 'str-truncated-60' do
- = user.name
- %span.light.float-right
- #{time_ago_with_tooltip(user.created_at)}
- .col-md-4
- .info-well
- .well-segment.admin-well
- %h4 Latest groups
- - @groups.each do |group|
- %p
- = link_to [:admin, group], class: 'str-truncated-60' do
- = group.full_name
- %span.light.float-right
- #{time_ago_with_tooltip(group.created_at)}
+ %p
+ Ruby
+ %span.float-right
+ #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
+ %p
+ Rails
+ %span.float-right
+ #{Rails::VERSION::STRING}
+ %p
+ = Gitlab::Database.human_adapter_name
+ %span.float-right
+ = Gitlab::Database.version
+ %p
+ = link_to "Gitaly Servers", admin_gitaly_servers_path
+ .row
+ .col-md-4
+ .info-well
+ .well-segment.admin-well
+ %h4 Latest projects
+ - @projects.each do |project|
+ %p
+ = link_to project.full_name, admin_project_path(project), class: 'str-truncated-60'
+ %span.light.float-right
+ #{time_ago_with_tooltip(project.created_at)}
+ .col-md-4
+ .info-well
+ .well-segment.admin-well
+ %h4 Latest users
+ - @users.each do |user|
+ %p
+ = link_to [:admin, user], class: 'str-truncated-60' do
+ = user.name
+ %span.light.float-right
+ #{time_ago_with_tooltip(user.created_at)}
+ .col-md-4
+ .info-well
+ .well-segment.admin-well
+ %h4 Latest groups
+ - @groups.each do |group|
+ %p
+ = link_to [:admin, group], class: 'str-truncated-60' do
+ = group.full_name
+ %span.light.float-right
+ #{time_ago_with_tooltip(group.created_at)}
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index cb833ffd9ac..434b6e3a37e 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -1,20 +1,18 @@
-- @no_container = true
- page_title _("Groups")
-%div{ class: container_class }
- .top-area
- .prepend-top-default.append-bottom-default
- = form_tag admin_groups_path, method: :get, class: 'js-search-form' do |f|
- = hidden_field_tag :sort, @sort
- .search-holder
- - project_name = params[:name].present? ? params[:name] : nil
- .search-field-holder
- = search_field_tag :name, project_name, class: "form-control search-text-input js-search-input", autofocus: true, spellcheck: false, placeholder: 'Search by name'
- = icon("search", class: "search-icon")
- = render "shared/groups/dropdown", options_hash: admin_groups_sort_options_hash
- = link_to new_admin_group_path, class: "btn btn-success" do
- = _('New group')
- %ul.content-list
- = render @groups
+.top-area
+ .prepend-top-default.append-bottom-default
+ = form_tag admin_groups_path, method: :get, class: 'js-search-form' do |f|
+ = hidden_field_tag :sort, @sort
+ .search-holder
+ - project_name = params[:name].present? ? params[:name] : nil
+ .search-field-holder
+ = search_field_tag :name, project_name, class: "form-control search-text-input js-search-input", autofocus: true, spellcheck: false, placeholder: 'Search by name'
+ = icon("search", class: "search-icon")
+ = render "shared/groups/dropdown", options_hash: admin_groups_sort_options_hash
+ = link_to new_admin_group_path, class: "btn btn-success" do
+ = _('New group')
+%ul.content-list
+ = render @groups
- = paginate @groups, theme: "gitlab"
+= paginate @groups, theme: "gitlab"
diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml
index ac56e354a4d..587bfba8d47 100644
--- a/app/views/admin/health_check/show.html.haml
+++ b/app/views/admin/health_check/show.html.haml
@@ -1,41 +1,39 @@
-- @no_container = true
- page_title _('Health Check')
- no_errors = @errors.blank?
-%div{ class: container_class }
- %h3.page-title= page_title
- .bs-callout.clearfix
- .float-left
- %p
- #{ s_('HealthCheck|Access token is') }
- %code#health-check-token= Gitlab::CurrentSettings.health_check_access_token
- .prepend-top-10
- = button_to _("Reset health check access token"), reset_health_check_token_admin_application_settings_path,
- method: :put, class: 'btn btn-default',
- data: { confirm: _('Are you sure you want to reset the health check token?') }
- %p.light
- #{ _('Health information can be retrieved from the following endpoints. More information is available') }
- = link_to s_('More information is available|here'), help_page_path('user/admin_area/monitoring/health_check')
- %ul
- %li
- %code= readiness_url(token: Gitlab::CurrentSettings.health_check_access_token)
- %li
- %code= liveness_url(token: Gitlab::CurrentSettings.health_check_access_token)
- %li
- %code= metrics_url(token: Gitlab::CurrentSettings.health_check_access_token)
- = render_if_exists 'admin/health_check/health_check_url'
- %hr
- .card
- .card-header
- Current Status:
- - if no_errors
- = icon('circle', class: 'cgreen')
- #{ s_('HealthCheck|Healthy') }
- - else
- = icon('warning', class: 'cred')
- #{ s_('HealthCheck|Unhealthy') }
- .card-body
- - if no_errors
- #{ s_('HealthCheck|No Health Problems Detected') }
- - else
- = @errors
+%h3.page-title= page_title
+.bs-callout.clearfix
+ .float-left
+ %p
+ #{ s_('HealthCheck|Access token is') }
+ %code#health-check-token= Gitlab::CurrentSettings.health_check_access_token
+ .prepend-top-10
+ = button_to _("Reset health check access token"), reset_health_check_token_admin_application_settings_path,
+ method: :put, class: 'btn btn-default',
+ data: { confirm: _('Are you sure you want to reset the health check token?') }
+%p.light
+ #{ _('Health information can be retrieved from the following endpoints. More information is available') }
+ = link_to s_('More information is available|here'), help_page_path('user/admin_area/monitoring/health_check')
+ %ul
+ %li
+ %code= readiness_url(token: Gitlab::CurrentSettings.health_check_access_token)
+ %li
+ %code= liveness_url(token: Gitlab::CurrentSettings.health_check_access_token)
+ %li
+ %code= metrics_url(token: Gitlab::CurrentSettings.health_check_access_token)
+ = render_if_exists 'admin/health_check/health_check_url'
+%hr
+.card
+ .card-header
+ Current Status:
+ - if no_errors
+ = icon('circle', class: 'cgreen')
+ #{ s_('HealthCheck|Healthy') }
+ - else
+ = icon('warning', class: 'cred')
+ #{ s_('HealthCheck|Unhealthy') }
+ .card-body
+ - if no_errors
+ #{ s_('HealthCheck|No Health Problems Detected') }
+ - else
+ = @errors
diff --git a/app/views/admin/jobs/index.html.haml b/app/views/admin/jobs/index.html.haml
index 4e3e2f7a475..f1bdd52b399 100644
--- a/app/views/admin/jobs/index.html.haml
+++ b/app/views/admin/jobs/index.html.haml
@@ -1,22 +1,19 @@
- breadcrumb_title "Jobs"
-- @no_container = true
-%div{ class: container_class }
+.top-area.scrolling-tabs-container.inner-page-scroll-tabs
+ - build_path_proc = ->(scope) { admin_jobs_path(scope: scope) }
+ = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope
- .top-area.scrolling-tabs-container.inner-page-scroll-tabs
- - build_path_proc = ->(scope) { admin_jobs_path(scope: scope) }
- = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope
+ - if @all_builds.running_or_pending.any?
+ #stop-jobs-modal
+ .nav-controls
+ %button#stop-jobs-button.btn.btn-danger{ data: { toggle: 'modal',
+ target: '#stop-jobs-modal',
+ url: cancel_all_admin_jobs_path } }
+ = s_('AdminArea|Stop all jobs')
- - if @all_builds.running_or_pending.any?
- #stop-jobs-modal
- .nav-controls
- %button#stop-jobs-button.btn.btn-danger{ data: { toggle: 'modal',
- target: '#stop-jobs-modal',
- url: cancel_all_admin_jobs_path } }
- = s_('AdminArea|Stop all jobs')
+.row-content-block.second-block
+ #{(@scope || 'all').capitalize} jobs
- .row-content-block.second-block
- #{(@scope || 'all').capitalize} jobs
-
- %ul.content-list.builds-content-list.admin-builds-table
- = render "projects/jobs/table", builds: @builds, admin: true
+%ul.content-list.builds-content-list.admin-builds-table
+ = render "projects/jobs/table", builds: @builds, admin: true
diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml
index e4c0382a437..eb93f645ea6 100644
--- a/app/views/admin/logs/show.html.haml
+++ b/app/views/admin/logs/show.html.haml
@@ -1,26 +1,24 @@
-- @no_container = true
- page_title "Logs"
-%div{ class: container_class }
- %ul.nav-links.log-tabs.nav.nav-tabs
- - @loggers.each do |klass|
- %li.nav-item
- = link_to klass.file_name, "##{klass.file_name_noext}", data: { toggle: 'tab' }, class: "#{active_when(klass == @loggers.first)} nav-link"
- .row-content-block
- To prevent performance issues admin logs output the last 2000 lines
- .tab-content
- - @loggers.each do |klass|
- .tab-pane{ class: active_when(klass == @loggers.first), id: klass.file_name_noext }
- .file-holder#README
- .js-file-title.file-title
- %i.fa.fa-file
- = klass.file_name
- .float-right
- = link_to '#', class: 'log-bottom' do
- %i.fa.fa-arrow-down
- Scroll down
- .file-content.logs
- %ol
- - klass.read_latest.each do |line|
- %li
- %p= line
+%ul.nav-links.log-tabs.nav.nav-tabs
+ - @loggers.each do |klass|
+ %li.nav-item
+ = link_to klass.file_name, "##{klass.file_name_noext}", data: { toggle: 'tab' }, class: "#{active_when(klass == @loggers.first)} nav-link"
+.row-content-block
+ To prevent performance issues admin logs output the last 2000 lines
+.tab-content
+ - @loggers.each do |klass|
+ .tab-pane{ class: active_when(klass == @loggers.first), id: klass.file_name_noext }
+ .file-holder#README
+ .js-file-title.file-title
+ %i.fa.fa-file
+ = klass.file_name
+ .float-right
+ = link_to '#', class: 'log-bottom' do
+ %i.fa.fa-arrow-down
+ Scroll down
+ .file-content.logs
+ %ol
+ - klass.read_latest.each do |line|
+ %li
+ %p= line
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index b88b760536d..7e03eb4f075 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -1,44 +1,41 @@
-- @no_container = true
- page_title "Projects"
- params[:visibility_level] ||= []
+.top-area.scrolling-tabs-container.inner-page-scroll-tabs
+ .prepend-top-default
+ .search-holder
+ = render 'shared/projects/search_form', autofocus: true, icon: true, admin_view: true
+ .dropdown
+ - toggle_text = 'Namespace'
+ - if params[:namespace_id].present?
+ = hidden_field_tag :namespace_id, params[:namespace_id]
+ - namespace = Namespace.find(params[:namespace_id])
+ - toggle_text = "#{namespace.kind}: #{namespace.full_path}"
+ = dropdown_toggle(toggle_text, { toggle: 'dropdown', is_filter: 'true' }, { toggle_class: 'js-namespace-select large' })
+ .dropdown-menu.dropdown-select.dropdown-menu-right
+ = dropdown_title('Namespaces')
+ = dropdown_filter("Search for Namespace")
+ = dropdown_content
+ = dropdown_loading
+ = render 'shared/projects/dropdown'
+ = link_to new_project_path, class: 'btn btn-success' do
+ New Project
+ = button_tag "Search", class: "btn btn-primary btn-search hide"
-%div{ class: container_class }
- .top-area.scrolling-tabs-container.inner-page-scroll-tabs
- .prepend-top-default
- .search-holder
- = render 'shared/projects/search_form', autofocus: true, icon: true, admin_view: true
- .dropdown
- - toggle_text = 'Namespace'
- - if params[:namespace_id].present?
- = hidden_field_tag :namespace_id, params[:namespace_id]
- - namespace = Namespace.find(params[:namespace_id])
- - toggle_text = "#{namespace.kind}: #{namespace.full_path}"
- = dropdown_toggle(toggle_text, { toggle: 'dropdown', is_filter: 'true' }, { toggle_class: 'js-namespace-select large' })
- .dropdown-menu.dropdown-select.dropdown-menu-right
- = dropdown_title('Namespaces')
- = dropdown_filter("Search for Namespace")
- = dropdown_content
- = dropdown_loading
- = render 'shared/projects/dropdown'
- = link_to new_project_path, class: 'btn btn-success' do
- New Project
- = button_tag "Search", class: "btn btn-primary btn-search hide"
+ %ul.nav-links.nav.nav-tabs
+ - opts = params[:visibility_level].present? ? {} : { page: admin_projects_path }
+ = nav_link(opts) do
+ = link_to admin_projects_path do
+ All
- %ul.nav-links.nav.nav-tabs
- - opts = params[:visibility_level].present? ? {} : { page: admin_projects_path }
- = nav_link(opts) do
- = link_to admin_projects_path do
- All
+ = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PRIVATE.to_s) }) do
+ = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE) do
+ Private
+ = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::INTERNAL.to_s) }) do
+ = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL) do
+ Internal
+ = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PUBLIC.to_s) }) do
+ = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do
+ Public
- = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PRIVATE.to_s) }) do
- = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE) do
- Private
- = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::INTERNAL.to_s) }) do
- = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL) do
- Internal
- = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PUBLIC.to_s) }) do
- = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do
- Public
-
- = render 'projects'
+= render 'projects'
diff --git a/app/views/admin/requests_profiles/index.html.haml b/app/views/admin/requests_profiles/index.html.haml
index 86bfeef580c..efc16bb4d3b 100644
--- a/app/views/admin/requests_profiles/index.html.haml
+++ b/app/views/admin/requests_profiles/index.html.haml
@@ -1,26 +1,24 @@
-- @no_container = true
- page_title 'Requests Profiles'
-%div{ class: container_class }
- %h3.page-title
- = page_title
+%h3.page-title
+ = page_title
- .bs-callout.clearfix
- Pass the header
- %code X-Profile-Token: #{@profile_token}
- to profile the request
+.bs-callout.clearfix
+ Pass the header
+ %code X-Profile-Token: #{@profile_token}
+ to profile the request
- - if @profiles.present?
- .prepend-top-default
- - @profiles.each do |path, profiles|
- .card.card-small
- .card-header
- %code= path
- %ul.content-list
- - profiles.each do |profile|
- %li
- = link_to profile.time.to_s(:long) + ' ' + profile.profile_mode.capitalize,
- admin_requests_profile_path(profile)
- - else
- %p
- No profiles found
+- if @profiles.present?
+ .prepend-top-default
+ - @profiles.each do |path, profiles|
+ .card.card-small
+ .card-header
+ %code= path
+ %ul.content-list
+ - profiles.each do |profile|
+ %li
+ = link_to profile.time.to_s(:long) + ' ' + profile.profile_mode.capitalize,
+ admin_requests_profile_path(profile)
+- else
+ %p
+ No profiles found
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index 5129f5d193b..76af4189b5b 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -1,96 +1,87 @@
- breadcrumb_title _('Runners')
-- @no_container = true
-%div{ class: container_class }
- .row
- .col-sm-6
- .bs-callout
- %p
- = (_"A 'Runner' is a process which runs a job. You can set up as many Runners as you need.")
- %br
- = _('Runners can be placed on separate users, servers, even on your local machine.')
- %br
+.row
+ .col-sm-6
+ .bs-callout
+ %p
+ = (_"A 'Runner' is a process which runs a job. You can set up as many Runners as you need.")
+ %br
+ = _('Runners can be placed on separate users, servers, even on your local machine.')
+ %br
- %div
- %span= _('Each Runner can be in one of the following states:')
- %ul
- %li
- %span.badge.badge-success shared
- \-
- = _('Runner runs jobs from all unassigned projects')
- %li
- %span.badge.badge-success group
- \-
- = _('Runner runs jobs from all unassigned projects in its group')
- %li
- %span.badge.badge-info specific
- \-
- = _('Runner runs jobs from assigned projects')
- %li
- %span.badge.badge-warning locked
- \-
- = _('Runner cannot be assigned to other projects')
- %li
- %span.badge.badge-danger paused
- \-
- = _('Runner will not receive any new jobs')
+ %div
+ %span= _('Each Runner can be in one of the following states:')
+ %ul
+ %li
+ %span.badge.badge-success shared
+ \-
+ = _('Runner runs jobs from all unassigned projects')
+ %li
+ %span.badge.badge-success group
+ \-
+ = _('Runner runs jobs from all unassigned projects in its group')
+ %li
+ %span.badge.badge-info specific
+ \-
+ = _('Runner runs jobs from assigned projects')
+ %li
+ %span.badge.badge-warning locked
+ \-
+ = _('Runner cannot be assigned to other projects')
+ %li
+ %span.badge.badge-danger paused
+ \-
+ = _('Runner will not receive any new jobs')
- .col-sm-6
- .bs-callout
- = render partial: 'ci/runner/how_to_setup_runner',
- locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token,
- type: 'shared',
- reset_token_url: reset_registration_token_admin_application_settings_path }
+ .col-sm-6
+ .bs-callout
+ = render partial: 'ci/runner/how_to_setup_runner',
+ locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token,
+ type: 'shared',
+ reset_token_url: reset_registration_token_admin_application_settings_path }
- .row
- .col-sm-9
- = form_tag admin_runners_path, id: 'runners-search', method: :get, class: 'filter-form js-filter-form' do
- .filtered-search-wrapper
- .filtered-search-box
- = dropdown_tag(custom_icon('icon_history'),
- options: { wrapper_class: 'filtered-search-history-dropdown-wrapper',
- toggle_class: 'filtered-search-history-dropdown-toggle-button',
- dropdown_class: 'filtered-search-history-dropdown',
- content_class: 'filtered-search-history-dropdown-content',
- title: _('Recent searches') }) do
- .js-filtered-search-history-dropdown{ data: { full_path: admin_runners_path } }
- .filtered-search-box-input-container.droplab-dropdown
- .scroll-container
- %ul.tokens-container.list-unstyled
- %li.input-token
- %input.form-control.filtered-search{ search_filter_input_options('runners') }
- #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { action: 'submit' } }
- = button_tag class: %w[btn btn-link] do
- = sprite_icon('search')
- %span
- = _('Press Enter or click to search')
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item
- = button_tag class: %w[btn btn-link] do
- -# Encapsulate static class name `{{icon}}` inside #{} to bypass
- -# haml lint's ClassAttributeWithStaticValue
- %svg
- %use{ 'xlink:href': "#{'{{icon}}'}" }
- %span.js-filter-hint
- {{hint}}
- %span.js-filter-tag.dropdown-light-content
- {{tag}}
-
- #js-dropdown-admin-runner-status.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- - Ci::Runner::AVAILABLE_STATUSES.each do |status|
- %li.filter-dropdown-item{ data: { value: status } }
- = button_tag class: %w[btn btn-link] do
- = status.titleize
+.row
+ .col-sm-9
+ = form_tag admin_runners_path, id: 'runners-search', method: :get, class: 'filter-form js-filter-form' do
+ .filtered-search-wrapper
+ .filtered-search-box
+ = dropdown_tag(custom_icon('icon_history'),
+ options: { wrapper_class: 'filtered-search-history-dropdown-wrapper',
+ toggle_class: 'filtered-search-history-dropdown-toggle-button',
+ dropdown_class: 'filtered-search-history-dropdown',
+ content_class: 'filtered-search-history-dropdown-content',
+ title: _('Recent searches') }) do
+ .js-filtered-search-history-dropdown{ data: { full_path: admin_runners_path } }
+ .filtered-search-box-input-container.droplab-dropdown
+ .scroll-container
+ %ul.tokens-container.list-unstyled
+ %li.input-token
+ %input.form-control.filtered-search{ search_filter_input_options('runners') }
+ #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { action: 'submit' } }
+ = button_tag class: %w[btn btn-link] do
+ = sprite_icon('search')
+ %span
+ = _('Press Enter or click to search')
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ = button_tag class: %w[btn btn-link] do
+ -# Encapsulate static class name `{{icon}}` inside #{} to bypass
+ -# haml lint's ClassAttributeWithStaticValue
+ %svg
+ %use{ 'xlink:href': "#{'{{icon}}'}" }
+ %span.js-filter-hint
+ {{hint}}
+ %span.js-filter-tag.dropdown-light-content
+ {{tag}}
- #js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- - Ci::Runner::AVAILABLE_TYPES.each do |runner_type|
- %li.filter-dropdown-item{ data: { value: runner_type } }
- = button_tag class: %w[btn btn-link] do
- = runner_type.titleize
+ #js-dropdown-admin-runner-status.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ - Ci::Runner::AVAILABLE_STATUSES.each do |status|
+ %li.filter-dropdown-item{ data: { value: status } }
+ = button_tag class: %w[btn btn-link] do
+ = status.titleize
#js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
@@ -99,43 +90,50 @@
= button_tag class: %w[btn btn-link] do
= runner_type.titleize
- #js-dropdown-runner-tag.filtered-search-input-dropdown-menu.dropdown-menu
- %ul{ data: { dropdown: true } }
- %li.filter-dropdown-item{ data: { value: 'none' } }
- %button.btn.btn-link
- = _('No Tag')
- %li.divider.droplab-item-ignore
- %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
- %li.filter-dropdown-item
- %button.btn.btn-link.js-data-value
- %span.dropdown-light-content
- {{name}}
+ #js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ - Ci::Runner::AVAILABLE_TYPES.each do |runner_type|
+ %li.filter-dropdown-item{ data: { value: runner_type } }
+ = button_tag class: %w[btn btn-link] do
+ = runner_type.titleize
+
+ #js-dropdown-runner-tag.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul{ data: { dropdown: true } }
+ %li.filter-dropdown-item{ data: { value: 'none' } }
+ %button.btn.btn-link
+ = _('No Tag')
+ %li.divider.droplab-item-ignore
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.btn.btn-link.js-data-value
+ %span.dropdown-light-content
+ {{name}}
- = button_tag class: %w[clear-search hidden] do
- = icon('times')
- .filter-dropdown-container
- = render 'sort_dropdown'
+ = button_tag class: %w[clear-search hidden] do
+ = icon('times')
+ .filter-dropdown-container
+ = render 'sort_dropdown'
- .col-sm-3.text-right-lg
- = _('Runners currently online: %{active_runners_count}') % { active_runners_count: @active_runners_count }
+ .col-sm-3.text-right-lg
+ = _('Runners currently online: %{active_runners_count}') % { active_runners_count: @active_runners_count }
- - if @runners.any?
- .runners-content.content-list
- .table-holder
- .gl-responsive-table-row.table-row-header{ role: 'row' }
- .table-section.section-10{ role: 'rowheader' }= _('Type')
- .table-section.section-10{ role: 'rowheader' }= _('Runner token')
- .table-section.section-20{ role: 'rowheader' }= _('Description')
- .table-section.section-10{ role: 'rowheader' }= _('Version')
- .table-section.section-10{ role: 'rowheader' }= _('IP Address')
- .table-section.section-5{ role: 'rowheader' }= _('Projects')
- .table-section.section-5{ role: 'rowheader' }= _('Jobs')
- .table-section.section-10{ role: 'rowheader' }= _('Tags')
- .table-section.section-10{ role: 'rowheader' }= _('Last contact')
- .table-section.section-10{ role: 'rowheader' }
+- if @runners.any?
+ .runners-content.content-list
+ .table-holder
+ .gl-responsive-table-row.table-row-header{ role: 'row' }
+ .table-section.section-10{ role: 'rowheader' }= _('Type')
+ .table-section.section-10{ role: 'rowheader' }= _('Runner token')
+ .table-section.section-20{ role: 'rowheader' }= _('Description')
+ .table-section.section-10{ role: 'rowheader' }= _('Version')
+ .table-section.section-10{ role: 'rowheader' }= _('IP Address')
+ .table-section.section-5{ role: 'rowheader' }= _('Projects')
+ .table-section.section-5{ role: 'rowheader' }= _('Jobs')
+ .table-section.section-10{ role: 'rowheader' }= _('Tags')
+ .table-section.section-10{ role: 'rowheader' }= _('Last contact')
+ .table-section.section-10{ role: 'rowheader' }
- - @runners.each do |runner|
- = render 'admin/runners/runner', runner: runner
- = paginate @runners, theme: 'gitlab'
- - else
- .nothing-here-block= _('No runners found')
+ - @runners.each do |runner|
+ = render 'admin/runners/runner', runner: runner
+ = paginate @runners, theme: 'gitlab'
+- else
+ .nothing-here-block= _('No runners found')
diff --git a/app/views/admin/system_info/show.html.haml b/app/views/admin/system_info/show.html.haml
index b19934e028d..948a11646f7 100644
--- a/app/views/admin/system_info/show.html.haml
+++ b/app/views/admin/system_info/show.html.haml
@@ -1,37 +1,35 @@
-- @no_container = true
- page_title "System Info"
-%div{ class: container_class }
- .prepend-top-default
- .row
- .col-sm-4
- .card.bg-light.light-well
- %h4 CPU
- .data
- - if @cpus
- %h1 #{@cpus.length} cores
- - else
- = icon('warning', class: 'text-warning')
- Unable to collect CPU info
- .col-sm-4
- .card.bg-light.light-well
- %h4 Memory Usage
- .data
- - if @memory
- %h1 #{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)}
- - else
- = icon('warning', class: 'text-warning')
- Unable to collect memory info
- .col-sm-4
- .card.bg-light.light-well
- %h4 Disk Usage
- .data
- - @disks.each do |disk|
- %h1 #{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])}
- %p= disk[:disk_name]
- %p= disk[:mount_path]
- .col-sm-4
- .card.bg-light.light-well
- %h4 Uptime
- .data
- %h1= distance_of_time_in_words_to_now(Rails.application.config.booted_at)
+.prepend-top-default
+.row
+ .col-sm-4
+ .card.bg-light.light-well
+ %h4 CPU
+ .data
+ - if @cpus
+ %h1 #{@cpus.length} cores
+ - else
+ = icon('warning', class: 'text-warning')
+ Unable to collect CPU info
+ .col-sm-4
+ .card.bg-light.light-well
+ %h4 Memory Usage
+ .data
+ - if @memory
+ %h1 #{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)}
+ - else
+ = icon('warning', class: 'text-warning')
+ Unable to collect memory info
+ .col-sm-4
+ .card.bg-light.light-well
+ %h4 Disk Usage
+ .data
+ - @disks.each do |disk|
+ %h1 #{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])}
+ %p= disk[:disk_name]
+ %p= disk[:mount_path]
+ .col-sm-4
+ .card.bg-light.light-well
+ %h4 Uptime
+ .data
+ %h1= distance_of_time_in_words_to_now(Rails.application.config.booted_at)
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index 6fc7ec1bb6f..36b62557fa6 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -1,79 +1,77 @@
-- @no_container = true
- page_title "Users"
-%div{ class: container_class }
- .top-area.scrolling-tabs-container.inner-page-scroll-tabs
- .fade-left
- = icon('angle-left')
- .fade-right
- = icon('angle-right')
- %ul.nav-links.nav.nav-tabs.scrolling-tabs
- = nav_link(html_options: { class: active_when(params[:filter].nil?) }) do
- = link_to admin_users_path do
- = s_('AdminUsers|Active')
- %small.badge.badge-pill= limited_counter_with_delimiter(User.active)
- = nav_link(html_options: { class: active_when(params[:filter] == 'admins') }) do
- = link_to admin_users_path(filter: "admins") do
- = s_('AdminUsers|Admins')
- %small.badge.badge-pill= limited_counter_with_delimiter(User.admins)
- = nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_enabled')} filter-two-factor-enabled" }) do
- = link_to admin_users_path(filter: 'two_factor_enabled') do
- = s_('AdminUsers|2FA Enabled')
- %small.badge.badge-pill= limited_counter_with_delimiter(User.with_two_factor)
- = nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_disabled')} filter-two-factor-disabled" }) do
- = link_to admin_users_path(filter: 'two_factor_disabled') do
- = s_('AdminUsers|2FA Disabled')
- %small.badge.badge-pill= limited_counter_with_delimiter(User.without_two_factor)
- = nav_link(html_options: { class: active_when(params[:filter] == 'external') }) do
- = link_to admin_users_path(filter: 'external') do
- = s_('AdminUsers|External')
- %small.badge.badge-pill= limited_counter_with_delimiter(User.external)
- = nav_link(html_options: { class: active_when(params[:filter] == 'blocked') }) do
- = link_to admin_users_path(filter: "blocked") do
- = s_('AdminUsers|Blocked')
- %small.badge.badge-pill= limited_counter_with_delimiter(User.blocked)
- = nav_link(html_options: { class: active_when(params[:filter] == 'wop') }) do
- = link_to admin_users_path(filter: "wop") do
- = s_('AdminUsers|Without projects')
- %small.badge.badge-pill= limited_counter_with_delimiter(User.without_projects)
- .nav-controls
- = render_if_exists 'admin/users/admin_email_users'
- = link_to s_('AdminUsers|New user'), new_admin_user_path, class: 'btn btn-success btn-search float-right'
+.top-area.scrolling-tabs-container.inner-page-scroll-tabs
+ .fade-left
+ = icon('angle-left')
+ .fade-right
+ = icon('angle-right')
+ %ul.nav-links.nav.nav-tabs.scrolling-tabs
+ = nav_link(html_options: { class: active_when(params[:filter].nil?) }) do
+ = link_to admin_users_path do
+ = s_('AdminUsers|Active')
+ %small.badge.badge-pill= limited_counter_with_delimiter(User.active)
+ = nav_link(html_options: { class: active_when(params[:filter] == 'admins') }) do
+ = link_to admin_users_path(filter: "admins") do
+ = s_('AdminUsers|Admins')
+ %small.badge.badge-pill= limited_counter_with_delimiter(User.admins)
+ = nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_enabled')} filter-two-factor-enabled" }) do
+ = link_to admin_users_path(filter: 'two_factor_enabled') do
+ = s_('AdminUsers|2FA Enabled')
+ %small.badge.badge-pill= limited_counter_with_delimiter(User.with_two_factor)
+ = nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_disabled')} filter-two-factor-disabled" }) do
+ = link_to admin_users_path(filter: 'two_factor_disabled') do
+ = s_('AdminUsers|2FA Disabled')
+ %small.badge.badge-pill= limited_counter_with_delimiter(User.without_two_factor)
+ = nav_link(html_options: { class: active_when(params[:filter] == 'external') }) do
+ = link_to admin_users_path(filter: 'external') do
+ = s_('AdminUsers|External')
+ %small.badge.badge-pill= limited_counter_with_delimiter(User.external)
+ = nav_link(html_options: { class: active_when(params[:filter] == 'blocked') }) do
+ = link_to admin_users_path(filter: "blocked") do
+ = s_('AdminUsers|Blocked')
+ %small.badge.badge-pill= limited_counter_with_delimiter(User.blocked)
+ = nav_link(html_options: { class: active_when(params[:filter] == 'wop') }) do
+ = link_to admin_users_path(filter: "wop") do
+ = s_('AdminUsers|Without projects')
+ %small.badge.badge-pill= limited_counter_with_delimiter(User.without_projects)
+ .nav-controls
+ = render_if_exists 'admin/users/admin_email_users'
+ = link_to s_('AdminUsers|New user'), new_admin_user_path, class: 'btn btn-success btn-search float-right'
- .filtered-search-block.row-content-block.border-top-0
- = form_tag admin_users_path, method: :get do
- - if params[:filter].present?
- = hidden_field_tag "filter", h(params[:filter])
- .search-holder
- .search-field-holder
- = search_field_tag :search_query, params[:search_query], placeholder: s_('AdminUsers|Search by name, email or username'), class: 'form-control search-text-input js-search-input', spellcheck: false
- - if @sort.present?
- = hidden_field_tag :sort, @sort
- = icon("search", class: "search-icon")
- = button_tag s_('AdminUsers|Search users') if Rails.env.test?
- .dropdown.user-sort-dropdown
- - toggle_text = @sort.present? ? users_sort_options_hash[@sort] : sort_title_name
- = dropdown_toggle(toggle_text, { toggle: 'dropdown' })
- %ul.dropdown-menu.dropdown-menu-right
- %li.dropdown-header
- = s_('AdminUsers|Sort by')
- %li
- - users_sort_options_hash.each do |value, title|
- = link_to admin_users_path(sort: value, filter: params[:filter], search_query: params[:search_query]) do
- = title
+.filtered-search-block.row-content-block.border-top-0
+ = form_tag admin_users_path, method: :get do
+ - if params[:filter].present?
+ = hidden_field_tag "filter", h(params[:filter])
+ .search-holder
+ .search-field-holder
+ = search_field_tag :search_query, params[:search_query], placeholder: s_('AdminUsers|Search by name, email or username'), class: 'form-control search-text-input js-search-input', spellcheck: false
+ - if @sort.present?
+ = hidden_field_tag :sort, @sort
+ = icon("search", class: "search-icon")
+ = button_tag s_('AdminUsers|Search users') if Rails.env.test?
+ .dropdown.user-sort-dropdown
+ - toggle_text = @sort.present? ? users_sort_options_hash[@sort] : sort_title_name
+ = dropdown_toggle(toggle_text, { toggle: 'dropdown' })
+ %ul.dropdown-menu.dropdown-menu-right
+ %li.dropdown-header
+ = s_('AdminUsers|Sort by')
+ %li
+ - users_sort_options_hash.each do |value, title|
+ = link_to admin_users_path(sort: value, filter: params[:filter], search_query: params[:search_query]) do
+ = title
- - if @users.empty?
- .nothing-here-block.border-top-0
- = s_('AdminUsers|No users found')
- - else
- .table-holder
- .thead-white.text-nowrap.gl-responsive-table-row.table-row-header{ role: 'row' }
- .table-section.section-40{ role: 'rowheader' }= _('Name')
- .table-section.section-25{ role: 'rowheader' }= _('Created on')
- .table-section.section-15{ role: 'rowheader' }= _('Last activity')
+- if @users.empty?
+ .nothing-here-block.border-top-0
+ = s_('AdminUsers|No users found')
+- else
+ .table-holder
+ .thead-white.text-nowrap.gl-responsive-table-row.table-row-header{ role: 'row' }
+ .table-section.section-40{ role: 'rowheader' }= _('Name')
+ .table-section.section-25{ role: 'rowheader' }= _('Created on')
+ .table-section.section-15{ role: 'rowheader' }= _('Last activity')
- = render partial: 'admin/users/user', collection: @users
+ = render partial: 'admin/users/user', collection: @users
- = paginate @users, theme: "gitlab"
+= paginate @users, theme: "gitlab"
#delete-user-modal
diff --git a/app/views/clusters/clusters/user/_form.html.haml b/app/views/clusters/clusters/user/_form.html.haml
index 1d212553c3b..5507f12b73b 100644
--- a/app/views/clusters/clusters/user/_form.html.haml
+++ b/app/views/clusters/clusters/user/_form.html.haml
@@ -25,6 +25,7 @@
help: '%{help_text} %{help_link}'.html_safe % { help_text: api_url_help_text, help_link: more_info_link }
= platform_kubernetes_field.text_area :ca_cert,
+ rows: '10',
placeholder: s_('ClusterIntegration|Certificate Authority bundle (PEM format)'),
label: s_('ClusterIntegration|CA Certificate'), label_class: 'label-bold',
help: '%{help_text} %{help_link}'.html_safe % { help_text: ca_cert_help_text, help_link: more_info_link }
diff --git a/app/views/clusters/platforms/kubernetes/_form.html.haml b/app/views/clusters/platforms/kubernetes/_form.html.haml
index e50c573bd90..41701b5614a 100644
--- a/app/views/clusters/platforms/kubernetes/_form.html.haml
+++ b/app/views/clusters/platforms/kubernetes/_form.html.haml
@@ -19,7 +19,7 @@
- copy_ca_cert_btn = clipboard_button(text: platform.ca_cert, title: s_('ClusterIntegration|Copy CA Certificate'),
class: 'input-group-text btn-default') if cluster.read_only_kubernetes_platform_fields?
- = platform_field.text_area :ca_cert, class: 'js-select-on-focus', rows: '5',
+ = platform_field.text_area :ca_cert, class: 'js-select-on-focus', rows: '10',
readonly: cluster.read_only_kubernetes_platform_fields?,
placeholder: s_('ClusterIntegration|Certificate Authority bundle (PEM format)'),
label: s_('ClusterIntegration|CA Certificate'), label_class: 'label-bold',
diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml
index b1c192d7bad..d7306f5932d 100644
--- a/app/views/dashboard/activity.html.haml
+++ b/app/views/dashboard/activity.html.haml
@@ -1,18 +1,15 @@
- @hide_top_links = true
-- @no_container = true
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
-
= render_dashboard_gold_trial(current_user)
- page_title "Activity"
- header_title "Activity", activity_dashboard_path
-%div{ class: container_class }
- = render "projects/last_push"
- = render 'dashboard/activity_head'
+= render "projects/last_push"
+= render 'dashboard/activity_head'
- %section.activities
- = render 'activities'
+%section.activities
+ = render 'activities'
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 0298f539b4b..d2aa07bab22 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -1,4 +1,3 @@
-- @no_container = true
- @hide_top_links = true
= content_for :meta_tags do
@@ -9,11 +8,10 @@
- page_title "Projects"
- header_title "Projects", dashboard_projects_path
-%div{ class: container_class }
- = render "projects/last_push"
- - if show_projects?(@projects, params)
- = render 'dashboard/projects_head'
- = render 'nav' unless Feature.enabled?(:project_list_filter_bar)
- = render 'projects'
- - else
- = render "zero_authorized_projects"
+= render "projects/last_push"
+- if show_projects?(@projects, params)
+ = render 'dashboard/projects_head'
+ = render 'nav' unless Feature.enabled?(:project_list_filter_bar)
+ = render 'projects'
+- else
+ = render "zero_authorized_projects"
diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index 0fcc6894b68..2924918aa4f 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -1,16 +1,14 @@
- @hide_top_links = true
-- @no_container = true
- breadcrumb_title _("Projects")
- page_title _("Starred Projects")
- header_title _("Projects"), dashboard_projects_path
= render_dashboard_gold_trial(current_user)
-%div{ class: container_class }
- = render "projects/last_push"
- = render 'dashboard/projects_head', project_tab_filter: :starred
+= render "projects/last_push"
+= render 'dashboard/projects_head', project_tab_filter: :starred
- - if params[:filter_projects] || any_projects?(@projects)
- = render 'projects'
- - else
- = render 'starred_empty_state'
+- if params[:filter_projects] || any_projects?(@projects)
+ = render 'projects'
+- else
+ = render 'starred_empty_state'
diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml
index 0b1d3d1ddb3..6e9efcb0597 100644
--- a/app/views/devise/sessions/_new_base.html.haml
+++ b/app/views/devise/sessions/_new_base.html.haml
@@ -16,7 +16,7 @@
- else
= link_to _('Forgot your password?'), new_password_path(:user)
%div
- - if captcha_enabled?
+ - if captcha_enabled? || captcha_on_login_required?
= recaptcha_tags
.submit-container.move-submit-down
diff --git a/app/views/groups/_group_admin_settings.html.haml b/app/views/groups/_group_admin_settings.html.haml
index 733cb36cc3d..c3c7d102b28 100644
--- a/app/views/groups/_group_admin_settings.html.haml
+++ b/app/views/groups/_group_admin_settings.html.haml
@@ -9,7 +9,7 @@
Allow projects within this group to use Git LFS
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
%br/
- %span.descr This setting can be overridden in each project.
+ %span This setting can be overridden in each project.
.form-group.row
.col-sm-2.col-form-label
= f.label s_('ProjectCreationLevel|Allowed to create projects')
diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml
index a8358704b03..41c1d3e84b7 100644
--- a/app/views/groups/labels/index.html.haml
+++ b/app/views/groups/labels/index.html.haml
@@ -1,4 +1,3 @@
-- @no_container = true
- page_title 'Labels'
- can_admin_label = can?(current_user, :admin_label, @group)
- search = params[:search]
@@ -7,24 +6,23 @@
- if labels_or_filters
#promote-label-modal
- %div{ class: container_class }
- = render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label
+ = render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label
- .labels-container.prepend-top-5
- - if @labels.any?
- .text-muted
- = _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuable_types.to_sentence }
- .other-labels
- %h5= _('Labels')
- %ul.content-list.manage-labels-list.js-other-labels
- = render partial: 'shared/label', collection: @labels, as: :label, locals: { use_label_priority: false, subject: @group }
- = paginate @labels, theme: 'gitlab'
- - elsif search.present?
- .nothing-here-block
- = _('No labels with such name or description')
- - elsif subscribed.present?
- .nothing-here-block
- = _('You do not have any subscriptions yet')
+ .labels-container.prepend-top-5
+ - if @labels.any?
+ .text-muted
+ = _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuable_types.to_sentence }
+ .other-labels
+ %h5= _('Labels')
+ %ul.content-list.manage-labels-list.js-other-labels
+ = render partial: 'shared/label', collection: @labels, as: :label, locals: { use_label_priority: false, subject: @group }
+ = paginate @labels, theme: 'gitlab'
+ - elsif search.present?
+ .nothing-here-block
+ = _('No labels with such name or description')
+ - elsif subscribed.present?
+ .nothing-here-block
+ = _('You do not have any subscriptions yet')
- else
= render 'shared/empty_states/labels'
diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml
index 248cb3b0ba5..2c93b0e4efd 100644
--- a/app/views/groups/milestones/new.html.haml
+++ b/app/views/groups/milestones/new.html.haml
@@ -1,12 +1,10 @@
-- @no_container = true
- add_to_breadcrumbs _("Milestones"), group_milestones_path(@group)
- breadcrumb_title _("New")
- page_title _("Milestones"), @milestone.name, _("Milestones")
-%div{ class: container_class }
- %h3.page-title
- New Milestone
+%h3.page-title
+ New Milestone
- %hr
+%hr
- = render "form"
+= render "form"
diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml
index c55730ccd31..4c88660ccb9 100644
--- a/app/views/groups/settings/_permissions.html.haml
+++ b/app/views/groups/settings/_permissions.html.haml
@@ -14,7 +14,7 @@
%span.d-block
- group_link = link_to @group.name, group_path(@group)
= s_('GroupSettings|Prevent sharing a project within %{group} with other groups').html_safe % { group: group_link }
- %span.descr.text-muted= share_with_group_lock_help_text(@group)
+ %span.js-descr.text-muted= share_with_group_lock_help_text(@group)
.form-group.append-bottom-default
.form-check
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 255a9ad038c..0e6c16f0f06 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -1,11 +1,10 @@
-- @no_container = true
- breadcrumb_title _("Details")
- @content_class = "limit-container-width" unless fluid_layout
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity")
-%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
+%div{ class: [("limit-container-width" unless fluid_layout)] }
= render 'groups/home_panel'
.groups-listing{ data: { endpoints: { default: group_children_path(@group, format: :json), shared: group_shared_projects_path(@group, format: :json) } } }
diff --git a/app/views/instance_statistics/cohorts/index.html.haml b/app/views/instance_statistics/cohorts/index.html.haml
index e135bab10d8..c438566cb05 100644
--- a/app/views/instance_statistics/cohorts/index.html.haml
+++ b/app/views/instance_statistics/cohorts/index.html.haml
@@ -1,16 +1,14 @@
- breadcrumb_title _("Cohorts")
-- @no_container = true
-%div{ class: container_class }
- - if @cohorts
- = render 'cohorts_table'
- - else
- .bs-callout.bs-callout-warning.clearfix
- %p
- - usage_ping_path = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'usage-ping')
- - usage_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: usage_ping_path }
- = s_('User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled.').html_safe % { usage_ping_link_start: usage_ping_link_start, usage_ping_link_end: '</a>'.html_safe }
- - if current_user.admin?
- - application_settings_path = admin_application_settings_path(anchor: 'usage-statistics')
- - application_settings_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: application_settings_path }
- = s_('To enable it and see User Cohorts, visit %{application_settings_link_start}application settings%{application_settings_link_end}.').html_safe % { application_settings_link_start: application_settings_link_start, application_settings_link_end: '</a>'.html_safe }
+- if @cohorts
+ = render 'cohorts_table'
+- else
+ .bs-callout.bs-callout-warning.clearfix
+ %p
+ - usage_ping_path = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'usage-ping')
+ - usage_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: usage_ping_path }
+ = s_('User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled.').html_safe % { usage_ping_link_start: usage_ping_link_start, usage_ping_link_end: '</a>'.html_safe }
+ - if current_user.admin?
+ - application_settings_path = admin_application_settings_path(anchor: 'usage-statistics')
+ - application_settings_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: application_settings_path }
+ = s_('To enable it and see User Cohorts, visit %{application_settings_link_start}application settings%{application_settings_link_end}.').html_safe % { application_settings_link_start: application_settings_link_start, application_settings_link_end: '</a>'.html_safe }
diff --git a/app/views/instance_statistics/conversational_development_index/index.html.haml b/app/views/instance_statistics/conversational_development_index/index.html.haml
index 23f90b876a0..49c8fdc9630 100644
--- a/app/views/instance_statistics/conversational_development_index/index.html.haml
+++ b/app/views/instance_statistics/conversational_development_index/index.html.haml
@@ -1,4 +1,3 @@
-- @no_container = true
- page_title _('ConvDev Index')
- usage_ping_enabled = Gitlab::CurrentSettings.usage_ping_enabled
diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml
index 2cdaa85bdaa..d673d7164b3 100644
--- a/app/views/layouts/_flash.html.haml
+++ b/app/views/layouts/_flash.html.haml
@@ -1,10 +1,8 @@
-- extra_flash_class = local_assigns.fetch(:extra_flash_class, nil)
-
-.flash-container.flash-container-page
+.flash-container.flash-container-page.sticky
-# We currently only support `alert`, `notice`, `success`
- flash.each do |key, value|
-# Don't show a flash message if the message is nil
- if value
- %div{ class: "flash-#{key}" }
- %div{ class: "#{(container_class unless fluid_layout)} #{(extra_flash_class unless @no_container)} #{@content_class}" }
- %span= value
+ %div{ class: "flash-content flash-#{key} rounded" }
+ %span= value
+ = sprite_icon('close', size: 16, css_class: 'close-icon')
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 006334ade07..443a73f5cce 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -13,8 +13,8 @@
= render "shared/ping_consent"
- unless @hide_breadcrumbs
= render "layouts/nav/breadcrumbs"
- = render "layouts/flash", extra_flash_class: 'limit-container-width'
.d-flex
%div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
.content{ id: "content-body" }
+ = render "layouts/flash", extra_flash_class: 'limit-container-width'
= yield
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index e6a235e39da..ba5cd0fdd41 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -47,6 +47,7 @@
= hidden_field_tag :snippets, true
= hidden_field_tag :repository_ref, @ref
= hidden_field_tag :nav_source, 'navbar'
- -# workaround for non-JS feature specs, for JS you need to use find('#search').send_keys(:enter)
- = button_tag 'Go' if ENV['RAILS_ENV'] == 'test'
+ -# workaround for non-JS feature specs, see spec/support/helpers/search_helpers.rb
+ - if ENV['RAILS_ENV'] == 'test'
+ %noscript= button_tag 'Search'
.search-autocomplete-opts.hide{ :'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref }
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index e9a4a068599..d36e08f44a4 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -1,8 +1,9 @@
!!! 5
%html.devise-layout-html{ class: system_message_class }
= render "layouts/head"
- %body.ui-indigo.login-page.application.navless{ data: { page: body_data_page, qa_selector: 'login_page' } }
+ %body.ui-indigo.login-page.application.navless{ class: "#{client_class_list}", data: { page: body_data_page, qa_selector: 'login_page' } }
= header_message
+ = render "layouts/init_client_detection_flags"
.page-wrap
= render "layouts/header/empty"
.login-page-broadcast
diff --git a/app/views/layouts/devise_empty.html.haml b/app/views/layouts/devise_empty.html.haml
index 6c9c8aa4431..ff2b00ea376 100644
--- a/app/views/layouts/devise_empty.html.haml
+++ b/app/views/layouts/devise_empty.html.haml
@@ -1,8 +1,9 @@
!!! 5
%html{ lang: "en", class: system_message_class }
= render "layouts/head"
- %body.ui-indigo.login-page.application.navless
+ %body.ui-indigo.login-page.application.navless{ class: "#{client_class_list}" }
= header_message
+ = render "layouts/init_client_detection_flags"
= render "layouts/header/empty"
= render "layouts/broadcast"
.container.navless-container
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index 9e92ced9f89..f76268bc29b 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -270,7 +270,7 @@
%span
= _('Geo')
= nav_link(path: 'application_settings#preferences') do
- = link_to preferences_admin_application_settings_path, title: _('Preferences') do
+ = link_to preferences_admin_application_settings_path, title: _('Preferences'), data: { qa_selector: 'admin_settings_preferences_link' } do
%span
= _('Preferences')
diff --git a/app/views/notify/new_issue_email.text.erb b/app/views/notify/new_issue_email.text.erb
index b93d95ef02f..bd61db3ee76 100644
--- a/app/views/notify/new_issue_email.text.erb
+++ b/app/views/notify/new_issue_email.text.erb
@@ -1,9 +1,5 @@
-<%= sanitize_name(@issue.author_name) %> <%= 'created an issue:' %>
+<%= sanitize_name(@issue.author_name) %> <%= 'created an issue:' %> <%= url_for(project_issue_url(@issue.project, @issue)) %>
-<% if @issue.assignees.any? -%>
- <%= assignees_label(@issue) %>
-<% end %>
+<%= assignees_label(@issue) if @issue.assignees.any? %>
-<% if @issue.description -%>
- <%= @issue.description %>
-<% end %>
+<%= @issue.description %>
diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb
index 6c0d7b1e60b..f4b0ed0f886 100644
--- a/app/views/notify/new_merge_request_email.text.erb
+++ b/app/views/notify/new_merge_request_email.text.erb
@@ -1,8 +1,8 @@
-<%= @merge_request.author_name %> <%= 'created a merge request:' %> <%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
+<%= sanitize_name(@merge_request.author_name) %> <%= 'created a merge request:' %> <%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
<%= merge_path_description(@merge_request, 'to') %>
<%= 'Author:' %> <%= @merge_request.author_name %>
-<%= assignees_label(@merge_request) %>
+<%= assignees_label(@merge_request) if @merge_request.assignees.any? %>
<%= render_if_exists 'notify/merge_request_approvers', presenter: @mr_presenter %>
<%= @merge_request.description %>
diff --git a/app/views/peek/_bar.html.haml b/app/views/peek/_bar.html.haml
index 5228930293c..9725f640be9 100644
--- a/app/views/peek/_bar.html.haml
+++ b/app/views/peek/_bar.html.haml
@@ -1,6 +1,6 @@
-- return unless peek_enabled?
+- return unless performance_bar_enabled?
#js-peek{ data: { env: Peek.env,
- request_id: Peek.request_id,
+ request_id: peek_request_id,
peek_url: "#{peek_routes_path}/results" },
class: Peek.env }
diff --git a/app/views/projects/_merge_request_merge_checks_settings.html.haml b/app/views/projects/_merge_request_merge_checks_settings.html.haml
index c21d333f21a..d3fcb52422b 100644
--- a/app/views/projects/_merge_request_merge_checks_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_checks_settings.html.haml
@@ -7,7 +7,7 @@
= form.check_box :only_allow_merge_if_pipeline_succeeds, class: 'form-check-input'
= form.label :only_allow_merge_if_pipeline_succeeds, class: 'form-check-label' do
= s_('ProjectSettings|Pipelines must succeed')
- .descr.text-secondary
+ .text-secondary
= s_('ProjectSettings|Pipelines need to be configured to enable this feature.')
= link_to icon('question-circle'),
help_page_path('ci/merge_request_pipelines/index.md',
diff --git a/app/views/projects/_merge_request_merge_method_settings.html.haml b/app/views/projects/_merge_request_merge_method_settings.html.haml
index 47c311f42d0..1be7f7bb418 100644
--- a/app/views/projects/_merge_request_merge_method_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_method_settings.html.haml
@@ -7,14 +7,14 @@
= form.radio_button :merge_method, :merge, class: "js-merge-method-radio form-check-input"
= label_tag :project_merge_method_merge, class: 'form-check-label' do
= s_('ProjectSettings|Merge commit')
- .descr.text-secondary
+ .text-secondary
= s_('ProjectSettings|Every merge creates a merge commit')
.form-check.mb-2
= form.radio_button :merge_method, :rebase_merge, class: "js-merge-method-radio form-check-input"
= label_tag :project_merge_method_rebase_merge, class: 'form-check-label' do
= s_('ProjectSettings|Merge commit with semi-linear history')
- .descr.text-secondary
+ .text-secondary
= s_('ProjectSettings|Every merge creates a merge commit')
%br
= s_('ProjectSettings|Fast-forward merges only')
@@ -25,7 +25,7 @@
= form.radio_button :merge_method, :ff, class: "js-merge-method-radio qa-radio-button-merge-ff form-check-input"
= label_tag :project_merge_method_ff, class: 'form-check-label' do
= s_('ProjectSettings|Fast-forward merge')
- .descr.text-secondary
+ .text-secondary
= s_('ProjectSettings|No merge commits are created')
%br
= s_('ProjectSettings|Fast-forward merges only')
diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml
index b28a375e956..6a4760c3954 100644
--- a/app/views/projects/activity.html.haml
+++ b/app/views/projects/activity.html.haml
@@ -1,8 +1,4 @@
-- @no_container = true
-
- page_title _("Activity")
-%div{ class: container_class }
- = render 'projects/last_push'
-
+= render 'projects/last_push'
= render 'projects/activity'
diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index ef6f5c76de6..f2215765974 100644
--- a/app/views/projects/blame/show.html.haml
+++ b/app/views/projects/blame/show.html.haml
@@ -1,44 +1,42 @@
-- @no_container = true
- project_duration = age_map_duration(@blame_groups, @project)
- page_title "Blame", @blob.path, @ref
-%div{ class: container_class }
- #blob-content-holder.tree-holder
- = render "projects/blob/breadcrumb", blob: @blob, blame: true
+#blob-content-holder.tree-holder
+ = render "projects/blob/breadcrumb", blob: @blob, blame: true
- .file-holder
- = render "projects/blob/header", blob: @blob, blame: true
- .file-blame-legend
- = render 'age_map_legend'
- .table-responsive.file-content.blame.code.js-syntax-highlight
- %table
- - current_line = 1
- - @blame_groups.each do |blame_group|
- %tr
- - commit = blame_group[:commit]
- %td.blame-commit{ class: age_map_class(commit.committed_date, project_duration) }
- .commit
- = author_avatar(commit, size: 36, has_tooltip: false)
- .commit-row-title
- %span.item-title.str-truncated-100
- = link_to_markdown commit.title, project_commit_path(@project, commit.id), class: "cdark", title: commit.title
- .float-right
- = link_to commit.short_id, project_commit_path(@project, commit), class: "commit-sha"
- &nbsp;
- .light
- = commit_author_link(commit, avatar: false)
- committed
- #{time_ago_with_tooltip(commit.committed_date)}
- %td.line-numbers
- - line_count = blame_group[:lines].count
- - (current_line...(current_line + line_count)).each do |i|
- %a.diff-line-num{ href: "#L#{i}", id: "L#{i}", 'data-line-number' => i }
- = icon("link")
- = i
- \
- - current_line += line_count
- %td.lines
- %pre.code.highlight
- %code
- - blame_group[:lines].each do |line|
- #{line}
+ .file-holder
+ = render "projects/blob/header", blob: @blob, blame: true
+ .file-blame-legend
+ = render 'age_map_legend'
+ .table-responsive.file-content.blame.code.js-syntax-highlight
+ %table
+ - current_line = 1
+ - @blame_groups.each do |blame_group|
+ %tr
+ - commit = blame_group[:commit]
+ %td.blame-commit{ class: age_map_class(commit.committed_date, project_duration) }
+ .commit
+ = author_avatar(commit, size: 36, has_tooltip: false)
+ .commit-row-title
+ %span.item-title.str-truncated-100
+ = link_to_markdown commit.title, project_commit_path(@project, commit.id), class: "cdark", title: commit.title
+ .float-right
+ = link_to commit.short_id, project_commit_path(@project, commit), class: "commit-sha"
+ &nbsp;
+ .light
+ = commit_author_link(commit, avatar: false)
+ committed
+ #{time_ago_with_tooltip(commit.committed_date)}
+ %td.line-numbers
+ - line_count = blame_group[:lines].count
+ - (current_line...(current_line + line_count)).each do |i|
+ %a.diff-line-num{ href: "#L#{i}", id: "L#{i}", 'data-line-number' => i }
+ = icon("link")
+ = i
+ \
+ - current_line += line_count
+ %td.lines
+ %pre.code.highlight
+ %code
+ - blame_group[:lines].each do |line|
+ #{line}
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index 4520cca8cf5..51e42091ab8 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -1,33 +1,31 @@
- breadcrumb_title "Repository"
-- @no_container = true
- page_title "Edit", @blob.path, @ref
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
-%div{ class: container_class }
- - if @conflict
- .alert.alert-danger
- Someone edited the file the same time you did. Please check out
- = link_to "the file", project_blob_path(@project, tree_join(@branch_name, @file_path)), target: "_blank", rel: 'noopener noreferrer'
- and make sure your changes will not unintentionally remove theirs.
- .editor-title-row
- %h3.page-title.blob-edit-page-title
- Edit file
- = render 'template_selectors'
- .file-editor
- %ul.nav-links.no-bottom.js-edit-mode.nav.nav-tabs
- %li.active
- = link_to '#editor' do
- Write
+- if @conflict
+ .alert.alert-danger
+ Someone edited the file the same time you did. Please check out
+ = link_to "the file", project_blob_path(@project, tree_join(@branch_name, @file_path)), target: "_blank", rel: 'noopener noreferrer'
+ and make sure your changes will not unintentionally remove theirs.
+.editor-title-row
+ %h3.page-title.blob-edit-page-title
+ Edit file
+ = render 'template_selectors'
+.file-editor
+ %ul.nav-links.no-bottom.js-edit-mode.nav.nav-tabs
+ %li.active
+ = link_to '#editor' do
+ Write
- %li
- = link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id) do
- = editing_preview_title(@blob.name)
+ %li
+ = link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id) do
+ = editing_preview_title(@blob.name)
- = form_tag(project_update_blob_path(@project, @id), method: :put, class: 'js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths(@project)) do
- = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
- = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
- = hidden_field_tag 'last_commit_sha', @last_commit_sha
- = hidden_field_tag 'content', '', id: "file-content"
- = hidden_field_tag 'from_merge_request_iid', params[:from_merge_request_iid]
- = render 'projects/commit_button', ref: @ref, cancel_path: project_blob_path(@project, @id)
+ = form_tag(project_update_blob_path(@project, @id), method: :put, class: 'js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths(@project)) do
+ = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
+ = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
+ = hidden_field_tag 'last_commit_sha', @last_commit_sha
+ = hidden_field_tag 'content', '', id: "file-content"
+ = hidden_field_tag 'from_merge_request_iid', params[:from_merge_request_iid]
+ = render 'projects/commit_button', ref: @ref, cancel_path: project_blob_path(@project, @id)
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index a0b0384d78d..688b8f001c3 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -1,19 +1,16 @@
- breadcrumb_title "Repository"
-- @no_container = true
-
- page_title @blob.path, @ref
-
- signatures_path = namespace_project_signatures_path(namespace_id: @project.namespace.full_path, project_id: @project.path, id: @last_commit)
+
.js-signature-container{ data: { 'signatures-path': signatures_path } }
-%div{ class: container_class }
- = render 'projects/last_push'
+= render 'projects/last_push'
- #tree-holder.tree-holder
- = render 'blob', blob: @blob
+#tree-holder.tree-holder
+ = render 'blob', blob: @blob
- - if can_modify_blob?(@blob)
- = render 'projects/blob/remove'
+ - if can_modify_blob?(@blob)
+ = render 'projects/blob/remove'
- - title = "Replace #{@blob.name}"
- = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put
+ - title = "Replace #{@blob.name}"
+ = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 11340d12423..6bdc6f716fe 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -1,70 +1,68 @@
-- @no_container = true
- page_title _('Branches')
- add_to_breadcrumbs(_('Repository'), project_tree_path(@project))
-%div{ class: container_class }
- .top-area.adjust
- %ul.nav-links.issues-state-filters.nav.nav-tabs
- %li{ class: active_when(@mode == 'overview') }>
- = link_to s_('Branches|Overview'), project_branches_path(@project), title: s_('Branches|Show overview of the branches')
+.top-area.adjust
+ %ul.nav-links.issues-state-filters.nav.nav-tabs
+ %li{ class: active_when(@mode == 'overview') }>
+ = link_to s_('Branches|Overview'), project_branches_path(@project), title: s_('Branches|Show overview of the branches')
- %li{ class: active_when(@mode == 'active') }>
- = link_to s_('Branches|Active'), project_branches_filtered_path(@project, state: 'active'), title: s_('Branches|Show active branches')
+ %li{ class: active_when(@mode == 'active') }>
+ = link_to s_('Branches|Active'), project_branches_filtered_path(@project, state: 'active'), title: s_('Branches|Show active branches')
- %li{ class: active_when(@mode == 'stale') }>
- = link_to s_('Branches|Stale'), project_branches_filtered_path(@project, state: 'stale'), title: s_('Branches|Show stale branches')
+ %li{ class: active_when(@mode == 'stale') }>
+ = link_to s_('Branches|Stale'), project_branches_filtered_path(@project, state: 'stale'), title: s_('Branches|Show stale branches')
- %li{ class: active_when(!%w[overview active stale].include?(@mode)) }>
- = link_to s_('Branches|All'), project_branches_filtered_path(@project, state: 'all'), title: s_('Branches|Show all branches')
+ %li{ class: active_when(!%w[overview active stale].include?(@mode)) }>
+ = link_to s_('Branches|All'), project_branches_filtered_path(@project, state: 'all'), title: s_('Branches|Show all branches')
- .nav-controls
- = form_tag(project_branches_filtered_path(@project, state: 'all'), method: :get) do
- = search_field_tag :search, params[:search], { placeholder: s_('Branches|Filter by branch name'), id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false }
+ .nav-controls
+ = form_tag(project_branches_filtered_path(@project, state: 'all'), method: :get) do
+ = search_field_tag :search, params[:search], { placeholder: s_('Branches|Filter by branch name'), id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false }
- - unless @mode == 'overview'
- .dropdown.inline>
- %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
- %span.light
- = branches_sort_options_hash[@sort]
- = icon('chevron-down')
- %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable
- %li.dropdown-header
- = s_('Branches|Sort by')
- - branches_sort_options_hash.each do |value, title|
- %li
- = link_to title, project_branches_filtered_path(@project, state: 'all', search: params[:search], sort: value), class: ("is-active" if @sort == value)
+ - unless @mode == 'overview'
+ .dropdown.inline>
+ %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
+ %span.light
+ = branches_sort_options_hash[@sort]
+ = icon('chevron-down')
+ %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable
+ %li.dropdown-header
+ = s_('Branches|Sort by')
+ - branches_sort_options_hash.each do |value, title|
+ %li
+ = link_to title, project_branches_filtered_path(@project, state: 'all', search: params[:search], sort: value), class: ("is-active" if @sort == value)
- - if can? current_user, :push_code, @project
- = link_to project_merged_branches_path(@project),
- class: 'btn btn-inverted btn-remove has-tooltip qa-delete-merged-branches',
- title: s_("Branches|Delete all branches that are merged into '%{default_branch}'") % { default_branch: @project.repository.root_ref },
- method: :delete,
- data: { confirm: s_('Branches|Deleting the merged branches cannot be undone. Are you sure?'),
- container: 'body' } do
- = s_('Branches|Delete merged branches')
- = link_to new_project_branch_path(@project), class: 'btn btn-success' do
- = s_('Branches|New branch')
+ - if can? current_user, :push_code, @project
+ = link_to project_merged_branches_path(@project),
+ class: 'btn btn-inverted btn-remove has-tooltip qa-delete-merged-branches',
+ title: s_("Branches|Delete all branches that are merged into '%{default_branch}'") % { default_branch: @project.repository.root_ref },
+ method: :delete,
+ data: { confirm: s_('Branches|Deleting the merged branches cannot be undone. Are you sure?'),
+ container: 'body' } do
+ = s_('Branches|Delete merged branches')
+ = link_to new_project_branch_path(@project), class: 'btn btn-success' do
+ = s_('Branches|New branch')
- = render_if_exists 'projects/commits/mirror_status'
+= render_if_exists 'projects/commits/mirror_status'
- .js-branch-list{ data: { diverging_counts_endpoint: diverging_commit_counts_namespace_project_branches_path(@project.namespace, @project, format: :json) } }
- - if can?(current_user, :admin_project, @project)
- - project_settings_link = link_to s_('Branches|project settings'), project_protected_branches_path(@project)
- .row-content-block
- %h5
- = s_('Branches|Protected branches can be managed in %{project_settings_link}.').html_safe % { project_settings_link: project_settings_link }
+.js-branch-list{ data: { diverging_counts_endpoint: diverging_commit_counts_namespace_project_branches_path(@project.namespace, @project, format: :json) } }
+- if can?(current_user, :admin_project, @project)
+ - project_settings_link = link_to s_('Branches|project settings'), project_protected_branches_path(@project)
+ .row-content-block
+ %h5
+ = s_('Branches|Protected branches can be managed in %{project_settings_link}.').html_safe % { project_settings_link: project_settings_link }
- - if @mode == 'overview' && (@active_branches.any? || @stale_branches.any?)
- = render "projects/branches/panel", branches: @active_branches, state: 'active', panel_title: s_('Branches|Active branches'), show_more_text: s_('Branches|Show more active branches'), project: @project, overview_max_branches: @overview_max_branches
- = render "projects/branches/panel", branches: @stale_branches, state: 'stale', panel_title: s_('Branches|Stale branches'), show_more_text: s_('Branches|Show more stale branches'), project: @project, overview_max_branches: @overview_max_branches
+- if @mode == 'overview' && (@active_branches.any? || @stale_branches.any?)
+ = render "projects/branches/panel", branches: @active_branches, state: 'active', panel_title: s_('Branches|Active branches'), show_more_text: s_('Branches|Show more active branches'), project: @project, overview_max_branches: @overview_max_branches
+ = render "projects/branches/panel", branches: @stale_branches, state: 'stale', panel_title: s_('Branches|Stale branches'), show_more_text: s_('Branches|Show more stale branches'), project: @project, overview_max_branches: @overview_max_branches
- - elsif @branches.any?
- %ul.content-list.all-branches
- - @branches.each do |branch|
- = render "projects/branches/branch", branch: branch, merged: @merged_branch_names.include?(branch.name)
- = paginate @branches, theme: 'gitlab'
- - else
- .nothing-here-block
- = s_('Branches|No branches to show')
+- elsif @branches.any?
+ %ul.content-list.all-branches
+ - @branches.each do |branch|
+ = render "projects/branches/branch", branch: branch, merged: @merged_branch_names.include?(branch.name)
+ = paginate @branches, theme: 'gitlab'
+- else
+ .nothing-here-block
+ = s_('Branches|No branches to show')
= render 'projects/branches/delete_protected_modal'
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index a766dd51463..6c77036a85b 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -5,7 +5,7 @@
= render partial: 'signature', object: @commit.signature
%strong
#{ s_('CommitBoxTitle|Commit') }
- %span.commit-sha= @commit.short_id
+ %span.commit-sha{ data: { qa_selector: 'commit_sha_content' } }= @commit.short_id
= clipboard_button(text: @commit.id, title: _('Copy commit SHA to clipboard'))
%span.d-none.d-sm-inline= _('authored')
#{time_ago_with_tooltip(@commit.authored_date)}
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 34226167288..40b96ca477e 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -1,3 +1,4 @@
+-# no_container is needed here because of full width side-by-side diff view
- @no_container = true
- add_to_breadcrumbs _('Commits'), project_commits_path(@project)
- breadcrumb_title @commit.short_id
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 2db1efdd52f..e155e3758fb 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -1,4 +1,3 @@
-- @no_container = true
- breadcrumb_title _("Commits")
- page_title _("Commits"), @ref
@@ -6,33 +5,32 @@
= auto_discovery_link_tag(:atom, project_commits_path(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
.js-project-commits-show{ 'data-commits-limit' => @limit }
- %div{ class: container_class }
- .tree-holder
- .nav-block
- .tree-ref-container
- .tree-ref-holder
- = render 'shared/ref_switcher', destination: 'commits'
-
- %ul.breadcrumb.repo-breadcrumb
- = commits_breadcrumbs
- .tree-controls.d-none.d-sm-none.d-md-block
- - if @merge_request.present?
- .control
- = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn'
- - elsif create_mr_button?(@repository.root_ref, @ref)
- .control
- = link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
+ .tree-holder
+ .nav-block
+ .tree-ref-container
+ .tree-ref-holder
+ = render 'shared/ref_switcher', destination: 'commits'
+ %ul.breadcrumb.repo-breadcrumb
+ = commits_breadcrumbs
+ .tree-controls.d-none.d-sm-none.d-md-block
+ - if @merge_request.present?
.control
- = form_tag(project_commits_path(@project, @id), method: :get, class: 'commits-search-form js-signature-container', data: { 'signatures-path' => namespace_project_signatures_path }) do
- = search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false }
+ = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn'
+ - elsif create_mr_button?(@repository.root_ref, @ref)
.control
- = link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do
- = icon("rss")
+ = link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success'
+
+ .control
+ = form_tag(project_commits_path(@project, @id), method: :get, class: 'commits-search-form js-signature-container', data: { 'signatures-path' => namespace_project_signatures_path }) do
+ = search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false }
+ .control
+ = link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do
+ = icon("rss")
- = render_if_exists 'projects/commits/mirror_status'
+ = render_if_exists 'projects/commits/mirror_status'
- %div{ id: dom_id(@project) }
- %ol#commits-list.list-unstyled.content_list
- = render 'commits', project: @project, ref: @ref
- = spinner
+ %div{ id: dom_id(@project) }
+ %ol#commits-list.list-unstyled.content_list
+ = render 'commits', project: @project, ref: @ref
+ = spinner
diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml
index 14c64b3534a..02f2b104ce3 100644
--- a/app/views/projects/compare/index.html.haml
+++ b/app/views/projects/compare/index.html.haml
@@ -1,18 +1,16 @@
-- @no_container = true
- breadcrumb_title "Compare Revisions"
- page_title "Compare"
-%div{ class: container_class }
- %h3.page-title
- = _("Compare Git revisions")
- .sub-header-block
- - example_master = capture do
- %code.ref-name master
- - example_sha = capture do
- %code.ref-name 4eedf23
- = (_("Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request.") % { master: example_master, sha: example_sha }).html_safe
- %br
- = (_("Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision.")).html_safe
+%h3.page-title
+ = _("Compare Git revisions")
+.sub-header-block
+ - example_master = capture do
+ %code.ref-name master
+ - example_sha = capture do
+ %code.ref-name 4eedf23
+ = (_("Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request.") % { master: example_master, sha: example_sha }).html_safe
+ %br
+ = (_("Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision.")).html_safe
- .prepend-top-20
- = render "form"
+.prepend-top-20
+ = render "form"
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index 5774b48a054..51cf95dc84b 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -1,25 +1,23 @@
-- @no_container = true
- add_to_breadcrumbs _("Compare Revisions"), project_compare_index_path(@project)
- page_title "#{params[:from]}...#{params[:to]}"
-%div{ class: container_class }
- .sub-header-block.no-bottom-space
- = render "form"
+.sub-header-block.no-bottom-space
+ = render "form"
- - if @commits.present?
- = render "projects/commits/commit_list"
- = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, diff_page_context: "is-compare"
- - else
- .card.bg-light
- .center
- %h4
- = s_("CompareBranches|There isn't anything to compare.")
- %p.slead
- - if params[:to] == params[:from]
- - source_branch = capture do
- %span.ref-name= params[:from]
- - target_branch = capture do
- %span.ref-name= params[:to]
- = (s_("CompareBranches|%{source_branch} and %{target_branch} are the same.") % { source_branch: source_branch, target_branch: target_branch }).html_safe
- - else
- = _("You'll need to use different branch names to get a valid comparison.")
+- if @commits.present?
+ = render "projects/commits/commit_list"
+ = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, diff_page_context: "is-compare"
+- else
+ .card.bg-light
+ .center
+ %h4
+ = s_("CompareBranches|There isn't anything to compare.")
+ %p.slead
+ - if params[:to] == params[:from]
+ - source_branch = capture do
+ %span.ref-name= params[:from]
+ - target_branch = capture do
+ %span.ref-name= params[:to]
+ = (s_("CompareBranches|%{source_branch} and %{target_branch} are the same.") % { source_branch: source_branch, target_branch: target_branch }).html_safe
+ - else
+ = _("You'll need to use different branch names to get a valid comparison.")
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index 2b594c125f4..6b56a4ee7ab 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -1,7 +1,6 @@
-- @no_container = true
- page_title "Cycle Analytics"
-#cycle-analytics{ class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } }
+#cycle-analytics{ "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } }
- if @cycle_analytics_no_data
%banner{ "v-if" => "!isOverviewDialogDismissed",
"documentation-link": help_page_path('user/project/cycle_analytics'),
diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml
index 752be02443c..ef2ab4c698e 100644
--- a/app/views/projects/deployments/_deployment.html.haml
+++ b/app/views/projects/deployments/_deployment.html.haml
@@ -22,7 +22,8 @@
.table-section.section-15{ role: 'gridcell' }
.table-mobile-header{ role: 'rowheader' }= _("Created")
- %span.table-mobile-content= time_ago_with_tooltip(deployment.created_at)
+ - if deployment.deployed_at
+ %span.table-mobile-content= time_ago_with_tooltip(deployment.deployed_at)
.table-section.section-20.table-button-footer{ role: 'gridcell' }
.btn-group.table-action-buttons
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 9fa31c147eb..a9b6b397968 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -1,10 +1,9 @@
- @content_class = "limit-container-width" unless fluid_layout
-- @no_container = true
- breadcrumb_title _("Details")
= render partial: 'flash_messages', locals: { project: @project }
-%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
+%div{ class: [("limit-container-width" unless fluid_layout)] }
= render "home_panel"
%h4.prepend-top-0.append-bottom-8
diff --git a/app/views/projects/environments/edit.html.haml b/app/views/projects/environments/edit.html.haml
index d581bd3aeab..56af252d785 100644
--- a/app/views/projects/environments/edit.html.haml
+++ b/app/views/projects/environments/edit.html.haml
@@ -1,8 +1,6 @@
-- @no_container = true
- page_title _("Edit"), @environment.name, _("Environments")
-%div{ class: container_class }
- %h3.page-title
- = _('Edit environment')
- %hr
- = render 'form'
+%h3.page-title
+ = _('Edit environment')
+%hr
+= render 'form'
diff --git a/app/views/projects/environments/folder.html.haml b/app/views/projects/environments/folder.html.haml
index aebd176af9b..f85c57d9aa1 100644
--- a/app/views/projects/environments/folder.html.haml
+++ b/app/views/projects/environments/folder.html.haml
@@ -1,5 +1,3 @@
-- @no_container = true
- page_title _("Environments")
-#environments-folder-list-view{ data: { environments_data: environments_folder_list_view_data,
- "css-class" => container_class } }
+#environments-folder-list-view{ data: { environments_data: environments_folder_list_view_data } }
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index 3ec92676cde..2ba88da3375 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -1,4 +1,3 @@
-- @no_container = true
- page_title _("Environments")
#environments-list-view{ data: { environments_data: environments_list_data,
@@ -6,5 +5,4 @@
"can-create-environment" => can?(current_user, :create_environment, @project).to_s,
"new-environment-path" => new_project_environment_path(@project),
"help-page-path" => help_page_path("ci/environments"),
- "deploy-boards-help-path" => help_page_path("user/project/deploy_boards", anchor: "enabling-deploy-boards"),
- "css-class" => container_class } }
+ "deploy-boards-help-path" => help_page_path("user/project/deploy_boards", anchor: "enabling-deploy-boards") } }
diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml
index 7b847a85686..aab30af5ed4 100644
--- a/app/views/projects/environments/metrics.html.haml
+++ b/app/views/projects/environments/metrics.html.haml
@@ -1,5 +1,4 @@
-- @no_container = true
- page_title _("Metrics for environment"), @environment.name
-.prometheus-container{ class: container_class }
+.prometheus-container
#prometheus-graphs{ data: metrics_data(@project, @environment) }
diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml
index c1067fdff78..96edd3f0bd7 100644
--- a/app/views/projects/environments/new.html.haml
+++ b/app/views/projects/environments/new.html.haml
@@ -1,9 +1,7 @@
-- @no_container = true
- breadcrumb_title _("Environments")
- page_title _("New Environment")
-%div{ class: container_class }
- %h3.page-title
- = _("New environment")
- %hr
- = render 'form'
+%h3.page-title
+ = _("New environment")
+%hr
+= render 'form'
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 6100fd3ad37..75da151f329 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -1,4 +1,3 @@
-- @no_container = true
- add_to_breadcrumbs _("Environments"), project_environments_path(@project)
- breadcrumb_title @environment.name
- page_title _("Environments")
@@ -6,67 +5,66 @@
- content_for :page_specific_javascripts do
= stylesheet_link_tag 'page_bundles/xterm'
-%div{ class: container_class }
- - if can?(current_user, :stop_environment, @environment)
- #stop-environment-modal.modal.fade{ tabindex: -1 }
- .modal-dialog
- .modal-content
- .modal-header
- %h4.modal-title.d-flex.mw-100
- = s_("Environments|Stopping")
- %span.has-tooltip.text-truncate.ml-1.mr-1.flex-fill{ title: @environment.name, data: { container: '#stop-environment-modal' } }
- = @environment.name
- ?
- .modal-body
- %p= s_('Environments|Are you sure you want to stop this environment?')
- - unless @environment.stop_action_available?
- .warning_message
- %p= s_('Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file.').html_safe % { emphasis_start: '<strong>'.html_safe,
- emphasis_end: '</strong>'.html_safe,
- ci_config_link_start: '<a href="https://docs.gitlab.com/ee/ci/yaml/" target="_blank" rel="noopener noreferrer">'.html_safe,
- ci_config_link_end: '</a>'.html_safe }
- %a{ href: 'https://docs.gitlab.com/ee/ci/environments.html#stopping-an-environment',
- target: '_blank',
- rel: 'noopener noreferrer' }
- = s_('Environments|Learn more about stopping environments')
- .modal-footer
- = button_tag _('Cancel'), type: 'button', class: 'btn btn-cancel', data: { dismiss: 'modal' }
- = button_to stop_project_environment_path(@project, @environment), class: 'btn btn-danger has-tooltip', method: :post do
- = s_('Environments|Stop environment')
+- if can?(current_user, :stop_environment, @environment)
+ #stop-environment-modal.modal.fade{ tabindex: -1 }
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %h4.modal-title.d-flex.mw-100
+ = s_("Environments|Stopping")
+ %span.has-tooltip.text-truncate.ml-1.mr-1.flex-fill{ title: @environment.name, data: { container: '#stop-environment-modal' } }
+ = @environment.name
+ ?
+ .modal-body
+ %p= s_('Environments|Are you sure you want to stop this environment?')
+ - unless @environment.stop_action_available?
+ .warning_message
+ %p= s_('Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file.').html_safe % { emphasis_start: '<strong>'.html_safe,
+ emphasis_end: '</strong>'.html_safe,
+ ci_config_link_start: '<a href="https://docs.gitlab.com/ee/ci/yaml/" target="_blank" rel="noopener noreferrer">'.html_safe,
+ ci_config_link_end: '</a>'.html_safe }
+ %a{ href: 'https://docs.gitlab.com/ee/ci/environments.html#stopping-an-environment',
+ target: '_blank',
+ rel: 'noopener noreferrer' }
+ = s_('Environments|Learn more about stopping environments')
+ .modal-footer
+ = button_tag _('Cancel'), type: 'button', class: 'btn btn-cancel', data: { dismiss: 'modal' }
+ = button_to stop_project_environment_path(@project, @environment), class: 'btn btn-danger has-tooltip', method: :post do
+ = s_('Environments|Stop environment')
- .top-area
- %h3.page-title= @environment.name
- .nav-controls.ml-auto.my-2
- = render 'projects/environments/terminal_button', environment: @environment
- = render 'projects/environments/external_url', environment: @environment
- = render 'projects/environments/metrics_button', environment: @environment
- - if can?(current_user, :update_environment, @environment)
- = link_to _('Edit'), edit_project_environment_path(@project, @environment), class: 'btn'
- - if can?(current_user, :stop_environment, @environment)
- = button_tag class: 'btn btn-danger', type: 'button', data: { toggle: 'modal',
- target: '#stop-environment-modal' } do
- = sprite_icon('stop')
- = s_('Environments|Stop')
+.top-area
+ %h3.page-title= @environment.name
+ .nav-controls.ml-auto.my-2
+ = render 'projects/environments/terminal_button', environment: @environment
+ = render 'projects/environments/external_url', environment: @environment
+ = render 'projects/environments/metrics_button', environment: @environment
+ - if can?(current_user, :update_environment, @environment)
+ = link_to _('Edit'), edit_project_environment_path(@project, @environment), class: 'btn'
+ - if can?(current_user, :stop_environment, @environment)
+ = button_tag class: 'btn btn-danger', type: 'button', data: { toggle: 'modal',
+ target: '#stop-environment-modal' } do
+ = sprite_icon('stop')
+ = s_('Environments|Stop')
- .environments-container
- - if @deployments.blank?
- .empty-state
- .text-content
- %h4.state-title
- = _("You don't have any deployments right now.")
- %p.blank-state-text
- = _("Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here.").html_safe
- .text-center
- = link_to _("Read more"), help_page_path("ci/environments"), class: "btn btn-success"
- - else
- .table-holder
- .ci-table.environments{ role: 'grid' }
- .gl-responsive-table-row.table-row-header{ role: 'row' }
- .table-section.section-10{ role: 'columnheader' }= _('ID')
- .table-section.section-30{ role: 'columnheader' }= _('Commit')
- .table-section.section-25{ role: 'columnheader' }= _('Job')
- .table-section.section-15{ role: 'columnheader' }= _('Created')
+.environments-container
+ - if @deployments.blank?
+ .empty-state
+ .text-content
+ %h4.state-title
+ = _("You don't have any deployments right now.")
+ %p.blank-state-text
+ = _("Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here.").html_safe
+ .text-center
+ = link_to _("Read more"), help_page_path("ci/environments"), class: "btn btn-success"
+ - else
+ .table-holder
+ .ci-table.environments{ role: 'grid' }
+ .gl-responsive-table-row.table-row-header{ role: 'row' }
+ .table-section.section-10{ role: 'columnheader' }= _('ID')
+ .table-section.section-30{ role: 'columnheader' }= _('Commit')
+ .table-section.section-25{ role: 'columnheader' }= _('Job')
+ .table-section.section-15{ role: 'columnheader' }= _('Created')
- = render @deployments
+ = render @deployments
- = paginate @deployments, theme: 'gitlab'
+ = paginate @deployments, theme: 'gitlab'
diff --git a/app/views/projects/environments/terminal.html.haml b/app/views/projects/environments/terminal.html.haml
index e837d3d56ac..3a705d736f3 100644
--- a/app/views/projects/environments/terminal.html.haml
+++ b/app/views/projects/environments/terminal.html.haml
@@ -1,23 +1,21 @@
-- @no_container = true
- page_title _("Terminal for environment"), @environment.name
- content_for :page_specific_javascripts do
= stylesheet_link_tag "xterm.css"
-%div{ class: container_class }
- .top-area
- .row
- .col-sm-6
- %h3.page-title
- = _("Terminal for environment")
- = @environment.name
+.top-area
+ .row
+ .col-sm-6
+ %h3.page-title
+ = _("Terminal for environment")
+ = @environment.name
- .col-sm-6
- .nav-controls
- - if @environment.external_url.present?
- = link_to @environment.external_url, class: 'btn btn-default', target: '_blank', rel: 'noopener noreferrer nofollow' do
- = sprite_icon('external-link')
- = render 'projects/deployments/actions', deployment: @environment.last_deployment
+ .col-sm-6
+ .nav-controls
+ - if @environment.external_url.present?
+ = link_to @environment.external_url, class: 'btn btn-default', target: '_blank', rel: 'noopener noreferrer nofollow' do
+ = sprite_icon('external-link')
+ = render 'projects/deployments/actions', deployment: @environment.last_deployment
.terminal-container{ class: container_class }
#terminal{ data: { project_path: "#{terminal_project_environment_path(@project, @environment)}.ws" } }
diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml
index 60160f521ad..2a2ccf8a6de 100644
--- a/app/views/projects/graphs/charts.html.haml
+++ b/app/views/projects/graphs/charts.html.haml
@@ -1,7 +1,6 @@
-- @no_container = true
- page_title _("Contribution Charts")
-.repo-charts{ class: container_class }
+.repo-charts
%h4.sub-header
= _("Programming languages used in this repository")
@@ -20,7 +19,7 @@
.col-md-8
%canvas#languages-chart{ height: 400 }
-.repo-charts{ class: container_class }
+.repo-charts
.sub-header-block.border-top
.row.tree-ref-header
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index 4b2417ff43b..6e5e4607232 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -1,7 +1,6 @@
-- @no_container = true
- page_title _('Contributors')
-.js-graphs-show{ class: container_class, 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json) }
+.js-graphs-show{ 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json) }
.sub-header-block
.tree-ref-holder.inline.vertical-align-middle
= render 'shared/ref_switcher', destination: 'graphs'
diff --git a/app/views/projects/imports/show.html.haml b/app/views/projects/imports/show.html.haml
index 422a3a22f87..87b027a1802 100644
--- a/app/views/projects/imports/show.html.haml
+++ b/app/views/projects/imports/show.html.haml
@@ -1,5 +1,4 @@
- page_title import_in_progress_title
-- @no_container = true
- @content_class = "limit-container-width" unless fluid_layout
.save-project-loader
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 39e9e9171cf..49e482ff1df 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -1,4 +1,3 @@
-- @no_container = true
- @can_bulk_update = can?(current_user, :admin_issue, @project)
- page_title "Issues"
@@ -8,18 +7,17 @@
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues")
- if project_issues(@project).exists?
- %div{ class: (container_class) }
- .top-area
- = render 'shared/issuable/nav', type: :issues
- = render "projects/issues/nav_btns"
- = render 'shared/issuable/search_bar', type: :issues
+ .top-area
+ = render 'shared/issuable/nav', type: :issues
+ = render "projects/issues/nav_btns"
+ = render 'shared/issuable/search_bar', type: :issues
- - if @can_bulk_update
- = render 'shared/issuable/bulk_update_sidebar', type: :issues
+ - if @can_bulk_update
+ = render 'shared/issuable/bulk_update_sidebar', type: :issues
- .issues-holder
- = render 'issues'
- - if new_issue_email
- = render 'projects/issuable_by_email', email: new_issue_email, issuable_type: 'issue'
+ .issues-holder
+ = render 'issues'
+ - if new_issue_email
+ = render 'projects/issuable_by_email', email: new_issue_email, issuable_type: 'issue'
- else
= render 'shared/empty_states/issues', button_path: new_project_issue_path(@project), show_import_button: true
diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml
index afea5268006..5acb2af08e4 100644
--- a/app/views/projects/jobs/index.html.haml
+++ b/app/views/projects/jobs/index.html.haml
@@ -1,18 +1,16 @@
-- @no_container = true
- page_title "Jobs"
-%div{ class: container_class }
- .top-area
- - build_path_proc = ->(scope) { project_jobs_path(@project, scope: scope) }
- = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope
+.top-area
+ - build_path_proc = ->(scope) { project_jobs_path(@project, scope: scope) }
+ = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope
- .nav-controls
- - if can?(current_user, :update_build, @project)
- - unless @repository.gitlab_ci_yml
- = link_to 'Get started with Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
+ .nav-controls
+ - if can?(current_user, :update_build, @project)
+ - unless @repository.gitlab_ci_yml
+ = link_to 'Get started with Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
- = link_to project_ci_lint_path(@project), class: 'btn btn-default' do
- %span CI lint
+ = link_to project_ci_lint_path(@project), class: 'btn btn-default' do
+ %span CI lint
- .content-list.builds-content-list
- = render "table", builds: @builds, project: @project
+.content-list.builds-content-list
+ = render "table", builds: @builds, project: @project
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index a3688c17041..6bb27a65142 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -1,4 +1,3 @@
-- @no_container = true
- add_to_breadcrumbs _("Jobs"), project_jobs_path(@project)
- breadcrumb_title "##{@build.id}"
- page_title "#{@build.name} (##{@build.id})", _("Jobs")
@@ -6,11 +5,10 @@
- content_for :page_specific_javascripts do
= stylesheet_link_tag 'page_bundles/xterm'
-%div{ class: container_class }
- #js-job-vue-app{ data: { endpoint: project_job_path(@project, @build, format: :json), project_path: @project.full_path,
- deployment_help_url: help_page_path('user/project/clusters/index.html', anchor: 'troubleshooting-failed-deployment-jobs'),
- runner_help_url: help_page_path('ci/runners/README.html', anchor: 'setting-maximum-job-timeout-for-a-runner'),
- runner_settings_url: project_runners_path(@build.project, anchor: 'js-runners-settings'),
- variables_settings_url: project_variables_path(@build.project, anchor: 'js-cicd-variables-settings'),
- page_path: project_job_path(@project, @build), build_status: @build.status, build_stage: @build.stage, log_state: '',
- build_options: javascript_build_options } }
+#js-job-vue-app{ data: { endpoint: project_job_path(@project, @build, format: :json), project_path: @project.full_path,
+ deployment_help_url: help_page_path('user/project/clusters/index.html', anchor: 'troubleshooting-failed-deployment-jobs'),
+ runner_help_url: help_page_path('ci/runners/README.html', anchor: 'setting-maximum-job-timeout-for-a-runner'),
+ runner_settings_url: project_runners_path(@build.project, anchor: 'js-runners-settings'),
+ variables_settings_url: project_variables_path(@build.project, anchor: 'js-cicd-variables-settings'),
+ page_path: project_job_path(@project, @build), build_status: @build.status, build_stage: @build.stage, log_state: '',
+ build_options: javascript_build_options } }
diff --git a/app/views/projects/jobs/terminal.html.haml b/app/views/projects/jobs/terminal.html.haml
index f7e7535ee92..5439a4b5d5c 100644
--- a/app/views/projects/jobs/terminal.html.haml
+++ b/app/views/projects/jobs/terminal.html.haml
@@ -1,4 +1,3 @@
-- @no_container = true
- add_to_breadcrumbs 'Jobs', project_jobs_path(@project)
- add_to_breadcrumbs "##{@build.id}", project_job_path(@project, @build)
- breadcrumb_title 'Terminal'
@@ -7,5 +6,5 @@
- content_for :page_specific_javascripts do
= stylesheet_link_tag "xterm.css"
-.terminal-container{ class: container_class }
+.terminal-container
#terminal{ data: { project_path: terminal_project_job_path(@project, @build, format: :ws) } }
diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml
index b9d45e83032..b7996f0dad1 100644
--- a/app/views/projects/labels/edit.html.haml
+++ b/app/views/projects/labels/edit.html.haml
@@ -1,10 +1,8 @@
-- @no_container = true
- add_to_breadcrumbs "Labels", project_labels_path(@project)
- breadcrumb_title "Edit"
- page_title "Edit", @label.name, "Labels"
-%div{ class: container_class }
- %h3.page-title
- Edit Label
- %hr
- = render 'shared/labels/form', url: project_label_path(@project, @label), back_path: project_labels_path(@project)
+%h3.page-title
+ Edit Label
+%hr
+= render 'shared/labels/form', url: project_label_path(@project, @label), back_path: project_labels_path(@project)
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 511d7a82d1b..0328751c68c 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -1,4 +1,3 @@
-- @no_container = true
- page_title "Labels"
- can_admin_label = can?(current_user, :admin_label, @project)
- search = params[:search]
@@ -7,48 +6,47 @@
- if labels_or_filters
#promote-label-modal
- %div{ class: container_class }
- = render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label
+ = render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label
- .labels-container.prepend-top-10
- - if can_admin_label && search.blank?
- %p.text-muted
- = _('Labels can be applied to issues and merge requests.')
- %br
- = _('Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.')
+ .labels-container.prepend-top-10
+ - if can_admin_label && search.blank?
+ %p.text-muted
+ = _('Labels can be applied to issues and merge requests.')
+ %br
+ = _('Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.')
- -# Only show it in the first page
- - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
- .prioritized-labels{ class: [('hide' if hide), ('is-not-draggable' unless can_admin_label)] }
- %h5.prepend-top-10= _('Prioritized Labels')
- .content-list.manage-labels-list.js-prioritized-labels{ data: { url: set_priorities_project_labels_path(@project), sortable: can_admin_label } }
- #js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty? && search.blank?}" }
- = render 'shared/empty_states/priority_labels'
- - if @prioritized_labels.present?
- = render partial: 'shared/label', collection: @prioritized_labels, as: :label, locals: { force_priority: true, subject: @project }
- - elsif search.present?
- .nothing-here-block
- = _('No prioritised labels with such name or description')
+ -# Only show it in the first page
+ - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1')
+ .prioritized-labels{ class: [('hide' if hide), ('is-not-draggable' unless can_admin_label)] }
+ %h5.prepend-top-10= _('Prioritized Labels')
+ .content-list.manage-labels-list.js-prioritized-labels{ data: { url: set_priorities_project_labels_path(@project), sortable: can_admin_label } }
+ #js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty? && search.blank?}" }
+ = render 'shared/empty_states/priority_labels'
+ - if @prioritized_labels.present?
+ = render partial: 'shared/label', collection: @prioritized_labels, as: :label, locals: { force_priority: true, subject: @project }
+ - elsif search.present?
+ .nothing-here-block
+ = _('No prioritised labels with such name or description')
- - if @labels.present?
- .other-labels
- %h5{ class: ('hide' if hide) }= _('Other Labels')
- .content-list.manage-labels-list.js-other-labels
- = render partial: 'shared/label', collection: @labels, as: :label, locals: { subject: @project }
- = paginate @labels, theme: 'gitlab'
- - elsif search.present?
- .other-labels
- - if @available_labels.any?
- %h5
- = _('Other Labels')
- .nothing-here-block
- = _('No other labels with such name or description')
- - else
- .nothing-here-block
- = _('No labels with such name or description')
- - elsif subscribed.present?
- .nothing-here-block
- = _('You do not have any subscriptions yet')
+ - if @labels.present?
+ .other-labels
+ %h5{ class: ('hide' if hide) }= _('Other Labels')
+ .content-list.manage-labels-list.js-other-labels
+ = render partial: 'shared/label', collection: @labels, as: :label, locals: { subject: @project }
+ = paginate @labels, theme: 'gitlab'
+ - elsif search.present?
+ .other-labels
+ - if @available_labels.any?
+ %h5
+ = _('Other Labels')
+ .nothing-here-block
+ = _('No other labels with such name or description')
+ - else
+ .nothing-here-block
+ = _('No labels with such name or description')
+ - elsif subscribed.present?
+ .nothing-here-block
+ = _('You do not have any subscriptions yet')
- else
= render 'shared/empty_states/labels'
diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml
index c6739231e36..96ce0eba2c6 100644
--- a/app/views/projects/labels/new.html.haml
+++ b/app/views/projects/labels/new.html.haml
@@ -1,10 +1,8 @@
-- @no_container = true
- add_to_breadcrumbs "Labels", project_labels_path(@project)
- breadcrumb_title "New"
- page_title "New Label"
-%div{ class: container_class }
- %h3.page-title
- New Label
- %hr
- = render 'shared/labels/form', url: project_labels_path(@project), back_path: project_labels_path(@project)
+%h3.page-title
+ New Label
+%hr
+= render 'shared/labels/form', url: project_labels_path(@project), back_path: project_labels_path(@project)
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 623380c9c61..4e30f09b9a2 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,4 +1,3 @@
-- @no_container = true
- @can_bulk_update = can?(current_user, :admin_merge_request, @project)
- merge_project = merge_request_source_project_for_project(@project)
- new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project
@@ -6,24 +5,22 @@
- page_title "Merge Requests"
- new_merge_request_email = @project.new_issuable_address(current_user, 'merge_request')
-%div{ class: container_class }
- = render 'projects/last_push'
+= render 'projects/last_push'
- if @project.merge_requests.exists?
- %div{ class: container_class }
- .top-area
- = render 'shared/issuable/nav', type: :merge_requests
- .nav-controls
- = render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path
+ .top-area
+ = render 'shared/issuable/nav', type: :merge_requests
+ .nav-controls
+ = render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path
- = render 'shared/issuable/search_bar', type: :merge_requests
+ = render 'shared/issuable/search_bar', type: :merge_requests
- - if @can_bulk_update
- = render 'shared/issuable/bulk_update_sidebar', type: :merge_requests
+ - if @can_bulk_update
+ = render 'shared/issuable/bulk_update_sidebar', type: :merge_requests
- .merge-requests-holder
- = render 'merge_requests'
- - if new_merge_request_email
- = render 'projects/issuable_by_email', email: new_merge_request_email, issuable_type: 'merge_request'
+ .merge-requests-holder
+ = render 'merge_requests'
+ - if new_merge_request_email
+ = render 'projects/issuable_by_email', email: new_merge_request_email, issuable_type: 'merge_request'
- else
= render 'shared/empty_states/merge_requests', button_path: new_merge_request_path
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index af3bd8dcd69..ea166d622eb 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -6,6 +6,7 @@
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
- suggest_changes_help_path = help_page_path('user/discussions/index.md', anchor: 'suggest-changes')
+- number_of_pipelines = @pipelines.size
.merge-request{ data: { mr_action: j(params[:tab].presence || 'show'), url: merge_request_path(@merge_request, format: :json), project_path: project_path(@merge_request.project), lock_version: @merge_request.lock_version } }
= render "projects/merge_requests/mr_title"
@@ -41,11 +42,11 @@
= tab_link_for @merge_request, :commits do
= _("Commits")
%span.badge.badge-pill= @commits_count
- - if @pipelines.any?
+ - if number_of_pipelines.nonzero?
%li.pipelines-tab
= tab_link_for @merge_request, :pipelines do
= _("Pipelines")
- %span.badge.badge-pill.js-pipelines-mr-count= @pipelines.size
+ %span.badge.badge-pill.js-pipelines-mr-count= number_of_pipelines
%li.diffs-tab.qa-diffs-tab
= tab_link_for @merge_request, :diffs do
= _("Changes")
@@ -63,21 +64,21 @@
%script.js-notes-data{ type: "application/json" }= initial_notes_data(true).to_json.html_safe
.issuable-discussion.js-vue-notes-event
#js-vue-mr-discussions{ data: { notes_data: notes_data(@merge_request).to_json,
- noteable_data: serialize_issuable(@merge_request),
+ noteable_data: serialize_issuable(@merge_request, serializer: 'noteable'),
noteable_type: 'MergeRequest',
target_type: 'merge_request',
help_page_path: suggest_changes_help_path,
- current_user_data: UserSerializer.new(project: @project).represent(current_user, {}, MergeRequestUserEntity).to_json} }
+ current_user_data: @current_user_data} }
#commits.commits.tab-pane
-# This tab is always loaded via AJAX
#pipelines.pipelines.tab-pane
- - if @pipelines.any?
+ - if number_of_pipelines.nonzero?
= render 'projects/commit/pipelines_list', disable_initialization: true, endpoint: pipelines_project_merge_request_path(@project, @merge_request)
#js-diffs-app.diffs.tab-pane{ data: { "is-locked" => @merge_request.discussion_locked?,
endpoint: diffs_project_merge_request_path(@project, @merge_request, 'json', request.query_parameters),
help_page_path: suggest_changes_help_path,
- current_user_data: UserSerializer.new(project: @project).represent(current_user, {}, MergeRequestUserEntity).to_json,
+ current_user_data: @current_user_data,
project_path: project_path(@merge_request.project),
changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg'),
is_fluid_layout: fluid_layout.to_s,
diff --git a/app/views/projects/milestones/_deprecation_message.html.haml b/app/views/projects/milestones/_deprecation_message.html.haml
deleted file mode 100644
index b2cca3690d6..00000000000
--- a/app/views/projects/milestones/_deprecation_message.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-.banner-callout.compact.milestone-deprecation-message.prepend-top-20
- .banner-graphic= image_tag 'illustrations/milestone_removing-page.svg'
- .banner-body.prepend-left-10.append-right-10
- %h5.banner-title.prepend-top-0
- = _('The tabs below will be removed in a future version')
- %p.milestone-banner-text
- = _('Learn more about %{issue_boards_url}, to keep track of issues in multiple lists, using labels, assignees, and milestones. If you’re missing something from issue boards, please create an issue on %{gitlab_issues_url}.').html_safe % { issue_boards_url: link_to(_('issue boards'), help_page_url('user/project/issue_board'), target: '_blank', rel: 'noopener noreferrer'), gitlab_issues_url: link_to(_('GitLab’s issue tracker'), 'https://gitlab.com/gitlab-org/gitlab-ce/issues', target: '_blank', rel: 'noopener noreferrer') }
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 5cc6b5a173b..e1797e6db2a 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -21,7 +21,7 @@
.form-actions
- if @milestone.new_record?
- = f.submit _('Create milestone'), class: 'btn-create btn qa-milestone-create-button'
+ = f.submit _('Create milestone'), class: 'btn-success btn qa-milestone-create-button'
= link_to _('Cancel'), project_milestones_path(@project), class: 'btn btn-cancel'
- else
= f.submit _('Save changes'), class: 'btn-success btn'
diff --git a/app/views/projects/milestones/edit.html.haml b/app/views/projects/milestones/edit.html.haml
index aa564e00af9..0d040a5cdb3 100644
--- a/app/views/projects/milestones/edit.html.haml
+++ b/app/views/projects/milestones/edit.html.haml
@@ -1,14 +1,10 @@
-- @no_container = true
- breadcrumb_title _('Edit')
- add_to_breadcrumbs _('Milestones'), project_milestones_path(@project)
- page_title _('Edit'), @milestone.title, _('Milestones')
+%h3.page-title
+ = _('Edit Milestone')
-%div{ class: container_class }
+%hr
- %h3.page-title
- = _('Edit Milestone')
-
- %hr
-
- = render 'form'
+= render 'form'
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index a3414c16d73..c89566dac90 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -1,26 +1,24 @@
-- @no_container = true
- page_title _('Milestones')
-%div{ class: container_class }
- .top-area
- = render 'shared/milestones_filter', counts: milestone_counts(@project.milestones)
+.top-area
+ = render 'shared/milestones_filter', counts: milestone_counts(@project.milestones)
- .nav-controls
- = render 'shared/milestones/search_form'
- = render 'shared/milestones_sort_dropdown'
- - if can?(current_user, :admin_milestone, @project)
- = link_to new_project_milestone_path(@project), class: 'btn btn-success qa-new-project-milestone', title: _('New milestone') do
- = _('New milestone')
+ .nav-controls
+ = render 'shared/milestones/search_form'
+ = render 'shared/milestones_sort_dropdown'
+ - if can?(current_user, :admin_milestone, @project)
+ = link_to new_project_milestone_path(@project), class: 'btn btn-success qa-new-project-milestone', title: _('New milestone') do
+ = _('New milestone')
- .milestones
- #delete-milestone-modal
- #promote-milestone-modal
+.milestones
+ #delete-milestone-modal
+ #promote-milestone-modal
- %ul.content-list
- = render @milestones
+ %ul.content-list
+ = render @milestones
- - if @milestones.blank?
- %li
- .nothing-here-block= _('No milestones to show')
+ - if @milestones.blank?
+ %li
+ .nothing-here-block= _('No milestones to show')
- = paginate @milestones, theme: 'gitlab'
+ = paginate @milestones, theme: 'gitlab'
diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml
index 79207fd70b5..721506a2201 100644
--- a/app/views/projects/milestones/new.html.haml
+++ b/app/views/projects/milestones/new.html.haml
@@ -1,12 +1,10 @@
-- @no_container = true
- add_to_breadcrumbs _('Milestones'), project_milestones_path(@project)
- breadcrumb_title _('New')
- page_title _('New Milestone')
-%div{ class: container_class }
- %h3.page-title
- = _('New Milestone')
+%h3.page-title
+ = _('New Milestone')
- %hr
+%hr
- = render 'form'
+= render 'form'
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 1cee8be604a..49d3039d0c9 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -1,71 +1,68 @@
-- @no_container = true
- add_to_breadcrumbs _('Milestones'), project_milestones_path(@project)
- breadcrumb_title @milestone.title
- page_title @milestone.title, _('Milestones')
- page_description @milestone.description
-%div{ class: container_class }
- .detail-page-header.milestone-page-header
- .status-box{ class: status_box_class(@milestone) }
- - if @milestone.closed?
- = _('Closed')
- - elsif @milestone.expired?
- = _('Past due')
- - elsif @milestone.upcoming?
- = _('Upcoming')
- - else
- = _('Open')
- .header-text-content
- %span.identifier
- %strong
- = _('Milestone')
- - if @milestone.due_date || @milestone.start_date
- = milestone_date_range(@milestone)
- .milestone-buttons
- - if can?(current_user, :admin_milestone, @project)
- = link_to edit_project_milestone_path(@project, @milestone), class: 'btn btn-grouped btn-nr' do
- = _('Edit')
+.detail-page-header.milestone-page-header
+ .status-box{ class: status_box_class(@milestone) }
+ - if @milestone.closed?
+ = _('Closed')
+ - elsif @milestone.expired?
+ = _('Past due')
+ - elsif @milestone.upcoming?
+ = _('Upcoming')
+ - else
+ = _('Open')
+ .header-text-content
+ %span.identifier
+ %strong
+ = _('Milestone')
+ - if @milestone.due_date || @milestone.start_date
+ = milestone_date_range(@milestone)
+ .milestone-buttons
+ - if can?(current_user, :admin_milestone, @project)
+ = link_to edit_project_milestone_path(@project, @milestone), class: 'btn btn-grouped btn-nr' do
+ = _('Edit')
- - if @project.group
- %button.js-promote-project-milestone-button.btn.btn-grouped{ data: { toggle: 'modal',
- target: '#promote-milestone-modal',
- milestone_title: @milestone.title,
- group_name: @project.group.name,
- url: promote_project_milestone_path(@milestone.project, @milestone),
- container: 'body' },
- disabled: true,
- type: 'button' }
- = _('Promote')
- #promote-milestone-modal
+ - if @project.group
+ %button.js-promote-project-milestone-button.btn.btn-grouped{ data: { toggle: 'modal',
+ target: '#promote-milestone-modal',
+ milestone_title: @milestone.title,
+ group_name: @project.group.name,
+ url: promote_project_milestone_path(@milestone.project, @milestone),
+ container: 'body' },
+ disabled: true,
+ type: 'button' }
+ = _('Promote')
+ #promote-milestone-modal
- - if @milestone.active?
- = link_to _('Close milestone'), project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: 'btn btn-close btn-nr btn-grouped'
- - else
- = link_to _('Reopen milestone'), project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: 'btn btn-reopen btn-nr btn-grouped'
+ - if @milestone.active?
+ = link_to _('Close milestone'), project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: 'btn btn-close btn-nr btn-grouped'
+ - else
+ = link_to _('Reopen milestone'), project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: 'btn btn-reopen btn-nr btn-grouped'
- = render 'shared/milestones/delete_button'
+ = render 'shared/milestones/delete_button'
- %a.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ href: '#' }
- = icon('angle-double-left')
+ %a.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ href: '#' }
+ = icon('angle-double-left')
- .detail-page-description.milestone-detail
- %h2.title.qa-milestone-title
- = markdown_field(@milestone, :title)
+.detail-page-description.milestone-detail
+ %h2.title.qa-milestone-title
+ = markdown_field(@milestone, :title)
- %div
- - if @milestone.description.present?
- .description.md
- = markdown_field(@milestone, :description)
+ %div
+ - if @milestone.description.present?
+ .description.md
+ = markdown_field(@milestone, :description)
- = render_if_exists 'shared/milestones/burndown', milestone: @milestone, project: @project
+= render_if_exists 'shared/milestones/burndown', milestone: @milestone, project: @project
- - if can?(current_user, :read_issue, @project) && @milestone.total_issues_count(current_user).zero?
- .alert.alert-success.prepend-top-default
- %span= _('Assign some issues to this milestone.')
- - elsif @milestone.complete?(current_user) && @milestone.active?
- .alert.alert-success.prepend-top-default
- %span= _('All issues for this milestone are closed. You may close this milestone now.')
+- if can?(current_user, :read_issue, @project) && @milestone.total_issues_count(current_user).zero?
+ .alert.alert-success.prepend-top-default
+ %span= _('Assign some issues to this milestone.')
+- elsif @milestone.complete?(current_user) && @milestone.active?
+ .alert.alert-success.prepend-top-default
+ %span= _('All issues for this milestone are closed. You may close this milestone now.')
- = render 'deprecation_message'
- = render 'shared/milestones/tabs', milestone: @milestone
- = render 'shared/milestones/sidebar', milestone: @milestone, project: @project, affix_offset: 153
+= render 'shared/milestones/tabs', milestone: @milestone
+= render 'shared/milestones/sidebar', milestone: @milestone, project: @project, affix_offset: 153
diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml
index 6f8a93fbcf5..84f0900d9c1 100644
--- a/app/views/projects/mirrors/_mirror_repos.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos.html.haml
@@ -50,7 +50,7 @@
- @project.remote_mirrors.each_with_index do |mirror, index|
- next if mirror.new_record?
%tr.qa-mirrored-repository-row.rspec-mirrored-repository-row{ class: ('bg-secondary' if mirror.disabled?) }
- %td.qa-mirror-repository-url= mirror.safe_url
+ %td.qa-mirror-repository-url= mirror.safe_url || _('Invalid URL')
%td= _('Push')
%td
= mirror.last_update_started_at.present? ? time_ago_with_tooltip(mirror.last_update_started_at) : _('Never')
diff --git a/app/views/projects/network/_head.html.haml b/app/views/projects/network/_head.html.haml
index f08526f485e..701cb37a1c8 100644
--- a/app/views/projects/network/_head.html.haml
+++ b/app/views/projects/network/_head.html.haml
@@ -1,9 +1,6 @@
-- @no_container = true
+.row-content-block.second-block.content-component-block
+ .tree-ref-holder
+ = render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
-%div{ class: container_class }
- .row-content-block.second-block.content-component-block
- .tree-ref-holder
- = render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
-
- .oneline
- = _("You can move around the graph by using the arrow keys.")
+ .oneline
+ = _("You can move around the graph by using the arrow keys.")
diff --git a/app/views/projects/pages/_access.html.haml b/app/views/projects/pages/_access.html.haml
index 73ea30e1d3d..539f223ca9b 100644
--- a/app/views/projects/pages/_access.html.haml
+++ b/app/views/projects/pages/_access.html.haml
@@ -5,9 +5,11 @@
.card-body
%p
%strong
- Congratulations! Your pages are served under:
+ = _("Your pages are served under:")
%p= link_to @project.pages_url, @project.pages_url
- @project.pages_domains.each do |domain|
%p= link_to domain.url, domain.url
+ .card-footer.alert-primary
+ = _("It may take up to 30 minutes before the site is available after the first deployment.")
diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml
index 0580c15ad15..4a0be9e67cb 100644
--- a/app/views/projects/pipeline_schedules/index.html.haml
+++ b/app/views/projects/pipeline_schedules/index.html.haml
@@ -1,22 +1,20 @@
- breadcrumb_title _("Schedules")
-- @no_container = true
- page_title _("Pipeline Schedules")
-%div{ class: container_class }
- #pipeline-schedules-callout{ data: { docs_url: help_page_path('user/project/pipelines/schedules') } }
- .top-area
- - schedule_path_proc = ->(scope) { pipeline_schedules_path(@project, scope: scope) }
- = render "tabs", schedule_path_proc: schedule_path_proc, all_schedules: @all_schedules, scope: @scope
+#pipeline-schedules-callout{ data: { docs_url: help_page_path('user/project/pipelines/schedules') } }
+.top-area
+ - schedule_path_proc = ->(scope) { pipeline_schedules_path(@project, scope: scope) }
+ = render "tabs", schedule_path_proc: schedule_path_proc, all_schedules: @all_schedules, scope: @scope
- - if can?(current_user, :create_pipeline_schedule, @project)
- .nav-controls
- = link_to new_project_pipeline_schedule_path(@project), class: 'btn btn-success' do
- %span= _('New schedule')
+ - if can?(current_user, :create_pipeline_schedule, @project)
+ .nav-controls
+ = link_to new_project_pipeline_schedule_path(@project), class: 'btn btn-success' do
+ %span= _('New schedule')
- - if @schedules.present?
- %ul.content-list
- = render partial: "table"
- - else
- .card.bg-light
- .nothing-here-block= _("No schedules")
+- if @schedules.present?
+ %ul.content-list
+ = render partial: "table"
+- else
+ .card.bg-light
+ .nothing-here-block= _("No schedules")
diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml
index 6b4110e07d2..c9a50b97fea 100644
--- a/app/views/projects/pipelines/charts.html.haml
+++ b/app/views/projects/pipelines/charts.html.haml
@@ -1,10 +1,6 @@
-- @no_container = true
- page_title _('CI / CD Charts')
-%div{ class: container_class }
-
- #charts.ci-charts
- = render 'projects/pipelines/charts/overall'
-
- %hr
- = render 'projects/pipelines/charts/pipelines'
+#charts.ci-charts
+ = render 'projects/pipelines/charts/overall'
+ %hr
+ = render 'projects/pipelines/charts/pipelines'
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index 4e4638085fd..f64f07487fd 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -1,17 +1,15 @@
-- @no_container = true
- page_title _('Pipelines')
= render_if_exists "shared/shared_runners_minutes_limit_flash_message"
-%div{ 'class' => container_class }
- #pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json),
- "help-page-path" => help_page_path('ci/quick_start/README'),
- "help-auto-devops-path" => help_page_path('topics/autodevops/index.md'),
- "empty-state-svg-path" => image_path('illustrations/pipelines_empty.svg'),
- "error-state-svg-path" => image_path('illustrations/pipelines_failed.svg'),
- "no-pipelines-svg-path" => image_path('illustrations/pipelines_pending.svg'),
- "can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s,
- "new-pipeline-path" => can?(current_user, :create_pipeline, @project) && new_project_pipeline_path(@project),
- "ci-lint-path" => can?(current_user, :create_pipeline, @project) && project_ci_lint_path(@project),
- "reset-cache-path" => can?(current_user, :admin_pipeline, @project) && reset_cache_project_settings_ci_cd_path(@project) ,
- "has-gitlab-ci" => (@project.has_ci? && @project.builds_enabled?).to_s } }
+#pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json),
+ "help-page-path" => help_page_path('ci/quick_start/README'),
+ "help-auto-devops-path" => help_page_path('topics/autodevops/index.md'),
+ "empty-state-svg-path" => image_path('illustrations/pipelines_empty.svg'),
+ "error-state-svg-path" => image_path('illustrations/pipelines_failed.svg'),
+ "no-pipelines-svg-path" => image_path('illustrations/pipelines_pending.svg'),
+ "can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s,
+ "new-pipeline-path" => can?(current_user, :create_pipeline, @project) && new_project_pipeline_path(@project),
+ "ci-lint-path" => can?(current_user, :create_pipeline, @project) && project_ci_lint_path(@project),
+ "reset-cache-path" => can?(current_user, :admin_pipeline, @project) && reset_cache_project_settings_ci_cd_path(@project) ,
+ "has-gitlab-ci" => (@project.has_ci? && @project.builds_enabled?).to_s } }
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index 8a6d7b082e3..2b2133b8296 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -1,9 +1,8 @@
-- @no_container = true
- add_to_breadcrumbs _('Pipelines'), project_pipelines_path(@project)
- breadcrumb_title "##{@pipeline.id}"
- page_title _('Pipeline')
-.js-pipeline-container{ class: container_class, data: { controller_action: "#{controller.action_name}" } }
+.js-pipeline-container{ data: { controller_action: "#{controller.action_name}" } }
#js-pipeline-header-vue.pipeline-header-container
- if @pipeline.commit.present?
diff --git a/app/views/projects/project_templates/_built_in_templates.html.haml b/app/views/projects/project_templates/_built_in_templates.html.haml
index d1c09e83fd3..a583eb39eb3 100644
--- a/app/views/projects/project_templates/_built_in_templates.html.haml
+++ b/app/views/projects/project_templates/_built_in_templates.html.haml
@@ -1,5 +1,5 @@
- Gitlab::ProjectTemplate.all.each do |template|
- .template-option.d-flex.align-items-center
+ .template-option.d-flex.align-items-center{ data: { qa_selector: 'template_option_row' } }
.logo.append-right-10.px-1
= image_tag template.logo, size: 32, class: "btn-template-icon icon-#{template.name}"
.description
@@ -13,5 +13,5 @@
= _("Preview")
%label.btn.btn-success.template-button.choose-template.append-bottom-0{ for: template.name }
%input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name, data: { track_label: "template_use", track_property: template.name, track_event: "click_button", track_value: "" } }
- %span
+ %span{ data: { qa_selector: 'use_template_button' } }
= _("Use template")
diff --git a/app/views/projects/releases/index.html.haml b/app/views/projects/releases/index.html.haml
index 28bb4e032eb..326b83c856e 100644
--- a/app/views/projects/releases/index.html.haml
+++ b/app/views/projects/releases/index.html.haml
@@ -1,5 +1,3 @@
-- @no_container = true
- page_title _('Releases')
-%div{ class: container_class }
- #js-releases-page{ data: { project_id: @project.id, illustration_path: image_path('illustrations/releases.svg'), documentation_path: help_page_path('user/project/releases/index') } }
+#js-releases-page{ data: { project_id: @project.id, illustration_path: image_path('illustrations/releases.svg'), documentation_path: help_page_path('user/project/releases/index') } }
diff --git a/app/views/projects/serverless/functions/index.html.haml b/app/views/projects/serverless/functions/index.html.haml
index bac6c76684b..09f4e556949 100644
--- a/app/views/projects/serverless/functions/index.html.haml
+++ b/app/views/projects/serverless/functions/index.html.haml
@@ -1,4 +1,3 @@
-- @no_container = true
- @content_class = "limit-container-width" unless fluid_layout
- breadcrumb_title 'Serverless'
- page_title 'Serverless'
@@ -10,7 +9,7 @@
clusters_path: clusters_path,
help_path: help_page_path('user/project/clusters/serverless/index') } }
-%div{ class: [container_class, ('limit-container-width' unless fluid_layout)] }
+%div{ class: [('limit-container-width' unless fluid_layout)] }
.js-serverless-functions-notice
.flash-container
diff --git a/app/views/projects/serverless/functions/show.html.haml b/app/views/projects/serverless/functions/show.html.haml
index d1fe208ce60..79bb943d6ed 100644
--- a/app/views/projects/serverless/functions/show.html.haml
+++ b/app/views/projects/serverless/functions/show.html.haml
@@ -1,4 +1,3 @@
-- @no_container = true
- @content_class = "limit-container-width" unless fluid_layout
- clusters_path = project_clusters_path(@project)
- help_path = help_page_path('user/project/clusters/serverless/index')
@@ -12,7 +11,7 @@
clusters_path: clusters_path,
help_path: help_path } }
-%div{ class: [container_class, ('limit-container-width' unless fluid_layout)] }
+%div{ class: [('limit-container-width' unless fluid_layout)] }
.serverless-function-details#js-serverless-function-details
.js-serverless-function-notice
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index de1b95692d6..2f277e8147a 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -15,7 +15,7 @@
.footer-block.row-content-block
= service_save_button(@service)
&nbsp;
- = link_to 'Cancel', project_settings_integrations_path(@project), class: 'btn btn-cancel'
+ = link_to _('Cancel'), project_settings_integrations_path(@project), class: 'btn btn-cancel'
- if lookup_context.template_exists?('show', "projects/services/#{@service.to_param}", true)
%hr
diff --git a/app/views/projects/services/_index.html.haml b/app/views/projects/services/_index.html.haml
index 16e48814578..7748a7a6a8e 100644
--- a/app/views/projects/services/_index.html.haml
+++ b/app/views/projects/services/_index.html.haml
@@ -1,8 +1,8 @@
.row.prepend-top-default.append-bottom-default
.col-lg-4
%h4.prepend-top-0
- Project services
- %p Project services allow you to integrate GitLab with other applications
+ = s_("ProjectService|Project services")
+ %p= s_("ProjectService|Project services allow you to integrate GitLab with other applications")
.col-lg-8
%table.table
%colgroup
@@ -13,12 +13,12 @@
%thead
%tr
%th
- %th Service
- %th.d-none.d-sm-block Description
- %th Last edit
+ %th= s_("ProjectService|Service")
+ %th.d-none.d-sm-block= _("Description")
+ %th= s_("ProjectService|Last edit")
- @services.sort_by(&:title).each do |service|
%tr
- %td{ "aria-label" => "#{service.title}: status " + (service.activated? ? "on" : "off") }
+ %td{ "aria-label" => (service.activated? ? s_("ProjectService|%{service_title}: status on") : s_("ProjectService|%{service_title}: status off")) % { service_title: service.title } }
= boolean_to_icon service.activated?
%td
= link_to edit_project_service_path(@project, service.to_param) do
diff --git a/app/views/projects/services/edit.html.haml b/app/views/projects/services/edit.html.haml
index df1fd583670..fc20bc52d1c 100644
--- a/app/views/projects/services/edit.html.haml
+++ b/app/views/projects/services/edit.html.haml
@@ -1,6 +1,6 @@
-- breadcrumb_title "Integrations"
-- page_title @service.title, "Services"
-- add_to_breadcrumbs("Settings", edit_project_path(@project))
+- breadcrumb_title s_("ProjectService|Integrations")
+- page_title @service.title, s_("ProjectService|Services")
+- add_to_breadcrumbs(s_("ProjectService|Settings"), edit_project_path(@project))
= render 'deprecated_message' if @service.deprecation_message
diff --git a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
index 82c1d57c97e..395df502ddb 100644
--- a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
+++ b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
@@ -1,6 +1,6 @@
-- run_actions_text = "Perform common operations on GitLab project: #{@project.full_name}"
+- run_actions_text = s_("ProjectService|Perform common operations on GitLab project: %{project_name}") % { project_name: @project.full_name }
-%p To set up this service:
+%p= s_("ProjectService|To set up this service:")
%ul.list-unstyled.indent-list
%li
1.
@@ -18,67 +18,67 @@
.help-form
.form-group
- = label_tag :display_name, 'Display name', class: 'col-12 col-form-label label-bold'
+ = label_tag :display_name, _('Display name'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :display_name, "GitLab / #{@project.full_name}", class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#display_name', class: 'input-group-text')
.form-group
- = label_tag :description, 'Description', class: 'col-12 col-form-label label-bold'
+ = label_tag :description, _('Description'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :description, run_actions_text, class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#description', class: 'input-group-text')
.form-group
- = label_tag nil, 'Command trigger word', class: 'col-12 col-form-label label-bold'
+ = label_tag nil, s_('MattermostService|Command trigger word'), class: 'col-12 col-form-label label-bold'
.col-12
- %p Fill in the word that works best for your team.
+ %p= s_('MattermostService|Fill in the word that works best for your team.')
%p
- Suggestions:
+ = s_('MattermostService|Suggestions:')
%code= 'gitlab'
%code= @project.path # Path contains no spaces, but dashes
%code= @project.full_path
.form-group
- = label_tag :request_url, 'Request URL', class: 'col-12 col-form-label label-bold'
+ = label_tag :request_url, s_('MattermostService|Request URL'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :request_url, service_trigger_url(subject), class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#request_url', class: 'input-group-text')
.form-group
- = label_tag nil, 'Request method', class: 'col-12 col-form-label label-bold'
+ = label_tag nil, s_('MattermostService|Request method'), class: 'col-12 col-form-label label-bold'
.col-12 POST
.form-group
- = label_tag :response_username, 'Response username', class: 'col-12 col-form-label label-bold'
+ = label_tag :response_username, s_('MattermostService|Response username'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :response_username, 'GitLab', class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#response_username', class: 'input-group-text')
.form-group
- = label_tag :response_icon, 'Response icon', class: 'col-12 col-form-label label-bold'
+ = label_tag :response_icon, s_('MattermostService|Response icon'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :response_icon, asset_url('gitlab_logo.png'), class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#response_icon', class: 'input-group-text')
.form-group
- = label_tag nil, 'Autocomplete', class: 'col-12 col-form-label label-bold'
+ = label_tag nil, _('Autocomplete'), class: 'col-12 col-form-label label-bold'
.col-12 Yes
.form-group
- = label_tag :autocomplete_hint, 'Autocomplete hint', class: 'col-12 col-12 col-form-label label-bold'
+ = label_tag :autocomplete_hint, _('Autocomplete hint'), class: 'col-12 col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :autocomplete_hint, '[help]', class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#autocomplete_hint', class: 'input-group-text')
.form-group
- = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-12 col-form-label label-bold'
+ = label_tag :autocomplete_description, _('Autocomplete description'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :autocomplete_description, run_actions_text, class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
diff --git a/app/views/projects/services/mattermost_slash_commands/_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_help.html.haml
index f51dd581d29..cc005dd69b7 100644
--- a/app/views/projects/services/mattermost_slash_commands/_help.html.haml
+++ b/app/views/projects/services/mattermost_slash_commands/_help.html.haml
@@ -3,14 +3,12 @@
.info-well
.well-segment
%p
- This service allows users to perform common operations on this
- project by entering slash commands in Mattermost.
+ = s_("MattermostService|This service allows users to perform common operations on this project by entering slash commands in Mattermost.")
= link_to help_page_path('user/project/integrations/mattermost_slash_commands.md'), target: '_blank' do
- View documentation
+ = _("View documentation")
= sprite_icon('external-link', size: 16)
%p.inline
- See list of available commands in Mattermost after setting up this service,
- by entering
+ = s_("MattermostService|See list of available commands in Mattermost after setting up this service, by entering")
%kbd.inline /&lt;trigger&gt; help
- unless enabled || @service.template?
= render 'projects/services/mattermost_slash_commands/detailed_help', subject: @service
diff --git a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml b/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml
index 2da8e5428ec..aee81ea744a 100644
--- a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml
+++ b/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml
@@ -4,4 +4,4 @@
.col-sm-9.offset-sm-3
= link_to new_project_mattermost_path(@project), class: 'btn btn-lg' do
= custom_icon('mattermost_logo', size: 15)
- Add to Mattermost
+ = s_("MattermostService|Add to Mattermost")
diff --git a/app/views/projects/services/slack_slash_commands/_help.html.haml b/app/views/projects/services/slack_slash_commands/_help.html.haml
index 9b7732abc62..7f6717e298c 100644
--- a/app/views/projects/services/slack_slash_commands/_help.html.haml
+++ b/app/views/projects/services/slack_slash_commands/_help.html.haml
@@ -4,17 +4,15 @@
.info-well
.well-segment
%p
- This service allows users to perform common operations on this
- project by entering slash commands in Slack.
+ = s_("SlackService|This service allows users to perform common operations on this project by entering slash commands in Slack.")
= link_to help_page_path('user/project/integrations/slack_slash_commands.md'), target: '_blank' do
- View documentation
+ = _("View documentation")
= sprite_icon('external-link', size: 16)
%p.inline
- See list of available commands in Slack after setting up this service,
- by entering
+ = s_("SlackService|See list of available commands in Slack after setting up this service, by entering")
%kbd.inline /&lt;command&gt; help
- unless @service.template?
- %p To set up this service:
+ %p= _("To set up this service:")
%ul.list-unstyled.indent-list
%li
1.
@@ -27,11 +25,11 @@
.help-form
.form-group
- = label_tag nil, 'Command', class: 'col-12 col-form-label label-bold'
+ = label_tag nil, _('Command'), class: 'col-12 col-form-label label-bold'
.col-12
- %p Fill in the word that works best for your team.
+ %p= s_('SlackService|Fill in the word that works best for your team.')
%p
- Suggestions:
+ = _("Suggestions:")
%code= 'gitlab'
%code= @project.path # Path contains no spaces, but dashes
%code= @project.full_path
@@ -44,44 +42,44 @@
= clipboard_button(target: '#url', class: 'input-group-text')
.form-group
- = label_tag nil, 'Method', class: 'col-12 col-form-label label-bold'
+ = label_tag nil, _('Method'), class: 'col-12 col-form-label label-bold'
.col-12 POST
.form-group
- = label_tag :customize_name, 'Customize name', class: 'col-12 col-form-label label-bold'
+ = label_tag :customize_name, _('Customize name'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :customize_name, 'GitLab', class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#customize_name', class: 'input-group-text')
.form-group
- = label_tag nil, 'Customize icon', class: 'col-12 col-form-label label-bold'
+ = label_tag nil, _('Customize icon'), class: 'col-12 col-form-label label-bold'
.col-12
= image_tag(asset_url('slash-command-logo.png'), width: 36, height: 36, class: 'mr-3')
- = link_to('Download image', asset_url('gitlab_logo.png'), class: 'btn btn-sm', target: '_blank', rel: 'noopener noreferrer')
+ = link_to(_('Download image'), asset_url('gitlab_logo.png'), class: 'btn btn-sm', target: '_blank', rel: 'noopener noreferrer')
.form-group
- = label_tag nil, 'Autocomplete', class: 'col-12 col-form-label label-bold'
+ = label_tag nil, _('Autocomplete'), class: 'col-12 col-form-label label-bold'
.col-12 Show this command in the autocomplete list
.form-group
- = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-12 col-form-label label-bold'
+ = label_tag :autocomplete_description, _('Autocomplete description'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :autocomplete_description, run_actions_text, class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#autocomplete_description', class: 'input-group-text')
.form-group
- = label_tag :autocomplete_usage_hint, 'Autocomplete usage hint', class: 'col-12 col-form-label label-bold'
+ = label_tag :autocomplete_usage_hint, _('Autocomplete usage hint'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
= text_field_tag :autocomplete_usage_hint, '[help]', class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#autocomplete_usage_hint', class: 'input-group-text')
.form-group
- = label_tag :descriptive_label, 'Descriptive label', class: 'col-12 col-form-label label-bold'
+ = label_tag :descriptive_label, _('Descriptive label'), class: 'col-12 col-form-label label-bold'
.col-12.input-group
- = text_field_tag :descriptive_label, 'Perform common operations on GitLab project', class: 'form-control form-control-sm', readonly: 'readonly'
+ = text_field_tag :descriptive_label, _('Perform common operations on GitLab project'), class: 'form-control form-control-sm', readonly: 'readonly'
.input-group-append
= clipboard_button(target: '#descriptive_label', class: 'input-group-text')
@@ -89,12 +87,6 @@
%ul.list-unstyled.indent-list
%li
- 2. Paste the
- %strong Token
- into the field below
+ = s_("SlackService|2. Paste the <strong>Token</strong> into the field below").html_safe
%li
- 3. Select the
- %strong Active
- checkbox, press
- %strong Save changes
- and start using GitLab inside Slack!
+ = s_("SlackService|3. Select the <strong>Active</strong> checkbox, press <strong>Save changes</strong> and start using GitLab inside Slack!").html_safe
diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
index 04b77fb987a..1d5d90593ae 100644
--- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
@@ -1,3 +1,14 @@
+- has_base_domain = @project.all_clusters.any? { |cluster| cluster.base_domain && !cluster.base_domain.empty? }
+
+- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe
+- link_end = '</a>'.html_safe
+
+- kubernetes_cluster_path = help_page_path('user/project/clusters/index')
+- kubernetes_cluster_link_start = link_start % { url: kubernetes_cluster_path }
+
+- base_domain_path = help_page_path('user/project/clusters/index', anchor: 'base-domain')
+- base_domain_link_start = link_start % { url: base_domain_path }
+
.row
.col-lg-12
= form_for @project, url: project_settings_ci_cd_path(@project, anchor: 'autodevops-settings') do |f|
@@ -19,9 +30,10 @@
.card-footer.js-extra-settings{ class: auto_devops_enabled || 'hidden' }
- if @project.all_clusters.empty?
%p.settings-message.text-center
- - kubernetes_cluster_link = help_page_path('user/project/clusters/index')
- - kubernetes_cluster_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: kubernetes_cluster_link }
- = s_('CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly.').html_safe % { kubernetes_cluster_start: kubernetes_cluster_start, kubernetes_cluster_end: '</a>'.html_safe }
+ = s_('CICD|You must add a %{kubernetes_cluster_link_start}Kubernetes cluster integration%{link_end} to this project with a domain in order for your deployment strategy to work correctly.').html_safe % { kubernetes_cluster_link_start: kubernetes_cluster_link_start, link_end: link_end }
+ - elsif !has_base_domain
+ %p.settings-message.text-center
+ = s_('CICD|You must add a %{base_domain_link_start}base domain%{link_end} to your %{kubernetes_cluster_link_start}Kubernetes cluster%{link_end} in order for your deployment strategy to work.').html_safe % { base_domain_link_start: base_domain_link_start, kubernetes_cluster_link_start: kubernetes_cluster_link_start, link_end: link_end }
%label.prepend-top-10
%strong= s_('CICD|Deployment strategy')
.form-check
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index 498a9744783..430d6071468 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -14,14 +14,14 @@
= f.label :build_allow_git_fetch_false, class: 'form-check-label' do
%strong git clone
%br
- %span.descr
+ %span
= _("Slower but makes sure the project workspace is pristine as it clones the repository from scratch for every job")
.form-check
= f.radio_button :build_allow_git_fetch, 'true', { class: 'form-check-input' }
= f.label :build_allow_git_fetch_true, class: 'form-check-label' do
%strong git fetch
%br
- %span.descr
+ %span
= _("Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)")
%hr
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index c87a084740b..b58af545439 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,4 +1,3 @@
-- @no_container = true
- breadcrumb_title _("Details")
- @content_class = "limit-container-width" unless fluid_layout
@@ -11,7 +10,7 @@
- signatures_path = project_signatures_path(@project, @project.default_branch)
.js-signature-container{ data: { 'signatures-path': signatures_path } }
-%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
+%div{ class: [("limit-container-width" unless fluid_layout)] }
= render "projects/last_push"
= render "home_panel"
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 1f0de1e2603..6ad7cf1848f 100644
--- a/app/views/projects/tags/index.html.haml
+++ b/app/views/projects/tags/index.html.haml
@@ -1,10 +1,9 @@
-- @no_container = true
- @sort ||= sort_value_recently_updated
- page_title s_('TagsPage|Tags')
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, project_tags_url(@project, rss_url_options), title: "#{@project.name} tags")
-.flex-list{ class: container_class }
+.flex-list
.top-area.adjust
.nav-text.row-main-content
= s_('TagsPage|Tags give the ability to mark specific points in history as being important')
diff --git a/app/views/projects/tags/releases/edit.html.haml b/app/views/projects/tags/releases/edit.html.haml
index e4efeed04f0..40d886ff1af 100644
--- a/app/views/projects/tags/releases/edit.html.haml
+++ b/app/views/projects/tags/releases/edit.html.haml
@@ -1,22 +1,19 @@
-- @no_container = true
- add_to_breadcrumbs "Tags", project_tags_path(@project)
- breadcrumb_title @tag.name
- page_title "Edit", @tag.name, "Tags"
-%div{ class: container_class }
- .sub-header-block.no-bottom-space
- .oneline
- .title
- Release notes for tag
- %strong= @tag.name
+.sub-header-block.no-bottom-space
+ .oneline
+ .title
+ Release notes for tag
+ %strong= @tag.name
-
- = form_for(@release, method: :put, url: project_tag_release_path(@project, @tag.name),
- html: { class: 'common-note-form release-form js-quick-submit' }) do |f|
- = render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do
- = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here…"
- = render 'shared/notes/hints'
- .error-alert
- .prepend-top-default
- = f.submit 'Save changes', class: 'btn btn-success'
- = link_to "Cancel", project_tag_path(@project, @tag.name), class: "btn btn-default btn-cancel"
+= form_for(@release, method: :put, url: project_tag_release_path(@project, @tag.name),
+ html: { class: 'common-note-form release-form js-quick-submit' }) do |f|
+ = render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do
+ = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here…"
+ = render 'shared/notes/hints'
+ .error-alert
+ .prepend-top-default
+ = f.submit 'Save changes', class: 'btn btn-success'
+ = link_to "Cancel", project_tag_path(@project, @tag.name), class: "btn btn-default btn-cancel"
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 78cce58938e..417cd7a8fee 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -1,45 +1,43 @@
-- @no_container = true
- add_to_breadcrumbs s_('TagsPage|Tags'), project_tags_path(@project)
- breadcrumb_title @tag.name
- page_title @tag.name, s_('TagsPage|Tags')
-%div{ class: container_class }
- .top-area.multi-line.flex-wrap
- .nav-text
- .title
- %span.item-title.ref-name
- = icon('tag')
- = @tag.name
- - if protected_tag?(@project, @tag)
- %span.badge.badge-success
- = s_('TagsPage|protected')
- - if @commit
- = render 'projects/branches/commit', commit: @commit, project: @project
- - else
- = s_("TagsPage|Can't find HEAD commit for this tag")
+.top-area.multi-line.flex-wrap
+ .nav-text
+ .title
+ %span.item-title.ref-name
+ = icon('tag')
+ = @tag.name
+ - if protected_tag?(@project, @tag)
+ %span.badge.badge-success
+ = s_('TagsPage|protected')
+ - if @commit
+ = render 'projects/branches/commit', commit: @commit, project: @project
+ - else
+ = s_("TagsPage|Can't find HEAD commit for this tag")
- .nav-controls
- - if can?(current_user, :admin_tag, @project)
- = link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn btn-edit controls-item has-tooltip', title: s_('TagsPage|Edit release notes') do
- = icon("pencil")
- = link_to project_tree_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Browse files') do
- = sprite_icon('folder-open')
- = link_to project_commits_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Browse commits') do
- = icon('history')
- .btn-container.controls-item
- = render 'projects/buttons/download', project: @project, ref: @tag.name
- - if can?(current_user, :admin_tag, @project)
- .btn-container.controls-item-full
- = link_to project_tag_path(@project, @tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, @tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: @tag.name } } do
- %i.fa.fa-trash-o
+ .nav-controls
+ - if can?(current_user, :admin_tag, @project)
+ = link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn btn-edit controls-item has-tooltip', title: s_('TagsPage|Edit release notes') do
+ = icon("pencil")
+ = link_to project_tree_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Browse files') do
+ = sprite_icon('folder-open')
+ = link_to project_commits_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Browse commits') do
+ = icon('history')
+ .btn-container.controls-item
+ = render 'projects/buttons/download', project: @project, ref: @tag.name
+ - if can?(current_user, :admin_tag, @project)
+ .btn-container.controls-item-full
+ = link_to project_tag_path(@project, @tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, @tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: @tag.name } } do
+ %i.fa.fa-trash-o
- - if @tag.message.present?
- %pre.wrap
- = strip_gpg_signature(@tag.message)
+ - if @tag.message.present?
+ %pre.wrap
+ = strip_gpg_signature(@tag.message)
- .append-bottom-default.prepend-top-default
- - if @release.description.present?
- .description.md
- = markdown_field(@release, :description)
- - else
- = s_('TagsPage|This tag has no release notes.')
+.append-bottom-default.prepend-top-default
+ - if @release.description.present?
+ .description.md
+ = markdown_field(@release, :description)
+ - else
+ = s_('TagsPage|This tag has no release notes.')
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 9d2aee7a8bd..39b29a20df6 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -1,4 +1,3 @@
-- @no_container = true
- breadcrumb_title _("Repository")
- @content_class = "limit-container-width" unless fluid_layout
- signatures_path = namespace_project_signatures_path(namespace_id: @project.namespace.full_path, project_id: @project.path, id: @last_commit)
@@ -9,6 +8,5 @@
.js-signature-container{ data: { 'signatures-path': signatures_path } }
-%div{ class: [(container_class), ("limit-container-width" unless fluid_layout)] }
- = render 'projects/last_push'
- = render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id)
+= render 'projects/last_push'
+= render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id)
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index 275dc5dbd23..d9dcd8f9acd 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -1,34 +1,32 @@
-- @no_container = true
- add_to_breadcrumbs "Wiki", project_wiki_path(@project, :home)
- breadcrumb_title s_("Wiki|Pages")
- page_title s_("Wiki|Pages"), _("Wiki")
- sort_title = wiki_sort_title(params[:sort])
-%div{ class: container_class }
- .wiki-page-header.top-area.flex-column.flex-lg-row
+.wiki-page-header.top-area.flex-column.flex-lg-row
- .nav-text.flex-fill
- %h2.wiki-page-title
- = s_("Wiki|Wiki Pages")
+ .nav-text.flex-fill
+ %h2.wiki-page-title
+ = s_("Wiki|Wiki Pages")
- .nav-controls.pb-md-3.pb-lg-0
- = link_to project_wikis_git_access_path(@project), class: 'btn' do
- = icon('cloud-download')
- = _("Clone repository")
+ .nav-controls.pb-md-3.pb-lg-0
+ = link_to project_wikis_git_access_path(@project), class: 'btn' do
+ = icon('cloud-download')
+ = _("Clone repository")
- .dropdown.inline.wiki-sort-dropdown
+ .dropdown.inline.wiki-sort-dropdown
+ .btn-group{ role: 'group' }
.btn-group{ role: 'group' }
- .btn-group{ role: 'group' }
- %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' }
- = sort_title
- = icon('chevron-down')
- %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
- %li
- = sortable_item(s_("Wiki|Title"), project_wikis_pages_path(@project, sort: ProjectWiki::TITLE_ORDER), sort_title)
- = sortable_item(s_("Wiki|Created date"), project_wikis_pages_path(@project, sort: ProjectWiki::CREATED_AT_ORDER), sort_title)
- = wiki_sort_controls(@project, params[:sort], params[:direction])
+ %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' }
+ = sort_title
+ = icon('chevron-down')
+ %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
+ %li
+ = sortable_item(s_("Wiki|Title"), project_wikis_pages_path(@project, sort: ProjectWiki::TITLE_ORDER), sort_title)
+ = sortable_item(s_("Wiki|Created date"), project_wikis_pages_path(@project, sort: ProjectWiki::CREATED_AT_ORDER), sort_title)
+ = wiki_sort_controls(@project, params[:sort], params[:direction])
- %ul.wiki-pages-list.content-list
- = render @wiki_entries, context: 'pages'
+%ul.wiki-pages-list.content-list
+ = render @wiki_entries, context: 'pages'
- = paginate @wiki_pages, theme: 'gitlab'
+= paginate @wiki_pages, theme: 'gitlab'
diff --git a/app/views/shared/empty_states/_priority_labels.html.haml b/app/views/shared/empty_states/_priority_labels.html.haml
index bba3475d244..a93f6e4c795 100644
--- a/app/views/shared/empty_states/_priority_labels.html.haml
+++ b/app/views/shared/empty_states/_priority_labels.html.haml
@@ -1,4 +1,6 @@
.text-center
.svg-content.qa-label-svg
= image_tag 'illustrations/priority_labels.svg'
- %p Star labels to start sorting by priority
+ - if can?(current_user, :admin_label, @project)
+ %p
+ = _("Star labels to start sorting by priority")
diff --git a/app/views/shared/empty_states/_profile_tabs.html.haml b/app/views/shared/empty_states/_profile_tabs.html.haml
index 6da40e1b059..98a5a5953d0 100644
--- a/app/views/shared/empty_states/_profile_tabs.html.haml
+++ b/app/views/shared/empty_states/_profile_tabs.html.haml
@@ -12,7 +12,7 @@
%p= current_user_empty_message_description
- if secondary_button_link.present?
- = link_to secondary_button_label, secondary_button_link, class: 'btn btn-create btn-inverted'
+ = link_to secondary_button_label, secondary_button_link, class: 'btn btn-success btn-inverted'
= link_to primary_button_label, primary_button_link, class: 'btn btn-success'
- else
diff --git a/app/views/shared/issuable/_close_reopen_button.html.haml b/app/views/shared/issuable/_close_reopen_button.html.haml
index 4f6a71b6071..875cacd1f4f 100644
--- a/app/views/shared/issuable/_close_reopen_button.html.haml
+++ b/app/views/shared/issuable/_close_reopen_button.html.haml
@@ -9,7 +9,7 @@
class: "d-none d-sm-none d-md-block btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)}", title: "Close #{display_issuable_type}"
- if can_reopen
= link_to "Reopen #{display_issuable_type}", reopen_issuable_path(issuable), method: button_method,
- class: "d-none d-sm-none d-md-block btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}"
+ class: "d-none d-sm-none d-md-block btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}", data: { qa_selector: 'reopen_issue_button' }
- else
- if can_update && !are_close_and_open_buttons_hidden
= render 'shared/issuable/close_reopen_report_toggle', issuable: issuable
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 214e87052da..04a70e406ca 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -66,7 +66,7 @@
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel'
- else
- if can?(current_user, :"destroy_#{issuable.to_ability_name}", @project)
- = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.human_class_name} will be removed! Are you sure?" }, method: :delete, class: 'btn btn-danger btn-grouped'
+ = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable], params: { destroy_confirm: true }), data: { confirm: "#{issuable.human_class_name} will be removed! Are you sure?" }, method: :delete, class: 'btn btn-danger btn-grouped'
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel'
%span.append-right-10
diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml
index 71123740ee4..93408e0bfc0 100644
--- a/app/views/shared/issuable/_nav.html.haml
+++ b/app/views/shared/issuable/_nav.html.haml
@@ -17,7 +17,7 @@
#{issuables_state_counter_text(type, :closed, display_count)}
- else
%li{ class: active_when(params[:state] == 'closed') }>
- = link_to page_filter_path(state: 'closed'), id: 'state-closed', title: 'Filter by issues that are currently closed.', data: { state: 'closed' } do
+ = link_to page_filter_path(state: 'closed'), id: 'state-closed', title: 'Filter by issues that are currently closed.', data: { state: 'closed', qa_selector: 'closed_issues_link' } do
#{issuables_state_counter_text(type, :closed, display_count)}
= render 'shared/issuable/nav_links/all', page_context_word: page_context_word, counter: issuables_state_counter_text(type, :all, display_count)
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 73bee7c2586..e1c75d5d0f4 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -1,9 +1,9 @@
- @hide_top_links = true
- @hide_breadcrumbs = true
+- @no_container = true
- page_title @user.name
- page_description @user.bio
- header_title @user.name, user_path(@user)
-- @no_container = true
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
diff --git a/bin/rspec-stackprof b/bin/rspec-stackprof
index 810863ea4a0..8058d165196 100755
--- a/bin/rspec-stackprof
+++ b/bin/rspec-stackprof
@@ -3,7 +3,7 @@
require 'bundler/setup'
require 'stackprof'
$:.unshift 'spec'
-require 'rails_helper'
+require 'spec_helper'
filename = ARGV[0].split('/').last
interval = ENV.fetch('INTERVAL', 1000).to_i
diff --git a/changelogs/unreleased/28643-access-request-emails-limit-to-ten-owners.yml b/changelogs/unreleased/28643-access-request-emails-limit-to-ten-owners.yml
new file mode 100644
index 00000000000..25e83eaf68e
--- /dev/null
+++ b/changelogs/unreleased/28643-access-request-emails-limit-to-ten-owners.yml
@@ -0,0 +1,5 @@
+---
+title: Limit access request emails to ten most recently active owners or maintainers
+merge_request: 32141
+author:
+type: changed
diff --git a/changelogs/unreleased/36383-improve-search-result-labels.yml b/changelogs/unreleased/36383-improve-search-result-labels.yml
new file mode 100644
index 00000000000..b17c173af7a
--- /dev/null
+++ b/changelogs/unreleased/36383-improve-search-result-labels.yml
@@ -0,0 +1,5 @@
+---
+title: Improve search result labels
+merge_request: 32101
+author:
+type: changed
diff --git a/changelogs/unreleased/36765-flash-notification.yml b/changelogs/unreleased/36765-flash-notification.yml
new file mode 100644
index 00000000000..3229cf6235a
--- /dev/null
+++ b/changelogs/unreleased/36765-flash-notification.yml
@@ -0,0 +1,5 @@
+---
+title: Make flash notifications sticky
+merge_request: 30141
+author:
+type: changed
diff --git a/changelogs/unreleased/40096-allow-ci-token-to-delete-from-registry.yml b/changelogs/unreleased/40096-allow-ci-token-to-delete-from-registry.yml
new file mode 100644
index 00000000000..3e5de08f6ae
--- /dev/null
+++ b/changelogs/unreleased/40096-allow-ci-token-to-delete-from-registry.yml
@@ -0,0 +1,5 @@
+---
+title: Allow $CI_REGISTRY_USER to delete tags
+merge_request: 31796
+author:
+type: added
diff --git a/changelogs/unreleased/51372-remove-milestone-tabs-deprecation-message.yml b/changelogs/unreleased/51372-remove-milestone-tabs-deprecation-message.yml
new file mode 100644
index 00000000000..5f05b150b93
--- /dev/null
+++ b/changelogs/unreleased/51372-remove-milestone-tabs-deprecation-message.yml
@@ -0,0 +1,5 @@
+---
+title: Remove deprecation message for milestone tabs
+merge_request: 32252
+author:
+type: other
diff --git a/changelogs/unreleased/56130-operations-environments-shows-incorrect-deployment-date-for-manual-.yml b/changelogs/unreleased/56130-operations-environments-shows-incorrect-deployment-date-for-manual-.yml
new file mode 100644
index 00000000000..92f25ac07e2
--- /dev/null
+++ b/changelogs/unreleased/56130-operations-environments-shows-incorrect-deployment-date-for-manual-.yml
@@ -0,0 +1,6 @@
+---
+title: Update the timestamp in Operations > Environments to show correct deployment
+ date for manual deploy jobs
+merge_request: 32072
+author:
+type: fixed
diff --git a/changelogs/unreleased/56295-some-avatars-not-visible-in-commit-trailers.yml b/changelogs/unreleased/56295-some-avatars-not-visible-in-commit-trailers.yml
new file mode 100644
index 00000000000..23450c0fdc3
--- /dev/null
+++ b/changelogs/unreleased/56295-some-avatars-not-visible-in-commit-trailers.yml
@@ -0,0 +1,5 @@
+---
+title: Fix for missing avatar images dislpayed in commit trailers.
+merge_request: 32374
+author: Jesse Hall @jessehall3
+type: fixed
diff --git a/changelogs/unreleased/60561-quick-action-label-first-for-issue.yml b/changelogs/unreleased/60561-quick-action-label-first-for-issue.yml
new file mode 100644
index 00000000000..c4bcb2b05f3
--- /dev/null
+++ b/changelogs/unreleased/60561-quick-action-label-first-for-issue.yml
@@ -0,0 +1,5 @@
+---
+title: Quick action label must be first in issue comment
+merge_request: 32367
+author: Romain Maneschi
+type: fixed
diff --git a/changelogs/unreleased/62055-find-file-links-encoding.yml b/changelogs/unreleased/62055-find-file-links-encoding.yml
new file mode 100644
index 00000000000..20a359a9d64
--- /dev/null
+++ b/changelogs/unreleased/62055-find-file-links-encoding.yml
@@ -0,0 +1,5 @@
+---
+title: Fix encoding of special characters in "Find File"
+merge_request: 31311
+author: Jan Beckmann
+type: fixed
diff --git a/changelogs/unreleased/62284-follow-up-from-resolve-api-to-get-all-project-group-members-returns-duplicates.yml b/changelogs/unreleased/62284-follow-up-from-resolve-api-to-get-all-project-group-members-returns-duplicates.yml
new file mode 100644
index 00000000000..0c73f73c297
--- /dev/null
+++ b/changelogs/unreleased/62284-follow-up-from-resolve-api-to-get-all-project-group-members-returns-duplicates.yml
@@ -0,0 +1,5 @@
+---
+title: Uses projects_authorizations.access_level in MembersFinder
+merge_request: 28887
+author: Jacopo Beschi @jacopo-beschi
+type: fixed
diff --git a/changelogs/unreleased/62402-milestone-release-be.yml b/changelogs/unreleased/62402-milestone-release-be.yml
new file mode 100644
index 00000000000..3b1f6edfe6b
--- /dev/null
+++ b/changelogs/unreleased/62402-milestone-release-be.yml
@@ -0,0 +1,5 @@
+---
+title: Allow milestones to be associated with a release (backend)
+merge_request: 30816
+author:
+type: added
diff --git a/changelogs/unreleased/62591-fix-milestone-due-date-today-wording.yml b/changelogs/unreleased/62591-fix-milestone-due-date-today-wording.yml
new file mode 100644
index 00000000000..532b582f407
--- /dev/null
+++ b/changelogs/unreleased/62591-fix-milestone-due-date-today-wording.yml
@@ -0,0 +1,5 @@
+---
+title: Fix wording on milestone due date when milestone is due today
+merge_request: 32096
+author:
+type: changed
diff --git a/changelogs/unreleased/62673-clean-note-app-tests.yml b/changelogs/unreleased/62673-clean-note-app-tests.yml
new file mode 100644
index 00000000000..abfcf8fe364
--- /dev/null
+++ b/changelogs/unreleased/62673-clean-note-app-tests.yml
@@ -0,0 +1,5 @@
+---
+title: make test of note app with comments disabled dry
+merge_request: 32383
+author: Romain Maneschi
+type: other
diff --git a/changelogs/unreleased/63262-notes-are-persisted-with-the-user-s-locale.yml b/changelogs/unreleased/63262-notes-are-persisted-with-the-user-s-locale.yml
new file mode 100644
index 00000000000..e55beb9db09
--- /dev/null
+++ b/changelogs/unreleased/63262-notes-are-persisted-with-the-user-s-locale.yml
@@ -0,0 +1,5 @@
+---
+title: Do not translate system notes into author's language
+merge_request: 32264
+author:
+type: fixed
diff --git a/changelogs/unreleased/64251-branch-name-set-cache.yml b/changelogs/unreleased/64251-branch-name-set-cache.yml
deleted file mode 100644
index 6ce4bdf5e43..00000000000
--- a/changelogs/unreleased/64251-branch-name-set-cache.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Cache branch and tag names as Redis sets
-merge_request: 30476
-author:
-type: performance
diff --git a/changelogs/unreleased/65063-Embeded-metrics-inconsistent-styles.yml b/changelogs/unreleased/65063-Embeded-metrics-inconsistent-styles.yml
new file mode 100644
index 00000000000..126cf29afc0
--- /dev/null
+++ b/changelogs/unreleased/65063-Embeded-metrics-inconsistent-styles.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed embeded metrics tooltip inconsistent styling
+merge_request: 31517
+author:
+type: fixed
diff --git a/changelogs/unreleased/65152-unfolded-lines-perf-improvement.yml b/changelogs/unreleased/65152-unfolded-lines-perf-improvement.yml
new file mode 100644
index 00000000000..835ed037b83
--- /dev/null
+++ b/changelogs/unreleased/65152-unfolded-lines-perf-improvement.yml
@@ -0,0 +1,5 @@
+---
+title: Support selective highlighting of lines
+merge_request: 32514
+author:
+type: performance
diff --git a/changelogs/unreleased/65251-default-clusters-namespace_per_environment-column-to-true.yml b/changelogs/unreleased/65251-default-clusters-namespace_per_environment-column-to-true.yml
new file mode 100644
index 00000000000..c0bc20b2485
--- /dev/null
+++ b/changelogs/unreleased/65251-default-clusters-namespace_per_environment-column-to-true.yml
@@ -0,0 +1,5 @@
+---
+title: Default clusters namespace_per_environment column to true
+merge_request: 32139
+author:
+type: other
diff --git a/changelogs/unreleased/65304-add-pages-first-deployment-message.yml b/changelogs/unreleased/65304-add-pages-first-deployment-message.yml
new file mode 100644
index 00000000000..d4c3533acaf
--- /dev/null
+++ b/changelogs/unreleased/65304-add-pages-first-deployment-message.yml
@@ -0,0 +1,5 @@
+---
+title: Add warning about initial deployment delay for GitLab Pages sites
+merge_request: 32122
+author:
+type: added
diff --git a/changelogs/unreleased/65389-wrong-format-on-ms-teams-integration-push-events-with-multi-line-commit-messages.yml b/changelogs/unreleased/65389-wrong-format-on-ms-teams-integration-push-events-with-multi-line-commit-messages.yml
new file mode 100644
index 00000000000..36e1da2c17c
--- /dev/null
+++ b/changelogs/unreleased/65389-wrong-format-on-ms-teams-integration-push-events-with-multi-line-commit-messages.yml
@@ -0,0 +1,5 @@
+---
+title: Wrong format on MS teams integration push events with multi line commit messages
+merge_request: 32180
+author: Massimeddu Cireddu
+type: fixed
diff --git a/changelogs/unreleased/66066-dark-theme-style-for-expansion-on-mr-diffs.yml b/changelogs/unreleased/66066-dark-theme-style-for-expansion-on-mr-diffs.yml
deleted file mode 100644
index 13607ae938a..00000000000
--- a/changelogs/unreleased/66066-dark-theme-style-for-expansion-on-mr-diffs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add syntax highlighting for line expansion
-merge_request: 31821
-author:
-type: fixed
diff --git a/changelogs/unreleased/66264-moved-issue-reference.yml b/changelogs/unreleased/66264-moved-issue-reference.yml
new file mode 100644
index 00000000000..5876e9bbf79
--- /dev/null
+++ b/changelogs/unreleased/66264-moved-issue-reference.yml
@@ -0,0 +1,5 @@
+---
+title: Use moved instead of closed in issue references
+merge_request: 32277
+author: juliette-derancourt
+type: changed
diff --git a/changelogs/unreleased/66454-utils-parser.yml b/changelogs/unreleased/66454-utils-parser.yml
new file mode 100644
index 00000000000..80cd699937f
--- /dev/null
+++ b/changelogs/unreleased/66454-utils-parser.yml
@@ -0,0 +1,5 @@
+---
+title: Creates utility parser for the job log
+merge_request: 32555
+author:
+type: added
diff --git a/changelogs/unreleased/66524-issue-due-notification-emails-are-threaded-incorrectly.yml b/changelogs/unreleased/66524-issue-due-notification-emails-are-threaded-incorrectly.yml
new file mode 100644
index 00000000000..a6a0ba4b4f4
--- /dev/null
+++ b/changelogs/unreleased/66524-issue-due-notification-emails-are-threaded-incorrectly.yml
@@ -0,0 +1,5 @@
+---
+title: Fix issue due notification emails not being threaded correctly
+merge_request: 32325
+author:
+type: fixed
diff --git a/changelogs/unreleased/66715-delete-search-animation.yml b/changelogs/unreleased/66715-delete-search-animation.yml
new file mode 100644
index 00000000000..9cb796e4164
--- /dev/null
+++ b/changelogs/unreleased/66715-delete-search-animation.yml
@@ -0,0 +1,5 @@
+---
+title: delete animation width on global search input
+merge_request: 32399
+author: Romain Maneschi
+type: other
diff --git a/changelogs/unreleased/ab-admin-page-user-active-count.yml b/changelogs/unreleased/ab-admin-page-user-active-count.yml
new file mode 100644
index 00000000000..e08715a1586
--- /dev/null
+++ b/changelogs/unreleased/ab-admin-page-user-active-count.yml
@@ -0,0 +1,5 @@
+---
+title: Replace indexes for counting active users
+merge_request: 32538
+author:
+type: performance
diff --git a/changelogs/unreleased/ab-remove-support-bot-column.yml b/changelogs/unreleased/ab-remove-support-bot-column.yml
new file mode 100644
index 00000000000..d256ded0410
--- /dev/null
+++ b/changelogs/unreleased/ab-remove-support-bot-column.yml
@@ -0,0 +1,5 @@
+---
+title: Remove Users.support_bot column
+merge_request: 32554
+author:
+type: other
diff --git a/changelogs/unreleased/ab-routable-nplus1.yml b/changelogs/unreleased/ab-routable-nplus1.yml
new file mode 100644
index 00000000000..7b59bc78dae
--- /dev/null
+++ b/changelogs/unreleased/ab-routable-nplus1.yml
@@ -0,0 +1,5 @@
+---
+title: Preload routes information to fix N+1 issue
+merge_request: 32352
+author:
+type: performance
diff --git a/changelogs/unreleased/add-notification-reason-to-note-emails.yml b/changelogs/unreleased/add-notification-reason-to-note-emails.yml
new file mode 100644
index 00000000000..c27247b22cb
--- /dev/null
+++ b/changelogs/unreleased/add-notification-reason-to-note-emails.yml
@@ -0,0 +1,5 @@
+---
+title: Add X-GitLab-NotificationReason header to note emails
+merge_request: 32422
+author:
+type: fixed
diff --git a/changelogs/unreleased/add-warning-note-to-project-container-registry-setting.yml b/changelogs/unreleased/add-warning-note-to-project-container-registry-setting.yml
new file mode 100644
index 00000000000..0663788b680
--- /dev/null
+++ b/changelogs/unreleased/add-warning-note-to-project-container-registry-setting.yml
@@ -0,0 +1,6 @@
+---
+title: Added warning note on the project container registry setting informing users
+ that the registry is public for public projects
+merge_request: 32447
+author:
+type: other
diff --git a/changelogs/unreleased/ce-60465-prevent-comments-on-private-mrs.yml b/changelogs/unreleased/ce-60465-prevent-comments-on-private-mrs.yml
new file mode 100644
index 00000000000..ba970162447
--- /dev/null
+++ b/changelogs/unreleased/ce-60465-prevent-comments-on-private-mrs.yml
@@ -0,0 +1,3 @@
+---
+title: Ensure only authorised users can create notes on Merge Requests and Issues
+type: security
diff --git a/changelogs/unreleased/ce-slack-close-command.yml b/changelogs/unreleased/ce-slack-close-command.yml
new file mode 100644
index 00000000000..c20e623b117
--- /dev/null
+++ b/changelogs/unreleased/ce-slack-close-command.yml
@@ -0,0 +1,5 @@
+---
+title: Add a close issue slack slash command
+merge_request: 32150
+author:
+type: added
diff --git a/changelogs/unreleased/ce-xanf-move-auto-merge-failed-to-jest.yml b/changelogs/unreleased/ce-xanf-move-auto-merge-failed-to-jest.yml
new file mode 100644
index 00000000000..5a56a668c54
--- /dev/null
+++ b/changelogs/unreleased/ce-xanf-move-auto-merge-failed-to-jest.yml
@@ -0,0 +1,5 @@
+---
+title: Refactored Karma spec to Jest for mr_widget_auto_merge_failed
+merge_request: 32282
+author: Illya Klymov
+type: other
diff --git a/changelogs/unreleased/cert_manager_v0_9.yml b/changelogs/unreleased/cert_manager_v0_9.yml
new file mode 100644
index 00000000000..bda5bbffab5
--- /dev/null
+++ b/changelogs/unreleased/cert_manager_v0_9.yml
@@ -0,0 +1,5 @@
+---
+title: Install cert-manager v0.9.1
+merge_request: 32243
+author:
+type: changed
diff --git a/changelogs/unreleased/change-prioritized-labels-empty-state-message.yml b/changelogs/unreleased/change-prioritized-labels-empty-state-message.yml
new file mode 100644
index 00000000000..d5df889d15c
--- /dev/null
+++ b/changelogs/unreleased/change-prioritized-labels-empty-state-message.yml
@@ -0,0 +1,5 @@
+---
+title: Change prioritized labels empty state message
+merge_request: 32338
+author: Lee Tickett
+type: other
diff --git a/changelogs/unreleased/change-role-system-hook.yml b/changelogs/unreleased/change-role-system-hook.yml
new file mode 100644
index 00000000000..adc9e43b1f2
--- /dev/null
+++ b/changelogs/unreleased/change-role-system-hook.yml
@@ -0,0 +1,5 @@
+---
+title: Add system hooks for project/group membership updates
+merge_request: 32371
+author: Brandon Williams
+type: added
diff --git a/changelogs/unreleased/cluster-form-ca-cert-larger.yml b/changelogs/unreleased/cluster-form-ca-cert-larger.yml
new file mode 100644
index 00000000000..ed38d003a37
--- /dev/null
+++ b/changelogs/unreleased/cluster-form-ca-cert-larger.yml
@@ -0,0 +1,5 @@
+---
+title: Expand textarea for CA cert in cluster form
+merge_request: 32508
+author:
+type: fixed
diff --git a/changelogs/unreleased/cluster_deployments.yml b/changelogs/unreleased/cluster_deployments.yml
new file mode 100644
index 00000000000..d854d16ea72
--- /dev/null
+++ b/changelogs/unreleased/cluster_deployments.yml
@@ -0,0 +1,5 @@
+---
+title: Add index to improve group cluster deployments query performance
+merge_request: 31988
+author:
+type: other
diff --git a/changelogs/unreleased/fe-fix-issuable-sidebar-icon-of-notification-disabled.yml b/changelogs/unreleased/fe-fix-issuable-sidebar-icon-of-notification-disabled.yml
deleted file mode 100644
index 736e12ff694..00000000000
--- a/changelogs/unreleased/fe-fix-issuable-sidebar-icon-of-notification-disabled.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix issuable sidebar icon on notification disabled
-merge_request: 32134
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-create-milestone-btn-success.yml b/changelogs/unreleased/fix-create-milestone-btn-success.yml
new file mode 100644
index 00000000000..a1b1d305ce3
--- /dev/null
+++ b/changelogs/unreleased/fix-create-milestone-btn-success.yml
@@ -0,0 +1,5 @@
+---
+title: Fix style of secondary profile tab buttons.
+merge_request: 32010
+author: Wolfgang Faust
+type: fixed
diff --git a/changelogs/unreleased/fix-dropdown-closing.yml b/changelogs/unreleased/fix-dropdown-closing.yml
new file mode 100644
index 00000000000..5ce3a6b478e
--- /dev/null
+++ b/changelogs/unreleased/fix-dropdown-closing.yml
@@ -0,0 +1,5 @@
+---
+title: Fix dropdowns closing when click is released outside the dropdown
+merge_request: 32084
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-nil-deployable-exception-on-job-controller-show.yml b/changelogs/unreleased/fix-nil-deployable-exception-on-job-controller-show.yml
new file mode 100644
index 00000000000..b79317e3ab7
--- /dev/null
+++ b/changelogs/unreleased/fix-nil-deployable-exception-on-job-controller-show.yml
@@ -0,0 +1,5 @@
+---
+title: Fix users cannot access job detail page when deployable does not exist
+merge_request: 32247
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-peek-on-puma.yml b/changelogs/unreleased/fix-peek-on-puma.yml
new file mode 100644
index 00000000000..1071607b628
--- /dev/null
+++ b/changelogs/unreleased/fix-peek-on-puma.yml
@@ -0,0 +1,5 @@
+---
+title: Fix performance bar on Puma
+merge_request: 32213
+author:
+type: fixed
diff --git a/changelogs/unreleased/georgekoltsov-13698-override-params.yml b/changelogs/unreleased/georgekoltsov-13698-override-params.yml
new file mode 100644
index 00000000000..1bbde160e18
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-13698-override-params.yml
@@ -0,0 +1,5 @@
+---
+title: Allow project feature permissions to be overridden during import with override_params
+merge_request: 32348
+author:
+type: fixed
diff --git a/changelogs/unreleased/handle-invalid-mirror-url.yml b/changelogs/unreleased/handle-invalid-mirror-url.yml
new file mode 100644
index 00000000000..c72707a2cad
--- /dev/null
+++ b/changelogs/unreleased/handle-invalid-mirror-url.yml
@@ -0,0 +1,5 @@
+---
+title: Handle invalid mirror url
+merge_request: 32353
+author: Lee Tickett
+type: fixed
diff --git a/changelogs/unreleased/id-optimize-sql-requests-mr-show.yml b/changelogs/unreleased/id-optimize-sql-requests-mr-show.yml
new file mode 100644
index 00000000000..8b171a96316
--- /dev/null
+++ b/changelogs/unreleased/id-optimize-sql-requests-mr-show.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce the number of SQL requests on MR-show
+merge_request: 32192
+author:
+type: performance
diff --git a/changelogs/unreleased/improve-chatops-help.yml b/changelogs/unreleased/improve-chatops-help.yml
new file mode 100644
index 00000000000..77e6f2e5308
--- /dev/null
+++ b/changelogs/unreleased/improve-chatops-help.yml
@@ -0,0 +1,5 @@
+---
+title: Improve chatops help output
+merge_request: 32208
+author:
+type: changed
diff --git a/changelogs/unreleased/issue_10770.yml b/changelogs/unreleased/issue_10770.yml
new file mode 100644
index 00000000000..728160b24ad
--- /dev/null
+++ b/changelogs/unreleased/issue_10770.yml
@@ -0,0 +1,5 @@
+---
+title: Rename epic column state to state_id
+merge_request: 32270
+author:
+type: changed
diff --git a/changelogs/unreleased/issue_40630.yml b/changelogs/unreleased/issue_40630.yml
new file mode 100644
index 00000000000..61cc773d5a9
--- /dev/null
+++ b/changelogs/unreleased/issue_40630.yml
@@ -0,0 +1,5 @@
+---
+title: Save collapsed option for board lists in database
+merge_request: 31069
+author:
+type: added
diff --git a/changelogs/unreleased/je-add-cluster-domain-warning.yml b/changelogs/unreleased/je-add-cluster-domain-warning.yml
new file mode 100644
index 00000000000..e7d244f730f
--- /dev/null
+++ b/changelogs/unreleased/je-add-cluster-domain-warning.yml
@@ -0,0 +1,5 @@
+---
+title: Add cluster domain warning
+merge_request: 32260
+author:
+type: changed
diff --git a/changelogs/unreleased/new-project-milestone-primary-button.yml b/changelogs/unreleased/new-project-milestone-primary-button.yml
new file mode 100644
index 00000000000..ac0305a2e21
--- /dev/null
+++ b/changelogs/unreleased/new-project-milestone-primary-button.yml
@@ -0,0 +1,5 @@
+---
+title: New project milestone primary button
+merge_request: 32355
+author: Lee Tickett
+type: fixed
diff --git a/changelogs/unreleased/refactor-showStagedIcon.yml b/changelogs/unreleased/refactor-showStagedIcon.yml
new file mode 100644
index 00000000000..94c58c4dc45
--- /dev/null
+++ b/changelogs/unreleased/refactor-showStagedIcon.yml
@@ -0,0 +1,5 @@
+---
+title: Refactor showStagedIcon property to reflect the behavior its name represents.
+merge_request: 32333
+author: Arun Kumar Mohan
+type: other
diff --git a/changelogs/unreleased/remove-vue-resource-from-group-service.yml b/changelogs/unreleased/remove-vue-resource-from-group-service.yml
new file mode 100644
index 00000000000..771d301cabf
--- /dev/null
+++ b/changelogs/unreleased/remove-vue-resource-from-group-service.yml
@@ -0,0 +1,5 @@
+---
+title: Remove vue resource from group service
+merge_request:
+author: Lee Tickett
+type: other
diff --git a/changelogs/unreleased/remove-vue-resource-from-issue.yml b/changelogs/unreleased/remove-vue-resource-from-issue.yml
new file mode 100644
index 00000000000..b2de1b0913a
--- /dev/null
+++ b/changelogs/unreleased/remove-vue-resource-from-issue.yml
@@ -0,0 +1,5 @@
+---
+title: Remove vue resource from issue
+merge_request: 32421
+author: Lee Tickett
+type: other
diff --git a/changelogs/unreleased/remove-vue-resource-from-remove-issue.yml b/changelogs/unreleased/remove-vue-resource-from-remove-issue.yml
new file mode 100644
index 00000000000..5be1c832ac7
--- /dev/null
+++ b/changelogs/unreleased/remove-vue-resource-from-remove-issue.yml
@@ -0,0 +1,5 @@
+---
+title: Remove vue resource from remove issue
+merge_request: 32425
+author: Lee Tickett
+type: other
diff --git a/changelogs/unreleased/runner-chart-repo-use-new-location.yml b/changelogs/unreleased/runner-chart-repo-use-new-location.yml
new file mode 100644
index 00000000000..790e5704c04
--- /dev/null
+++ b/changelogs/unreleased/runner-chart-repo-use-new-location.yml
@@ -0,0 +1,5 @@
+---
+title: Use new location for gitlab-runner helm charts
+merge_request: 32384
+author:
+type: other
diff --git a/changelogs/unreleased/security-59549-add-capcha-for-failed-logins.yml b/changelogs/unreleased/security-59549-add-capcha-for-failed-logins.yml
new file mode 100644
index 00000000000..55f9e36c39c
--- /dev/null
+++ b/changelogs/unreleased/security-59549-add-capcha-for-failed-logins.yml
@@ -0,0 +1,5 @@
+---
+title: Add :login_recaptcha_protection_enabled setting to prevent bots from brute-force attacks.
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-61974-limit-issue-comment-size-2.yml b/changelogs/unreleased/security-61974-limit-issue-comment-size-2.yml
new file mode 100644
index 00000000000..962171dc6f8
--- /dev/null
+++ b/changelogs/unreleased/security-61974-limit-issue-comment-size-2.yml
@@ -0,0 +1,5 @@
+---
+title: Speed up regexp in namespace format by failing fast after reaching maximum namespace depth
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-61974-limit-issue-comment-size.yml b/changelogs/unreleased/security-61974-limit-issue-comment-size.yml
new file mode 100644
index 00000000000..6d5ef057d83
--- /dev/null
+++ b/changelogs/unreleased/security-61974-limit-issue-comment-size.yml
@@ -0,0 +1,5 @@
+---
+title: Limit the size of issuable description and comments
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-64711-fix-commit-todos.yml b/changelogs/unreleased/security-64711-fix-commit-todos.yml
new file mode 100644
index 00000000000..ce4b3cdeeaf
--- /dev/null
+++ b/changelogs/unreleased/security-64711-fix-commit-todos.yml
@@ -0,0 +1,5 @@
+---
+title: Send TODOs for comments on commits correctly
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-ci-metrics-permissions.yml b/changelogs/unreleased/security-ci-metrics-permissions.yml
new file mode 100644
index 00000000000..51c6493442a
--- /dev/null
+++ b/changelogs/unreleased/security-ci-metrics-permissions.yml
@@ -0,0 +1,6 @@
+---
+title: Restrict MergeRequests#test_reports to authenticated users with read-access
+ on Builds
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-enable-image-proxy.yml b/changelogs/unreleased/security-enable-image-proxy.yml
new file mode 100644
index 00000000000..88b49ffd9e8
--- /dev/null
+++ b/changelogs/unreleased/security-enable-image-proxy.yml
@@ -0,0 +1,5 @@
+---
+title: Added image proxy to mitigate potential stealing of IP addresses
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-epic-notes-api-reveals-historical-info-ce-master.yml b/changelogs/unreleased/security-epic-notes-api-reveals-historical-info-ce-master.yml
new file mode 100644
index 00000000000..c639098721e
--- /dev/null
+++ b/changelogs/unreleased/security-epic-notes-api-reveals-historical-info-ce-master.yml
@@ -0,0 +1,5 @@
+---
+title: Filter out old system notes for epics in notes api endpoint response
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-exposed-default-branch.yml b/changelogs/unreleased/security-exposed-default-branch.yml
new file mode 100644
index 00000000000..bf32617ee8a
--- /dev/null
+++ b/changelogs/unreleased/security-exposed-default-branch.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid exposing unaccessible repo data upon GFM post processing
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fix-html-injection-for-label-description-ce-master.yml b/changelogs/unreleased/security-fix-html-injection-for-label-description-ce-master.yml
new file mode 100644
index 00000000000..07124ac399b
--- /dev/null
+++ b/changelogs/unreleased/security-fix-html-injection-for-label-description-ce-master.yml
@@ -0,0 +1,5 @@
+---
+title: Fix HTML injection for label description
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fix-markdown-xss.yml b/changelogs/unreleased/security-fix-markdown-xss.yml
new file mode 100644
index 00000000000..7ef19f13fd5
--- /dev/null
+++ b/changelogs/unreleased/security-fix-markdown-xss.yml
@@ -0,0 +1,5 @@
+---
+title: Make sure HTML text is always escaped when replacing label/milestone references.
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fix_jira_ssrf_vulnerability.yml b/changelogs/unreleased/security-fix_jira_ssrf_vulnerability.yml
new file mode 100644
index 00000000000..25518dd2d05
--- /dev/null
+++ b/changelogs/unreleased/security-fix_jira_ssrf_vulnerability.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent DNS rebind on JIRA service integration
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-gitaly-1-61-0.yml b/changelogs/unreleased/security-gitaly-1-61-0.yml
new file mode 100644
index 00000000000..cbcd5f545a0
--- /dev/null
+++ b/changelogs/unreleased/security-gitaly-1-61-0.yml
@@ -0,0 +1,5 @@
+---
+title: "Gitaly: ignore git redirects"
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-group-runners-permissions.yml b/changelogs/unreleased/security-group-runners-permissions.yml
new file mode 100644
index 00000000000..6c74be30b6d
--- /dev/null
+++ b/changelogs/unreleased/security-group-runners-permissions.yml
@@ -0,0 +1,5 @@
+---
+title: Use admin_group authorization in Groups::RunnersController
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-hide_merge_request_ids_on_emails.yml b/changelogs/unreleased/security-hide_merge_request_ids_on_emails.yml
new file mode 100644
index 00000000000..cd8c9590a70
--- /dev/null
+++ b/changelogs/unreleased/security-hide_merge_request_ids_on_emails.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent disclosure of merge request ID via email
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-id-filter-timeline-activities-for-guests.yml b/changelogs/unreleased/security-id-filter-timeline-activities-for-guests.yml
new file mode 100644
index 00000000000..0fa5f89e2c0
--- /dev/null
+++ b/changelogs/unreleased/security-id-filter-timeline-activities-for-guests.yml
@@ -0,0 +1,5 @@
+---
+title: Show cross-referenced MR-id in issues' activities only to authorized users
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-katex-dos-master.yml b/changelogs/unreleased/security-katex-dos-master.yml
new file mode 100644
index 00000000000..df803a5eafd
--- /dev/null
+++ b/changelogs/unreleased/security-katex-dos-master.yml
@@ -0,0 +1,5 @@
+---
+title: Enforce max chars and max render time in markdown math
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-mr-head-pipeline-leak.yml b/changelogs/unreleased/security-mr-head-pipeline-leak.yml
new file mode 100644
index 00000000000..b15b353ff41
--- /dev/null
+++ b/changelogs/unreleased/security-mr-head-pipeline-leak.yml
@@ -0,0 +1,5 @@
+---
+title: Check permissions before responding in MergeController#pipeline_status
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-personal-snippets.yml b/changelogs/unreleased/security-personal-snippets.yml
new file mode 100644
index 00000000000..95f61993b98
--- /dev/null
+++ b/changelogs/unreleased/security-personal-snippets.yml
@@ -0,0 +1,5 @@
+---
+title: Remove EXIF from users/personal snippet uploads.
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-project-import-bypass.yml b/changelogs/unreleased/security-project-import-bypass.yml
new file mode 100644
index 00000000000..fc7b823509c
--- /dev/null
+++ b/changelogs/unreleased/security-project-import-bypass.yml
@@ -0,0 +1,5 @@
+---
+title: Fix project import restricted visibility bypass via API
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-sarcila-fix-weak-session-management.yml b/changelogs/unreleased/security-sarcila-fix-weak-session-management.yml
new file mode 100644
index 00000000000..a37a3099519
--- /dev/null
+++ b/changelogs/unreleased/security-sarcila-fix-weak-session-management.yml
@@ -0,0 +1,6 @@
+---
+title: Fix weak session management by clearing password reset tokens after login (username/email)
+ are updated
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-ssrf-kubernetes-dns.yml b/changelogs/unreleased/security-ssrf-kubernetes-dns.yml
new file mode 100644
index 00000000000..4d6335e4b08
--- /dev/null
+++ b/changelogs/unreleased/security-ssrf-kubernetes-dns.yml
@@ -0,0 +1,5 @@
+---
+title: Fix SSRF via DNS rebinding in Kubernetes Integration
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/sh-add-delete-confirmation.yml b/changelogs/unreleased/sh-add-delete-confirmation.yml
new file mode 100644
index 00000000000..21c383183e7
--- /dev/null
+++ b/changelogs/unreleased/sh-add-delete-confirmation.yml
@@ -0,0 +1,5 @@
+---
+title: Make it harder to delete issuables accidentally
+merge_request: 32376
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-ci-lint-500-error.yml b/changelogs/unreleased/sh-fix-ci-lint-500-error.yml
new file mode 100644
index 00000000000..74d9f980d46
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-ci-lint-500-error.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 500 error in CI lint when included templates are an array
+merge_request: 32232
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-nplusone-issues.yml b/changelogs/unreleased/sh-fix-nplusone-issues.yml
deleted file mode 100644
index f749b5eeb40..00000000000
--- a/changelogs/unreleased/sh-fix-nplusone-issues.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix N+1 Gitaly calls in /api/v4/projects/:id/issues
-merge_request: 32171
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-fix-piwik-template.yml b/changelogs/unreleased/sh-fix-piwik-template.yml
deleted file mode 100644
index f0baed6a2e0..00000000000
--- a/changelogs/unreleased/sh-fix-piwik-template.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix Piwik not working
-merge_request: 32234
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-support-content-for-snippets-api.yml b/changelogs/unreleased/sh-support-content-for-snippets-api.yml
new file mode 100644
index 00000000000..60a5d98da46
--- /dev/null
+++ b/changelogs/unreleased/sh-support-content-for-snippets-api.yml
@@ -0,0 +1,5 @@
+---
+title: Standardize use of `content` parameter in snippets API
+merge_request: 32296
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-suppress-diverging-count-commits-request.yml b/changelogs/unreleased/sh-suppress-diverging-count-commits-request.yml
new file mode 100644
index 00000000000..68b95ff9318
--- /dev/null
+++ b/changelogs/unreleased/sh-suppress-diverging-count-commits-request.yml
@@ -0,0 +1,5 @@
+---
+title: Skip requesting diverging commit counts if no branches are listed
+merge_request: 32496
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-upgrade-mermaid-8-2-4.yml b/changelogs/unreleased/sh-upgrade-mermaid-8-2-4.yml
deleted file mode 100644
index bdb64e43ecf..00000000000
--- a/changelogs/unreleased/sh-upgrade-mermaid-8-2-4.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade Mermaid to v8.2.4
-merge_request: 32186
-author:
-type: fixed
diff --git a/changelogs/unreleased/swagger-ui-ci-page-template.yml b/changelogs/unreleased/swagger-ui-ci-page-template.yml
new file mode 100644
index 00000000000..6d17baf82c8
--- /dev/null
+++ b/changelogs/unreleased/swagger-ui-ci-page-template.yml
@@ -0,0 +1,5 @@
+---
+title: "Add SwaggerUI Pages template for .gitlab-ci.yml"
+merge_request: 31183
+author: mdhtr
+type: added
diff --git a/changelogs/unreleased/tc-cleanup-issue-created-text-mail.yml b/changelogs/unreleased/tc-cleanup-issue-created-text-mail.yml
new file mode 100644
index 00000000000..cc590ee38eb
--- /dev/null
+++ b/changelogs/unreleased/tc-cleanup-issue-created-text-mail.yml
@@ -0,0 +1,5 @@
+---
+title: Bring text mail for new issue & MR more in line
+merge_request: 32254
+author:
+type: changed
diff --git a/changelogs/unreleased/update-workhorse.yml b/changelogs/unreleased/update-workhorse.yml
new file mode 100644
index 00000000000..e6e5720e9b6
--- /dev/null
+++ b/changelogs/unreleased/update-workhorse.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Workhorse to v8.10.0
+merge_request: 32501
+author:
+type: other
diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml
index 40a80429afa..33c90989548 100644
--- a/config/dependency_decisions.yml
+++ b/config/dependency_decisions.yml
@@ -606,3 +606,10 @@
:why: https://github.com/egonSchiele/contracts.ruby/blob/master/LICENSE
:versions: []
:when: 2019-04-01 11:29:39.361015000 Z
+- - :license
+ - atlassian-jwt
+ - Apache 2.0
+ - :who: Takuya Noguchi
+ :why: https://bitbucket.org/atlassian/atlassian-jwt-ruby/src/master/LICENSE.txt
+ :versions: []
+ :when: 2019-08-30 05:45:35.317663000 Z
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 20b1020e025..6dcaefc05d5 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -431,6 +431,7 @@ production: &base
# key: config/registry.key
# path: shared/registry
# issuer: gitlab-issuer
+ # notification_secret: '' # only set it when you use Geo replication feature without built-in Registry
# Add notification settings if you plan to use Geo Replication for the registry
# notifications:
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 81433b620bc..4160f488a7a 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -515,7 +515,7 @@ Settings['sidekiq']['log_format'] ||= 'default'
Settings['gitlab_shell'] ||= Settingslogic.new({})
Settings.gitlab_shell['path'] = Settings.absolute(Settings.gitlab_shell['path'] || Settings.gitlab['user_home'] + '/gitlab-shell/')
Settings.gitlab_shell['hooks_path'] = :deprecated_use_gitlab_shell_path_instead
-Settings.gitlab_shell['authorized_keys_file'] ||= nil
+Settings.gitlab_shell['authorized_keys_file'] ||= File.join(Dir.home, '.ssh', 'authorized_keys')
Settings.gitlab_shell['secret_file'] ||= Rails.root.join('.gitlab_shell_secret')
Settings.gitlab_shell['receive_pack'] = true if Settings.gitlab_shell['receive_pack'].nil?
Settings.gitlab_shell['upload_pack'] = true if Settings.gitlab_shell['upload_pack'].nil?
diff --git a/config/initializers/asset_proxy_settings.rb b/config/initializers/asset_proxy_settings.rb
new file mode 100644
index 00000000000..92247aba1b8
--- /dev/null
+++ b/config/initializers/asset_proxy_settings.rb
@@ -0,0 +1,6 @@
+#
+# Asset proxy settings
+#
+ActiveSupport.on_load(:active_record) do
+ Banzai::Filter::AssetProxyFilter.initialize_settings
+end
diff --git a/config/initializers/fill_shards.rb b/config/initializers/fill_shards.rb
index 18e067c8854..cad662e12f3 100644
--- a/config/initializers/fill_shards.rb
+++ b/config/initializers/fill_shards.rb
@@ -1,3 +1,5 @@
-if Shard.connected? && !Gitlab::Database.read_only?
+# The `table_exists?` check is needed because during our migration rollback testing,
+# `Shard.connected?` could be cached and return true even though the table doesn't exist
+if Shard.connected? && Shard.table_exists? && !Gitlab::Database.read_only?
Shard.populate!
end
diff --git a/config/initializers/peek.rb b/config/initializers/peek.rb
index f9055285e5c..a3810be70b2 100644
--- a/config/initializers/peek.rb
+++ b/config/initializers/peek.rb
@@ -1,6 +1,7 @@
require 'peek/adapters/redis'
Peek::Adapters::Redis.prepend ::Gitlab::PerformanceBar::RedisAdapterWhenPeekEnabled
+Peek.singleton_class.prepend ::Gitlab::PerformanceBar::WithTopLevelWarnings
Rails.application.config.peek.adapter = :redis, { client: ::Redis.new(Gitlab::Redis::Cache.params) }
diff --git a/config/initializers/rest-client-hostname_override.rb b/config/initializers/rest-client-hostname_override.rb
new file mode 100644
index 00000000000..80b123ebe61
--- /dev/null
+++ b/config/initializers/rest-client-hostname_override.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module RestClient
+ class Request
+ attr_accessor :hostname_override
+
+ module UrlBlocker
+ def transmit(uri, req, payload, &block)
+ begin
+ ip, hostname_override = Gitlab::UrlBlocker.validate!(uri, allow_local_network: allow_settings_local_requests?,
+ allow_localhost: allow_settings_local_requests?,
+ dns_rebind_protection: dns_rebind_protection?)
+
+ self.hostname_override = hostname_override
+ rescue Gitlab::UrlBlocker::BlockedUrlError => e
+ raise ArgumentError, "URL '#{uri}' is blocked: #{e.message}"
+ end
+
+ # Gitlab::UrlBlocker returns a Addressable::URI which we need to coerce
+ # to URI so that rest-client can use it to determine if it's a
+ # URI::HTTPS or not. It uses it to set `net.use_ssl` to true or not:
+ #
+ # https://github.com/rest-client/rest-client/blob/f450a0f086f1cd1049abbef2a2c66166a1a9ba71/lib/restclient/request.rb#L656
+ ip_as_uri = URI.parse(ip)
+ super(ip_as_uri, req, payload, &block)
+ end
+
+ def net_http_object(hostname, port)
+ super.tap do |http|
+ http.hostname_override = hostname_override if hostname_override
+ end
+ end
+
+ private
+
+ def dns_rebind_protection?
+ return false if Gitlab.http_proxy_env?
+
+ Gitlab::CurrentSettings.dns_rebinding_protection_enabled?
+ end
+
+ def allow_settings_local_requests?
+ Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
+ end
+ end
+
+ prepend UrlBlocker
+ end
+end
diff --git a/config/initializers/warden.rb b/config/initializers/warden.rb
index 1d2bb2bce0a..d8a4da8cdf9 100644
--- a/config/initializers/warden.rb
+++ b/config/initializers/warden.rb
@@ -19,6 +19,7 @@ Rails.application.configure do |config|
Warden::Manager.after_authentication(scope: :user) do |user, auth, opts|
ActiveSession.cleanup(user)
+ Gitlab::AnonymousSession.new(auth.request.remote_ip, session_id: auth.request.session.id).cleanup_session_per_ip_entries
end
Warden::Manager.after_set_user(scope: :user, only: :fetch) do |user, auth, opts|
diff --git a/config/routes.rb b/config/routes.rb
index d633228a495..c333550f758 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -110,6 +110,7 @@ Rails.application.routes.draw do
draw :smartcard
draw :jira_connect
draw :username
+ draw :trial_registration
end
Gitlab.ee do
diff --git a/config/routes/uploads.rb b/config/routes/uploads.rb
index 920f8454ce2..096ef146e07 100644
--- a/config/routes/uploads.rb
+++ b/config/routes/uploads.rb
@@ -30,6 +30,10 @@ scope path: :uploads do
to: 'uploads#create',
constraints: { model: /personal_snippet|user/, id: /\d+/ },
as: 'upload'
+
+ post ':model/authorize',
+ to: 'uploads#authorize',
+ constraints: { model: /personal_snippet|user/ }
end
# Redirect old note attachments path to new uploads path.
diff --git a/db/fixtures/development/17_cycle_analytics.rb b/db/fixtures/development/17_cycle_analytics.rb
index 78ceb74da65..9d293f425e6 100644
--- a/db/fixtures/development/17_cycle_analytics.rb
+++ b/db/fixtures/development/17_cycle_analytics.rb
@@ -18,6 +18,7 @@ class Gitlab::Seeder::CycleAnalytics
# Milestones / Labels
Timecop.travel 5.days.from_now
+
if index.even?
issue_metrics.first_associated_with_milestone_at = rand(6..12).hours.from_now
else
@@ -146,7 +147,7 @@ class Gitlab::Seeder::CycleAnalytics
commit_sha = issue.project.repository.create_file(@user, filename, "content", message: "Commit for #{issue.to_reference}", branch_name: branch_name)
issue.project.repository.commit(commit_sha)
- Git::BranchPushService.new(
+ ::Git::BranchPushService.new(
issue.project,
@user,
oldrev: issue.project.repository.commit("master").sha,
@@ -182,7 +183,8 @@ class Gitlab::Seeder::CycleAnalytics
ref: "refs/heads/#{merge_request.source_branch}")
pipeline = service.execute(:push, ignore_skip_ci: true, save_on_errors: false)
- pipeline.builds.map(&:run!)
+ pipeline.builds.each(&:enqueue) # make sure all pipelines in pending state
+ pipeline.builds.each(&:run!)
pipeline.update_status
end
end
diff --git a/db/fixtures/development/98_gitlab_instance_administration_project.rb b/db/fixtures/development/98_gitlab_instance_administration_project.rb
new file mode 100644
index 00000000000..6cf3ae95cee
--- /dev/null
+++ b/db/fixtures/development/98_gitlab_instance_administration_project.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+::Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService.new.execute!
diff --git a/db/fixtures/production/998_gitlab_instance_administration_project.rb b/db/fixtures/production/998_gitlab_instance_administration_project.rb
new file mode 100644
index 00000000000..6cf3ae95cee
--- /dev/null
+++ b/db/fixtures/production/998_gitlab_instance_administration_project.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+::Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService.new.execute!
diff --git a/db/migrate/20190219201635_add_asset_proxy_settings.rb b/db/migrate/20190219201635_add_asset_proxy_settings.rb
new file mode 100644
index 00000000000..9de38cf8a89
--- /dev/null
+++ b/db/migrate/20190219201635_add_asset_proxy_settings.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddAssetProxySettings < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :asset_proxy_enabled, :boolean, default: false, null: false
+ add_column :application_settings, :asset_proxy_url, :string # rubocop:disable Migration/AddLimitToStringColumns
+ add_column :application_settings, :asset_proxy_whitelist, :text
+ add_column :application_settings, :encrypted_asset_proxy_secret_key, :text
+ add_column :application_settings, :encrypted_asset_proxy_secret_key_iv, :string # rubocop:disable Migration/AddLimitToStringColumns
+ end
+end
diff --git a/db/migrate/20190719122333_add_login_recaptcha_protection_enabled_to_application_settings.rb b/db/migrate/20190719122333_add_login_recaptcha_protection_enabled_to_application_settings.rb
new file mode 100644
index 00000000000..4561e1e8aa9
--- /dev/null
+++ b/db/migrate/20190719122333_add_login_recaptcha_protection_enabled_to_application_settings.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddLoginRecaptchaProtectionEnabledToApplicationSettings < ActiveRecord::Migration[5.1]
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :login_recaptcha_protection_enabled, :boolean, default: false, null: false
+ end
+end
diff --git a/db/migrate/20190722144316_create_milestone_releases_table.rb b/db/migrate/20190722144316_create_milestone_releases_table.rb
new file mode 100644
index 00000000000..55878bcec41
--- /dev/null
+++ b/db/migrate/20190722144316_create_milestone_releases_table.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class CreateMilestoneReleasesTable < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ create_table :milestone_releases do |t|
+ t.references :milestone, foreign_key: { on_delete: :cascade }, null: false, index: false
+ t.references :release, foreign_key: { on_delete: :cascade }, null: false
+ end
+
+ add_index :milestone_releases, [:milestone_id, :release_id], unique: true, name: 'index_miletone_releases_on_milestone_and_release'
+ end
+
+ def down
+ drop_table :milestone_releases
+ end
+end
diff --git a/db/migrate/20190816151221_add_active_jobs_limit_to_plans.rb b/db/migrate/20190816151221_add_active_jobs_limit_to_plans.rb
new file mode 100644
index 00000000000..951ff41f1a8
--- /dev/null
+++ b/db/migrate/20190816151221_add_active_jobs_limit_to_plans.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddActiveJobsLimitToPlans < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :plans, :active_jobs_limit, :integer, default: 0
+ end
+
+ def down
+ remove_column :plans, :active_jobs_limit
+ end
+end
diff --git a/db/migrate/20190819131155_add_cluster_status_index_to_deployments.rb b/db/migrate/20190819131155_add_cluster_status_index_to_deployments.rb
new file mode 100644
index 00000000000..bfa91e33558
--- /dev/null
+++ b/db/migrate/20190819131155_add_cluster_status_index_to_deployments.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddClusterStatusIndexToDeployments < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :deployments, [:cluster_id, :status]
+ end
+
+ def down
+ remove_concurrent_index :deployments, [:cluster_id, :status]
+ end
+end
diff --git a/db/migrate/20190822175441_rename_epics_state_to_state_id.rb b/db/migrate/20190822175441_rename_epics_state_to_state_id.rb
new file mode 100644
index 00000000000..7f40d164a8e
--- /dev/null
+++ b/db/migrate/20190822175441_rename_epics_state_to_state_id.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class RenameEpicsStateToStateId < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ rename_column_concurrently :epics, :state, :state_id
+ end
+
+ def down
+ cleanup_concurrent_column_rename :epics, :state_id, :state
+ end
+end
diff --git a/db/migrate/20190822181528_create_list_user_preferences.rb b/db/migrate/20190822181528_create_list_user_preferences.rb
new file mode 100644
index 00000000000..a7993818b50
--- /dev/null
+++ b/db/migrate/20190822181528_create_list_user_preferences.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class CreateListUserPreferences < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ create_table :list_user_preferences do |t|
+ t.references :user, index: true, null: false, foreign_key: { on_delete: :cascade }
+ t.references :list, index: true, null: false, foreign_key: { on_delete: :cascade }
+ t.timestamps_with_timezone null: false
+ t.boolean :collapsed
+ end
+
+ add_index :list_user_preferences, [:user_id, :list_id], unique: true
+ end
+end
diff --git a/db/migrate/20190823055948_change_clusters_namespace_per_environment_default.rb b/db/migrate/20190823055948_change_clusters_namespace_per_environment_default.rb
new file mode 100644
index 00000000000..919ce807869
--- /dev/null
+++ b/db/migrate/20190823055948_change_clusters_namespace_per_environment_default.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class ChangeClustersNamespacePerEnvironmentDefault < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ change_column_default :clusters, :namespace_per_environment, from: false, to: true
+ end
+end
diff --git a/db/migrate/20190826090628_remove_redundant_deployments_index.rb b/db/migrate/20190826090628_remove_redundant_deployments_index.rb
new file mode 100644
index 00000000000..6b009c17d64
--- /dev/null
+++ b/db/migrate/20190826090628_remove_redundant_deployments_index.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class RemoveRedundantDeploymentsIndex < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index :deployments, :cluster_id
+ end
+
+ def down
+ add_concurrent_index :deployments, :cluster_id
+ end
+end
diff --git a/db/migrate/20190826100605_add_group_column_to_events.rb b/db/migrate/20190826100605_add_group_column_to_events.rb
new file mode 100644
index 00000000000..cd7b2b1d96a
--- /dev/null
+++ b/db/migrate/20190826100605_add_group_column_to_events.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddGroupColumnToEvents < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_reference :events, :group, index: true, foreign_key: { to_table: :namespaces, on_delete: :cascade }
+ end
+end
diff --git a/db/migrate/20190828110802_add_not_null_constraints_to_prometheus_metrics_y_label_and_unit.rb b/db/migrate/20190828110802_add_not_null_constraints_to_prometheus_metrics_y_label_and_unit.rb
new file mode 100644
index 00000000000..6f3650ca966
--- /dev/null
+++ b/db/migrate/20190828110802_add_not_null_constraints_to_prometheus_metrics_y_label_and_unit.rb
@@ -0,0 +1,8 @@
+class AddNotNullConstraintsToPrometheusMetricsYLabelAndUnit < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ change_column_null(:prometheus_metrics, :y_label, false)
+ change_column_null(:prometheus_metrics, :unit, false)
+ end
+end
diff --git a/db/migrate/20190902131045_replace_indexes_for_counting_active_users.rb b/db/migrate/20190902131045_replace_indexes_for_counting_active_users.rb
new file mode 100644
index 00000000000..2c7c47bee96
--- /dev/null
+++ b/db/migrate/20190902131045_replace_indexes_for_counting_active_users.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class ReplaceIndexesForCountingActiveUsers < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index_by_name(:users, 'index_users_on_state_and_internal')
+
+ add_concurrent_index(:users, :state, where: 'ghost IS NOT TRUE', name: 'index_users_on_state_and_internal')
+ add_concurrent_index(:users, :state, where: 'ghost IS NOT TRUE AND bot_type IS NULL', name: 'index_users_on_state_and_internal_ee')
+ end
+
+ def down
+ remove_concurrent_index_by_name(:users, 'index_users_on_state_and_internal_ee')
+ remove_concurrent_index_by_name(:users, 'index_users_on_state_and_internal')
+
+ add_concurrent_index(:users, :state, where: 'ghost <> true AND bot_type IS NULL', name: 'index_users_on_state_and_internal')
+ end
+end
diff --git a/db/post_migrate/20190822185441_cleanup_epics_state_id_rename.rb b/db/post_migrate/20190822185441_cleanup_epics_state_id_rename.rb
new file mode 100644
index 00000000000..471b2ab9ca2
--- /dev/null
+++ b/db/post_migrate/20190822185441_cleanup_epics_state_id_rename.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class CleanupEpicsStateIdRename < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ cleanup_concurrent_column_rename :epics, :state, :state_id
+ end
+
+ def down
+ rename_column_concurrently :epics, :state_id, :state
+ end
+end
diff --git a/db/post_migrate/20190902160015_remove_support_bot_column_from_users.rb b/db/post_migrate/20190902160015_remove_support_bot_column_from_users.rb
new file mode 100644
index 00000000000..80d69b57f5d
--- /dev/null
+++ b/db/post_migrate/20190902160015_remove_support_bot_column_from_users.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RemoveSupportBotColumnFromUsers < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ return unless column_exists?(:users, :support_bot)
+
+ remove_column :users, :support_bot
+ end
+
+ def down
+ # no-op because the column should not exist in the previous version
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 88a7ff77ab4..f2d6f70217b 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2019_08_28_083843) do
+ActiveRecord::Schema.define(version: 2019_09_02_160015) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@@ -272,12 +272,18 @@ ActiveRecord::Schema.define(version: 2019_08_28_083843) do
t.boolean "lock_memberships_to_ldap", default: false, null: false
t.boolean "time_tracking_limit_to_hours", default: false, null: false
t.string "grafana_url", default: "/-/grafana", null: false
+ t.boolean "login_recaptcha_protection_enabled", default: false, null: false
t.string "outbound_local_requests_whitelist", limit: 255, default: [], null: false, array: true
t.integer "raw_blob_request_limit", default: 300, null: false
t.boolean "allow_local_requests_from_web_hooks_and_services", default: false, null: false
t.boolean "allow_local_requests_from_system_hooks", default: true, null: false
t.bigint "instance_administration_project_id"
t.string "snowplow_collector_hostname"
+ t.boolean "asset_proxy_enabled", default: false, null: false
+ t.string "asset_proxy_url"
+ t.text "asset_proxy_whitelist"
+ t.text "encrypted_asset_proxy_secret_key"
+ t.string "encrypted_asset_proxy_secret_key_iv"
t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id"
t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id"
t.index ["instance_administration_project_id"], name: "index_applicationsettings_on_instance_administration_project_id"
@@ -929,7 +935,7 @@ ActiveRecord::Schema.define(version: 2019_08_28_083843) do
t.integer "cluster_type", limit: 2, default: 3, null: false
t.string "domain"
t.boolean "managed", default: true, null: false
- t.boolean "namespace_per_environment", default: false, null: false
+ t.boolean "namespace_per_environment", default: true, null: false
t.index ["enabled"], name: "index_clusters_on_enabled"
t.index ["user_id"], name: "index_clusters_on_user_id"
end
@@ -1146,7 +1152,7 @@ ActiveRecord::Schema.define(version: 2019_08_28_083843) do
t.integer "status", limit: 2, null: false
t.datetime_with_timezone "finished_at"
t.integer "cluster_id"
- t.index ["cluster_id"], name: "index_deployments_on_cluster_id"
+ t.index ["cluster_id", "status"], name: "index_deployments_on_cluster_id_and_status"
t.index ["created_at"], name: "index_deployments_on_created_at"
t.index ["deployable_type", "deployable_id"], name: "index_deployments_on_deployable_type_and_deployable_id"
t.index ["environment_id", "id"], name: "index_deployments_on_environment_id_and_id"
@@ -1280,11 +1286,11 @@ ActiveRecord::Schema.define(version: 2019_08_28_083843) do
t.date "due_date_fixed"
t.boolean "start_date_is_fixed"
t.boolean "due_date_is_fixed"
- t.integer "state", limit: 2, default: 1, null: false
t.integer "closed_by_id"
t.datetime "closed_at"
t.integer "parent_id"
t.integer "relative_position"
+ t.integer "state_id", limit: 2, default: 1, null: false
t.index ["assignee_id"], name: "index_epics_on_assignee_id"
t.index ["author_id"], name: "index_epics_on_author_id"
t.index ["closed_by_id"], name: "index_epics_on_closed_by_id"
@@ -1304,9 +1310,11 @@ ActiveRecord::Schema.define(version: 2019_08_28_083843) do
t.datetime_with_timezone "updated_at", null: false
t.integer "action", limit: 2, null: false
t.string "target_type"
+ t.bigint "group_id"
t.index ["action"], name: "index_events_on_action"
t.index ["author_id", "project_id"], name: "index_events_on_author_id_and_project_id"
t.index ["created_at", "author_id"], name: "analytics_index_events_on_created_at_and_author_id"
+ t.index ["group_id"], name: "index_events_on_group_id"
t.index ["project_id", "created_at"], name: "index_events_on_project_id_and_created_at"
t.index ["project_id", "id"], name: "index_events_on_project_id_and_id"
t.index ["target_type", "target_id"], name: "index_events_on_target_type_and_target_id"
@@ -1922,6 +1930,17 @@ ActiveRecord::Schema.define(version: 2019_08_28_083843) do
t.datetime "updated_at"
end
+ create_table "list_user_preferences", force: :cascade do |t|
+ t.bigint "user_id", null: false
+ t.bigint "list_id", null: false
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.boolean "collapsed"
+ t.index ["list_id"], name: "index_list_user_preferences_on_list_id"
+ t.index ["user_id", "list_id"], name: "index_list_user_preferences_on_user_id_and_list_id", unique: true
+ t.index ["user_id"], name: "index_list_user_preferences_on_user_id"
+ end
+
create_table "lists", id: :serial, force: :cascade do |t|
t.integer "board_id", null: false
t.integer "label_id"
@@ -2141,6 +2160,13 @@ ActiveRecord::Schema.define(version: 2019_08_28_083843) do
t.index ["user_id"], name: "index_merge_trains_on_user_id"
end
+ create_table "milestone_releases", force: :cascade do |t|
+ t.bigint "milestone_id", null: false
+ t.bigint "release_id", null: false
+ t.index ["milestone_id", "release_id"], name: "index_miletone_releases_on_milestone_and_release", unique: true
+ t.index ["release_id"], name: "index_milestone_releases_on_release_id"
+ end
+
create_table "milestones", id: :serial, force: :cascade do |t|
t.string "title", null: false
t.integer "project_id"
@@ -2503,6 +2529,7 @@ ActiveRecord::Schema.define(version: 2019_08_28_083843) do
t.string "title"
t.integer "active_pipelines_limit"
t.integer "pipeline_size_limit"
+ t.integer "active_jobs_limit", default: 0
t.index ["name"], name: "index_plans_on_name"
end
@@ -2851,8 +2878,8 @@ ActiveRecord::Schema.define(version: 2019_08_28_083843) do
t.integer "project_id"
t.string "title", null: false
t.string "query", null: false
- t.string "y_label"
- t.string "unit"
+ t.string "y_label", null: false
+ t.string "unit", null: false
t.string "legend"
t.integer "group", null: false
t.datetime_with_timezone "created_at", null: false
@@ -3533,7 +3560,8 @@ ActiveRecord::Schema.define(version: 2019_08_28_083843) do
t.index ["public_email"], name: "index_users_on_public_email", where: "((public_email)::text <> ''::text)"
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["state"], name: "index_users_on_state"
- t.index ["state"], name: "index_users_on_state_and_internal", where: "((ghost <> true) AND (bot_type IS NULL))"
+ t.index ["state"], name: "index_users_on_state_and_internal", where: "(ghost IS NOT TRUE)"
+ t.index ["state"], name: "index_users_on_state_and_internal_ee", where: "((ghost IS NOT TRUE) AND (bot_type IS NULL))"
t.index ["username"], name: "index_users_on_username"
t.index ["username"], name: "index_users_on_username_trigram", opclass: :gin_trgm_ops, using: :gin
end
@@ -3812,6 +3840,7 @@ ActiveRecord::Schema.define(version: 2019_08_28_083843) do
add_foreign_key "epics", "users", column: "assignee_id", name: "fk_dccd3f98fc", on_delete: :nullify
add_foreign_key "epics", "users", column: "author_id", name: "fk_3654b61b03", on_delete: :cascade
add_foreign_key "epics", "users", column: "closed_by_id", name: "fk_aa5798e761", on_delete: :nullify
+ add_foreign_key "events", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "events", "projects", on_delete: :cascade
add_foreign_key "events", "users", column: "author_id", name: "fk_edfd187b6f", on_delete: :cascade
add_foreign_key "fork_network_members", "fork_networks", on_delete: :cascade
@@ -3880,6 +3909,8 @@ ActiveRecord::Schema.define(version: 2019_08_28_083843) do
add_foreign_key "labels", "projects", name: "fk_7de4989a69", on_delete: :cascade
add_foreign_key "lfs_file_locks", "projects", on_delete: :cascade
add_foreign_key "lfs_file_locks", "users", on_delete: :cascade
+ add_foreign_key "list_user_preferences", "lists", on_delete: :cascade
+ add_foreign_key "list_user_preferences", "users", on_delete: :cascade
add_foreign_key "lists", "boards", name: "fk_0d3f677137", on_delete: :cascade
add_foreign_key "lists", "labels", name: "fk_7a5553d60f", on_delete: :cascade
add_foreign_key "lists", "milestones", on_delete: :cascade
@@ -3911,6 +3942,8 @@ ActiveRecord::Schema.define(version: 2019_08_28_083843) do
add_foreign_key "merge_trains", "merge_requests", on_delete: :cascade
add_foreign_key "merge_trains", "projects", column: "target_project_id", on_delete: :cascade
add_foreign_key "merge_trains", "users", on_delete: :cascade
+ add_foreign_key "milestone_releases", "milestones", on_delete: :cascade
+ add_foreign_key "milestone_releases", "releases", on_delete: :cascade
add_foreign_key "milestones", "namespaces", column: "group_id", name: "fk_95650a40d4", on_delete: :cascade
add_foreign_key "milestones", "projects", name: "fk_9bd0a0c791", on_delete: :cascade
add_foreign_key "namespace_aggregation_schedules", "namespaces", on_delete: :cascade
diff --git a/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/group_linking.gif b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/group_linking.gif
index d35cf55804f..a0ec2d4f10a 100644
--- a/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/group_linking.gif
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/group_linking.gif
Binary files differ
diff --git a/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/manual_permissions.gif b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/manual_permissions.gif
index 29b28df1cbd..a9d5dd7e73e 100644
--- a/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/manual_permissions.gif
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/img/manual_permissions.gif
Binary files differ
diff --git a/doc/administration/geo/replication/updating_the_geo_nodes.md b/doc/administration/geo/replication/updating_the_geo_nodes.md
index 39174780e24..8c27c4dac4f 100644
--- a/doc/administration/geo/replication/updating_the_geo_nodes.md
+++ b/doc/administration/geo/replication/updating_the_geo_nodes.md
@@ -1,15 +1,18 @@
# Updating the Geo nodes **(PREMIUM ONLY)**
-Depending on which version of Geo you are updating to/from, there may be
-different steps.
+Depending on which version of Geo you are updating to/from, there may be different steps.
## General update steps
-In order to update the Geo nodes when a new GitLab version is released,
-all you need to do is update GitLab itself:
+NOTE: **Note:** These general update steps are not intended for [high-availability deployments](https://docs.gitlab.com/omnibus/update/README.html#multi-node--ha-deployment), and will cause downtime. If you want to avoid downtime, consider using [zero downtime updates](https://docs.gitlab.com/omnibus/update/README.html#zero-downtime-updates).
-1. Log into each node (**primary** and **secondary** nodes).
-1. [Update GitLab][update].
+To update the Geo nodes when a new GitLab version is released, update **primary**
+and all **secondary** nodes:
+
+1. Log into the **primary** node.
+1. [Update GitLab on the **primary** node using Omnibus](https://docs.gitlab.com/omnibus/update/README.html).
+1. Log into each **secondary** node.
+1. [Update GitLab on each **secondary** node using Omnibus](https://docs.gitlab.com/omnibus/update/README.html).
1. [Test](#check-status-after-updating) **primary** and **secondary** nodes, and check version in each.
### Check status after updating
@@ -27,6 +30,8 @@ everything is working correctly:
1. Test the data replication by pushing code to the **primary** node and see if it
is received by **secondary** nodes.
+If you encounter any issues, please consult the [Geo troubleshooting guide](troubleshooting.md).
+
## Upgrading to GitLab 12.1
By default, GitLab 12.1 will attempt to automatically upgrade the embedded PostgreSQL server to 10.7 from 9.6. Please see [the omnibus documentation](https://docs.gitlab.com/omnibus/settings/database.html#upgrading-a-geo-instance) for the recommended procedure.
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index eab4b2c6eea..53b354d2f92 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -148,7 +148,7 @@ Check the directory layout on your Gitaly server to be sure.
<!--
updates to following example must also be made at
- https://gitlab.com/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab
+ https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab
-->
```ruby
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 9b1efb610f8..4c43a434817 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -341,9 +341,10 @@ installations from source.
## `database_load_balancing.log`
-Introduced in GitLab 12.3 for observability of [Database Load
-Balancing](https://docs.gitlab.com/ee/administration/database_load_balancing.html)
-when enabled. This file lives in
-`/var/log/gitlab/gitlab-rails/database_load_balancing.log` for Omnibus GitLab
-packages or in `/home/git/gitlab/log/database_load_balancing.log` for
-installations from source.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/15442) in GitLab 12.3.
+
+This log is used for observability of [Database Load Balancing](database_load_balancing.md).
+It is stored at:
+
+- `/var/log/gitlab/gitlab-rails/database_load_balancing.log` for Omnibus GitLab packages.
+- `/home/git/gitlab/log/database_load_balancing.log` for installations from source.
diff --git a/doc/administration/raketasks/uploads/sanitize.md b/doc/administration/raketasks/uploads/sanitize.md
index ae5ccfb9e37..7574660d848 100644
--- a/doc/administration/raketasks/uploads/sanitize.md
+++ b/doc/administration/raketasks/uploads/sanitize.md
@@ -37,6 +37,8 @@ Parameter | Type | Description
`stop_id` | integer | Only uploads with equal or smaller ID will be processed
`dry_run` | boolean | Do not remove EXIF data, only check if EXIF data are present or not, default: true
`sleep_time` | float | Pause for number of seconds after processing each image, default: 0.3 seconds
+`uploader` | string | Run sanitization only for uploads of the given uploader (`FileUploader`, `PersonalFileUploader`, `NamespaceFileUploader`)
+`since` | date | Run sanitization only for uploads newer than given date (e.g. `2019-05-01`)
If you have too many uploads, you can speed up sanitization by setting
`sleep_time` to a lower value or by running multiple rake tasks in parallel,
diff --git a/doc/administration/troubleshooting/kubernetes_cheat_sheet.md b/doc/administration/troubleshooting/kubernetes_cheat_sheet.md
index 260af333e8e..6bcd4c48e5a 100644
--- a/doc/administration/troubleshooting/kubernetes_cheat_sheet.md
+++ b/doc/administration/troubleshooting/kubernetes_cheat_sheet.md
@@ -75,7 +75,7 @@ and they will assist you with any issues you are having.
## GitLab-specific kubernetes information
- Minimal config that can be used to test a Kubernetes helm chart can be found
- [here](https://gitlab.com/charts/gitlab/issues/620).
+ [here](https://gitlab.com/gitlab-org/charts/gitlab/issues/620).
- Tailing logs of a separate pod. An example for a unicorn pod:
@@ -176,7 +176,7 @@ and they will assist you with any issues you are having.
helm upgrade <release name> <chart path> -f gitlab.yaml
```
- After <https://canary.gitlab.com/charts/gitlab/issues/780> is fixed, it should
+ After <https://gitlab.com/gitlab-org/charts/gitlab/issues/780> is fixed, it should
be possible to use [Updating GitLab using the Helm Chart](https://docs.gitlab.com/ee/install/kubernetes/gitlab_chart.html#updating-gitlab-using-the-helm-chart)
for upgrades.
@@ -191,8 +191,8 @@ and they will assist you with any issues you are having.
## Installation of minimal GitLab config via Minukube on macOS
-This section is based on [Developing for Kubernetes with Minikube](https://gitlab.com/charts/gitlab/blob/master/doc/minikube/index.md)
-and [Helm](https://gitlab.com/charts/gitlab/blob/master/doc/helm/index.md). Refer
+This section is based on [Developing for Kubernetes with Minikube](https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/minikube/index.md)
+and [Helm](https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/helm/index.md). Refer
to those documents for details.
- Install Kubectl via Homebrew:
@@ -223,7 +223,7 @@ to those documents for details.
helm init --service-account tiller
```
-- Copy the file <https://gitlab.com/charts/gitlab/raw/master/examples/values-minikube-minimum.yaml>
+- Copy the file <https://gitlab.com/gitlab-org/charts/gitlab/raw/master/examples/values-minikube-minimum.yaml>
to your workstation.
- Find the IP address in the output of `minikube ip` and update the yaml file with
diff --git a/doc/api/applications.md b/doc/api/applications.md
index 82955f0c1db..807a0e57e8b 100644
--- a/doc/api/applications.md
+++ b/doc/api/applications.md
@@ -88,7 +88,9 @@ DELETE /applications/:id
Parameters:
-- `id` (required) - The id of the application (not the application_id)
+| Attribute | Type | Required | Description |
+|:----------|:--------|:---------|:----------------------------------------------------|
+| `id` | integer | yes | The id of the application (not the application_id). |
Example request:
diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md
index df8cf06fc4a..2e2e1bb5e1e 100644
--- a/doc/api/deploy_keys.md
+++ b/doc/api/deploy_keys.md
@@ -212,22 +212,25 @@ group, this can be achieved quite easily with the API.
First, find the ID of the projects you're interested in, by either listing all
projects:
-```
+```bash
curl --header 'PRIVATE-TOKEN: <your_access_token>' https://gitlab.example.com/api/v4/projects
```
-Or finding the ID of a group and then listing all projects in that group:
+Or finding the ID of a group:
-```
+```bash
curl --header 'PRIVATE-TOKEN: <your_access_token>' https://gitlab.example.com/api/v4/groups
+```
-# For group 1234:
+Then listing all projects in that group (for example, group 1234):
+
+```bash
curl --header 'PRIVATE-TOKEN: <your_access_token>' https://gitlab.example.com/api/v4/groups/1234
```
With those IDs, add the same deploy key to all:
-```
+```bash
for project_id in 321 456 987; do
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" \
--data '{"title": "my key", "key": "ssh-rsa AAAA..."}' https://gitlab.example.com/api/v4/projects/${project_id}/deploy_keys
diff --git a/doc/api/epics.md b/doc/api/epics.md
index 87ae0c48199..675b88649e0 100644
--- a/doc/api/epics.md
+++ b/doc/api/epics.md
@@ -165,7 +165,7 @@ POST /groups/:id/epics
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `title` | string | yes | The title of the epic |
| `labels` | string | no | The comma separated list of labels |
-| `description` | string | no | The description of the epic |
+| `description` | string | no | The description of the epic. Limited to 1 000 000 characters. |
| `start_date_is_fixed` | boolean | no | Whether start date should be sourced from `start_date_fixed` or from milestones (since 11.3) |
| `start_date_fixed` | string | no | The fixed start date of an epic (since 11.3) |
| `due_date_is_fixed` | boolean | no | Whether due date should be sourced from `due_date_fixed` or from milestones (since 11.3) |
@@ -231,7 +231,7 @@ PUT /groups/:id/epics/:epic_iid
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `epic_iid` | integer/string | yes | The internal ID of the epic |
| `title` | string | no | The title of an epic |
-| `description` | string | no | The description of an epic |
+| `description` | string | no | The description of an epic. Limited to 1 000 000 characters. |
| `labels` | string | no | The comma separated list of labels |
| `start_date_is_fixed` | boolean | no | Whether start date should be sourced from `start_date_fixed` or from milestones (since 11.3) |
| `start_date_fixed` | string | no | The fixed start date of an epic (since 11.3) |
diff --git a/doc/api/issues.md b/doc/api/issues.md
index cadc9291489..7498d2d840b 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -590,7 +590,7 @@ POST /projects/:id/issues
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `iid` | integer/string | no | The internal ID of the project's issue (requires admin or project owner rights) |
| `title` | string | yes | The title of an issue |
-| `description` | string | no | The description of an issue |
+| `description` | string | no | The description of an issue. Limited to 1 000 000 characters. |
| `confidential` | boolean | no | Set an issue to be confidential. Default is `false`. |
| `assignee_ids` | integer array | no | The ID of a user to assign issue |
| `milestone_id` | integer | no | The global ID of a milestone to assign issue |
@@ -691,7 +691,7 @@ PUT /projects/:id/issues/:issue_iid
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `issue_iid` | integer | yes | The internal ID of a project's issue |
| `title` | string | no | The title of an issue |
-| `description` | string | no | The description of an issue |
+| `description` | string | no | The description of an issue. Limited to 1 000 000 characters. |
| `confidential` | boolean | no | Updates an issue to be confidential |
| `assignee_ids` | integer array | no | The ID of the user(s) to assign the issue to. Set to `0` or provide an empty value to unassign all assignees. |
| `milestone_id` | integer | no | The global ID of a milestone to assign the issue to. Set to `0` or provide an empty value to unassign a milestone.|
diff --git a/doc/api/merge_request_approvals.md b/doc/api/merge_request_approvals.md
index bf83bfd7e6a..b73fe38f53e 100644
--- a/doc/api/merge_request_approvals.md
+++ b/doc/api/merge_request_approvals.md
@@ -152,8 +152,8 @@ POST /projects/:id/approval_rules
| `id` | integer | yes | The ID of a project |
| `name` | string | yes | The name of the approval rule |
| `approvals_required` | integer | yes | The number of required approvals for this rule |
-| `users` | integer | no | The ids of users as approvers |
-| `groups` | integer | no | The ids of groups as approvers |
+| `user_ids` | Array | no | The ids of users as approvers |
+| `group_ids` | Array | no | The ids of groups as approvers |
```json
{
@@ -231,8 +231,8 @@ PUT /projects/:id/approval_rules/:approval_rule_id
| `approval_rule_id` | integer | yes | The ID of a approval rule |
| `name` | string | yes | The name of the approval rule |
| `approvals_required` | integer | yes | The number of required approvals for this rule |
-| `users` | integer | no | The ids of users as approvers |
-| `groups` | integer | no | The ids of groups as approvers |
+| `user_ids` | Array | no | The ids of users as approvers |
+| `group_ids` | Array | no | The ids of groups as approvers |
```json
{
@@ -525,6 +525,270 @@ PUT /projects/:id/merge_requests/:merge_request_iid/approvers
}
```
+### Get merge request level rules
+
+>**Note:** This API endpoint is only available on 12.3 Starter and above.
+
+You can request information about a merge request's approval rules using the following endpoint:
+
+```
+GET /projects/:id/merge_requests/:merge_request_iid/approval_rules
+```
+
+**Parameters:**
+
+| Attribute | Type | Required | Description |
+|---------------------|---------|----------|---------------------|
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The IID of MR |
+
+```json
+[
+ {
+ "id": 1,
+ "name": "security",
+ "rule_type": "regular",
+ "eligible_approvers": [
+ {
+ "id": 5,
+ "name": "John Doe",
+ "username": "jdoe",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/jdoe"
+ },
+ {
+ "id": 50,
+ "name": "Group Member 1",
+ "username": "group_member_1",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/group_member_1"
+ }
+ ],
+ "approvals_required": 3,
+ "source_rule": null,
+ "users": [
+ {
+ "id": 5,
+ "name": "John Doe",
+ "username": "jdoe",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/jdoe"
+ }
+ ],
+ "groups": [
+ {
+ "id": 5,
+ "name": "group1",
+ "path": "group1",
+ "description": "",
+ "visibility": "public",
+ "lfs_enabled": false,
+ "avatar_url": null,
+ "web_url": "http://localhost/groups/group1",
+ "request_access_enabled": false,
+ "full_name": "group1",
+ "full_path": "group1",
+ "parent_id": null,
+ "ldap_cn": null,
+ "ldap_access": null
+ }
+ ],
+ "contains_hidden_groups": false
+ }
+]
+```
+
+### Create merge request level rule
+
+>**Note:** This API endpoint is only available on 12.3 Starter and above.
+
+You can create merge request approval rules using the following endpoint:
+
+```
+POST /projects/:id/merge_requests/:merge_request_iid/approval_rules
+```
+
+**Parameters:**
+
+| Attribute | Type | Required | Description |
+|----------------------------|---------|----------|------------------------------------------------|
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The IID of MR |
+| `name` | string | yes | The name of the approval rule |
+| `approvals_required` | integer | yes | The number of required approvals for this rule |
+| `approval_project_rule_id` | integer | no | The ID of a project-level approval rule |
+| `user_ids` | Array | no | The ids of users as approvers |
+| `group_ids` | Array | no | The ids of groups as approvers |
+
+**Important:** When `approval_project_rule_id` is set, the `name`, `users` and
+`groups` of project-level rule will be copied. The `approvals_required` specified
+will be used.
+
+```json
+{
+ "id": 1,
+ "name": "security",
+ "rule_type": "regular",
+ "eligible_approvers": [
+ {
+ "id": 2,
+ "name": "John Doe",
+ "username": "jdoe",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/jdoe"
+ },
+ {
+ "id": 50,
+ "name": "Group Member 1",
+ "username": "group_member_1",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/group_member_1"
+ }
+ ],
+ "approvals_required": 1,
+ "source_rule": null,
+ "users": [
+ {
+ "id": 2,
+ "name": "John Doe",
+ "username": "jdoe",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/jdoe"
+ }
+ ],
+ "groups": [
+ {
+ "id": 5,
+ "name": "group1",
+ "path": "group1",
+ "description": "",
+ "visibility": "public",
+ "lfs_enabled": false,
+ "avatar_url": null,
+ "web_url": "http://localhost/groups/group1",
+ "request_access_enabled": false,
+ "full_name": "group1",
+ "full_path": "group1",
+ "parent_id": null,
+ "ldap_cn": null,
+ "ldap_access": null
+ }
+ ],
+ "contains_hidden_groups": false
+}
+```
+
+### Update merge request level rule
+
+>**Note:** This API endpoint is only available on 12.3 Starter and above.
+
+You can update merge request approval rules using the following endpoint:
+
+```
+PUT /projects/:id/merge_request/:merge_request_iid/approval_rules/:approval_rule_id
+```
+
+**Important:** Approvers and groups not in the `users`/`groups` param will be **removed**
+
+**Important:** Updating a `report_approver` or `code_owner` rule is not allowed.
+These are system generated rules.
+
+**Parameters:**
+
+| Attribute | Type | Required | Description |
+|----------------------|---------|----------|------------------------------------------------|
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The ID of MR |
+| `approval_rule_id` | integer | yes | The ID of a approval rule |
+| `name` | string | yes | The name of the approval rule |
+| `approvals_required` | integer | yes | The number of required approvals for this rule |
+| `user_ids` | Array | no | The ids of users as approvers |
+| `group_ids` | Array | no | The ids of groups as approvers |
+
+```json
+{
+ "id": 1,
+ "name": "security",
+ "rule_type": "regular",
+ "eligible_approvers": [
+ {
+ "id": 2,
+ "name": "John Doe",
+ "username": "jdoe",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/jdoe"
+ },
+ {
+ "id": 50,
+ "name": "Group Member 1",
+ "username": "group_member_1",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/group_member_1"
+ }
+ ],
+ "approvals_required": 1,
+ "source_rule": null,
+ "users": [
+ {
+ "id": 2,
+ "name": "John Doe",
+ "username": "jdoe",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/jdoe"
+ }
+ ],
+ "groups": [
+ {
+ "id": 5,
+ "name": "group1",
+ "path": "group1",
+ "description": "",
+ "visibility": "public",
+ "lfs_enabled": false,
+ "avatar_url": null,
+ "web_url": "http://localhost/groups/group1",
+ "request_access_enabled": false,
+ "full_name": "group1",
+ "full_path": "group1",
+ "parent_id": null,
+ "ldap_cn": null,
+ "ldap_access": null
+ }
+ ],
+ "contains_hidden_groups": false
+}
+```
+
+### Delete merge request level rule
+
+>**Note:** This API endpoint is only available on 12.3 Starter and above.
+
+You can delete merge request approval rules using the following endpoint:
+
+```
+DELETE /projects/:id/merge_requests/:merge_request_iid/approval_rules/:approval_rule_id
+```
+
+**Important:** Deleting a `report_approver` or `code_owner` rule is not allowed.
+These are system generated rules.
+
+**Parameters:**
+
+| Attribute | Type | Required | Description |
+|---------------------|---------|----------|---------------------------|
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The ID of MR |
+| `approval_rule_id` | integer | yes | The ID of a approval rule |
+
## Approve Merge Request
>**Note:** This API endpoint is only available on 8.9 Starter and above.
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 49ed4968b0d..0d030ef30c8 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -837,7 +837,7 @@ POST /projects/:id/merge_requests
| `title` | string | yes | Title of MR |
| `assignee_id` | integer | no | Assignee user ID |
| `assignee_ids` | integer array | no | The ID of the user(s) to assign the MR to. Set to `0` or provide an empty value to unassign all assignees. |
-| `description` | string | no | Description of MR |
+| `description` | string | no | Description of MR. Limited to 1 000 000 characters. |
| `target_project_id` | integer | no | The target project (numeric id) |
| `labels` | string | no | Labels for MR as a comma-separated list |
| `milestone_id` | integer | no | The global ID of a milestone |
@@ -990,7 +990,7 @@ PUT /projects/:id/merge_requests/:merge_request_iid
| `assignee_ids` | integer array | no | The ID of the user(s) to assign the MR to. Set to `0` or provide an empty value to unassign all assignees. |
| `milestone_id` | integer | no | The global ID of a milestone to assign the merge request to. Set to `0` or provide an empty value to unassign a milestone.|
| `labels` | string | no | Comma-separated label names for a merge request. Set to an empty string to unassign all labels. |
-| `description` | string | no | Description of MR |
+| `description` | string | no | Description of MR. Limited to 1 000 000 characters. |
| `state_event` | string | no | New state (close/reopen) |
| `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging |
| `squash` | boolean | no | Squash commits into a single commit when merging |
diff --git a/doc/api/notes.md b/doc/api/notes.md
index acbf0334563..1f5baf7d0e1 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -113,7 +113,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
- `issue_iid` (required) - The IID of an issue
-- `body` (required) - The content of a note
+- `body` (required) - The content of a note. Limited to 1 000 000 characters.
- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights)
```bash
@@ -133,7 +133,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
- `issue_iid` (required) - The IID of an issue
- `note_id` (required) - The ID of a note
-- `body` (required) - The content of a note
+- `body` (required) - The content of a note. Limited to 1 000 000 characters.
```bash
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/notes?body=note
@@ -231,7 +231,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
- `snippet_id` (required) - The ID of a snippet
-- `body` (required) - The content of a note
+- `body` (required) - The content of a note. Limited to 1 000 000 characters.
- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z
```bash
@@ -251,7 +251,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
- `snippet_id` (required) - The ID of a snippet
- `note_id` (required) - The ID of a note
-- `body` (required) - The content of a note
+- `body` (required) - The content of a note. Limited to 1 000 000 characters.
```bash
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/11/notes?body=note
@@ -354,7 +354,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
- `merge_request_iid` (required) - The IID of a merge request
-- `body` (required) - The content of a note
+- `body` (required) - The content of a note. Limited to 1 000 000 characters.
- `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z
### Modify existing merge request note
@@ -370,7 +370,7 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding)
- `merge_request_iid` (required) - The IID of a merge request
- `note_id` (required) - The ID of a note
-- `body` (required) - The content of a note
+- `body` (required) - The content of a note. Limited to 1 000 000 characters.
```bash
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/notes?body=note
@@ -472,7 +472,7 @@ Parameters:
| --------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `epic_id` | integer | yes | The ID of an epic |
-| `body` | string | yes | The content of a note |
+| `body` | string | yes | The content of a note. Limited to 1 000 000 characters. |
```bash
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippet/11/notes?body=note
@@ -493,7 +493,7 @@ Parameters:
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `epic_id` | integer | yes | The ID of an epic |
| `note_id` | integer | yes | The ID of a note |
-| `body` | string | yes | The content of a note |
+| `body` | string | yes | The content of a note. Limited to 1 000 000 characters. |
```bash
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippet/11/notes?body=note
diff --git a/doc/api/releases/index.md b/doc/api/releases/index.md
index e74b35fd959..8d5b3a65789 100644
--- a/doc/api/releases/index.md
+++ b/doc/api/releases/index.md
@@ -12,14 +12,14 @@ Paginated list of Releases, sorted by `released_at`.
GET /projects/:id/releases
```
-| Attribute | Type | Required | Description |
-| ------------- | -------------- | -------- | --------------------------------------- |
+| Attribute | Type | Required | Description |
+| ------------- | -------------- | -------- | ----------------------------------------------------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
Example request:
```sh
-curl --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases"
+curl --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "https://gitlab.example.com/api/v4/projects/24/releases"
```
Example response:
@@ -39,7 +39,7 @@ Example response:
"username":"root",
"state":"active",
"avatar_url":"https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
- "web_url":"http://localhost:3000/root"
+ "web_url":"https://gitlab.example.com/root"
},
"commit":{
"id":"079e90101242458910cccd35eab0e211dfc359c0",
@@ -57,24 +57,37 @@ Example response:
"committer_email":"admin@example.com",
"committed_date":"2019-01-03T01:55:38.000Z"
},
+ "milestone":{
+ "id":51,
+ "iid":1,
+ "project_id":24,
+ "title":"v1.0-rc",
+ "description":"Voluptate fugiat possimus quis quod aliquam expedita.",
+ "state":"closed",
+ "created_at":"2019-07-12T19:45:44.256Z",
+ "updated_at":"2019-07-12T19:45:44.256Z",
+ "due_date":"2019-08-16T11:00:00.256Z",
+ "start_date":"2019-07-30T12:00:00.256Z",
+ "web_url":"http://localhost:3000/root/awesome-app/-/milestones/1"
+ },
"assets":{
"count":6,
"sources":[
{
"format":"zip",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.2/awesome-app-v0.2.zip"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.2/awesome-app-v0.2.zip"
},
{
"format":"tar.gz",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.2/awesome-app-v0.2.tar.gz"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.2/awesome-app-v0.2.tar.gz"
},
{
"format":"tar.bz2",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.2/awesome-app-v0.2.tar.bz2"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.2/awesome-app-v0.2.tar.bz2"
},
{
"format":"tar",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.2/awesome-app-v0.2.tar"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.2/awesome-app-v0.2.tar"
}
],
"links":[
@@ -106,7 +119,7 @@ Example response:
"username":"root",
"state":"active",
"avatar_url":"https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
- "web_url":"http://localhost:3000/root"
+ "web_url":"https://gitlab.example.com/root"
},
"commit":{
"id":"f8d3d94cbd347e924aa7b715845e439d00e80ca4",
@@ -129,19 +142,19 @@ Example response:
"sources":[
{
"format":"zip",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.zip"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.zip"
},
{
"format":"tar.gz",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.gz"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.gz"
},
{
"format":"tar.bz2",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.bz2"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.bz2"
},
{
"format":"tar",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar"
}
],
"links":[
@@ -160,15 +173,15 @@ Get a Release for the given tag.
GET /projects/:id/releases/:tag_name
```
-| Attribute | Type | Required | Description |
-| ------------- | -------------- | -------- | --------------------------------------- |
+| Attribute | Type | Required | Description |
+| ------------- | -------------- | -------- | ----------------------------------------------------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
-| `tag_name` | string | yes | The tag where the release will be created from. |
+| `tag_name` | string | yes | The tag where the release will be created from. |
Example request:
```sh
-curl --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases/v0.1"
+curl --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1"
```
Example response:
@@ -187,7 +200,7 @@ Example response:
"username":"root",
"state":"active",
"avatar_url":"https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
- "web_url":"http://localhost:3000/root"
+ "web_url":"https://gitlab.example.com/root"
},
"commit":{
"id":"f8d3d94cbd347e924aa7b715845e439d00e80ca4",
@@ -205,24 +218,37 @@ Example response:
"committer_email":"admin@example.com",
"committed_date":"2019-01-03T01:53:28.000Z"
},
+ "milestone":{
+ "id":51,
+ "iid":1,
+ "project_id":24,
+ "title":"v1.0-rc",
+ "description":"Voluptate fugiat possimus quis quod aliquam expedita.",
+ "state":"closed",
+ "created_at":"2019-07-12T19:45:44.256Z",
+ "updated_at":"2019-07-12T19:45:44.256Z",
+ "due_date":"2019-08-16T11:00:00.256Z",
+ "start_date":"2019-07-30T12:00:00.256Z",
+ "web_url":"http://localhost:3000/root/awesome-app/-/milestones/1"
+ },
"assets":{
"count":4,
"sources":[
{
"format":"zip",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.zip"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.zip"
},
{
"format":"tar.gz",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.gz"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.gz"
},
{
"format":"tar.bz2",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.bz2"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.bz2"
},
{
"format":"tar",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar"
}
],
"links":[
@@ -240,24 +266,25 @@ Create a Release. You need push access to the repository to create a Release.
POST /projects/:id/releases
```
-| Attribute | Type | Required | Description |
-| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
-| `name` | string | yes | The release name. |
-| `tag_name` | string | yes | The tag where the release will be created from. |
-| `description` | string | yes | The description of the release. You can use [markdown](../../user/markdown.md). |
-| `ref` | string | no | If `tag_name` doesn't exist, the release will be created from `ref`. It can be a commit SHA, another tag name, or a branch name. |
-| `assets:links`| array of hash | no | An array of assets links. |
-| `assets:links:name`| string | no (if `assets:links` specified, it's required) | The name of the link. |
-| `assets:links:url`| string | no (if `assets:links` specified, it's required) | The url of the link. |
-| `released_at` | datetime | no | The date when the release will be/was ready. Defaults to the current time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
+| Attribute | Type | Required | Description |
+| -------------------| --------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
+| `name` | string | yes | The release name. |
+| `tag_name` | string | yes | The tag where the release will be created from. |
+| `description` | string | yes | The description of the release. You can use [markdown](../../user/markdown.md). |
+| `ref` | string | no | If `tag_name` doesn't exist, the release will be created from `ref`. It can be a commit SHA, another tag name, or a branch name. |
+| `milestone` | string | no | The title of the milestone the release is associated with. |
+| `assets:links` | array of hash | no | An array of assets links. |
+| `assets:links:name`| string | required by: `assets:links` | The name of the link. |
+| `assets:links:url` | string | required by: `assets:links` | The url of the link. |
+| `released_at` | datetime | no | The date when the release will be/was ready. Defaults to the current time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
Example request:
```sh
curl --header 'Content-Type: application/json' --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" \
- --data '{ "name": "New release", "tag_name": "v0.3", "description": "Super nice release", "assets": { "links": [{ "name": "hoge", "url": "https://google.com" }] } }' \
- --request POST http://localhost:3000/api/v4/projects/24/releases
+ --data '{ "name": "New release", "tag_name": "v0.3", "description": "Super nice release", "milestone": "v1.0-rc", "assets": { "links": [{ "name": "hoge", "url": "https://google.com" }] } }' \
+ --request POST https://gitlab.example.com/api/v4/projects/24/releases
```
Example response:
@@ -276,7 +303,7 @@ Example response:
"username":"root",
"state":"active",
"avatar_url":"https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
- "web_url":"http://localhost:3000/root"
+ "web_url":"https://gitlab.example.com/root"
},
"commit":{
"id":"079e90101242458910cccd35eab0e211dfc359c0",
@@ -294,24 +321,37 @@ Example response:
"committer_email":"admin@example.com",
"committed_date":"2019-01-03T01:55:38.000Z"
},
+ "milestone":{
+ "id":51,
+ "iid":1,
+ "project_id":24,
+ "title":"v1.0-rc",
+ "description":"Voluptate fugiat possimus quis quod aliquam expedita.",
+ "state":"active",
+ "created_at":"2019-07-12T19:45:44.256Z",
+ "updated_at":"2019-07-12T19:45:44.256Z",
+ "due_date":"2019-08-16T11:00:00.256Z",
+ "start_date":"2019-07-30T12:00:00.256Z",
+ "web_url":"http://localhost:3000/root/awesome-app/-/milestones/1"
+ },
"assets":{
"count":5,
"sources":[
{
"format":"zip",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.3/awesome-app-v0.3.zip"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.3/awesome-app-v0.3.zip"
},
{
"format":"tar.gz",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.3/awesome-app-v0.3.tar.gz"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.3/awesome-app-v0.3.tar.gz"
},
{
"format":"tar.bz2",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.3/awesome-app-v0.3.tar.bz2"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.3/awesome-app-v0.3.tar.bz2"
},
{
"format":"tar",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.3/awesome-app-v0.3.tar"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.3/awesome-app-v0.3.tar"
}
],
"links":[
@@ -334,18 +374,19 @@ Update a Release.
PUT /projects/:id/releases/:tag_name
```
-| Attribute | Type | Required | Description |
-| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
-| `tag_name` | string | yes | The tag where the release will be created from. |
-| `name` | string | no | The release name. |
-| `description` | string | no | The description of the release. You can use [markdown](../../user/markdown.md). |
-| `released_at` | datetime | no | The date when the release will be/was ready. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
+| Attribute | Type | Required | Description |
+| ------------- | -------------- | -------- | --------------------------------------------------------------------------------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
+| `tag_name` | string | yes | The tag where the release will be created from. |
+| `name` | string | no | The release name. |
+| `description` | string | no | The description of the release. You can use [markdown](../../user/markdown.md). |
+| `milestone` | string | no | The title of the milestone to associate with the release (`""` to remove the milestone from the release). |
+| `released_at` | datetime | no | The date when the release will be/was ready. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
Example request:
```sh
-curl --request PUT --data name="new name" --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases/v0.1"
+curl --header 'Content-Type: application/json' --request PUT --data '{"name": "new name", "milestone": "v1.0"}' --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1"
```
Example response:
@@ -364,7 +405,7 @@ Example response:
"username":"root",
"state":"active",
"avatar_url":"https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
- "web_url":"http://localhost:3000/root"
+ "web_url":"https://gitlab.example.com/root"
},
"commit":{
"id":"f8d3d94cbd347e924aa7b715845e439d00e80ca4",
@@ -382,24 +423,37 @@ Example response:
"committer_email":"admin@example.com",
"committed_date":"2019-01-03T01:53:28.000Z"
},
+ "milestone":{
+ "id":53,
+ "iid":2,
+ "project_id":24,
+ "title":"v1.0",
+ "description":"Voluptate fugiat possimus quis quod aliquam expedita.",
+ "state":"active",
+ "created_at":"2019-09-01T13:00:00.256Z",
+ "updated_at":"2019-09-01T13:00:00.256Z",
+ "due_date":"2019-09-20T13:00:00.256Z",
+ "start_date":"2019-09-05T12:00:00.256Z",
+ "web_url":"http://localhost:3000/root/awesome-app/-/milestones/3"
+ },
"assets":{
"count":4,
"sources":[
{
"format":"zip",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.zip"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.zip"
},
{
"format":"tar.gz",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.gz"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.gz"
},
{
"format":"tar.bz2",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.bz2"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.bz2"
},
{
"format":"tar",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar"
}
],
"links":[
@@ -417,15 +471,15 @@ Delete a Release. Deleting a Release will not delete the associated tag.
DELETE /projects/:id/releases/:tag_name
```
-| Attribute | Type | Required | Description |
-| ------------- | -------------- | -------- | --------------------------------------- |
+| Attribute | Type | Required | Description |
+| ------------- | -------------- | -------- | ----------------------------------------------------------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
-| `tag_name` | string | yes | The tag where the release will be created from. |
+| `tag_name` | string | yes | The tag where the release will be created from. |
Example request:
```sh
-curl --request DELETE --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases/v0.1"
+curl --request DELETE --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1"
```
Example response:
@@ -444,7 +498,7 @@ Example response:
"username":"root",
"state":"active",
"avatar_url":"https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
- "web_url":"http://localhost:3000/root"
+ "web_url":"https://gitlab.example.com/root"
},
"commit":{
"id":"f8d3d94cbd347e924aa7b715845e439d00e80ca4",
@@ -467,19 +521,19 @@ Example response:
"sources":[
{
"format":"zip",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.zip"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.zip"
},
{
"format":"tar.gz",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.gz"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.gz"
},
{
"format":"tar.bz2",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.bz2"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar.bz2"
},
{
"format":"tar",
- "url":"http://localhost:3000/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar"
+ "url":"https://gitlab.example.com/root/awesome-app/-/archive/v0.1/awesome-app-v0.1.tar"
}
],
"links":[
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 710b63c9a2f..a14b0d3632a 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -67,7 +67,10 @@ Example response:
"local_markdown_version": 0,
"allow_local_requests_from_hooks_and_services": true,
"allow_local_requests_from_web_hooks_and_services": true,
- "allow_local_requests_from_system_hooks": false
+ "allow_local_requests_from_system_hooks": false,
+ "asset_proxy_enabled": true,
+ "asset_proxy_url": "https://assets.example.com",
+ "asset_proxy_whitelist": ["example.com", "*.example.com", "your-instance.com"]
}
```
@@ -121,6 +124,10 @@ Example response:
"domain_whitelist": [],
"domain_blacklist_enabled" : false,
"domain_blacklist" : [],
+ "external_authorization_service_enabled": true,
+ "external_authorization_service_url": "https://authorize.me",
+ "external_authorization_service_default_label": "default",
+ "external_authorization_service_timeout": 0.5,
"user_oauth_applications": true,
"after_sign_out_path": "",
"container_registry_token_expire_delay": 5,
@@ -141,6 +148,9 @@ Example response:
"user_show_add_ssh_key_message": true,
"file_template_project_id": 1,
"local_markdown_version": 0,
+ "asset_proxy_enabled": true,
+ "asset_proxy_url": "https://assets.example.com",
+ "asset_proxy_whitelist": ["example.com", "*.example.com", "your-instance.com"],
"geo_node_allowed_ips": "0.0.0.0/0, ::/0",
"allow_local_requests_from_hooks_and_services": true,
"allow_local_requests_from_web_hooks_and_services": true,
@@ -151,20 +161,13 @@ Example response:
Users on GitLab [Premium or Ultimate](https://about.gitlab.com/pricing/) may also see
these parameters:
-- `external_authorization_service_enabled`
-- `external_authorization_service_url`
-- `external_authorization_service_default_label`
-- `external_authorization_service_timeout`
- `file_template_project_id`
- `geo_node_allowed_ips`
+- `geo_status_timeout`
Example responses: **(PREMIUM ONLY)**
```json
- "external_authorization_service_enabled": true,
- "external_authorization_service_url": "https://authorize.me",
- "external_authorization_service_default_label": "default",
- "external_authorization_service_timeout": 0.5,
"file_template_project_id": 1,
"geo_node_allowed_ips": "0.0.0.0/0, ::/0"
```
@@ -184,57 +187,66 @@ are listed in the descriptions of the relevant settings.
| `akismet_enabled` | boolean | no | (**If enabled, requires:** `akismet_api_key`) Enable or disable akismet spam protection. |
| `allow_group_owners_to_manage_ldap` | boolean | no | **(PREMIUM)** Set to `true` to allow group owners to manage LDAP |
| `allow_local_requests_from_hooks_and_services` | boolean | no | (Deprecated: Use `allow_local_requests_from_web_hooks_and_services` instead) Allow requests to the local network from hooks and services. |
-| `allow_local_requests_from_web_hooks_and_services` | boolean | no | Allow requests to the local network from web hooks and services. |
| `allow_local_requests_from_system_hooks` | boolean | no | Allow requests to the local network from system hooks. |
+| `allow_local_requests_from_web_hooks_and_services` | boolean | no | Allow requests to the local network from web hooks and services. |
+| `archive_builds_in_human_readable` | string | no | Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>. |
+| `asset_proxy_enabled` | boolean | no | (**If enabled, requires:** `asset_proxy_url`) Enable proxying of assets. GitLab restart is required to apply changes. |
+| `asset_proxy_secret_key` | string | no | Shared secret with the asset proxy server. GitLab restart is required to apply changes. |
+| `asset_proxy_url` | string | no | URL of the asset proxy server. GitLab restart is required to apply changes. |
+| `asset_proxy_whitelist` | string or array of strings | no | Assets that match these domain(s) will NOT be proxied. Wildcards allowed. Your GitLab installation URL is automatically whitelisted. GitLab restart is required to apply changes. |
| `authorized_keys_enabled` | boolean | no | By default, we write to the `authorized_keys` file to support Git over SSH without additional configuration. GitLab can be optimized to authenticate SSH keys via the database file. Only disable this if you have configured your OpenSSH server to use the AuthorizedKeysCommand. |
| `auto_devops_domain` | string | no | Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages. |
| `auto_devops_enabled` | boolean | no | Enable Auto DevOps for projects by default. It will automatically build, test, and deploy applications based on a predefined CI/CD configuration. |
| `check_namespace_plan` | boolean | no | **(PREMIUM)** Enabling this will make only licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public. |
-| `clientside_sentry_dsn` | string | required by: `clientside_sentry_enabled` | Clientside Sentry Data Source Name. |
-| `clientside_sentry_enabled` | boolean | no | (**If enabled, requires:** `clientside_sentry_dsn`) Enable Sentry error reporting for the client side. |
+| `commit_email_hostname` | string | no | Custom hostname (for private commit emails). |
| `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes. |
| `default_artifacts_expire_in` | string | no | Set the default expiration time for each job's artifacts. |
| `default_branch_protection` | integer | no | Determine if developers can push to master. Can take: `0` _(not protected, both developers and maintainers can push new commits, force push, or delete the branch)_, `1` _(partially protected, developers and maintainers can push new commits, but cannot force push or delete the branch)_ or `2` _(fully protected, developers cannot push new commits, but maintainers can; no-one can force push or delete the branch)_ as a parameter. Default is `2`. |
| `default_group_visibility` | string | no | What visibility level new groups receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
-| `default_project_visibility` | string | no | What visibility level new projects receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
+| `default_project_creation` | integer | no | Default project creation protection. Can take: `0` _(No one)_, `1` _(Maintainers)_ or `2` _(Developers + Maintainers)_|
| `default_projects_limit` | integer | no | Project limit per user. Default is `100000`. |
+| `default_project_visibility` | string | no | What visibility level new projects receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
| `default_snippet_visibility` | string | no | What visibility level new snippets receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
+| `diff_max_patch_bytes` | integer | no | Maximum diff patch size (Bytes). |
| `disabled_oauth_sign_in_sources` | array of strings | no | Disabled OAuth sign-in sources. |
+| `dns_rebinding_protection_enabled` | boolean | no | Enforce DNS rebinding attack protection. |
| `domain_blacklist` | array of strings | required by: `domain_blacklist_enabled` | Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: `domain.com`, `*.domain.com`. |
| `domain_blacklist_enabled` | boolean | no | (**If enabled, requires:** `domain_blacklist`) Allows blocking sign-ups from emails from specific domains. |
| `domain_whitelist` | array of strings | no | Force people to use only corporate emails for sign-up. Default is `null`, meaning there is no restriction. |
-| `outbound_local_requests_whitelist` | array of strings | no | Define a list of trusted domains or ip addresses to which local requests are allowed when local requests for hooks and services are disabled.
| `dsa_key_restriction` | integer | no | The minimum allowed bit length of an uploaded DSA key. Default is `0` (no restriction). `-1` disables DSA keys. |
| `ecdsa_key_restriction` | integer | no | The minimum allowed curve size (in bits) of an uploaded ECDSA key. Default is `0` (no restriction). `-1` disables ECDSA keys. |
| `ed25519_key_restriction` | integer | no | The minimum allowed curve size (in bits) of an uploaded ED25519 key. Default is `0` (no restriction). `-1` disables ED25519 keys. |
-| `elasticsearch_aws` | boolean | no | **(PREMIUM)** Enable the use of AWS hosted Elasticsearch |
| `elasticsearch_aws_access_key` | string | no | **(PREMIUM)** AWS IAM access key |
+| `elasticsearch_aws` | boolean | no | **(PREMIUM)** Enable the use of AWS hosted Elasticsearch |
| `elasticsearch_aws_region` | string | no | **(PREMIUM)** The AWS region the elasticsearch domain is configured |
| `elasticsearch_aws_secret_access_key` | string | no | **(PREMIUM)** AWS IAM secret access key |
| `elasticsearch_experimental_indexer` | boolean | no | **(PREMIUM)** Use the experimental elasticsearch indexer. More info: <https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer> |
| `elasticsearch_indexing` | boolean | no | **(PREMIUM)** Enable Elasticsearch indexing |
-| `elasticsearch_search` | boolean | no | **(PREMIUM)** Enable Elasticsearch search |
-| `elasticsearch_url` | string | no | **(PREMIUM)** The url to use for connecting to Elasticsearch. Use a comma-separated list to support cluster (e.g., `http://localhost:9200, http://localhost:9201"`). If your Elasticsearch instance is password protected, pass the `username:password` in the URL (e.g., `http://<username>:<password>@<elastic_host>:9200/`). |
| `elasticsearch_limit_indexing` | boolean | no | **(PREMIUM)** Limit Elasticsearch to index certain namespaces and projects |
-| `elasticsearch_project_ids` | array of integers | no | **(PREMIUM)** The projects to index via Elasticsearch if `elasticsearch_limit_indexing` is enabled. |
| `elasticsearch_namespace_ids` | array of integers | no | **(PREMIUM)** The namespaces to index via Elasticsearch if `elasticsearch_limit_indexing` is enabled. |
+| `elasticsearch_project_ids` | array of integers | no | **(PREMIUM)** The projects to index via Elasticsearch if `elasticsearch_limit_indexing` is enabled. |
+| `elasticsearch_search` | boolean | no | **(PREMIUM)** Enable Elasticsearch search |
+| `elasticsearch_url` | string | no | **(PREMIUM)** The url to use for connecting to Elasticsearch. Use a comma-separated list to support cluster (e.g., `http://localhost:9200, http://localhost:9201"`). If your Elasticsearch instance is password protected, pass the `username:password` in the URL (e.g., `http://<username>:<password>@<elastic_host>:9200/`). |
| `email_additional_text` | string | no | **(PREMIUM)** Additional text added to the bottom of every email for legal/auditing/compliance reasons |
| `email_author_in_body` | boolean | no | Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead. |
| `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. |
| `enforce_terms` | boolean | no | (**If enabled, requires:** `terms`) Enforce application ToS to all users. |
-| `external_auth_client_cert` | string | no | **(PREMIUM)** (**If enabled, requires:** `external_auth_client_key`) The certificate to use to authenticate with the external authorization service |
-| `external_auth_client_key` | string | required by: `external_auth_client_cert` | **(PREMIUM)** Private key for the certificate when authentication is required for the external authorization service, this is encrypted when stored |
-| `external_auth_client_key_pass` | string | no | **(PREMIUM)** Passphrase to use for the private key when authenticating with the external service this is encrypted when stored |
-| `external_authorization_service_enabled` | boolean | no | **(PREMIUM)** (**If enabled, requires:** `external_authorization_service_default_label`, `external_authorization_service_timeout` and `external_authorization_service_url` ) Enable using an external authorization service for accessing projects |
-| `external_authorization_service_default_label` | string | required by: `external_authorization_service_enabled` | **(PREMIUM)** The default classification label to use when requesting authorization and no classification label has been specified on the project |
-| `external_authorization_service_timeout` | float | required by: `external_authorization_service_enabled` | **(PREMIUM)** The timeout after which an authorization request is aborted, in seconds. When a request times out, access is denied to the user. (min: 0.001, max: 10, step: 0.001) |
-| `external_authorization_service_url` | string | required by: `external_authorization_service_enabled` | **(PREMIUM)** URL to which authorization requests will be directed |
+| `external_auth_client_cert` | string | no | (**If enabled, requires:** `external_auth_client_key`) The certificate to use to authenticate with the external authorization service |
+| `external_auth_client_key_pass` | string | no | Passphrase to use for the private key when authenticating with the external service this is encrypted when stored |
+| `external_auth_client_key` | string | required by: `external_auth_client_cert` | Private key for the certificate when authentication is required for the external authorization service, this is encrypted when stored |
+| `external_authorization_service_default_label` | string | required by: `external_authorization_service_enabled` | The default classification label to use when requesting authorization and no classification label has been specified on the project |
+| `external_authorization_service_enabled` | boolean | no | (**If enabled, requires:** `external_authorization_service_default_label`, `external_authorization_service_timeout` and `external_authorization_service_url` ) Enable using an external authorization service for accessing projects |
+| `external_authorization_service_timeout` | float | required by: `external_authorization_service_enabled` | The timeout after which an authorization request is aborted, in seconds. When a request times out, access is denied to the user. (min: 0.001, max: 10, step: 0.001) |
+| `external_authorization_service_url` | string | required by: `external_authorization_service_enabled` | URL to which authorization requests will be directed |
| `file_template_project_id` | integer | no | **(PREMIUM)** The ID of a project to load custom file templates from |
| `first_day_of_week` | integer | no | Start day of the week for calendar views and date pickers. Valid values are `0` (default) for Sunday, `1` for Monday, and `6` for Saturday. |
+| `geo_node_allowed_ips` | string | yes | **(PREMIUM)** Comma-separated list of IPs and CIDRs of allowed secondary nodes. For example, `1.1.1.1, 2.2.2.0/24`. |
| `geo_status_timeout` | integer | no | **(PREMIUM)** The amount of seconds after which a request to get a secondary node status will time out. |
| `gitaly_timeout_default` | integer | no | Default Gitaly timeout, in seconds. This timeout is not enforced for Git fetch/push operations or Sidekiq jobs. Set to `0` to disable timeouts. |
| `gitaly_timeout_fast` | integer | no | Gitaly fast operation timeout, in seconds. Some Gitaly operations are expected to be fast. If they exceed this threshold, there may be a problem with a storage shard and 'failing fast' can help maintain the stability of the GitLab instance. Set to `0` to disable timeouts. |
| `gitaly_timeout_medium` | integer | no | Medium Gitaly timeout, in seconds. This should be a value between the Fast and the Default timeout. Set to `0` to disable timeouts. |
+| `grafana_enabled` | boolean | no | Enable Grafana. |
+| `grafana_url` | string | no | Grafana URL. |
| `gravatar_enabled` | boolean | no | Enable Gravatar. |
| `hashed_storage_enabled` | boolean | no | Create new projects using hashed storage paths: Enable immutable, hash-based paths and repository names to store repositories on disk. This prevents repositories from having to be moved or renamed when the Project URL changes and may improve disk I/O performance. (EXPERIMENTAL) |
| `help_page_hide_commercial_content` | boolean | no | Hide marketing-related entries from help. |
@@ -249,8 +261,10 @@ are listed in the descriptions of the relevant settings.
| `housekeeping_gc_period` | integer | required by: `housekeeping_enabled` | Number of Git pushes after which `git gc` is run. |
| `housekeeping_incremental_repack_period` | integer | required by: `housekeeping_enabled` | Number of Git pushes after which an incremental `git repack` is run. |
| `html_emails_enabled` | boolean | no | Enable HTML emails. |
+| `import_sources` | array of strings | no | Sources to allow project import from, possible values: `github`, `bitbucket`, `bitbucket_server`, `gitlab`, `google_code`, `fogbugz`, `git`, `gitlab_project`, `gitea`, `manifest`, and `phabricator`. |
+
| `instance_statistics_visibility_private` | boolean | no | When set to `true` Instance statistics will only be available to admins. |
-| `import_sources` | array of strings | no | Sources to allow project import from, possible values: `github`, `bitbucket`, `gitlab`, `google_code`, `fogbugz`, `git`, and `gitlab_project`. |
+| `local_markdown_version` | integer | no | Increase this value when any cached markdown should be invalidated. |
| `max_artifacts_size` | integer | no | Maximum artifacts size in MB |
| `max_attachment_size` | integer | no | Limit attachment size in MB |
| `max_pages_size` | integer | no | Maximum size of pages repositories in MB |
@@ -266,6 +280,7 @@ are listed in the descriptions of the relevant settings.
| `mirror_capacity_threshold` | integer | no | **(PREMIUM)** Minimum capacity to be available before scheduling more mirrors preemptively |
| `mirror_max_capacity` | integer | no | **(PREMIUM)** Maximum number of mirrors that can be synchronizing at the same time. |
| `mirror_max_delay` | integer | no | **(PREMIUM)** Maximum time (in minutes) between updates that a mirror can have when scheduled to synchronize. |
+| `outbound_local_requests_whitelist` | array of strings | no | Define a list of trusted domains or ip addresses to which local requests are allowed when local requests for hooks and services are disabled.
| `pages_domain_verification_enabled` | boolean | no | Require users to prove ownership of custom domains. Domain verification is an essential security measure for public GitLab sites. Users are required to demonstrate they control a domain before it is enabled. |
| `password_authentication_enabled_for_git` | boolean | no | Enable authentication for Git over HTTP(S) via a GitLab account password. Default is `true`. |
| `password_authentication_enabled_for_web` | boolean | no | Enable authentication for the web interface via a GitLab account password. Default is `true`. |
@@ -277,10 +292,12 @@ are listed in the descriptions of the relevant settings.
| `polling_interval_multiplier` | decimal | no | Interval multiplier used by endpoints that perform polling. Set to `0` to disable polling. |
| `project_export_enabled` | boolean | no | Enable project export. |
| `prometheus_metrics_enabled` | boolean | no | Enable prometheus metrics. |
+| `protected_ci_variables` | boolean | no | Environment variables are protected by default. |
| `pseudonymizer_enabled` | boolean | no | **(PREMIUM)** When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory.
| `recaptcha_enabled` | boolean | no | (**If enabled, requires:** `recaptcha_private_key` and `recaptcha_site_key`) Enable recaptcha. |
| `recaptcha_private_key` | string | required by: `recaptcha_enabled` | Private key for recaptcha. |
| `recaptcha_site_key` | string | required by: `recaptcha_enabled` | Site key for recaptcha. |
+| `receive_max_input_size` | integer | no | Maximum push size (MB). |
| `repository_checks_enabled` | boolean | no | GitLab will periodically run `git fsck` in all project and wiki repositories to look for silent disk corruption issues. |
| `repository_size_limit` | integer | no | **(PREMIUM)** Size limit per repository (MB) |
| `repository_storages` | array of strings | no | A list of names of enabled storage paths, taken from `gitlab.yml`. New projects will be created in one of these stores, chosen at random. |
@@ -292,13 +309,17 @@ are listed in the descriptions of the relevant settings.
| `shared_runners_enabled` | boolean | no | (**If enabled, requires:** `shared_runners_text` and `shared_runners_minutes`) Enable shared runners for new projects. |
| `shared_runners_minutes` | integer | required by: `shared_runners_enabled` | **(PREMIUM)** Set the maximum number of pipeline minutes that a group can use on shared Runners per month. |
| `shared_runners_text` | string | required by: `shared_runners_enabled` | Shared runners text. |
-| `sign_in_text` | string | no | Text on the login page. |
| `signin_enabled` | string | no | (Deprecated: Use `password_authentication_enabled_for_web` instead) Flag indicating if password authentication is enabled for the web interface. |
+| `sign_in_text` | string | no | Text on the login page. |
| `signup_enabled` | boolean | no | Enable registration. Default is `true`. |
| `slack_app_enabled` | boolean | no | **(PREMIUM)** (**If enabled, requires:** `slack_app_id`, `slack_app_secret` and `slack_app_secret`) Enable Slack app. |
| `slack_app_id` | string | required by: `slack_app_enabled` | **(PREMIUM)** The app id of the Slack-app. |
| `slack_app_secret` | string | required by: `slack_app_enabled` | **(PREMIUM)** The app secret of the Slack-app. |
| `slack_app_verification_token` | string | required by: `slack_app_enabled` | **(PREMIUM)** The verification token of the Slack-app. |
+| `snowplow_collector_hostname` | string | required by: `snowplow_enabled` | The Snowplow collector hostname. (e.g. `snowplow.trx.gitlab.net`) |
+| `snowplow_cookie_domain` | string | no | The Snowplow cookie domain. (e.g. `.gitlab.com`) |
+| `snowplow_enabled` | boolean | no | Enable snowplow tracking. |
+| `snowplow_site_id` | string | no | The Snowplow site name / application id. (e.g. `gitlab`) |
| `terminal_max_session_time` | integer | no | Maximum time for web terminal websocket connection (in seconds). Set to `0` for unlimited time. |
| `terms` | text | required by: `enforce_terms` | (**Required by:** `enforce_terms`) Markdown content for the ToS. |
| `throttle_authenticated_api_enabled` | boolean | no | (**If enabled, requires:** `throttle_authenticated_api_period_in_seconds` and `throttle_authenticated_api_requests_per_period`) Enable authenticated API request rate limit. Helps reduce request volume (e.g. from crawlers or abusive bots). |
@@ -317,12 +338,8 @@ are listed in the descriptions of the relevant settings.
| `unique_ips_limit_time_window` | integer | required by: `unique_ips_limit_enabled` | How many seconds an IP will be counted towards the limit. |
| `usage_ping_enabled` | boolean | no | Every week GitLab will report license usage back to GitLab, Inc. |
| `user_default_external` | boolean | no | Newly registered users will be external by default. |
+| `user_default_internal_regex` | string | no | Specify an e-mail address regex pattern to identify default internal users. |
| `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider. |
| `user_show_add_ssh_key_message` | boolean | no | When set to `false` disable the "You won't be able to pull or push project code via SSH" warning shown to users with no uploaded SSH key. |
| `version_check_enabled` | boolean | no | Let GitLab inform you when an update is available. |
-| `local_markdown_version` | integer | no | Increase this value when any cached markdown should be invalidated. |
-| `snowplow_enabled` | boolean | no | Enable snowplow tracking. |
-| `snowplow_collector_hostname` | string | required by: `snowplow_enabled` | The Snowplow collector hostname. (e.g. `snowplow.trx.gitlab.net`) |
-| `snowplow_site_id` | string | no | The Snowplow site name / application id. (e.g. `gitlab`) |
-| `snowplow_cookie_domain` | string | no | The Snowplow cookie domain. (e.g. `.gitlab.com`) |
-| `geo_node_allowed_ips` | string | yes | **(PREMIUM)** Comma-separated list of IPs and CIDRs of allowed secondary nodes. For example, `1.1.1.1, 2.2.2.0/24`. |
+| `web_ide_clientside_preview_enabled` | boolean | no | Client side evaluation (allow live previews of JavaScript projects in the Web IDE using CodeSandbox client side evaluation). |
diff --git a/doc/api/snippets.md b/doc/api/snippets.md
index 1ce0b1e7a62..f90447e124e 100644
--- a/doc/api/snippets.md
+++ b/doc/api/snippets.md
@@ -165,15 +165,15 @@ Parameters:
|:--------------|:-------|:---------|:---------------------------------------------------|
| `title` | string | yes | Title of a snippet. |
| `file_name` | string | yes | Name of a snippet file. |
-| `code` | string | yes | Content of a snippet. |
+| `content` | string | yes | Content of a snippet. |
| `description` | string | no | Description of a snippet. |
-| `visibility` | string | yes | Snippet's [visibility](#snippet-visibility-level). |
+| `visibility` | string | no | Snippet's [visibility](#snippet-visibility-level). |
Example request:
```sh
curl --request POST \
- --data '{"title": "This is a snippet", "code": "Hello world", "description": "Hello World snippet", "file_name": "test.txt", "visibility": "internal" }' \
+ --data '{"title": "This is a snippet", "content": "Hello world", "description": "Hello World snippet", "file_name": "test.txt", "visibility": "internal" }' \
--header 'Content-Type: application/json' \
--header "PRIVATE-TOKEN: valid_api_token" \
https://gitlab.example.com/api/v4/snippets
@@ -222,14 +222,14 @@ Parameters:
| `title` | string | no | Title of a snippet. |
| `file_name` | string | no | Name of a snippet file. |
| `description` | string | no | Description of a snippet. |
-| `code` | string | no | Content of a snippet. |
+| `content` | string | no | Content of a snippet. |
| `visibility` | string | no | Snippet's [visibility](#snippet-visibility-level). |
Example request:
```sh
curl --request PUT \
- --data '{"title": "foo", "code": "bar"}' \
+ --data '{"title": "foo", "content": "bar"}' \
--header 'Content-Type: application/json' \
--header "PRIVATE-TOKEN: valid_api_token" \
https://gitlab.example.com/api/v4/snippets/1
diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md
index a59a0477b80..ab9fa517e23 100644
--- a/doc/ci/caching/index.md
+++ b/doc/ci/caching/index.md
@@ -175,7 +175,7 @@ job:
### Inherit global config, but override specific settings per job
You can override cache settings without overwriting the global cache by using
-[anchors](../yaml/README.md#anchors). For example, if you want to override the
+[anchors](../yaml/README.md#anchors). For example, if you want to override the
`policy` for one job:
```yaml
diff --git a/doc/ci/ci_cd_for_external_repos/github_integration.md b/doc/ci/ci_cd_for_external_repos/github_integration.md
index f639e3dadee..fa56503072d 100644
--- a/doc/ci/ci_cd_for_external_repos/github_integration.md
+++ b/doc/ci/ci_cd_for_external_repos/github_integration.md
@@ -11,35 +11,10 @@ GitLab.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
Watch a video on [Using GitLab CI/CD pipelines with GitHub repositories](https://www.youtube.com/watch?v=qgl3F2j-1cI).
-## Connect with GitHub integration
-
-If the [GitHub integration](../../integration/github.md) has been enabled by your GitLab
-administrator:
-
-1. In GitLab create a **CI/CD for external repo** project and select
- **GitHub**.
-
- ![Create project](img/github_omniauth.png)
-
-1. Once authenticated, you will be redirected to a list of your repositories to
- connect. Click **Connect** to select the repository.
-
- ![Create project](img/github_repo_list.png)
-
-1. In GitHub, add a `.gitlab-ci.yml` to [configure GitLab CI/CD](../quick_start/README.md).
-
-GitLab will:
-
-1. Import the project.
-1. Enable [Pull Mirroring](../../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter).
-1. Enable [GitHub project integration](../../user/project/integrations/github.md).
-1. Create a web hook on GitHub to notify GitLab of new commits.
-
-CAUTION: **Caution:**
-Due to a 10-token limitation on the [GitHub OAuth Implementation](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/#creating-multiple-tokens-for-oauth-apps),
-if you import more than 10 times, your oldest imported project's token will be
-revoked. See issue [#9147](https://gitlab.com/gitlab-org/gitlab-ee/issues/9147)
-for more information.
+NOTE: **Note:**
+Because of [GitHub limitations](https://gitlab.com/gitlab-org/gitlab-ee/issues/9147),
+[GitHub OAuth](../../integration/github.html#enabling-github-oauth)
+cannot be used to authenticate with GitHub as an external CI/CD repository.
## Connect with Personal Access Token
@@ -47,8 +22,7 @@ NOTE: **Note:**
Personal access tokens can only be used to connect GitHub.com
repositories to GitLab.
-If you are not using the [GitHub integration](../../integration/github.md), you can
-still perform a one-off authorization with GitHub to grant GitLab access your
+To perform a one-off authorization with GitHub to grant GitLab access your
repositories:
1. Open <https://github.com/settings/tokens/new> to create a **Personal Access
@@ -69,18 +43,19 @@ repositories:
1. In GitHub, add a `.gitlab-ci.yml` to [configure GitLab CI/CD](../quick_start/README.md).
-GitLab will import the project, enable [Pull Mirroring](../../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter), enable
-[GitHub project integration](../../user/project/integrations/github.md), and create a web hook
-on GitHub to notify GitLab of new commits.
+GitLab will:
+
+1. Import the project.
+1. Enable [Pull Mirroring](../../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter)
+1. Enable [GitHub project integration](../../user/project/integrations/github.md)
+1. Create a web hook on GitHub to notify GitLab of new commits.
## Connect manually
NOTE: **Note:**
-To use **GitHub Enterprise** with **GitLab.com** use this method.
+To use **GitHub Enterprise** with **GitLab.com**, use this method.
-If the [GitHub integration](../../integration/github.md) is not enabled, or is enabled
-for a different GitHub instance, you GitLab CI/CD can be manually enabled for
-your repository:
+To manually enable GitLab CI/CD for your repository:
1. In GitHub open <https://github.com/settings/tokens/new> create a **Personal
Access Token.** GitLab will use this token to access your repository and
diff --git a/doc/ci/ci_cd_for_external_repos/img/github_repo_list.png b/doc/ci/ci_cd_for_external_repos/img/github_repo_list.png
deleted file mode 100644
index 73579dd3cf1..00000000000
--- a/doc/ci/ci_cd_for_external_repos/img/github_repo_list.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 2cbad5f101c..a48da557e09 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -575,6 +575,43 @@ For private and internal projects:
docker login -u $CI_DEPLOY_USER -p $CI_DEPLOY_PASSWORD $CI_REGISTRY
```
+### Using docker-in-docker image from Container Registry
+
+If you want to use your own Docker images for docker-in-docker there are a few things you need to do in addition to the steps in the [docker-in-docker](#use-docker-in-docker-workflow-with-docker-executor) section:
+
+1. Update the `image` and `service` to point to your registry.
+1. Add a service [alias](https://docs.gitlab.com/ee/ci/yaml/#servicesalias).
+
+Below is an example of what your `.gitlab-ci.yml` should look like,
+assuming you have it configured with [TLS enabled](#tls-enabled):
+
+```yaml
+ build:
+ image: $CI_REGISTRY/group/project/docker:19.03.1
+ services:
+ - name: $CI_REGISTRY/group/project/docker:19.03.1-dind
+ alias: docker
+ variables:
+ # Specify to Docker where to create the certificates, Docker will
+ # create them automatically on boot, and will create
+ # `/certs/client` that will be shared between the service and
+ # build container.
+ DOCKER_TLS_CERTDIR: "/certs"
+ DOCKER_DRIVER: overlay2
+ stage: build
+ script:
+ - docker build -t my-docker-image .
+ - docker run my-docker-image /script/to/run/tests
+```
+
+If you forget to set the service alias, the `docker:19.03.1` image won't find the
+`dind` service, and an error like the following is thrown:
+
+```sh
+$ docker info
+error during connect: Get http://docker:2376/v1.39/info: dial tcp: lookup docker on 192.168.0.1:53: no such host
+```
+
### Container Registry examples
If you're using docker-in-docker on your Runners, this is how your `.gitlab-ci.yml`
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index f6c47a99712..c2d444cb1d6 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -675,9 +675,10 @@ fetch line:
fetch = +refs/environments/*:refs/remotes/origin/environments/*
```
-### Scoping environments with specs **(PREMIUM)**
+### Scoping environments with specs
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2112) in [GitLab Premium](https://about.gitlab.com/pricing/) 9.4.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2112) in [GitLab Premium](https://about.gitlab.com/pricing/) 9.4.
+> - [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/30779) to Core in Gitlab 12.2.
You can limit the environment scope of a variable by
defining which environments it can be available for.
diff --git a/doc/ci/large_repositories/index.md b/doc/ci/large_repositories/index.md
index 29d649ad717..b1359537fca 100644
--- a/doc/ci/large_repositories/index.md
+++ b/doc/ci/large_repositories/index.md
@@ -84,7 +84,7 @@ Fork workflow from GitLab Runner's perspective is stored as a separate repositor
with separate worktree. That means that GitLab Runner cannot optimize the usage
of worktrees and you might have to instruct GitLab Runner to use that.
-In such cases, ideally you want to make the GitLab Runner executor be used only used only
+In such cases, ideally you want to make the GitLab Runner executor be used only
for the given project and not shared across different projects to make this
process more efficient.
diff --git a/doc/ci/multi_project_pipelines.md b/doc/ci/multi_project_pipelines.md
index 61f260cb70d..377ae9717b2 100644
--- a/doc/ci/multi_project_pipelines.md
+++ b/doc/ci/multi_project_pipelines.md
@@ -176,6 +176,18 @@ Upstream pipelines take precedence over downstream ones. If there are two
variables with the same name defined in both upstream and downstream projects,
the ones defined in the upstream project will take precedence.
+### Mirroring status from triggered pipeline
+
+You can mirror the pipeline status from the triggered pipeline to the source
+bridge job by using `strategy: depend`. For example:
+
+```yaml
+trigger_job:
+ trigger:
+ project: my/project
+ strategy: depend
+```
+
### Mirroring status from upstream pipeline
You can mirror the pipeline status from an upstream pipeline to a bridge job by
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 5a15b907da0..438b7c03b51 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -401,7 +401,7 @@ Once you set them, they will be available for all subsequent pipelines.
You can limit the environment scope of a variable by
[defining which environments][envs] it can be available for.
-To learn more about about scoping environments, see [Scoping environments with specs](../environments.md#scoping-environments-with-specs-premium).
+To learn more about about scoping environments, see [Scoping environments with specs](../environments.md#scoping-environments-with-specs).
### Deployment environment variables
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 7a60dedc206..bfba21646b5 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -100,7 +100,7 @@ The following table lists available parameters for jobs:
| [`stage`](#stage) | Defines a job stage (default: `test`). |
| [`only`](#onlyexcept-basic) | Limit when jobs are created. Also available: [`only:refs`, `only:kubernetes`, `only:variables`, and `only:changes`](#onlyexcept-advanced). |
| [`except`](#onlyexcept-basic) | Limit when jobs are not created. Also available: [`except:refs`, `except:kubernetes`, `except:variables`, and `except:changes`](#onlyexcept-advanced). |
-| [`rules`](#rules) | List of coniditions to evaluate and determine selected attributes of a build and whether or not it is created. May not be used alongside `only`/`except`.
+| [`rules`](#rules) | List of conditions to evaluate and determine selected attributes of a job, and whether or not it is created. May not be used alongside `only`/`except`. |
| [`tags`](#tags) | List of tags which are used to select Runner. |
| [`allow_failure`](#allow_failure) | Allow job to fail. Failed job doesn't contribute to commit status. |
| [`when`](#when) | When to run job. Also available: `when:manual` and `when:delayed`. |
@@ -113,7 +113,7 @@ The following table lists available parameters for jobs:
| [`parallel`](#parallel) | How many instances of a job should be run in parallel. |
| [`trigger`](#trigger-premium) | Defines a downstream pipeline trigger. |
| [`include`](#include) | Allows this job to include external YAML files. Also available: `include:local`, `include:file`, `include:template`, and `include:remote`. |
-| [`extends`](#extends) | Configuration entries that this job is going to inherit from. |
+| [`extends`](#extends) | Configuration entries that this job is going to inherit from. |
| [`pages`](#pages) | Upload the result of a job to use with GitLab Pages. |
| [`variables`](#variables) | Define job variables on a job level. |
@@ -693,31 +693,41 @@ and triggers the `docker build service one` job.
### `rules`
-Using `rules` allows for a list of individual rule objects to be evaluated
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29011) in GitLab 12.3.
+
+`rules` allows for a list of individual rule objects to be evaluated
*in order*, until one matches and dynamically provides attributes to the job.
Available rule clauses include:
-- `if` (similar to [`only:variables`](#onlyvariablesexceptvariables)).
-- `changes` (same as [`only:changes`](#onlychangesexceptchanges)).
+- [`if`](#rulesif)
+ (similar to [`only:variables`](#onlyvariablesexceptvariables)).
+- [`changes`](#ruleschanges)
+ (same as [`only:changes`](#onlychangesexceptchanges)).
-For example, using `if`:
+For example, using `if`. This configuration specifies that `job` should be built
+and run for every pipeline on merge requests targeting `master`, regardless of
+the status of other builds:
```yaml
job:
script: "echo Hello, Rules!"
rules:
- - if: '$CI_MERGE_REQUEST_TARGET_BRANCH == "master"' # This rule will be evaluated
+ - if: '$CI_MERGE_REQUEST_TARGET_BRANCH == "master"'
when: always
- - if: '$VAR =~ /pattern/' # This rule will only be evaluated if the first does not match
+ - if: '$VAR =~ /pattern/'
when: manual
- - when: on_success # A Rule entry with no conditional clauses evaluates to true. If neither of the first two Rules match, this one will and set job:when to "on_success"
+ - when: on_success
```
-If the first rule does not match, further rules will be evaluated sequentially
-until a match is found. The above configuration will specify that `job` should
-be built and run for every pipeline on merge requests targeting `master`,
-regardless of the status of other builds.
+In this example, if the first rule:
+
+- Matches, the job will be given the `when:always` attribute.
+- Does not match, the second and third rules will be evaluated sequentially
+ until a match is found. That is, the job will be given either the:
+ - `when: manual` attribute if the second rule matches.
+ - `when: on_success` attribute if the second rule does not match. The third
+ rule will always match when reached because it has no conditional clauses.
#### `rules:if`
@@ -744,10 +754,9 @@ at all, the behavior defaults to `job:when`, which continues to default to
#### `rules:changes`
`changes` works exactly the same way as [`only`/`except`](#onlychangesexceptchanges),
-accepting an array of paths. The following configuration configures a job to be
-run manually if `Dockerfile` has changed OR `$VAR == "string value"`. Otherwise
-it is set to `when:on_success` by the last rule, where 0 clauses evaluate as
-vacuously true.
+accepting an array of paths.
+
+For example:
```yaml
docker build:
@@ -762,12 +771,20 @@ docker build:
```
-#### Complex Rule Clauses
+In this example, a job either set to:
+
+- Run manually if `Dockerfile` has changed OR `$VAR == "string value"`.
+- `when:on_success` by the last rule, where no earlier clauses evaluate to true.
+
+#### Complex rule clauses
To conjoin `if` and `changes` clauses with an AND, use them in the same rule.
-Here we run the job manually if `Dockerfile` or any file in `docker/scripts/`
-has changed AND `$VAR == "string value"`. Otherwise, the job will not be
-included in the pipeline.
+
+In the following example:
+
+- We run the job manually if `Dockerfile` or any file in `docker/scripts/`
+ has changed AND `$VAR == "string value"`.
+- Otherwise, the job will not be included in the pipeline.
```yaml
docker build:
@@ -781,18 +798,27 @@ docker build:
# - when: never would be redundant here, this is implied any time rules are listed.
```
-The only clauses currently available are `if` and `changes`. Keywords such as
-`branches` or `refs` that are currently available for `only`/`except` are not
-yet available in `rules` as they are being individually considered for their
-usage and behavior in the newer context.
+The only clauses currently available are:
+
+- `if`
+- `changes`
+
+Keywords such as `branches` or `refs` that are currently available for
+`only`/`except` are not yet available in `rules` as they are being individually
+considered for their usage and behavior in this context.
#### Permitted attributes
-The only job attributes currently set by `rules` are `when` and `start_in`, if
-`when` is set to `delayed`. A job will be included in a pipeline if `when` is
-evaluated to any value except `never`.
+The only job attributes currently set by `rules` are:
+
+- `when`.
+- `start_in`, if `when` is set to `delayed`.
+
+A job will be included in a pipeline if `when` is evaluated to any value
+except `never`.
-Delayed jobs require a `start_in` value, so rule objects do as well. For example:
+Delayed jobs require a `start_in` value, so rule objects do as well. For
+example:
```yaml
docker build:
@@ -806,9 +832,9 @@ docker build:
```
-Additional Job configuration may be added to rules in the future, if something
-useful isn't available, please open an issue on
-[Gitlab CE](https://www.gitlab.com/gitlab-org/gitlab-ce/issues).
+Additional job configuration may be added to rules in the future. If something
+useful isn't available, please
+[open an issue](https://www.gitlab.com/gitlab-org/gitlab-ce/issues).
### `tags`
@@ -1801,7 +1827,8 @@ and bring back the old behavior.
### `needs`
-> Introduced in GitLab 12.2.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/47063) in GitLab 12.2.
+> - In GitLab 12.3, maximum number of jobs in `needs` array raised from five to 50.
The `needs:` keyword enables executing jobs out-of-order, allowing you to implement
a [directed acyclic graph](../directed_acyclic_graph/index.md) in your `.gitlab-ci.yml`.
@@ -1852,30 +1879,26 @@ This example creates three paths of execution:
#### Requirements and limitations
-1. If `needs:` is set to point to a job that is not instantiated
- because of `only/except` rules or otherwise does not exist, the
- job will fail.
-1. Note that on day one of the launch, we are temporarily limiting the
- maximum number of jobs that a single job can need in the `needs:` array. Track
- our [infrastructure issue](https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/7541)
- for details on the current limit.
-1. If you use `dependencies:` with `needs:`, it's important that you
- do not mark a job as having a dependency on something that won't
- have been run at the time it needs it. It's better to use both
- keywords in this case so that GitLab handles the ordering appropriately.
-1. It is impossible for now to have `needs: []` (empty needs),
- the job always needs to depend on something, unless this is the job
- in the first stage (see [gitlab-ce#65504](https://gitlab.com/gitlab-org/gitlab-ce/issues/65504)).
-1. If `needs:` refers to a job that is marked as `parallel:`.
- the current job will depend on all parallel jobs created.
-1. `needs:` is similar to `dependencies:` in that it needs to use jobs from
- prior stages, meaning it is impossible to create circular
- dependencies or depend on jobs in the current stage (see [gitlab-ce#65505](https://gitlab.com/gitlab-org/gitlab-ce/issues/65505)).
-1. Related to the above, stages must be explicitly defined for all jobs
- that have the keyword `needs:` or are referred to by one.
-1. For self-managed users, the feature must be turned on using the `ci_dag_support`
- feature flag. The `ci_dag_limit_needs` option, if set, will limit the number of
- jobs that a single job can need to `50`. If unset, the limit is `5`.
+- If `needs:` is set to point to a job that is not instantiated
+ because of `only/except` rules or otherwise does not exist, the
+ pipeline will be created with YAML error.
+- We are temporarily limiting the maximum number of jobs that a single job can
+ need in the `needs:` array:
+ - For GitLab.com, the limit is five. For more information, see our
+ [infrastructure issue](https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/7541).
+ - For self-managed instances, the limit is:
+ - Five by default (`ci_dag_limit_needs` feature flag is enabled).
+ - 50 if the `ci_dag_limit_needs` feature flag is disabled.
+- It is impossible for now to have `needs: []` (empty needs),
+ the job always needs to depend on something, unless this is the job
+ in the first stage (see [gitlab-ce#65504](https://gitlab.com/gitlab-org/gitlab-ce/issues/65504)).
+- If `needs:` refers to a job that is marked as `parallel:`.
+ the current job will depend on all parallel jobs created.
+- `needs:` is similar to `dependencies:` in that it needs to use jobs from
+ prior stages, meaning it is impossible to create circular
+ dependencies or depend on jobs in the current stage (see [gitlab-ce#65505](https://gitlab.com/gitlab-org/gitlab-ce/issues/65505)).
+- Related to the above, stages must be explicitly defined for all jobs
+ that have the keyword `needs:` or are referred to by one.
### `coverage`
@@ -1989,6 +2012,7 @@ Marking a job to be run in parallel requires only a simple addition to your conf
script: rspec
+ parallel: 5
```
+
TIP: **Tip:**
Parallelize tests suites across parallel jobs.
Different languages have different tools to facilitate this.
@@ -2170,6 +2194,14 @@ include:
- template: Auto-DevOps.gitlab-ci.yml
```
+Multiple `include:template` files:
+
+```yaml
+include:
+ - template: Android-Fastlane.gitlab-ci.yml
+ - template: Auto-DevOps.gitlab-ci.yml
+```
+
All [nested includes](#nested-includes) will be executed only with the permission of the user,
so it is possible to use project, remote or template includes.
diff --git a/doc/development/README.md b/doc/development/README.md
index 3912a828dec..bbe73570f49 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -22,6 +22,7 @@ description: 'Learn how to contribute to GitLab.'
- [Guidelines for implementing Enterprise Edition features](ee_features.md)
- [Security process for developers](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#security-releases-critical-non-critical-as-a-developer)
- [Requesting access to Chatops on GitLab.com](chatops_on_gitlabcom.md#requesting-access) (for GitLabbers)
+- [Danger bot](dangerbot.md)
## UX and Frontend guides
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index 2adca2dae28..ee5fc553e27 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -651,7 +651,7 @@ We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/ha
[shell-charts]: https://docs.gitlab.com/charts/charts/gitlab/gitlab-shell/
[shell-source]: ../install/installation.md#install-gitlab-shell
[pages-omnibus]: ../administration/pages/index.md
-[pages-charts]: https://gitlab.com/charts/gitlab/issues/37
+[pages-charts]: https://gitlab.com/gitlab-org/charts/gitlab/issues/37
[pages-source]: ../install/installation.md#install-gitlab-pages
[pages-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/pages.md
[registry-omnibus]: ../administration/container_registry.md#container-registry-domain-configuration
@@ -673,9 +673,9 @@ We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/ha
[grafana-omnibus]: ../administration/monitoring/performance/grafana_configuration.md
[grafana-charts]: https://github.com/helm/charts/tree/master/stable/grafana
[sentry-omnibus]: https://docs.gitlab.com/omnibus/settings/configuration.html#error-reporting-and-logging-with-sentry
-[sentry-charts]: https://gitlab.com/charts/gitlab/issues/1319
+[sentry-charts]: https://gitlab.com/gitlab-org/charts/gitlab/issues/1319
[jaeger-omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab/issues/4104
-[jaeger-charts]: https://gitlab.com/charts/gitlab/issues/1320
+[jaeger-charts]: https://gitlab.com/gitlab-org/charts/gitlab/issues/1320
[jaeger-source]: ../development/distributed_tracing.md#enabling-distributed-tracing
[jaeger-gdk]: ../development/distributed_tracing.html#using-jaeger-in-the-gitlab-development-kit
[redis-exporter-omnibus]: ../administration/monitoring/prometheus/redis_exporter.md
@@ -687,7 +687,7 @@ We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/ha
[gitlab-monitor-omnibus]: ../administration/monitoring/prometheus/gitlab_monitor_exporter.md
[gitlab-monitor-charts]: https://docs.gitlab.com/charts/charts/gitlab/gitlab-monitor/index.html
[node-exporter-omnibus]: ../administration/monitoring/prometheus/node_exporter.md
-[node-exporter-charts]: https://gitlab.com/charts/gitlab/issues/1332
+[node-exporter-charts]: https://gitlab.com/gitlab-org/charts/gitlab/issues/1332
[mattermost-omnibus]: https://docs.gitlab.com/omnibus/gitlab-mattermost/
[mattermost-charts]: https://docs.mattermost.com/install/install-mmte-helm-gitlab-helm.html
[minio-omnibus]: https://min.io/download
@@ -705,7 +705,7 @@ We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/ha
[certificate-management-source]: ../install/installation.md#using-https
[certificate-management-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/https.md
[geo-omnibus]: ../administration/geo/replication/index.md#setup-instructions
-[geo-charts]: https://gitlab.com/charts/gitlab/issues/8
+[geo-charts]: https://gitlab.com/gitlab-org/charts/gitlab/issues/8
[geo-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/geo.md
[ldap-omnibus]: ../administration/auth/ldap.md
[ldap-charts]: https://docs.gitlab.com/charts/charts/globals.html#ldap
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index b7d74b17eb3..80890d96159 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -34,7 +34,7 @@ more than one approval, the last maintainer to review and approve it will also m
### Reviewer roulette
-The `danger-review` CI job will randomly pick a reviewer and a maintainer for
+The [Danger bot](dangerbot.md) randomly picks a reviewer and a maintainer for
each area of the codebase that your merge request seems to touch. It only makes
recommendations - feel free to override it if you think someone else is a better
fit!
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index f00a810ec42..8b5d380ad9e 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -106,7 +106,7 @@ Stage labels specify which [stage](https://about.gitlab.com/handbook/product/cat
Stage labels respects the `devops::<stage_key>` naming convention.
`<stage_key>` is the stage key as it is in the single source of truth for stages at
<https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml>
-with `_` replaced with ` `.
+with `_` replaced with a space.
For instance, the "Manage" stage is represented by the ~"devops::manage" label in
the `gitlab-org` group since its key under `stages` is `manage`.
@@ -132,7 +132,7 @@ Group labels respects the `group::<group_key>` naming convention and
their color is `#A8D695`.
`<group_key>` is the group key as it is in the single source of truth for groups at
<https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml>,
-with `_` replaced with ` `.
+with `_` replaced with a space.
For instance, the "Continuous Integration" group is represented by the
~"group::continuous integration" label in the `gitlab-org` group since its key
diff --git a/doc/development/contributing/style_guides.md b/doc/development/contributing/style_guides.md
index 7832850a9f0..f825b3d7088 100644
--- a/doc/development/contributing/style_guides.md
+++ b/doc/development/contributing/style_guides.md
@@ -25,8 +25,13 @@
1. [Python](../python_guide/index.md)
1. [Shell scripting](../shell_scripting_guide/index.md)
+## Checking the style and other issues
+
This is also the style used by linting tools such as
[RuboCop](https://github.com/rubocop-hq/rubocop) and [Hound CI](https://houndci.com).
+You can run RuboCop by hand or install a tool like [Overcommit](https://github.com/sds/overcommit) to run it for you.
+Overcommit will automatically run the configured checks (like Rubocop) on every modified file before commit. You can use the example overcommit configuration found in `.overcommit.yml.example` as a quickstart.
+This saves you time as you don't have to wait for the same errors to be detected by the CI.
---
diff --git a/doc/development/dangerbot.md b/doc/development/dangerbot.md
new file mode 100644
index 00000000000..5fc5886e3a2
--- /dev/null
+++ b/doc/development/dangerbot.md
@@ -0,0 +1,116 @@
+# Danger bot
+
+The GitLab CI pipeline includes a `danger-review` job that uses [Danger](https://github.com/danger/danger)
+to perform a variety of automated checks on the code under test.
+
+Danger is a gem that runs in the CI environment, like any other analysis tool.
+What sets it apart from, e.g., Rubocop, is that it's designed to allow you to
+easily write arbitrary code to test properties of your code or changes. To this
+end, it provides a set of common helpers and access to information about what
+has actually changed in your environment, then simply runs your code!
+
+If Danger is asking you to change something about your merge request, it's best
+just to make the change. If you want to learn how Danger works, or make changes
+to the existing rules, then this is the document for you.
+
+## Operation
+
+On startup, Danger reads a [`Dangerfile`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/Dangerfile)
+from the project root. GitLab's Danger code is decomposed into a set of helpers
+and plugins, all within the [`danger/`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/danger/)
+subdirectory, so ours just tells Danger to load it all. Danger will then run
+each plugin against the merge request, collecting the output from each. A plugin
+may output notifications, warnings, or errors, all of which are copied to the
+CI job's log. If an error happens, the CI job (and so the entire pipeline) will
+be failed.
+
+On merge requests, Danger will also copy the output to a comment on the MR
+itself, increasing visibility.
+
+## Development guidelines
+
+Danger code is Ruby code, so all our [usual backend guidelines](README.md#backend-guides)
+continue to apply. However, there are a few things that deserve special emphasis.
+
+### When to use Danger
+
+Danger is a powerful tool and flexible tool, but not always the most appropriate
+way to solve a given problem or workflow.
+
+First, be aware of GitLab's [commitment to dogfooding](https://about.gitlab.com/handbook/engineering/#dogfooding).
+The code we write for Danger is GitLab-specific, and it **may not** be most
+appropriate place to implement functionality that addresses a need we encounter.
+Our users, customers, and even our own satellite projects, such as [Gitaly](https://gitlab.com/gitlab-org/gitaly),
+often face similar challenges, after all. Think about how you could fulfil the
+same need while ensuring everyone can benefit from the work, and do that instead
+if you can.
+
+If a standard tool (e.g. `rubocop`) exists for a task, it is better to use it
+directly, rather than calling it via Danger. Running and debugging the results
+of those tools locally is easier if Danger isn't involved, and unless you're
+using some Danger-specific functionality, there's no benefit to including it in
+the Danger run.
+
+Danger is well-suited to prototyping and rapidly iterating on solutions, so if
+what we want to build is unclear, a solution in Danger can be thought of as a
+trial run to gather information about a product area. If you're doing this, make
+sure the problem you're trying to solve, and the outcomes of that prototyping,
+are captured in an issue or epic as you go along. This will help us to address
+the need as part of the product in a future version of GitLab!
+
+### Implementation details
+
+Implement each task as an isolated piece of functionality and place it in its
+own directory under `danger` as `danger/<task-name>/Dangerfile`.
+
+Add a line to the top-level `Dangerfile` to ensure it is loaded like:
+
+```ruby
+danger.import_dangerfile('danger/<task-name>')
+```
+
+Each task should be isolated from the others, and able to function in isolation.
+If there is code that should be shared between multiple tasks, add a plugin to
+`danger/plugins/...` and require it in each task that needs it. You can also
+create plugins that are specific to a single task, which is a natural place for
+complex logic related to that task.
+
+Danger code is just Ruby code. It should adhere to our coding standards, and
+needs tests, like any other piece of Ruby in our codebase. However, we aren't
+able to test a `Dangerfile` directly! So, to maximise test coverage, try to
+minimize the number of lines of code in `danger/`. A non-trivial `Dangerfile`
+should mostly call plugin code with arguments derived from the methods provided
+by Danger. The plugin code itself should have unit tests.
+
+At present, we do this by putting the code in a module in `lib/gitlab/danger/...`,
+and including it in the matching `danger/plugins/...` file. Specs can then be
+added in `spec/lib/gitlab/danger/...`.
+
+You'll only know if your `Dangerfile` works by pushing the branch that contains
+it to GitLab. This can be quite frustrating, as it significantly increases the
+cycle time when developing a new task, or trying to debug something in an
+existing one. If you've followed the guidelines above, most of your code can
+be exercised locally in RSpec, minimizing the number of cycles you need to go
+through in CI. However, you can speed these cycles up somewhat by emptying the
+`.gitlab/ci/rails.gitlab-ci.yml` file in your merge request. Just don't forget
+to revert the change before merging!
+
+You should add the `~Danger bot` label to the merge request before sending it
+for review.
+
+## Current uses
+
+Here is a (non-exhaustive) list of the kinds of things Danger has been used for
+at GitLab so far:
+
+- Coding style
+- Database review workflow
+- Documentation review workflow
+- Merge request metrics
+- Reviewer roulette workflow
+- Single codebase effort
+
+## Limitations
+
+- [`danger local` does not work on GitLab](https://github.com/danger/danger/issues/458)
+- Danger output is not added to a merge request comment if working on a fork.
diff --git a/doc/development/database_review.md b/doc/development/database_review.md
index 367a481ee11..157c64b514c 100644
--- a/doc/development/database_review.md
+++ b/doc/development/database_review.md
@@ -38,6 +38,7 @@ A Merge Request author's role is to:
- If database review is needed, add the ~database label.
- Use the [database changes](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/merge_request_templates/Database%20changes.md)
merge request template, or include the appropriate items in the MR description.
+- [Prepare the merge request for a database review](#how-to-prepare-the-merge-request-for-a-database-review).
A database **reviewer**'s role is to:
@@ -68,7 +69,7 @@ make sure you have applied the ~database label and rerun the
`danger-review` CI job, or pick someone from the
[`@gl-database` team](https://gitlab.com/groups/gl-database/-/group_members).
-### How to prepare for speedy database reviews
+### How to prepare the merge request for a database review
In order to make reviewing easier and therefore faster, please consider preparing a comment
and details for a database reviewer:
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index c9ae00d148a..719b9aa212a 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -4,9 +4,9 @@ description: Learn how to contribute to GitLab Documentation.
# GitLab Documentation guidelines
-GitLab's documentation is [intended as the single source of truth (SSOT)](https://about.gitlab.com/handbook/documentation/) for information about how to configure, use, and troubleshoot GitLab. The documentation contains use cases and usage instructions covering every GitLab feature, organized by product area and subject. This includes topics and workflows that span multiple GitLab features, as well as the use of GitLab with other applications.
+GitLab's documentation is [intended as the single source of truth (SSOT)](https://about.gitlab.com/handbook/documentation/) for information about how to configure, use, and troubleshoot GitLab. The documentation contains use cases and usage instructions for every GitLab feature, organized by product area and subject. This includes topics and workflows that span multiple GitLab features, and the use of GitLab with other applications.
-In addition to this page, the following resources to help craft and contribute documentation are available:
+In addition to this page, the following resources can help you craft and contribute documentation:
- [Style Guide](styleguide.md) - What belongs in the docs, language guidelines, and more.
- [Structure and template](structure.md) - Learn the typical parts of a doc page and how to write each one.
@@ -18,9 +18,9 @@ In addition to this page, the following resources to help craft and contribute d
## Source files and rendered web locations
-Documentation for GitLab Community Edition (CE) and Enterprise Edition (EE), along with GitLab Runner and Omnibus, is published to [docs.gitlab.com](https://docs.gitlab.com). The documentation for CE and EE is also published within the application at `/help` on the domain of the GitLab instance, though there are [plans](https://gitlab.com/groups/gitlab-org/-/epics/693) to end this practice and instead link out from the GitLab application to docs.gitlab.com URLs.
+Documentation for GitLab Community Edition (CE), Enterprise Edition (EE), GitLab Runner, and Omnibus is published to [docs.gitlab.com](https://docs.gitlab.com). Documentation for CE and EE is also published within the application at `/help` on the domain of the GitLab instance.
-At `/help`, only content for your current edition and version is included, whereas multiple versions' content is available at docs.gitlab.com.
+At `/help`, only help for your current edition and version is included. Help for other versions is available at docs.gitlab.com.
The source of the documentation exists within the codebase of each GitLab application in the following repository locations:
@@ -37,9 +37,9 @@ Documentation issues and merge requests are part of their respective repositorie
[Contributions to GitLab docs](workflow.md) are welcome from the entire GitLab community.
-To ensure that GitLab docs keep up with changes to the product, special processes and responsibilities are in place concerning all [feature changes](feature-change-workflow.md)—i.e. development work that impacts the appearance, usage, or administration of a feature.
+To ensure that GitLab docs are current, there are special processes and responsibilities for all [feature changes](feature-change-workflow.md)—i.e. development work that impacts the appearance, usage, or administration of a feature.
-Meanwhile, anyone can contribute [documentation improvements](improvement-workflow.md) large or small that are not associated with a feature change. For example, adding a new doc on how to accomplish a use case that's already possible with GitLab or with third-party tools and GitLab.
+However, anyone can contribute [documentation improvements](improvement-workflow.md) that are not associated with a feature change. For example, adding a new doc on how to accomplish a use case that's already possible with GitLab or with third-party tools and GitLab.
## Markdown and styles
@@ -54,16 +54,14 @@ See the [Structure](styleguide.md#structure) section of the [Documentation Style
## Single codebase
-We currently maintain two sets of docs: one in the
+We maintain two sets of docs: one in the
[gitlab-ce](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc) repo and
one in [gitlab-ee](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc).
-They are identical, but they are different repositories. When the
-time comes to have only one codebase for the GitLab project, we'll be ready.
+These are identical, but they are different repositories.
### CE first
-All merge requests for documentation must be submitted to CE, regardless of the content
-it has. This means that:
+All merge requests for documentation must be submitted to CE. This means that:
- For **EE-only docs changes**, you only have to submit an MR in the CE project.
- For **EE-only features** that touch both the code and the docs, you have to submit
@@ -74,28 +72,28 @@ This might seem like a duplicate effort, but it's only for the short term.
Since the CE and EE docs are combined, it's crucial to add the relevant
[product badges](styleguide.md#product-badges) for all EE documentation, so that
-we can discern which features belong to which tier.
+we can determine which features belong to which tier.
### EE specific lines check
There's a special test in place
([`ee_specific_check.rb`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/scripts/ee_specific_check/ee_specific_check.rb)),
-which, among others, checks and prevents creating/editing new files and directories
+which checks and prevents creating or editing new files or directories
in EE under `doc/`. This should fail when changes to anything in `/doc` are submitted
in an EE MR. To pass the test, simply remove the docs changes from the EE MR, and
[submit them in CE](#ce-first).
## Changing document location
-Changing a document's location requires specific steps to be followed to ensure that
-users can seamlessly access the new doc page, whether they are accesing content
+Changing a document's location requires specific steps to ensure that
+users can seamlessly access the new doc page, whether they are accessing content
on a GitLab instance domain at `/help` or at docs.gitlab.com. Be sure to ping a
GitLab technical writer if you have any questions during the process (such as
whether the move is necessary), and ensure that a technical writer reviews this
change prior to merging.
If you indeed need to change a document's location, do not remove the old
-document, but rather replace all of its content with a new line:
+document, but instead replace all of its content with a new line:
```md
This document was moved to [another location](path/to/new_doc.md).
@@ -105,7 +103,7 @@ where `path/to/new_doc.md` is the relative path to the root directory `doc/`.
---
-For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to
+For example, if you move `doc/workflow/lfs/lfs_administration.md` to
`doc/administration/lfs.md`, then the steps would be:
1. Copy `doc/workflow/lfs/lfs_administration.md` to `doc/administration/lfs.md`
@@ -145,7 +143,7 @@ Things to note:
### Alternative redirection method
-Alternatively to the method described above, you can simply replace the content
+You can also replace the content
of the old file with a frontmatter containing a redirect link:
```yaml
@@ -163,7 +161,7 @@ land on the doc via `/help`.
### Redirections for pages with Disqus comments
-If the documentation page being relocated already has any Disqus comments,
+If the documentation page being relocated already has Disqus comments,
we need to preserve the Disqus thread.
Disqus uses an identifier per page, and for docs.gitlab.com, the page identifier
@@ -189,8 +187,7 @@ even if it's `index.html` or `README.html`.
## Branch naming
If your contribution contains **only** documentation changes, you can speed up
-the CI process by following some branch naming conventions. You have three
-choices:
+the CI process by following these branch naming conventions:
| Branch name | Valid example |
|:----------------------|:-----------------------------|
@@ -199,7 +196,7 @@ choices:
| Ending in `-docs` | `123-update-api-issues-docs` |
If your branch name matches any of the above, it will run only the docs
-tests. If it does not, the whole application test suite will run (including docs tests).
+tests. If not, the whole application test suite will run (including docs tests).
## Merge requests for GitLab documentation
@@ -399,8 +396,8 @@ preview the changes. The docs URL can be found in two places:
triggered pipeline so that you can investigate whether something went wrong
TIP: **Tip:**
-Someone that has no merge rights to the CE/EE projects (think of forks from
-contributors) will not be able to run the manual job. In that case, you can
+Someone with no merge rights to the CE/EE projects (think of forks from
+contributors) cannot run the manual job. In that case, you can
ask someone from the GitLab team who has the permissions to do that for you.
NOTE: **Note:**
@@ -458,8 +455,8 @@ The following GitLab features are used among others:
## Testing
-We treat documentation as code, thus have implemented some testing.
-Currently, the following tests are in place:
+We treat documentation as code, and so use tests to maintain the standards and quality of the docs.
+The current tests are:
1. `docs lint`: Check that all internal (relative) links work correctly and
that all cURL examples in API docs use the full switches. It's recommended
@@ -484,7 +481,7 @@ Currently, the following tests are in place:
### Linting
-To help adhere to the [documentation style guidelines](styleguide.md), and to improve the content
+To help adhere to the [documentation style guidelines](styleguide.md), and improve the content
added to documentation, consider locally installing and running documentation linters. This will
help you catch common issues before raising merge requests for review of documentation.
diff --git a/doc/development/documentation/structure.md b/doc/development/documentation/structure.md
index 025a946da0e..158e69df2a6 100644
--- a/doc/development/documentation/structure.md
+++ b/doc/development/documentation/structure.md
@@ -13,7 +13,7 @@ and the section on Content in the [Style Guide](styleguide.md).
## Components of a documentation page
-Most pages will be dedicated to a specifig GitLab feature or to a use case that involves
+Most pages will be dedicated to a specific GitLab feature or to a use case that involves
one or more features, potentially in conjunction with third-party tools.
Every feature or use case document should include the following content in the following sequence,
diff --git a/doc/development/git_object_deduplication.md b/doc/development/git_object_deduplication.md
index 4dd1edf9b5a..e8af6346524 100644
--- a/doc/development/git_object_deduplication.md
+++ b/doc/development/git_object_deduplication.md
@@ -186,7 +186,7 @@ When a pool repository record is created in SQL on a Geo primary, this
will eventually trigger an event on the Geo secondary. The Geo secondary
will then create the pool repository in Gitaly. This leads to an
"eventually consistent" situation because as each pool participant gets
-synchronized, Geo will eventuall trigger garbage collection in Gitaly on
+synchronized, Geo will eventually trigger garbage collection in Gitaly on
the secondary, at which stage Git objects will get deduplicated.
> TODO How do we handle the edge case where at the time the Geo
diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md
index 941eea2609e..10e8008bad3 100644
--- a/doc/development/gotchas.md
+++ b/doc/development/gotchas.md
@@ -18,7 +18,7 @@ end
Consider the following API spec:
```ruby
-require 'rails_helper'
+require 'spec_helper'
describe API::Labels do
it 'creates a first label' do
@@ -69,7 +69,7 @@ is ok, but `create(:label, title: 'label1')` is not.
Following is the fixed API spec:
```ruby
-require 'rails_helper'
+require 'spec_helper'
describe API::Labels do
it 'creates a first label' do
diff --git a/doc/development/prometheus_metrics.md b/doc/development/prometheus_metrics.md
index 576601372a3..2a34851d21c 100644
--- a/doc/development/prometheus_metrics.md
+++ b/doc/development/prometheus_metrics.md
@@ -44,3 +44,26 @@ class ImportCommonMetrics < ActiveRecord::Migration[4.2]
end
end
```
+
+## GitLab Prometheus metrics
+
+GitLab provides [Prometheus metrics](../administration/monitoring/prometheus/gitlab_metrics.md)
+to monitor itself.
+
+### Adding a new metric
+
+This section describes how to add new metrics for self-monitoring
+([example](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/15440)).
+
+1. Select the [type of metric](https://gitlab.com/gitlab-org/prometheus-client-mmap#metrics):
+
+ - `Gitlab::Metrics.counter`
+ - `Gitlab::Metrics.gauge`
+ - `Gitlab::Metrics.histogram`
+ - `Gitlab::Metrics.summary`
+
+1. Select the appropriate name for your metric. Refer to the guidelines
+ for [Prometheus metric names](https://prometheus.io/docs/practices/naming/#metric-names).
+1. Update the list of [GitLab Prometheus metrics](../administration/monitoring/prometheus/gitlab_metrics.md).
+1. Trigger the relevant page/code that will record the new metric.
+1. Check that the new metric appears at `/-/metrics`.
diff --git a/doc/development/python_guide/index.md b/doc/development/python_guide/index.md
index a80bee27d4a..47d9d96766c 100644
--- a/doc/development/python_guide/index.md
+++ b/doc/development/python_guide/index.md
@@ -7,9 +7,9 @@ As of GitLab 11.10, we require Python 3.
## Installation
-There are several ways of installing python on your system. To be able to use the same version we use in production,
-we suggest you use [pyenv](https://github.com/pyenv/pyenv). It works and behave similar to its counterpart in the
-ruby world: [rbenv](https://github.com/rbenv/rbenv).
+There are several ways of installing Python on your system. To be able to use the same version we use in production,
+we suggest you use [pyenv](https://github.com/pyenv/pyenv). It works and behaves similarly to its counterpart in the
+Ruby world: [rbenv](https://github.com/rbenv/rbenv).
### macOS
@@ -67,7 +67,7 @@ Running this command will install both the required Python version as well as re
## Use instructions
-To run any python code under the Pipenv environment, you need to first start a `virtualenv` based on the dependencies
+To run any Python code under the Pipenv environment, you need to first start a `virtualenv` based on the dependencies
of the application. With Pipenv, this is a simple as running:
```bash
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index f30a83a4c71..0f982c3a48b 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -13,7 +13,7 @@ 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
our test design. We can find some helpful heuristics documented in the Handbook in the
-[Test Design](https://about.gitlab.com/handbook/engineering/quality/guidelines/test-engineering/test-design/) section.
+[Test Engineering](https://about.gitlab.com/handbook/engineering/quality/test-engineering/#test-heuristics) section.
## Test speed
diff --git a/doc/development/testing_guide/end_to_end/index.md b/doc/development/testing_guide/end_to_end/index.md
index 3ae3ce183d9..f6a2f642274 100644
--- a/doc/development/testing_guide/end_to_end/index.md
+++ b/doc/development/testing_guide/end_to_end/index.md
@@ -118,7 +118,7 @@ Helm chart][helm-chart], itself deployed with custom
See [Review Apps][review-apps] for more details about Review Apps.
-[helm-chart]: https://gitlab.com/charts/gitlab/
+[helm-chart]: https://gitlab.com/gitlab-org/charts/gitlab/
[cng]: https://gitlab.com/gitlab-org/build/CNG
## How do I run the tests?
diff --git a/doc/development/testing_guide/index.md b/doc/development/testing_guide/index.md
index 96e8c30a679..173471e3af8 100644
--- a/doc/development/testing_guide/index.md
+++ b/doc/development/testing_guide/index.md
@@ -13,7 +13,7 @@ importance.
GitLab is built on top of [Ruby on Rails](https://rubyonrails.org/), and we're using [RSpec] for all
the backend tests, with [Capybara] for end-to-end integration testing.
-On the frontend side, we're using [Karma] and [Jasmine] for JavaScript unit and
+On the frontend side, we're using [Jest](https://jestjs.io/) and [Karma](http://karma-runner.github.io/)/[Jasmine](https://jasmine.github.io/) for JavaScript unit and
integration testing.
Following are two great articles that everyone should read to understand what
@@ -64,6 +64,4 @@ Everything you should know about how to run end-to-end tests using
[RSpec]: https://github.com/rspec/rspec-rails#feature-specs
[Capybara]: https://github.com/teamcapybara/capybara
-[Karma]: http://karma-runner.github.io/
-[Jasmine]: https://jasmine.github.io/
[gitlab-qa]: https://gitlab.com/gitlab-org/gitlab-qa
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index 28a60660995..13772cbe015 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -267,7 +267,7 @@ find a way to limit it to only us.**
- [Review Apps integration for CE/EE (presentation)](https://docs.google.com/presentation/d/1QPLr6FO4LduROU8pQIPkX1yfGvD13GEJIBOenqoKxR8/edit?usp=sharing)
-[charts-1068]: https://gitlab.com/charts/gitlab/issues/1068
+[charts-1068]: https://gitlab.com/gitlab-org/charts/gitlab/issues/1068
[gitlab-pipeline]: https://gitlab.com/gitlab-org/gitlab-ce/pipelines/44362587
[gitlab:assets:compile]: https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/149511610
[review-build-cng]: https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/149511623
@@ -276,7 +276,7 @@ find a way to limit it to only us.**
[cng]: https://gitlab.com/gitlab-org/build/CNG
[cng-mirror-pipeline]: https://gitlab.com/gitlab-org/build/CNG-mirror/pipelines/44364657
[cng-mirror-registry]: https://gitlab.com/gitlab-org/build/CNG-mirror/container_registry
-[helm-chart]: https://gitlab.com/charts/gitlab/
+[helm-chart]: https://gitlab.com/gitlab-org/charts/gitlab/
[review-apps-ce]: https://console.cloud.google.com/kubernetes/clusters/details/us-central1-a/review-apps-ce?project=gitlab-review-apps
[review-apps-ee]: https://console.cloud.google.com/kubernetes/clusters/details/us-central1-b/review-apps-ee?project=gitlab-review-apps
[review-apps.sh]: https://gitlab.com/gitlab-org/gitlab-ee/blob/master/scripts/review_apps/review-apps.sh
diff --git a/doc/development/ux_guide/animation.md b/doc/development/ux_guide/animation.md
index a998ab74a96..0f7a24042bb 100644
--- a/doc/development/ux_guide/animation.md
+++ b/doc/development/ux_guide/animation.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://design.gitlab.com/product-foundations/motion'
+redirect_to: 'https://design.gitlab.com/product-foundations/motion/'
---
-The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/product-foundations/motion).
+The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/product-foundations/motion/).
diff --git a/doc/development/ux_guide/illustrations.md b/doc/development/ux_guide/illustrations.md
index 3592d25c95d..815f870f8c5 100644
--- a/doc/development/ux_guide/illustrations.md
+++ b/doc/development/ux_guide/illustrations.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://design.gitlab.com/product-foundations/illustration'
+redirect_to: 'https://design.gitlab.com/product-foundations/illustration/'
---
-The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/product-foundations/illustration).
+The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/product-foundations/illustration/).
diff --git a/doc/development/what_requires_downtime.md b/doc/development/what_requires_downtime.md
index 944bf5900c5..f4cee410066 100644
--- a/doc/development/what_requires_downtime.md
+++ b/doc/development/what_requires_downtime.md
@@ -45,15 +45,12 @@ rule.
The first step is to ignore the column in the application code. This is
necessary because Rails caches the columns and re-uses this cache in various
-places. This can be done by including the `IgnorableColumn` module into the
-model, followed by defining the columns to ignore. For example, to ignore
+places. This can be done by defining the columns to ignore. For example, to ignore
`updated_at` in the User model you'd use the following:
```ruby
-class User < ActiveRecord::Base
- include IgnorableColumn
-
- ignore_column :updated_at
+class User < ApplicationRecord
+ self.ignored_columns += %i[updated_at]
end
```
@@ -64,8 +61,7 @@ column. Both these changes should be submitted in the same merge request.
Once the changes from step 1 have been released & deployed you can set up a
separate merge request that removes the ignore rule. This merge request can
-simply remove the `ignore_column` line, and the `include IgnorableColumn` line
-if no other `ignore_column` calls remain.
+simply remove the `self.ignored_columns` line.
## Renaming Columns
diff --git a/doc/install/azure/index.md b/doc/install/azure/index.md
index c5939dc6856..dfb1dbcf1ed 100644
--- a/doc/install/azure/index.md
+++ b/doc/install/azure/index.md
@@ -179,7 +179,7 @@ to make sure your VM is configured to use a _static_ public IP address (i.e. not
or you will have to reconfigure the DNS `A` record each time Azure reassigns your VM a new public IP
address. Read [IP address types and allocation methods in Azure][Azure-IP-Address-Types] to learn more.
-## Let's open some ports!
+## Let's open some ports
At this stage you should have a running and fully operational VM. However, none of the services on
your VM (e.g. GitLab) will be publicly accessible via the internet until you have opened up the
@@ -333,6 +333,7 @@ If you're running Windows, you'll need to connect using [PuTTY] or an equivalent
If you're running Linux or macOS, then you already have an SSH client installed.
> **Note:**
+>
> - Remember that you will need to login with the username and password you specified
> [when you created](#basics) your Azure VM
> - If you need to reset your VM password, read
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index de49508b47a..c8c1bb00d83 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -92,10 +92,11 @@ export PKG_CONFIG_PATH="/usr/local/opt/icu4c/lib/pkgconfig:$PKG_CONFIG_PATH"
To build and install the indexer, run:
```sh
-git clone https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer.git
-cd gitlab-elasticsearch-indexer
-make
-sudo make install
+indexer_path=/home/git/gitlab-elasticsearch-indexer
+
+# Run the installation task for gitlab-elasticsearch-indexer:
+sudo -u git -H bundle exec rake gitlab:indexer:install[$indexer_path] RAILS_ENV=production
+cd $indexer_path && sudo make install
```
The `gitlab-elasticsearch-indexer` will be installed to `/usr/local/bin`.
@@ -128,8 +129,10 @@ total are being tracked in [epic &153](https://gitlab.com/groups/gitlab-org/-/ep
## Enabling Elasticsearch
-In order to enable Elasticsearch, you need to have admin access. Go to
-**Admin > Settings > Integrations** and find the "Elasticsearch" section.
+In order to enable Elasticsearch, you need to have admin access. Navigate to
+**Admin Area** (wrench icon), then **Settings > Integrations** and expand the **Elasticsearch** section.
+
+Click **Save changes** for the changes to take effect.
The following Elasticsearch settings are available:
@@ -171,171 +174,222 @@ from the Elasticsearch index as expected.
To disable the Elasticsearch integration:
-1. Navigate to the **Admin > Settings > Integrations**
-1. Find the 'Elasticsearch' section and uncheck 'Search with Elasticsearch enabled'
- and 'Elasticsearch indexing'
-1. Click **Save** for the changes to take effect
-1. (Optional) Delete the existing index by running the command `sudo gitlab-rake gitlab:elastic:delete_index`
+1. Navigate to the **Admin Area** (wrench icon), then **Settings > Integrations**.
+1. Expand the **Elasticsearch** section and uncheck **Elasticsearch indexing**
+ and **Search with Elasticsearch enabled**.
+1. Click **Save changes** for the changes to take effect.
+1. (Optional) Delete the existing index by running one of these commands:
+
+ ```sh
+ # Omnibus installations
+ sudo gitlab-rake gitlab:elastic:delete_index
+
+ # Installations from source
+ bundle exec rake gitlab:elastic:delete_index RAILS_ENV=production
+ ```
## Adding GitLab's data to the Elasticsearch index
-### Indexing small instances (database size less than 500 MiB, size of repos less than 5 GiB)
+While Elasticsearch indexing is enabled, new changes in your GitLab instance will be automatically indexed as they happen.
+To backfill existing data, you can use one of the methods below to index it in background jobs.
-Configure Elasticsearch's host and port in **Admin > Settings**. Then index the data using one of the following commands:
+### Indexing through the administration UI
-```sh
-# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/15390) in [GitLab Starter](https://about.gitlab.com/pricing/) 12.3.
-# Installations from source
-bundle exec rake gitlab:elastic:index RAILS_ENV=production
-```
+To index via the admin area:
+
+1. Navigate to the **Admin Area** (wrench icon), then **Settings > Integrations** and expand the **Elasticsearch** section.
+1. [Enable **Elasticsearch indexing** and configure your host and port](#enabling-elasticsearch).
+1. Create empty indexes using one of the following commands:
+
+ ```sh
+ # Omnibus installations
+ sudo gitlab-rake gitlab:elastic:create_empty_index
+
+ # Installations from source
+ bundle exec rake gitlab:elastic:create_empty_index RAILS_ENV=production
+ ```
+
+1. Click **Index all projects**.
+1. Click **Check progress** in the confirmation message to see the status of the background jobs.
+1. Personal snippets need to be indexed manually by running one of these commands:
+
+ ```sh
+ # Omnibus installations
+ sudo gitlab-rake gitlab:elastic:index_snippets
+
+ # Installations from source
+ bundle exec rake gitlab:elastic:index_snippets RAILS_ENV=production
+ ```
+
+1. After the indexing has completed, enable [**Search with Elasticsearch**](#enabling-elasticsearch).
+
+### Indexing through Rake tasks
+
+#### Indexing small instances
+
+CAUTION: **Warning**:
+This will delete your existing indexes.
+
+If the database size is less than 500 MiB, and the size of all hosted repos is less than 5 GiB:
+
+1. [Enable **Elasticsearch indexing** and configure your host and port](#enabling-elasticsearch).
+1. Index your data using one of the following commands:
+
+ ```sh
+ # Omnibus installations
+ sudo gitlab-rake gitlab:elastic:index
+
+ # Installations from source
+ bundle exec rake gitlab:elastic:index RAILS_ENV=production
+ ```
-After it completes the indexing process, [enable Elasticsearch searching](elasticsearch.md#enabling-elasticsearch).
+1. After the indexing has completed, enable [**Search with Elasticsearch**](#enabling-elasticsearch).
-### Indexing large instances
+#### Indexing large instances
-WARNING: **Warning**:
-Performing asynchronous indexing, as this will describe, will generate a lot of sidekiq jobs.
+CAUTION: **Warning**:
+Performing asynchronous indexing will generate a lot of Sidekiq jobs.
Make sure to prepare for this task by either [Horizontally Scaling](../administration/high_availability/README.md#basic-scaling)
-or creating [extra sidekiq processes](../administration/operations/extra_sidekiq_processes.md)
+or creating [extra Sidekiq processes](../administration/operations/extra_sidekiq_processes.md)
-Configure Elasticsearch's host and port in **Admin > Settings > Integrations**. Then create empty indexes using one of the following commands:
+1. [Enable **Elasticsearch indexing** and configure your host and port](#enabling-elasticsearch).
+1. Create empty indexes using one of the following commands:
-```sh
-# Omnibus installations
-sudo gitlab-rake gitlab:elastic:create_empty_index
+ ```sh
+ # Omnibus installations
+ sudo gitlab-rake gitlab:elastic:create_empty_index
-# Installations from source
-bundle exec rake gitlab:elastic:create_empty_index RAILS_ENV=production
-```
+ # Installations from source
+ bundle exec rake gitlab:elastic:create_empty_index RAILS_ENV=production
+ ```
-Indexing large Git repositories can take a while. To speed up the process, you
-can temporarily disable auto-refreshing and replicating. In our experience, you can expect a 20%
-decrease in indexing time. We'll enable them when indexing is done. This step is optional!
+1. Indexing large Git repositories can take a while. To speed up the process, you
+ can temporarily disable auto-refreshing and replicating. In our experience, you can expect a 20%
+ decrease in indexing time. We'll enable them when indexing is done. This step is optional!
-```bash
-curl --request PUT localhost:9200/gitlab-production/_settings --data '{
- "index" : {
- "refresh_interval" : "-1",
- "number_of_replicas" : 0
- } }'
-```
+ ```bash
+ curl --request PUT localhost:9200/gitlab-production/_settings --data '{
+ "index" : {
+ "refresh_interval" : "-1",
+ "number_of_replicas" : 0
+ } }'
+ ```
-Then enable Elasticsearch indexing and run project indexing tasks:
+1. Index projects and their associated data:
-```sh
-# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index_projects
+ ```sh
+ # Omnibus installations
+ sudo gitlab-rake gitlab:elastic:index_projects
-# Installations from source
-bundle exec rake gitlab:elastic:index_projects RAILS_ENV=production
-```
+ # Installations from source
+ bundle exec rake gitlab:elastic:index_projects RAILS_ENV=production
+ ```
-This enqueues a Sidekiq job for each project that needs to be indexed.
-You can view the jobs in the admin panel (they are placed in the `elastic_indexer`
-queue), or you can query indexing status using a rake task:
+ This enqueues a Sidekiq job for each project that needs to be indexed.
+ You can view the jobs in **Admin Area > Monitoring > Background Jobs > Queues Tab**
+ and click `elastic_indexer`, or you can query indexing status using a rake task:
-```sh
-# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index_projects_status
+ ```sh
+ # Omnibus installations
+ sudo gitlab-rake gitlab:elastic:index_projects_status
-# Installations from source
-bundle exec rake gitlab:elastic:index_projects_status RAILS_ENV=production
+ # Installations from source
+ bundle exec rake gitlab:elastic:index_projects_status RAILS_ENV=production
-Indexing is 65.55% complete (6555/10000 projects)
-```
+ Indexing is 65.55% complete (6555/10000 projects)
+ ```
-If you want to limit the index to a range of projects you can provide the
-`ID_FROM` and `ID_TO` parameters:
+ If you want to limit the index to a range of projects you can provide the
+ `ID_FROM` and `ID_TO` parameters:
-```sh
-# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index_projects ID_FROM=1001 ID_TO=2000
+ ```sh
+ # Omnibus installations
+ sudo gitlab-rake gitlab:elastic:index_projects ID_FROM=1001 ID_TO=2000
-# Installations from source
-bundle exec rake gitlab:elastic:index_projects ID_FROM=1001 ID_TO=2000 RAILS_ENV=production
-```
+ # Installations from source
+ bundle exec rake gitlab:elastic:index_projects ID_FROM=1001 ID_TO=2000 RAILS_ENV=production
+ ```
-Where `ID_FROM` and `ID_TO` are project IDs. Both parameters are optional.
-The above examples will index all projects starting with ID `1001` up to (and including) ID `2000`.
+ Where `ID_FROM` and `ID_TO` are project IDs. Both parameters are optional.
+ The above example will index all projects from ID `1001` up to (and including) ID `2000`.
-TIP: **Troubleshooting:**
-Sometimes the project indexing jobs queued by `gitlab:elastic:index_projects`
-can get interrupted. This may happen for many reasons, but it's always safe
-to run the indexing task again - it will skip those repositories that have
-already been indexed.
+ TIP: **Troubleshooting:**
+ Sometimes the project indexing jobs queued by `gitlab:elastic:index_projects`
+ can get interrupted. This may happen for many reasons, but it's always safe
+ to run the indexing task again. It will skip repositories that have
+ already been indexed.
-As the indexer stores the last commit SHA of every indexed repository in the
-database, you can run the indexer with the special parameter `UPDATE_INDEX` and
-it will check every project repository again to make sure that every commit in
-that repository is indexed, it can be useful in case if your index is outdated:
+ As the indexer stores the last commit SHA of every indexed repository in the
+ database, you can run the indexer with the special parameter `UPDATE_INDEX` and
+ it will check every project repository again to make sure that every commit in
+ a repository is indexed, which can be useful in case if your index is outdated:
-```sh
-# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index_projects UPDATE_INDEX=true ID_TO=1000
+ ```sh
+ # Omnibus installations
+ sudo gitlab-rake gitlab:elastic:index_projects UPDATE_INDEX=true ID_TO=1000
-# Installations from source
-bundle exec rake gitlab:elastic:index_projects UPDATE_INDEX=true ID_TO=1000 RAILS_ENV=production
-```
+ # Installations from source
+ bundle exec rake gitlab:elastic:index_projects UPDATE_INDEX=true ID_TO=1000 RAILS_ENV=production
+ ```
-You can also use the `gitlab:elastic:clear_index_status` Rake task to force the
-indexer to "forget" all progress, so retrying the indexing process from the
-start.
+ You can also use the `gitlab:elastic:clear_index_status` Rake task to force the
+ indexer to "forget" all progress, so it will retry the indexing process from the
+ start.
-The `index_projects` command enqueues jobs to index all project and wiki
-repositories, and most database content. However, snippets still need to be
-indexed separately. To do so, run one of these commands:
+1. Personal snippets are not associated with a project and need to be indexed separately
+ by running one of these commands:
-```sh
-# Omnibus installations
-sudo gitlab-rake gitlab:elastic:index_snippets
+ ```sh
+ # Omnibus installations
+ sudo gitlab-rake gitlab:elastic:index_snippets
-# Installations from source
-bundle exec rake gitlab:elastic:index_snippets RAILS_ENV=production
-```
+ # Installations from source
+ bundle exec rake gitlab:elastic:index_snippets RAILS_ENV=production
+ ```
-Enable replication and refreshing again after indexing (only if you previously disabled it):
+1. Enable replication and refreshing again after indexing (only if you previously disabled it):
-```bash
-curl --request PUT localhost:9200/gitlab-production/_settings --data '{
- "index" : {
- "number_of_replicas" : 1,
- "refresh_interval" : "1s"
- } }'
-```
+ ```bash
+ curl --request PUT localhost:9200/gitlab-production/_settings --data '{
+ "index" : {
+ "number_of_replicas" : 1,
+ "refresh_interval" : "1s"
+ } }'
+ ```
-A force merge should be called after enabling the refreshing above.
+ A force merge should be called after enabling the refreshing above.
-For Elasticsearch 6.x, before proceeding with the force merge, the index should be in read-only mode:
+ For Elasticsearch 6.x, the index should be in read-only mode before proceeding with the force merge:
-```bash
-curl --request PUT localhost:9200/gitlab-production/_settings --data '{
- "settings": {
- "index.blocks.write": true
- } }'
-```
+ ```bash
+ curl --request PUT localhost:9200/gitlab-production/_settings --data '{
+ "settings": {
+ "index.blocks.write": true
+ } }'
+ ```
-Then, initiate the force merge:
+ Then, initiate the force merge:
-```bash
-curl --request POST 'http://localhost:9200/gitlab-production/_forcemerge?max_num_segments=5'
-```
+ ```bash
+ curl --request POST 'http://localhost:9200/gitlab-production/_forcemerge?max_num_segments=5'
+ ```
-After this, if your index is in read-only, switch back to read-write:
+ After this, if your index is in read-only mode, switch back to read-write:
-```bash
-curl --request PUT localhost:9200/gitlab-production/_settings --data '{
- "settings": {
- "index.blocks.write": false
- } }'
-```
+ ```bash
+ curl --request PUT localhost:9200/gitlab-production/_settings --data '{
+ "settings": {
+ "index.blocks.write": false
+ } }'
+ ```
-Enable Elasticsearch search in **Admin > Settings > Integrations**. That's it. Enjoy it!
+1. After the indexing has completed, enable [**Search with Elasticsearch**](#enabling-elasticsearch).
-### Index limit
+### Indexing limitations
-Currently for repository and snippet files, GitLab would only index up to 1 MB of content, in order to avoid indexing timeout.
+For repository and snippet files, GitLab will only index up to 1 MiB of content, in order to avoid indexing timeouts.
## GitLab Elasticsearch Rake Tasks
@@ -352,7 +406,7 @@ There are several rake tasks available to you via the command line:
- [`sudo gitlab-rake gitlab:elastic:index_projects_status`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- This determines the overall status of the indexing. It is done by counting the total number of indexed projects, dividing by a count of the total number of projects, then multiplying by 100.
- [`sudo gitlab-rake gitlab:elastic:create_empty_index`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- - This generates an empty index on the Elasticsearch side.
+ - This generates an empty index on the Elasticsearch side, deleting the existing one if present.
- [`sudo gitlab-rake gitlab:elastic:clear_index_status`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- This deletes all instances of IndexStatus for all projects.
- [`sudo gitlab-rake gitlab:elastic:delete_index`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
@@ -468,7 +522,7 @@ Here are some common pitfalls and how to overcome them:
pp s.search_objects.to_a
```
- See [Elasticsearch Index Scopes](elasticsearch.md#elasticsearch-index-scopes) for more information on searching for specific types of data.
+ See [Elasticsearch Index Scopes](#elasticsearch-index-scopes) for more information on searching for specific types of data.
- **I indexed all the repositories but then switched Elasticsearch servers and now I can't find anything**
diff --git a/doc/integration/slash_commands.md b/doc/integration/slash_commands.md
index 71ea2e25533..86a66dc4569 100644
--- a/doc/integration/slash_commands.md
+++ b/doc/integration/slash_commands.md
@@ -15,6 +15,7 @@ Taking the trigger term as `project-name`, the commands are:
| `/project-name help` | Shows all available slash commands |
| `/project-name issue new <title> <shift+return> <description>` | Creates a new issue with title `<title>` and description `<description>` |
| `/project-name issue show <id>` | Shows the issue with id `<id>` |
+| `/project-name issue close <id>` | Closes the issue with id `<id>` |
| `/project-name issue search <query>` | Shows up to 5 issues matching `<query>` |
| `/project-name issue move <id> to <project>` | Moves issue ID `<id>` to `<project>` |
| `/project-name deploy <from> to <to>` | Deploy from the `<from>` environment to the `<to>` environment |
diff --git a/doc/legal/corporate_contributor_license_agreement.md b/doc/legal/corporate_contributor_license_agreement.md
index 8c1f4a126b1..c8782a2cfc2 100644
--- a/doc/legal/corporate_contributor_license_agreement.md
+++ b/doc/legal/corporate_contributor_license_agreement.md
@@ -16,7 +16,7 @@ You accept and agree to the following terms and conditions for Your present and
Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
- You represent that You are legally entitled to grant the above license. You represent further that each of Your employees is authorized to submit Contributions on Your behalf, but excluding employees that are designated in writing by You as "Not authorized to submit Contributions on behalf of (name of Your corporation here)." Such designations of exclusion for unauthorized employees are to be submitted via email to legal@gitlab.com. It is Your responsibility to notify GitLab B.V. when any change is required to the list of designated employees excluded from submitting Contributions on Your behalf. Such notification should also be sent via email to legal@gitlab.com.
+ You represent that You are legally entitled to grant the above license. You represent further that each of Your employees is authorized to submit Contributions on Your behalf, but excluding employees that are designated in writing by You as "Not authorized to submit Contributions on behalf of (name of Your corporation here)." Such designations of exclusion for unauthorized employees are to be submitted via email to `legal@gitlab.com`. It is Your responsibility to notify GitLab B.V. when any change is required to the list of designated employees excluded from submitting Contributions on Your behalf. Such notification should also be sent via email to `legal@gitlab.com`.
- **Contributions:**
diff --git a/doc/migrate_ci_to_ce/README.md b/doc/migrate_ci_to_ce/README.md
index 2b8379141c3..50e491f29a2 100644
--- a/doc/migrate_ci_to_ce/README.md
+++ b/doc/migrate_ci_to_ce/README.md
@@ -32,7 +32,7 @@ upgrade to 8.0 until you finish the migration procedure.
## Before upgrading
-If you have GitLab CI installed using omnibus-gitlab packages but **you don't want to migrate your existing data**:
+If you have GitLab CI installed using Omnibus GitLab packages but **you don't want to migrate your existing data**:
```bash
mv /var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/gitlab-ci/builds.$(date +%s)
@@ -353,7 +353,7 @@ Errno::EACCES: Permission denied @ rb_sysopen - config/secrets.yml
This can happen if you are updating from versions prior to 7.13 straight to 8.0.
The fix for this is to update to Omnibus 7.14 first and then update it to 8.0.
-### Permission denied when accessing /var/opt/gitlab/gitlab-ci/builds
+### Permission denied when accessing `/var/opt/gitlab/gitlab-ci/builds`
To fix that issue you have to change builds/ folder permission before doing final backup:
diff --git a/doc/push_rules/push_rules.md b/doc/push_rules/push_rules.md
index b1754131e76..7455b577af7 100644
--- a/doc/push_rules/push_rules.md
+++ b/doc/push_rules/push_rules.md
@@ -10,7 +10,7 @@ regular expressions to reject pushes based on commit contents, branch names or f
## Overview
GitLab already offers [protected branches][protected-branches], but there are
-cases when you need some specific rules like preventing git tag removal or
+cases when you need some specific rules like preventing Git tag removal or
enforcing a special format for commit messages.
Push rules are essentially [pre-receive Git hooks][hooks] that are easy to
@@ -27,7 +27,7 @@ Every push rule could have its own use case, but let's consider some examples.
Let's assume you have the following requirements for your workflow:
- every commit should reference a Jira issue, for example: `Refactored css. Fixes JIRA-123.`
-- users should not be able to remove git tags with `git push`
+- users should not be able to remove Git tags with `git push`
All you need to do is write a simple regular expression that requires the mention
of a Jira issue in the commit message, like `JIRA\-\d+`.
@@ -64,7 +64,7 @@ The following options are available.
| Push rule | GitLab version | Description |
| --------- | :------------: | ----------- |
-| Removal of tags with `git push` | **Starter** 7.10 | Forbid users to remove git tags with `git push`. Tags will still be able to be deleted through the web UI. |
+| Removal of tags with `git push` | **Starter** 7.10 | Forbid users to remove Git tags with `git push`. Tags will still be able to be deleted through the web UI. |
| Check whether author is a GitLab user | **Starter** 7.10 | Restrict commits by author (email) to existing GitLab users. |
| Committer restriction | **Premium** 10.2 | GitLab will reject any commit that was not committed by the current authenticated user |
| Check whether commit is signed through GPG | **Premium** 10.1 | Reject commit when it is not signed through GPG. Read [signing commits with GPG][signing-commits]. |
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 336000f6cb9..59b775d13bd 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -92,9 +92,9 @@ If you are running GitLab within a Docker container, you can run the backup from
docker exec -t <container name> gitlab-backup create
```
-If you are using the [GitLab helm chart](https://gitlab.com/charts/gitlab) on a
+If you are using the [GitLab helm chart](https://gitlab.com/gitlab-org/charts/gitlab) on a
Kubernetes cluster, you can run the backup task using `backup-utility` script on
-the GitLab task runner pod via `kubectl`. Refer to [backing up a GitLab installation](https://gitlab.com/charts/gitlab/blob/master/doc/backup-restore/backup.md#backing-up-a-gitlab-installation) for more details:
+the GitLab task runner pod via `kubectl`. Refer to [backing up a GitLab installation](https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/backup-restore/backup.md#backing-up-a-gitlab-installation) for more details:
```sh
kubectl exec -it <gitlab task-runner pod> backup-utility
@@ -764,7 +764,7 @@ docker exec -it <name of container> gitlab-backup restore
```
The GitLab helm chart uses a different process, documented in
-[restoring a GitLab helm chart installation](https://gitlab.com/charts/gitlab/blob/master/doc/backup-restore/restore.md).
+[restoring a GitLab helm chart installation](https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/backup-restore/restore.md).
## Alternative backup strategies
diff --git a/doc/security/README.md b/doc/security/README.md
index e3fb07c69c2..fe96f7f2846 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -18,3 +18,4 @@ type: index
- [Enforce Two-factor authentication](two_factor_authentication.md)
- [Send email confirmation on sign-up](user_email_confirmation.md)
- [Security of running jobs](https://docs.gitlab.com/runner/security/)
+- [Proxying images](asset_proxy.md)
diff --git a/doc/security/asset_proxy.md b/doc/security/asset_proxy.md
new file mode 100644
index 00000000000..6a2341c28c8
--- /dev/null
+++ b/doc/security/asset_proxy.md
@@ -0,0 +1,28 @@
+A possible security concern when managing a public facing GitLab instance is
+the ability to steal a users IP address by referencing images in issues, comments, etc.
+
+For example, adding `![Example image](http://example.com/example.png)` to
+an issue description will cause the image to be loaded from the external
+server in order to be displayed. However this also allows the external server
+to log the IP address of the user.
+
+One way to mitigate this is by proxying any external images to a server you
+control. GitLab handles this by allowing you to run the "Camo" server
+[cactus/go-camo](https://github.com/cactus/go-camo#how-it-works).
+The image request is sent to the Camo server, which then makes the request for
+the original image. This way an attacker only ever seems the IP address
+of your Camo server.
+
+Once you have your Camo server up and running, you can configure GitLab to
+proxy image requests to it. The following settings are supported:
+
+| Attribute | Description |
+| ------------------------- | ----------- |
+| `asset_proxy_enabled` | (**If enabled, requires:** `asset_proxy_url`) Enable proxying of assets. |
+| `asset_proxy_secret_key` | Shared secret with the asset proxy server. |
+| `asset_proxy_url` | URL of the asset proxy server. |
+| `asset_proxy_whitelist` | Assets that match these domain(s) will NOT be proxied. Wildcards allowed. Your GitLab installation URL is automatically whitelisted. |
+
+These can be set via the [Application setting API](../api/settings.md)
+
+Note that a GitLab restart is required to apply any changes.
diff --git a/doc/security/information_exclusivity.md b/doc/security/information_exclusivity.md
index 749ccf924b5..7c3d7284f25 100644
--- a/doc/security/information_exclusivity.md
+++ b/doc/security/information_exclusivity.md
@@ -15,7 +15,7 @@ another project that is under their control, or onto another server.
Therefore, it is impossible to build access controls that prevent the
intentional sharing of source code by users that have access to the source code.
-This is an inherent feature of a DVCS. All git management systems have this
+This is an inherent feature of a DVCS. All Git management systems have this
limitation.
You can take steps to prevent unintentional sharing and information
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
index 1e9eb15533a..24a334e7067 100644
--- a/doc/system_hooks/system_hooks.md
+++ b/doc/system_hooks/system_hooks.md
@@ -13,6 +13,7 @@ Your GitLab instance can perform HTTP POST requests on the following events:
- `project_update`
- `user_add_to_team`
- `user_remove_from_team`
+- `user_update_for_team`
- `user_create`
- `user_destroy`
- `user_failed_login`
@@ -24,6 +25,7 @@ Your GitLab instance can perform HTTP POST requests on the following events:
- `group_rename`
- `user_add_to_group`
- `user_remove_from_group`
+- `user_update_for_group`
The triggers for most of these are self-explanatory, but `project_update` and `project_rename` deserve some clarification: `project_update` is fired any time an attribute of a project is changed (name, description, tags, etc.) *unless* the `path` attribute is also changed. In that case, a `project_rename` is triggered instead (so that, for instance, if all you care about is the repo URL, you can just listen for `project_rename`).
@@ -173,6 +175,26 @@ Please refer to `group_rename` and `user_rename` for that case.
}
```
+**Team Member Updated:**
+
+```json
+{
+ "created_at": "2012-07-21T07:30:56Z",
+ "updated_at": "2012-07-21T07:38:22Z",
+ "event_name": "user_update_for_team",
+ "access_level": "Maintainer",
+ "project_id": 74,
+ "project_name": "StoreCloud",
+ "project_path": "storecloud",
+ "project_path_with_namespace": "jsmith/storecloud",
+ "user_email": "johnsmith@gmail.com",
+ "user_name": "John Smith",
+ "user_username": "johnsmith",
+ "user_id": 41,
+ "project_visibility": "visibilitylevel|private"
+}
+```
+
**User created:**
```json
@@ -349,6 +371,24 @@ If the user is blocked via LDAP, `state` will be `ldap_blocked`.
}
```
+**Group Member Updated:**
+
+```json
+{
+ "created_at": "2012-07-21T07:30:56Z",
+ "updated_at": "2012-07-21T07:38:22Z",
+ "event_name": "user_update_for_group",
+ "group_access": "Maintainer",
+ "group_id": 78,
+ "group_name": "StoreCloud",
+ "group_path": "storecloud",
+ "user_email": "johnsmith@gmail.com",
+ "user_name": "John Smith",
+ "user_username": "johnsmith",
+ "user_id": 41
+}
+```
+
## Push events
Triggered when you push to the repository, except when pushing tags.
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 19d8a6b5e2b..15fdb52ac00 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -95,33 +95,45 @@ Auto DevOps.
To make full use of Auto DevOps, you will need:
-- **GitLab Runner** (needed for all stages) - Your Runner needs to be
- configured to be able to run Docker. Generally this means using the
- [Docker](https://docs.gitlab.com/runner/executors/docker.html) or [Kubernetes
- executor](https://docs.gitlab.com/runner/executors/kubernetes.html), with
+- **GitLab Runner** (for all stages)
+
+ Your Runner needs to be configured to be able to run Docker. Generally this
+ means using the either the [Docker](https://docs.gitlab.com/runner/executors/docker.html)
+ or [Kubernetes](https://docs.gitlab.com/runner/executors/kubernetes.html) executors, with
[privileged mode enabled](https://docs.gitlab.com/runner/executors/docker.html#use-docker-in-docker-with-privileged-mode).
+
The Runners do not need to be installed in the Kubernetes cluster, but the
Kubernetes executor is easy to use and is automatically autoscaling.
Docker-based Runners can be configured to autoscale as well, using [Docker
- Machine](https://docs.gitlab.com/runner/install/autoscaling.html). Runners
- should be registered as [shared Runners](../../ci/runners/README.md#registering-a-shared-runner)
+ Machine](https://docs.gitlab.com/runner/install/autoscaling.html).
+
+ Runners should be registered as [shared Runners](../../ci/runners/README.md#registering-a-shared-runner)
for the entire GitLab instance, or [specific Runners](../../ci/runners/README.md#registering-a-specific-runner)
that are assigned to specific projects.
-- **Base domain** (needed for Auto Review Apps and Auto Deploy) - You will need
- a domain configured with wildcard DNS which is going to be used by all of your
- Auto DevOps applications. [Read the specifics](#auto-devops-base-domain).
-- **Kubernetes** (needed for Auto Review Apps, Auto Deploy, and Auto Monitoring) -
- To enable deployments, you will need Kubernetes 1.5+. You will need a [Kubernetes cluster][kubernetes-clusters]
- for the project.
- - **A load balancer** - You can use NGINX ingress by deploying it to your
- Kubernetes cluster using the
- [`nginx-ingress`](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress)
- Helm chart.
-- **Prometheus** (needed for Auto Monitoring) - To enable Auto Monitoring, you
+- **Base domain** (for Auto Review Apps and Auto Deploy)
+
+ You will need a domain configured with wildcard DNS which is going to be used
+ by all of your Auto DevOps applications.
+
+ Read the [specifics](#auto-devops-base-domain).
+- **Kubernetes** (for Auto Review Apps, Auto Deploy, and Auto Monitoring)
+
+ To enable deployments, you will need:
+
+ - Kubernetes 1.5+.
+ - A [Kubernetes cluster][kubernetes-clusters] for the project.
+ - A load balancer. You can use NGINX ingress by deploying it to your
+ Kubernetes cluster by either:
+ - Using the [`nginx-ingress`](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress) Helm chart.
+ - Installing the Ingress [GitLab Managed App](../../user/clusters/applications.md#ingress).
+- **Prometheus** (for Auto Monitoring)
+
+ To enable Auto Monitoring, you
will need Prometheus installed somewhere (inside or outside your cluster) and
configured to scrape your Kubernetes cluster. To get response metrics
(in addition to system metrics), you need to
[configure Prometheus to monitor NGINX](../../user/project/integrations/prometheus_library/nginx_ingress.md#configuring-nginx-ingress-monitoring).
+
The [Prometheus service](../../user/project/integrations/prometheus.md)
integration needs to be enabled for the project, or enabled as a
[default service template](../../user/project/integrations/services_templates.md)
@@ -694,6 +706,34 @@ will build a Docker image based on the Dockerfile rather than using buildpacks.
This can be much faster and result in smaller images, especially if your
Dockerfile is based on [Alpine](https://hub.docker.com/_/alpine/).
+### Passing arguments to `docker build`
+
+Arguments can be passed to the `docker build` command using the
+`AUTO_DEVOPS_BUILD_IMAGE_EXTRA_ARGS` project variable.
+
+For example, to build a Docker image based on based on the `ruby:alpine`
+instead of the default `ruby:latest`:
+
+1. Set `AUTO_DEVOPS_BUILD_IMAGE_EXTRA_ARGS` to `--build-arg=RUBY_VERSION=alpine`.
+1. Add the following to a custom `Dockerfile`:
+
+ ```docker
+ ARG RUBY_VERSION=latest
+ FROM ruby:$RUBY_VERSION
+
+ # ... put your stuff here
+ ```
+
+NOTE: **Note:**
+Passing in complex values (newlines and spaces, for example) will likely
+cause escaping issues due to the way this argument is used in Auto DevOps.
+Consider using Base64 encoding of such values to avoid this problem.
+
+CAUTION: **Warning:**
+Avoid passing secrets as Docker build arguments if possible, as they may be
+persisted in your image. See
+[this discussion](https://github.com/moby/moby/issues/13490) for details.
+
### Custom Helm Chart
Auto DevOps uses [Helm](https://helm.sh/) to deploy your application to Kubernetes.
@@ -782,6 +822,7 @@ applications.
|-----------------------------------------|------------------------------------|
| `ADDITIONAL_HOSTS` | Fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. |
| `<ENVIRONMENT>_ADDITIONAL_HOSTS` | For a specific environment, the fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. This takes precedence over `ADDITIONAL_HOSTS`. |
+| `AUTO_DEVOPS_BUILD_IMAGE_EXTRA_ARGS` | Extra arguments to be passed to the `docker build` command. Note that using quotes will not prevent word splitting. [More details](#passing-arguments-to-docker-build). |
| `AUTO_DEVOPS_CHART` | Helm Chart used to deploy your apps. Defaults to the one [provided by GitLab](https://gitlab.com/gitlab-org/charts/auto-deploy-app). |
| `AUTO_DEVOPS_CHART_REPOSITORY` | Helm Chart repository used to search for charts. Defaults to `https://charts.gitlab.io`. |
| `AUTO_DEVOPS_CHART_REPOSITORY_NAME` | From Gitlab 11.11, used to set the name of the helm repository. Defaults to `gitlab`. |
diff --git a/doc/topics/git/partial_clone.md b/doc/topics/git/partial_clone.md
index ea4223355d8..c9a5430b2c6 100644
--- a/doc/topics/git/partial_clone.md
+++ b/doc/topics/git/partial_clone.md
@@ -112,7 +112,7 @@ enabled on the Git server:
git init
# Add the remote
- git remote add origin git@gitlab.com/example/jumbo-repo
+ git remote add origin <url>
# Enable partial clone support for the remote
git config --local extensions.partialClone origin
diff --git a/doc/university/process/README.md b/doc/university/process/README.md
index b278e02ccd5..1a555f065ed 100644
--- a/doc/university/process/README.md
+++ b/doc/university/process/README.md
@@ -2,10 +2,6 @@
comments: false
---
----
-title: University | Process
----
-
# Suggesting improvements
If you would like to teach a class or participate or help in any way please
diff --git a/doc/university/training/gitlab_flow.md b/doc/university/training/gitlab_flow.md
index 0ce92be4994..66e645a0af8 100644
--- a/doc/university/training/gitlab_flow.md
+++ b/doc/university/training/gitlab_flow.md
@@ -23,8 +23,6 @@ type: reference
as opposed to individual stable branches
- Consider creating a tag for each version that gets deployed
-## Production branch
-
![inline](gitlab_flow/production_branch.png)
## Release branch
@@ -36,8 +34,6 @@ type: reference
- Cherry-pick critical bug fixes to stable branch for patch release
- Never commit bug fixes directly to stable branch
-## Release branch
-
![inline](gitlab_flow/release_branches.png)
## More details
diff --git a/doc/university/training/topics/git_intro.md b/doc/university/training/topics/git_intro.md
index 845bb7f0a81..c9a1cbb7839 100644
--- a/doc/university/training/topics/git_intro.md
+++ b/doc/university/training/topics/git_intro.md
@@ -15,7 +15,7 @@ comments: false
- Adapts to nearly any workflow
- Fast, reliable and stable file format
-## Help!
+## Help
Use the tools at your disposal when you get stuck.
diff --git a/doc/university/training/user_training.md b/doc/university/training/user_training.md
index e440652edfc..37107e13584 100644
--- a/doc/university/training/user_training.md
+++ b/doc/university/training/user_training.md
@@ -23,7 +23,7 @@ type: reference
- Adapts to nearly any workflow.
- Fast, reliable and stable file format.
-## Help!
+## Help
Use the tools at your disposal when you get stuck.
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index 6a41c6aec32..3c9fcbf7c7d 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -64,7 +64,7 @@ sudo -u git -H bundle exec rake gettext:po_to_json RAILS_ENV=production
sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile cache:clear RAILS_ENV=production NODE_ENV=production NODE_OPTIONS="--max_old_space_size=4096"
```
-### 4. Update gitlab-workhorse to the corresponding version
+### 4. Update GitLab Workhorse to the corresponding version
```bash
cd /home/git/gitlab
@@ -80,7 +80,7 @@ cd /home/git/gitlab
sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories]" RAILS_ENV=production
```
-### 6. Update gitlab-shell to the corresponding version
+### 6. Update GitLab Shell to the corresponding version
```bash
cd /home/git/gitlab-shell
@@ -90,7 +90,7 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION) -b v$(</h
sudo -u git -H sh -c 'if [ -x bin/compile ]; then bin/compile; fi'
```
-### 7. Update gitlab-pages to the corresponding version (skip if not using pages)
+### 7. Update GitLab Pages to the corresponding version (skip if not using pages)
```bash
cd /home/git/gitlab-pages
diff --git a/doc/update/upgrading_from_ce_to_ee.md b/doc/update/upgrading_from_ce_to_ee.md
index 49e2fb2568e..9ac73725f87 100644
--- a/doc/update/upgrading_from_ce_to_ee.md
+++ b/doc/update/upgrading_from_ce_to_ee.md
@@ -17,7 +17,7 @@ GitLab edition you are using (Community or Enterprise), see the
This guide assumes you have a correctly configured and tested installation of
GitLab Community Edition. If you run into any trouble or if you have any
-questions please contact us at [support@gitlab.com].
+questions please contact us at `support@gitlab.com`.
In all examples, replace `EE_BRANCH` with the Enterprise Edition branch for the
version you are using, and `CE_BRANCH` with the Community Edition branch.
@@ -131,5 +131,4 @@ Example:
Additional instructions here.
-->
-[support@gitlab.com]: mailto:support@gitlab.com
[old-ee-upgrade-docs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/11-8-stable-ee/doc/update
diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md
index f6b64dcf694..890a5ec8698 100644
--- a/doc/update/upgrading_from_source.md
+++ b/doc/update/upgrading_from_source.md
@@ -118,7 +118,7 @@ sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
rm go1.11.10.linux-amd64.tar.gz
```
-### 6. Update git
+### 6. Update Git
NOTE: **Note:**
GitLab 11.11 and higher only supports Git 2.21.x and newer, and
@@ -186,7 +186,7 @@ cd /home/git/gitlab
sudo -u git -H git checkout BRANCH-ee
```
-### 8. Update gitlab-shell
+### 8. Update GitLab Shell
```bash
cd /home/git/gitlab-shell
@@ -196,9 +196,9 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
sudo -u git -H bin/compile
```
-### 9. Update gitlab-workhorse
+### 9. Update GitLab Workhorse
-Install and compile gitlab-workhorse. GitLab-Workhorse uses
+Install and compile GitLab Workhorse. GitLab Workhorse uses
[GNU Make](https://www.gnu.org/software/make/).
If you are not using Linux you may have to run `gmake` instead of
`make` below.
@@ -222,11 +222,11 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
sudo -u git -H make
```
-### 11. Update gitlab-pages
+### 11. Update GitLab Pages
#### Only needed if you use GitLab Pages
-Install and compile gitlab-pages. GitLab-Pages uses
+Install and compile GitLab Pages. GitLab Pages uses
[GNU Make](https://www.gnu.org/software/make/).
If you are not using Linux you may have to run `gmake` instead of
`make` below.
@@ -273,8 +273,8 @@ longer handles setting it.
If you are using Apache instead of NGINX please see the updated [Apache templates].
Also note that because Apache does not support upstreams behind Unix sockets you
-will need to let gitlab-workhorse listen on a TCP port. You can do this
-via [/etc/default/gitlab].
+will need to let GitLab Workhorse listen on a TCP port. You can do this
+via [`/etc/default/gitlab`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/init.d/gitlab.default.example#L38).
#### SMTP configuration
@@ -404,4 +404,3 @@ If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of
[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/init.d/gitlab.default.example
[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/initializers/smtp_settings.rb.sample#L13
[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
-[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/init.d/gitlab.default.example#L38
diff --git a/doc/update/upgrading_postgresql_using_slony.md b/doc/update/upgrading_postgresql_using_slony.md
index 835166837a8..cf3a389b149 100644
--- a/doc/update/upgrading_postgresql_using_slony.md
+++ b/doc/update/upgrading_postgresql_using_slony.md
@@ -77,7 +77,7 @@ make
make install
```
-This assumes you have installed GitLab into /opt/gitlab.
+This assumes you have installed GitLab into `/opt/gitlab`.
To test if Slony is installed properly, run the following commands:
diff --git a/doc/user/admin_area/license.md b/doc/user/admin_area/license.md
index f5864e1f828..dbcf250bc57 100644
--- a/doc/user/admin_area/license.md
+++ b/doc/user/admin_area/license.md
@@ -117,4 +117,4 @@ questions that you know someone might ask.
Each scenario can be a third-level heading, e.g. `### Getting error message X`.
If you have none to add when creating a doc, leave this section in place
-but commented out to help encourage others to add to it in the future. --> \ No newline at end of file
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/settings/account_and_limit_settings.md b/doc/user/admin_area/settings/account_and_limit_settings.md
index 5e385b7216d..20691210fbd 100644
--- a/doc/user/admin_area/settings/account_and_limit_settings.md
+++ b/doc/user/admin_area/settings/account_and_limit_settings.md
@@ -44,12 +44,19 @@ there are no restrictions.
These settings can be found within:
-- Each project's settings.
-- A group's settings.
-- The **Size limit per repository (MB)** field in the **Account and limit** section of a GitLab instance's
- settings by navigating to either:
- - **Admin Area > Settings > General**.
- - The path `/admin/application_settings`.
+- Each project's settings:
+ 1. From the Project's homepage, navigate to **Settings > General**.
+ 1. Fill in the **Repository size limit (MB)** field in the **Naming, topics, avatar** section.
+ 1. Click **Save changes**.
+- Each group's settings:
+ 1. From the Group's homepage, navigate to **Settings > General**.
+ 1. Fill in the **Repository size limit (MB)** field in the **Naming, visibility** section.
+ 1. Click **Save changes**.
+- GitLab's global settings:
+ 1. From the Dashboard, navigate to **Admin Area > Settings > General**.
+ 1. Expand the **Account and limit** section.
+ 1. Fill in the **Size limit per repository (MB)** field.
+ 1. Click **Save changes**.
The first push of a new project, including LFS objects, will be checked for size
and **will** be rejected if the sum of their sizes exceeds the maximum allowed
diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md
index bd76b052422..fa14ecdf7cb 100644
--- a/doc/user/admin_area/settings/continuous_integration.md
+++ b/doc/user/admin_area/settings/continuous_integration.md
@@ -127,7 +127,7 @@ but commented out to help encourage others to add to it in the future. -->
GitLab administrators can force a pipeline configuration to run on every
pipeline.
-The configuration applies to all pipelines for a GitLab instance and is
+The configuration applies to all pipelines for a GitLab instance and is
sourced from:
- The [instance template repository](instance_template_repository.md).
diff --git a/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md b/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md
index 8e53a6995fb..6d2f74af660 100644
--- a/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md
+++ b/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md
@@ -6,7 +6,7 @@ type: reference
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/30829) in GitLab 12.2.
-This setting allows you to rate limit the requests to raw endpoints, defaults to `300` requests per minute.
+This setting allows you to rate limit the requests to raw endpoints, defaults to `300` requests per minute.
It can be modified in **Admin Area > Network > Performance Optimization**.
For example, requests over `300` per minute to `https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/controllers/application_controller.rb` will be blocked. Access to the raw file will be released after 1 minute.
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index 7b631a5a1cd..a030f8d96ef 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -96,7 +96,7 @@ them in a YAML file named `clair-whitelist.yml`. Read more in the
## Example
-The following is a sample `.gitlab-ci.yml` that will build your Docker Image, push it to the container registry and run Container Scanning.
+The following is a sample `.gitlab-ci.yml` that will build your Docker Image, push it to the container registry and run Container Scanning.
```yaml
variables:
@@ -155,4 +155,4 @@ docker: Error response from daemon: failed to copy xattrs: failed to set xattr "
This is a result of a bug in Docker which is now [fixed](https://github.com/containerd/continuity/pull/138 "fs: add WithAllowXAttrErrors CopyOpt").
To prevent the error, ensure the Docker version that the Runner is using is
`18.09.03` or higher. For more information, see
-[issue #10241](https://gitlab.com/gitlab-org/gitlab-ee/issues/10241 "Investigate why Container Scanning is not working with NFS mounts"). \ No newline at end of file
+[issue #10241](https://gitlab.com/gitlab-org/gitlab-ee/issues/10241 "Investigate why Container Scanning is not working with NFS mounts").
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index b40392e12d5..3276dc40dc0 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -77,7 +77,7 @@ like Gemnasium require sending data to GitLab central servers to analyze them:
The Gemnasium client does **NOT** send the exact package versions your project relies on.
You can disable the remote checks by [using](#customizing-the-dependency-scanning-settings)
-the `DS_DISABLE_REMOTE_CHECKS` environment variable and setting it to `true`.
+the `DS_DISABLE_REMOTE_CHECKS` environment variable and setting it to `"true"`.
## Configuration
@@ -115,7 +115,7 @@ include:
template: Dependency-Scanning.gitlab-ci.yml
variables:
- DS_DISABLE_REMOTE_CHECKS: true
+ DS_DISABLE_REMOTE_CHECKS: "true"
```
Because template is [evaluated before](../../../ci/yaml/README.md#include) the pipeline
diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md
index fcd683ca2db..69529d7420b 100644
--- a/doc/user/application_security/index.md
+++ b/doc/user/application_security/index.md
@@ -28,7 +28,7 @@ GitLab can scan and report any vulnerabilities found in your project.
| [Dependency List](dependency_list/index.md) **(ULTIMATE)** | View your project's dependencies and their known vulnerabilities. |
| [Dependency Scanning](dependency_scanning/index.md) **(ULTIMATE)** | Analyze your dependencies for known vulnerabilities. |
| [Dynamic Application Security Testing (DAST)](dast/index.md) **(ULTIMATE)** | Analyze running web applications for known vulnerabilities. |
-| [License Compliance](license_management/index.md) **(ULTIMATE)** | Search your project's dependencies for their licenses. |
+| [License Compliance](license_compliance/index.md) **(ULTIMATE)** | Search your project's dependencies for their licenses. |
| [Security Dashboard](security_dashboard/index.md) **(ULTIMATE)** | View vulnerabilities in all your projects and groups. |
| [Static Application Security Testing (SAST)](sast/index.md) **(ULTIMATE)** | Analyze source code for known vulnerabilities. |
@@ -153,7 +153,7 @@ Clicking on this button will create a merge request to apply the solution onto t
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9928) in [GitLab Ultimate](https://about.gitlab.com/pricing) 12.2.
-Merge Request Approvals can be configured to require approval from a member
+Merge Request Approvals can be configured to require approval from a member
of your security team when a vulnerability would be introduced by a merge request.
This threshold is defined as `high`, `critical`, or `unknown`
diff --git a/doc/user/application_security/license_compliance/img/license_compliance_add_license.png b/doc/user/application_security/license_compliance/img/license_compliance_add_license.png
deleted file mode 100644
index c9a5dc14c57..00000000000
--- a/doc/user/application_security/license_compliance/img/license_compliance_add_license.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/license_compliance/img/license_compliance_add_license_v12_3.png b/doc/user/application_security/license_compliance/img/license_compliance_add_license_v12_3.png
new file mode 100644
index 00000000000..79f6160e63f
--- /dev/null
+++ b/doc/user/application_security/license_compliance/img/license_compliance_add_license_v12_3.png
Binary files differ
diff --git a/doc/user/application_security/license_compliance/img/license_compliance_pipeline_tab.png b/doc/user/application_security/license_compliance/img/license_compliance_pipeline_tab.png
deleted file mode 100644
index 80ffca815b9..00000000000
--- a/doc/user/application_security/license_compliance/img/license_compliance_pipeline_tab.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/license_compliance/img/license_compliance_pipeline_tab_v12_3.png b/doc/user/application_security/license_compliance/img/license_compliance_pipeline_tab_v12_3.png
new file mode 100644
index 00000000000..fd519d63b3e
--- /dev/null
+++ b/doc/user/application_security/license_compliance/img/license_compliance_pipeline_tab_v12_3.png
Binary files differ
diff --git a/doc/user/application_security/license_compliance/img/license_compliance_search.png b/doc/user/application_security/license_compliance/img/license_compliance_search.png
deleted file mode 100644
index b3ffd8d95a1..00000000000
--- a/doc/user/application_security/license_compliance/img/license_compliance_search.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/license_compliance/img/license_compliance_search_v12_3.png b/doc/user/application_security/license_compliance/img/license_compliance_search_v12_3.png
new file mode 100644
index 00000000000..4a7cff2e85c
--- /dev/null
+++ b/doc/user/application_security/license_compliance/img/license_compliance_search_v12_3.png
Binary files differ
diff --git a/doc/user/application_security/license_compliance/img/license_compliance_settings.png b/doc/user/application_security/license_compliance/img/license_compliance_settings.png
deleted file mode 100644
index 2e3e8888e93..00000000000
--- a/doc/user/application_security/license_compliance/img/license_compliance_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/license_compliance/img/license_compliance_settings_v12_3.png b/doc/user/application_security/license_compliance/img/license_compliance_settings_v12_3.png
new file mode 100644
index 00000000000..72d0888a9dc
--- /dev/null
+++ b/doc/user/application_security/license_compliance/img/license_compliance_settings_v12_3.png
Binary files differ
diff --git a/doc/user/application_security/license_compliance/index.md b/doc/user/application_security/license_compliance/index.md
index f74b958cf67..6de1db8650d 100644
--- a/doc/user/application_security/license_compliance/index.md
+++ b/doc/user/application_security/license_compliance/index.md
@@ -198,7 +198,7 @@ To approve or blacklist a license:
**License Compliance** section.
1. Click the **Add a license** button.
- ![License Compliance Add License](img/license_compliance_add_license.png)
+ ![License Compliance Add License](img/license_compliance_add_license_v12_3.png)
1. In the **License name** dropdown, either:
- Select one of the available licenses. You can search for licenses in the field
@@ -212,13 +212,13 @@ To modify an existing license:
1. In the **License Compliance** list, click the **Approved/Declined** dropdown to change it to the desired status.
- ![License Compliance Settings](img/license_compliance_settings.png)
+ ![License Compliance Settings](img/license_compliance_settings_v12_3.png)
Searching for Licenses:
1. Use the **Search** box to search for a specific license.
- ![License Compliance Search](img/license_compliance_search.png)
+ ![License Compliance Search](img/license_compliance_search_v12_3.png)
## License Compliance report under pipelines
@@ -228,7 +228,7 @@ From your project's left sidebar, navigate to **CI/CD > Pipelines** and click on
pipeline ID that has a `license_management` job to see the Licenses tab with the listed
licenses (if any).
-![License Compliance Pipeline Tab](img/license_compliance_pipeline_tab.png)
+![License Compliance Pipeline Tab](img/license_compliance_pipeline_tab_v12_3.png)
<!-- ## Troubleshooting
diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md
index 3eead6ccd3f..5e7bc4142fb 100644
--- a/doc/user/application_security/sast/index.md
+++ b/doc/user/application_security/sast/index.md
@@ -125,6 +125,21 @@ variables:
Because the template is [evaluated before](../../../ci/yaml/README.md#include)
the pipeline configuration, the last mention of the variable will take precedence.
+#### Using a variable to pass username and password to a private Maven repository
+
+If you have a private Apache Maven repository that requires login credentials,
+you can use the `MAVEN_CLI_OPTS` [environment variable](#available-variables)
+to pass a username and password. You can set it under your project's settings
+so that your credentials aren't exposed in `.gitlab-ci.yml`.
+
+If the username is `myuser` and the password is `verysecret` then you would
+set the following [variable](../../../ci/variables/README.md#via-the-ui)
+under your project's settings:
+
+| Type | Key | Value |
+| ---- | --- | ----- |
+| Variable | `MAVEN_CLI_OPTS` | `-Drepository.password=verysecret -Drepository.user=myuser` |
+
### Overriding the SAST template
If you want to override the job definition (for example, change properties like
diff --git a/doc/user/application_security/security_dashboard/img/group_security_dashboard.png b/doc/user/application_security/security_dashboard/img/group_security_dashboard.png
deleted file mode 100644
index 85ab124f74c..00000000000
--- a/doc/user/application_security/security_dashboard/img/group_security_dashboard.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/img/group_security_dashboard_v12_3.png b/doc/user/application_security/security_dashboard/img/group_security_dashboard_v12_3.png
new file mode 100644
index 00000000000..61f683c1335
--- /dev/null
+++ b/doc/user/application_security/security_dashboard/img/group_security_dashboard_v12_3.png
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/index.md b/doc/user/application_security/security_dashboard/index.md
index 314f7c1766f..e7cda35eb98 100644
--- a/doc/user/application_security/security_dashboard/index.md
+++ b/doc/user/application_security/security_dashboard/index.md
@@ -63,18 +63,13 @@ Once you're on the dashboard, at the top you should see a series of filters for:
- Project
NOTE: **Note:**
-The dashboard only shows projects with [security reports](#supported-reports) enabled in a group.
+The dashboard only shows projects with [security reports](#supported-reports) enabled in a group.
-![dashboard with action buttons and metrics](img/group_security_dashboard.png)
+![dashboard with action buttons and metrics](img/group_security_dashboard_v12_3.png)
Selecting one or more filters will filter the results in this page.
-The first section is an overview of all the vulnerabilities, grouped by severity.
-Underneath this overview is a timeline chart that shows how many open
-vulnerabilities your projects had at various points in time. You can filter among 30, 60, and
-90 days, with the default being 90. Hover over the chart to get more details about
-the open vulnerabilities at a specific time.
-Finally, there is a list of all the vulnerabilities in the group, sorted by severity.
+The main section is a list of all the vulnerabilities in the group, sorted by severity.
In that list, you can see the severity of the vulnerability, its name, its
confidence (likelihood of the vulnerability to be a positive one), and the project
it's from.
@@ -85,6 +80,11 @@ If you hover over a row, there will appear some actions you can take:
- "Create issue"
- "Dismiss vulnerability"
+Next to the list is a timeline chart that shows how many open
+vulnerabilities your projects had at various points in time. You can filter among 30, 60, and
+90 days, with the default being 90. Hover over the chart to get more details about
+the open vulnerabilities at a specific time.
+
Read more on how to [interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
## Keeping the dashboards up to date
diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md
index 743d708399c..40ed0db4c57 100644
--- a/doc/user/clusters/applications.md
+++ b/doc/user/clusters/applications.md
@@ -103,7 +103,7 @@ implications](../project/clusters/index.md#security-implications) before doing s
NOTE: **Note:**
The
-[runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner)
+[runner/gitlab-runner](https://gitlab.com/gitlab-org/charts/gitlab-runner)
chart is used to install this application with a
[`values.yaml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/runner/values.yaml)
file.
diff --git a/doc/user/clusters/img/jupyter-git-extension.gif b/doc/user/clusters/img/jupyter-git-extension.gif
index 13a88d97425..14dc567af2a 100644
--- a/doc/user/clusters/img/jupyter-git-extension.gif
+++ b/doc/user/clusters/img/jupyter-git-extension.gif
Binary files differ
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index 6891682141c..6b748981106 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -452,7 +452,7 @@ Replying to a non-thread comment will convert the non-thread comment to a
thread once the reply is submitted. This conversion is considered an edit
to the original comment, so a note about when it was last edited will appear underneath it.
-This feature only exists for Issues, Merge requests, and Epics. Commits, Snippets and Merge request diff threads are
+This feature only exists for Issues, Merge requests, and Epics. Commits, Snippets and Merge request diff threads are
not supported yet.
[ce-5022]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5022
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index 72beb38fe76..2f2955f5a1c 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -331,13 +331,17 @@ No response headers are provided.
### Admin Area settings
-GitLab.com does not currently use these settings.
+GitLab.com:
+
+- Has [rate limits on raw endpoints](../../user/admin_area/settings/rate_limits_on_raw_endpoints.md)
+ set to the default.
+- Does not have the user and IP rate limits settings enabled.
## GitLab.com at scale
In addition to the GitLab Enterprise Edition Omnibus install, GitLab.com uses
the following applications and settings to achieve scale. All settings are
-located publicly available [chef cookbooks](https://gitlab.com/gitlab-cookbooks).
+publicly available at [chef cookbooks](https://gitlab.com/gitlab-cookbooks).
### ELK
diff --git a/doc/user/group/epics/index.md b/doc/user/group/epics/index.md
index 5968b91c9b7..b947a587b2b 100644
--- a/doc/user/group/epics/index.md
+++ b/doc/user/group/epics/index.md
@@ -4,7 +4,7 @@ type: reference, howto
# Epics **(ULTIMATE)**
-> Introduced in [GitLab Ultimate][ee] 10.2.
+> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.2.
Epics let you manage your portfolio of projects more efficiently and with less
effort by tracking groups of issues that share a theme, across projects and
@@ -116,7 +116,7 @@ To apply labels across multiple epics:
## Deleting an epic
NOTE: **Note:**
-To delete an epic, you need to be an [Owner][permissions] of a group/subgroup.
+To delete an epic, you need to be an [Owner](../../permissions.md#group-members-permissions) of a group/subgroup.
When inside a single epic view, click the **Delete** button to delete the epic.
A modal will pop-up to confirm your action.
@@ -154,7 +154,7 @@ link in the issue sidebar.
If you have [permissions](../../permissions.md) to close an issue and create an
epic in the parent group, you can promote an issue to an epic with the `/promote`
-[quick action](../../project/quick_actions.md#quick-actions-for-epics-ultimate).
+[quick action](../../project/quick_actions.md#quick-actions-for-issues-merge-requests-and-epics).
Only issues from projects that are in groups can be promoted.
When the quick action is executed:
@@ -171,7 +171,7 @@ The following issue metadata will be copied to the epic:
## Searching for an epic from epics list page
-> Introduced in [GitLab Ultimate][ee] 10.5.
+> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.5.
You can search for an epic from the list of epics using filtered search bar (similar to
that of Issues and Merge requests) based on following parameters:
@@ -210,10 +210,7 @@ Note that for a given group, the visibility of all projects must be the same as
the group, or less restrictive. That means if you have access to a group's epic,
then you already have access to its projects' issues.
-You may also consult the [group permissions table][permissions].
-
-[ee]: https://about.gitlab.com/pricing/
-[permissions]: ../../permissions.md#group-members-permissions
+You may also consult the [group permissions table](../../permissions.md#group-members-permissions).
## Thread
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 864f1a397d5..86fb7533e70 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -150,8 +150,11 @@ side of your screen.
![Request access button](img/request_access_button.png)
-Group owners and maintainers will be notified of your request and will be able to approve or
-decline it on the members page.
+Once access is requested:
+
+- Up to ten group owners are notified of your request via email.
+ Email is sent to the most recently active group owners.
+- Any group owner can approve or decline your request on the members page.
![Manage access requests](img/access_requests_management.png)
@@ -346,7 +349,7 @@ Add one or more whitelisted IP subnets using CIDR notation in comma separated fo
coming from a different IP address won't be able to access the restricted
content.
-Restriction currently applies to UI, API access is not restricted.
+Restriction currently applies to UI and API access, Git actions via ssh are not restricted.
To avoid accidental lock-out, admins and group owners are are able to access
the group regardless of the IP restriction.
@@ -358,7 +361,7 @@ the group regardless of the IP restriction.
You can restrict access to groups and their underlying projects by
allowing only users with email addresses in particular domains to be added to the group.
-Add email domains you want to whitelist and users with emails from different
+Add email domains you want to whitelist and users with emails from different
domains won't be allowed to be added to this group.
Some domains cannot be restricted. These are the most popular public email domains, such as:
@@ -417,7 +420,7 @@ You can disable all email notifications related to the group, which also include
it's subgroups and projects.
To enable this feature:
-
+
1. Navigate to the group's **Settings > General** page.
1. Expand the **Permissions, LFS, 2FA** section, and select **Disable email notifications**.
1. Click **Save changes**.
diff --git a/doc/user/instance/clusters/index.md b/doc/user/instance/clusters/index.md
index f557dcf4b3c..cb1bfc69826 100644
--- a/doc/user/instance/clusters/index.md
+++ b/doc/user/instance/clusters/index.md
@@ -19,4 +19,4 @@ GitLab will try match to clusters in the following order:
- Instance level
To be selected, the cluster must be enabled and
-match the [environment selector](../../../ci/environments.md#scoping-environments-with-specs-premium).
+match the [environment selector](../../../ci/environments.md#scoping-environments-with-specs).
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 840e1856dd9..edf2fedab3c 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -1148,7 +1148,7 @@ GFM will autolink almost any URL you put into your text:
- https://google.com/
- ftp://ftp.us.debian.org/debian/
- smb://foo/bar/baz
-- irc://irc.freenode.net/gitlab
+- irc://irc.freenode.net/
- http://localhost:3000
```
@@ -1156,7 +1156,7 @@ GFM will autolink almost any URL you put into your text:
- <https://google.com/>
- <ftp://ftp.us.debian.org/debian/>
- <smb://foo/bar/baz>
-- <irc://irc.freenode.net/gitlab>
+- <irc://irc.freenode.net/>
- <http://localhost:3000>
### Lists
diff --git a/doc/user/project/code_owners.md b/doc/user/project/code_owners.md
index 96c4f16fe04..79f0bb4db72 100644
--- a/doc/user/project/code_owners.md
+++ b/doc/user/project/code_owners.md
@@ -58,7 +58,7 @@ Example `CODEOWNERS` file:
\#file_with_pound.rb @owner-file-with-pound
# Multiple codeowners can be specified, separated by spaces or tabs
-CODEOWNERS @multiple @code @owners
+CODEOWNERS @multiple @code @owners
# Both usernames or email addresses can be used to match
# users. Everything else will be ignored. For example this will
diff --git a/doc/user/project/import/tfvc.md b/doc/user/project/import/tfvc.md
index 375522b77d0..9b148224e10 100644
--- a/doc/user/project/import/tfvc.md
+++ b/doc/user/project/import/tfvc.md
@@ -6,7 +6,7 @@ type: concepts
Team Foundation Server (TFS), renamed [Azure DevOps Server](https://azure.microsoft.com/en-us/services/devops/server/)
in 2019, is a set of tools developed by Microsoft which also includes
-[Team Foundation Version Control](https://docs.microsoft.com/en-us/azure/devops/repos/tfvc/overview)
+[Team Foundation Version Control](https://docs.microsoft.com/en-us/azure/devops/repos/tfvc/overview?view=azure-devops)
(TFVC), a centralized version control system similar to Git.
In this document, we focus on the TFVC to Git migration.
diff --git a/doc/user/project/integrations/bamboo.md b/doc/user/project/integrations/bamboo.md
index 3206a39dc08..94e0c9fd886 100644
--- a/doc/user/project/integrations/bamboo.md
+++ b/doc/user/project/integrations/bamboo.md
@@ -57,5 +57,5 @@ service in GitLab.
If builds are not triggered, ensure you entered the right GitLab IP address in
Bamboo under 'Trigger IP addresses'.
-> **Note:**
-> - Starting with GitLab 8.14.0, builds are triggered on push events.
+NOTE: **Note:**
+Starting with GitLab 8.14.0, builds are triggered on push events.
diff --git a/doc/user/project/integrations/img/grafana_live_embed.png b/doc/user/project/integrations/img/grafana_live_embed.png
new file mode 100644
index 00000000000..91970cd379a
--- /dev/null
+++ b/doc/user/project/integrations/img/grafana_live_embed.png
Binary files differ
diff --git a/doc/user/project/integrations/mock_ci.md b/doc/user/project/integrations/mock_ci.md
index 886094a6531..b06ccda8287 100644
--- a/doc/user/project/integrations/mock_ci.md
+++ b/doc/user/project/integrations/mock_ci.md
@@ -6,7 +6,7 @@ To set up the mock CI service server, respond to the following endpoints
- `commit_status`: `#{project.namespace.path}/#{project.path}/status/#{sha}.json`
- Have your service return `200 { status: ['failed'|'canceled'|'running'|'pending'|'success'|'success-with-warnings'|'skipped'|'not_found'] }`
- - If the service returns a 404, it is interpreted as `pending`
+ - If the service returns a 404, it is interpreted as `pending`
- `build_page`: `#{project.namespace.path}/#{project.path}/status/#{sha}`
- Just where the build is linked to, doesn't matter if implemented
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index d13592559b9..3583c0554ee 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -393,6 +393,27 @@ The following requirements must be met for the metric to unfurl:
![Embedded Metrics](img/embed_metrics.png)
+### Embedding live Grafana charts
+
+It is also possible to embed live [Grafana](https://docs.gitlab.com/omnibus/settings/grafana.html) charts within issues, as a [Direct Linked Rendered Image](https://grafana.com/docs/reference/sharing/#direct-link-rendered-image).
+
+The sharing dialog within Grafana provides the link, as highlighted below.
+
+![Grafana Direct Linked Rendered Image](img/grafana_live_embed.png)
+
+NOTE: **Note:**
+For this embed to display correctly the Grafana instance must be available to the target user, either as a public dashboard or on the same network.
+
+Copy the link and add an image tag as [inline HTML](../../markdown.md#inline-html) in your markdown. You may tweak the query parameters as required. For instance, removing the `&from=` and `&to=` parameters will give you a live chart. Here is example markup for a live chart from GitLab's public dashboard:
+
+```html
+<img src="https://dashboards.gitlab.com/render/d-solo/RZmbBr7mk/gitlab-triage?orgId=1&refresh=30s&var-env=gprd&var-environment=gprd&var-prometheus=prometheus-01-inf-gprd&var-prometheus_app=prometheus-app-01-inf-gprd&var-backend=All&var-type=All&var-stage=main&panelId=1247&width=1000&height=300"/>
+```
+
+This will render like so:
+
+<img src="https://dashboards.gitlab.com/render/d-solo/RZmbBr7mk/gitlab-triage?orgId=1&refresh=30s&var-env=gprd&var-environment=gprd&var-prometheus=prometheus-01-inf-gprd&var-prometheus_app=prometheus-app-01-inf-gprd&var-backend=All&var-type=All&var-stage=main&panelId=1247&width=1000&height=300"/>
+
## Troubleshooting
If the "No data found" screen continues to appear, it could be due to:
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 84adb9637fc..f5bc6cbd988 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -2,6 +2,7 @@
> **Note:**
> Starting from GitLab 8.5:
+>
> - the `repository` key is deprecated in favor of the `project` key
> - the `project.ssh_url` key is deprecated in favor of the `project.git_ssh_url` key
> - the `project.http_url` key is deprecated in favor of the `project.git_http_url` key
@@ -12,6 +13,7 @@
>
> **Note:**
> Starting from GitLab 11.2:
+>
> - The `description` field for issues, merge requests, comments, and wiki pages
> is rewritten so that simple Markdown image references (like
> `![](/uploads/...)`) have their target URL changed to an absolute URL. See
@@ -98,11 +100,12 @@ Below are described the supported events.
Triggered when you push to the repository except when pushing tags.
-> **Note:** When more than 20 commits are pushed at once, the `commits` webhook
- attribute will only contain the first 20 for performance reasons. Loading
- detailed commit data is expensive. Note that despite only 20 commits being
- present in the `commits` attribute, the `total_commits_count` attribute will
- contain the actual total.
+NOTE: **Note:**
+When more than 20 commits are pushed at once, the `commits` webhook
+attribute will only contain the first 20 for performance reasons. Loading
+detailed commit data is expensive. Note that despite only 20 commits being
+present in the `commits` attribute, the `total_commits_count` attribute will
+contain the actual total.
**Request header**:
@@ -1181,20 +1184,20 @@ X-Gitlab-Event: Job Hook
```json
{
- "object_kind": "job",
+ "object_kind": "build",
"ref": "gitlab-script-trigger",
"tag": false,
"before_sha": "2293ada6b400935a1378653304eaf6221e0fdb8f",
"sha": "2293ada6b400935a1378653304eaf6221e0fdb8f",
- "job_id": 1977,
- "job_name": "test",
- "job_stage": "test",
- "job_status": "created",
- "job_started_at": null,
- "job_finished_at": null,
- "job_duration": null,
- "job_allow_failure": false,
- "job_failure_reason": "script_failure",
+ "build_id": 1977,
+ "build_name": "test",
+ "build_stage": "test",
+ "build_status": "created",
+ "build_started_at": null,
+ "build_finished_at": null,
+ "build_duration": null,
+ "build_allow_failure": false,
+ "build_failure_reason": "script_failure",
"project_id": 380,
"project_name": "gitlab-org/gitlab-test",
"user": {
diff --git a/doc/user/project/issues/issue_data_and_actions.md b/doc/user/project/issues/issue_data_and_actions.md
index d7d168710ef..41a7ed09281 100644
--- a/doc/user/project/issues/issue_data_and_actions.md
+++ b/doc/user/project/issues/issue_data_and_actions.md
@@ -192,7 +192,7 @@ You can also click the `+` to add more related issues.
#### 19. Related Merge Requests
-Merge requests that were mentioned in that issue's description or in the issue thread
+Merge requests that were mentioned in that issue's description or in the issue thread
are listed as [related merge requests](crosslinking_issues.md#from-merge-requests) here.
Also, if the current issue was mentioned as related in another merge request, that
merge request will be listed here.
diff --git a/doc/user/project/issues/sorting_issue_lists.md b/doc/user/project/issues/sorting_issue_lists.md
index 0fe86e6f410..6e31acf80bc 100644
--- a/doc/user/project/issues/sorting_issue_lists.md
+++ b/doc/user/project/issues/sorting_issue_lists.md
@@ -15,14 +15,14 @@ When you select **Manual** sorting, you can change
the order by dragging and dropping the issues. The changed order will persist. Everyone who visits the same list will see the reordered list, with some exceptions.
Each issue is assigned a relative order value, representing its relative
-order with respect to the other issues in the list. When you drag-and-drop reorder
+order with respect to the other issues in the list. When you drag-and-drop reorder
an issue, its relative order value changes accordingly.
In addition, any time that issue appears in a manually sorted list,
the updated relative order value will be used for the ordering. This means that
if issue `A` is drag-and-drop reordered to be above issue `B` by any user in
a given list inside your GitLab instance, any time those two issues are subsequently
-loaded in any list in the same instance (could be a different project issue list or a
+loaded in any list in the same instance (could be a different project issue list or a
different group issue list, for example), that ordering will be maintained.
This ordering also affects [issue boards](../issue_board.md#issue-ordering-in-a-list).
diff --git a/doc/user/project/members/index.md b/doc/user/project/members/index.md
index e343fd45488..21016dca358 100644
--- a/doc/user/project/members/index.md
+++ b/doc/user/project/members/index.md
@@ -79,8 +79,15 @@ side of your screen.
![Request access button](img/request_access_button.png)
-Project owners & maintainers will be notified of your request and will be able to approve or
-decline it on the members page.
+Once access is requested:
+
+- Up to ten project maintainers are notified of your request via email.
+ Email is sent to the most recently active project maintainers.
+- Any project maintainer can approve or decline your request on the members page.
+
+NOTE: **Note:**
+If a project does not have any maintainers, the notification is sent to the
+most recently active owners of the project's group.
![Manage access requests](img/access_requests_management.png)
diff --git a/doc/user/project/merge_requests/allow_collaboration.md b/doc/user/project/merge_requests/allow_collaboration.md
index e94125e658d..a88fb4fc6f8 100644
--- a/doc/user/project/merge_requests/allow_collaboration.md
+++ b/doc/user/project/merge_requests/allow_collaboration.md
@@ -4,8 +4,7 @@ type: reference, howto
# Allow collaboration on merge requests across forks
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17395)
- in GitLab 10.6.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17395) in GitLab 10.6.
When a user opens a merge request from a fork, they are given the option to allow
upstream members to collaborate with them on the source branch. This allows
diff --git a/doc/user/project/merge_requests/code_quality.md b/doc/user/project/merge_requests/code_quality.md
index 3a409bab19d..3c6b660c63d 100644
--- a/doc/user/project/merge_requests/code_quality.md
+++ b/doc/user/project/merge_requests/code_quality.md
@@ -5,8 +5,7 @@ disqus_identifier: 'https://docs.gitlab.com/ee/user/project/merge_requests/code_
# Code Quality **(STARTER)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1984)
-in [GitLab Starter](https://about.gitlab.com/pricing/) 9.3.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1984) in [GitLab Starter](https://about.gitlab.com/pricing/) 9.3.
With the help of [GitLab CI/CD](../../../ci/README.md), you can analyze your
source code quality using GitLab Code Quality.
@@ -26,7 +25,7 @@ Code Quality:
Going a step further, GitLab can show the Code Quality report right
in the merge request widget area:
-![Code Quality Widget](img/code_quality.gif)
+![Code Quality Widget](img/code_quality.png)
## Use cases
diff --git a/doc/user/project/merge_requests/img/approvals_premium_project_edit_v12_3.png b/doc/user/project/merge_requests/img/approvals_premium_project_edit_v12_3.png
index 32b9a3b9ce4..bbb131e86e9 100644
--- a/doc/user/project/merge_requests/img/approvals_premium_project_edit_v12_3.png
+++ b/doc/user/project/merge_requests/img/approvals_premium_project_edit_v12_3.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/code_quality.gif b/doc/user/project/merge_requests/img/code_quality.gif
deleted file mode 100644
index bab921cf38b..00000000000
--- a/doc/user/project/merge_requests/img/code_quality.gif
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/img/code_quality.png b/doc/user/project/merge_requests/img/code_quality.png
new file mode 100644
index 00000000000..a20f6476fb8
--- /dev/null
+++ b/doc/user/project/merge_requests/img/code_quality.png
Binary files differ
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index a94057dc3a1..d6da8cb99c7 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -218,7 +218,7 @@ Similarly, assignees are removed by deselecting them from the same dropdown menu
It's also possible to manage multiple assignees:
- When creating a merge request.
-- Using [quick actions](../quick_actions.md#quick-actions-for-issues-and-merge-requests).
+- Using [quick actions](../quick_actions.md#quick-actions-for-issues-merge-requests-and-epics).
## Resolve conflicts
diff --git a/doc/user/project/merge_requests/merge_request_approvals.md b/doc/user/project/merge_requests/merge_request_approvals.md
index a0432414bcf..ca9ddd91e0d 100644
--- a/doc/user/project/merge_requests/merge_request_approvals.md
+++ b/doc/user/project/merge_requests/merge_request_approvals.md
@@ -330,7 +330,7 @@ the dropdown) `approver` and select the user.
Merge Request Approvals can be configured to require approval from a member
of your security team when a vulnerability would be introduced by a merge request.
-For more information, see
+For more information, see
[Security approvals in merge requests](../../application_security/index.md#security-approvals-in-merge-requests-ultimate).
<!-- ## Troubleshooting
diff --git a/doc/user/project/merge_requests/work_in_progress_merge_requests.md b/doc/user/project/merge_requests/work_in_progress_merge_requests.md
index ea59644fce6..8ac4131e10b 100644
--- a/doc/user/project/merge_requests/work_in_progress_merge_requests.md
+++ b/doc/user/project/merge_requests/work_in_progress_merge_requests.md
@@ -18,7 +18,7 @@ There are several ways to flag a merge request as a Work In Progress:
- Add `[WIP]` or `WIP:` to the start of the merge request's title. Clicking on
**Start the title with WIP:**, under the title box, when editing the merge request's
description will have the same effect.
-- Add the `/wip` [quick action](../quick_actions.md#quick-actions-for-issues-and-merge-requests)
+- Add the `/wip` [quick action](../quick_actions.md#quick-actions-for-issues-merge-requests-and-epics)
in a comment in the merge request. This is a toggle, and can be repeated
to change the status back. Note that any other text in the comment will be discarded.
- Add "wip" or "WIP" to the start of a commit message targeting the merge request's
@@ -33,7 +33,7 @@ Similar to above, when a Merge Request is ready to be merged, you can remove the
- Remove `[WIP]` or `WIP:` from the start of the merge request's title. Clicking on
**Remove the WIP: prefix from the title**, under the title box, when editing the merge
request's description, will have the same effect.
-- Add the `/wip` [quick action](../quick_actions.md#quick-actions-for-issues-and-merge-requests)
+- Add the `/wip` [quick action](../quick_actions.md#quick-actions-for-issues-merge-requests-and-epics)
in a comment in the merge request. This is a toggle, and can be repeated
to change the status back. Note that any other text in the comment will be discarded.
- Click on the **Resolve WIP status** button near the bottom of the merge request description,
diff --git a/doc/user/project/operations/feature_flags.md b/doc/user/project/operations/feature_flags.md
index 6536a1a0a4b..39ca1bd0c77 100644
--- a/doc/user/project/operations/feature_flags.md
+++ b/doc/user/project/operations/feature_flags.md
@@ -2,10 +2,6 @@
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845) in GitLab 11.4.
-CAUTION: **Warning:**
-This an _alpha_ feature and is subject to change at any time without
-prior notice.
-
Feature flags allow you to ship a project in different flavors by
dynamically toggling certain functionality.
@@ -67,15 +63,15 @@ For example, you may not want to enable a feature flag on production until your
first confirmed that the feature is working correctly on testing environments.
To handle these situations, you can enable a feature flag on a particular environment
-with [Environment specs](../../../ci/environments.md#scoping-environments-with-specs-premium).
+with [Environment specs](../../../ci/environments.md#scoping-environments-with-specs).
You can define multiple specs per flag so that you can control your feature flag more granularly.
To define specs for each environment:
1. Navigate to your project's **Operations > Feature Flags**.
1. Click on the **New Feature Flag** button or edit an existing flag.
-1. Set the status of the default [spec](../../../ci/environments.md#scoping-environments-with-specs-premium) (`*`). Choose a rollout strategy. This status and rollout strategy combination will be used for _all_ environments.
-1. If you want to enable/disable the feature on a specific environment, create a new [spec](../../../ci/environments.md#scoping-environments-with-specs-premium) and type the environment name.
+1. Set the status of the default [spec](../../../ci/environments.md#scoping-environments-with-specs) (`*`). Choose a rollout strategy. This status and rollout strategy combination will be used for _all_ environments.
+1. If you want to enable/disable the feature on a specific environment, create a new [spec](../../../ci/environments.md#scoping-environments-with-specs) and type the environment name.
1. Set the status and rollout strategy of the additional spec. This status and rollout strategy combination takes precedence over the default spec since we always use the most specific match available.
1. Click **Create feature flag** or **Update feature flag**.
diff --git a/doc/user/project/operations/tracing.md b/doc/user/project/operations/tracing.md
index b92d2e49839..3fb3be3c21f 100644
--- a/doc/user/project/operations/tracing.md
+++ b/doc/user/project/operations/tracing.md
@@ -17,8 +17,8 @@ systems.
### Deploying Jaeger
To learn more about deploying Jaeger, read the official
-[Getting Started documentation](https://www.jaegertracing.io/docs/1.13/getting-started/).
-There is an easy to use [all-in-one Docker image](https://www.jaegertracing.io/docs/1.13/getting-started/#AllinoneDockerimage),
+[Getting Started documentation](https://www.jaegertracing.io/docs/latest/getting-started/).
+There is an easy to use [all-in-one Docker image](https://www.jaegertracing.io/docs/latest/getting-started/#AllinoneDockerimage),
as well as deployment options for [Kubernetes](https://github.com/jaegertracing/jaeger-kubernetes)
and [OpenShift](https://github.com/jaegertracing/jaeger-openshift).
@@ -27,7 +27,7 @@ and [OpenShift](https://github.com/jaegertracing/jaeger-openshift).
GitLab provides an easy way to open the Jaeger UI from within your project:
1. [Set up Jaeger](#deploying-jaeger) and configure your application using one of the
- [client libraries](https://www.jaegertracing.io/docs/1.13/client-libraries/).
+ [client libraries](https://www.jaegertracing.io/docs/latest/client-libraries/).
1. Navigate to your project's **Settings > Operations** and provide the Jaeger URL.
1. Click **Save changes** for the changes to take effect.
1. You can now visit **Operations > Tracing** in your project's sidebar and
diff --git a/doc/user/project/packages/maven_repository.md b/doc/user/project/packages/maven_repository.md
index c61402afb2c..491ebc0c068 100644
--- a/doc/user/project/packages/maven_repository.md
+++ b/doc/user/project/packages/maven_repository.md
@@ -1,7 +1,6 @@
# GitLab Maven Repository **(PREMIUM)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5811) in
- [GitLab Premium](https://about.gitlab.com/pricing/) 11.3.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5811) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.3.
With the GitLab [Maven](https://maven.apache.org) Repository, every
project can have its own space to store its Maven artifacts.
diff --git a/doc/user/project/packages/npm_registry.md b/doc/user/project/packages/npm_registry.md
index e01bac6b26f..48186688da9 100644
--- a/doc/user/project/packages/npm_registry.md
+++ b/doc/user/project/packages/npm_registry.md
@@ -1,7 +1,6 @@
# GitLab NPM Registry **(PREMIUM)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5934)
- in [GitLab Premium](https://about.gitlab.com/pricing/) 11.7.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5934) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.7.
With the GitLab NPM Registry, every
project can have its own space to store NPM packages.
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md
index 3b80370d4a9..dc75eb450a3 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md
@@ -92,4 +92,4 @@ You can have one DNS record or more than one combined:
- `example.com` => `A` => `192.192.192.192`
- `www` => `CNAME` => `example.com`
- `MX` => `mail.example.com`
-- `example.com`=> TXT => `"google-site-verification=6P08Ow5E-8Q0m6vQ7FMAqAYIDprkVV8fUf_7hZ4Qvc8"` \ No newline at end of file
+- `example.com`=> TXT => `"google-site-verification=6P08Ow5E-8Q0m6vQ7FMAqAYIDprkVV8fUf_7hZ4Qvc8"`
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
index 1821d954af3..f68dfdf02cc 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
@@ -24,7 +24,7 @@ and steps below.
(`*.gitlab.io`, for GitLab.com).
- A custom domain name `example.com` or subdomain `subdomain.example.com`.
- Access to your domain's server control panel to set up DNS records:
- - A DNS A or CNAME record poiting your domain to GitLab Pages server.
+ - A DNS A or CNAME record pointing your domain to GitLab Pages server.
- A DNS TXT record to verify your domain's ownership.
### Steps
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
index 255d535645d..16fd0149101 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
@@ -21,6 +21,10 @@ such as [#64870](https://gitlab.com/gitlab-org/gitlab-ce/issues/64870).
See all the related issues linked from this [issue's description](https://gitlab.com/gitlab-org/gitlab-ce/issues/28996)
for more information.
+Note: **Note:**
+Using this feature requires two separate IP addresses, one for the GitLab domain
+and one GitLab Pages domain.
+
## Requirements
Before you can enable automatic provisioning of a SSL certificate for your domain, make sure you have:
diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md
index 6d538ca2455..45fdab1ca3a 100644
--- a/doc/user/project/pages/getting_started_part_one.md
+++ b/doc/user/project/pages/getting_started_part_one.md
@@ -100,4 +100,4 @@ _Read on about [Projects for GitLab Pages and URL structure](getting_started_par
- Read through this technical overview on [Static versus Dynamic Websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/)
- Understand [how modern Static Site Generators work](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) and what you can add to your static site
- You can use [any SSG with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
-- Fork an [example project](https://gitlab.com/pages) to build your website based upon \ No newline at end of file
+- Fork an [example project](https://gitlab.com/pages) to build your website based upon
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index a0bc10b0ed7..a9fd6544e64 100644
--- a/doc/user/project/pages/index.md
+++ b/doc/user/project/pages/index.md
@@ -106,12 +106,14 @@ To get started with GitLab Pages, you can either:
1. From the left sidebar, navigate to your project's **CI/CD > Pipelines**
and click **Run pipeline** to trigger GitLab CI/CD to build and deploy your
site to the server.
-1. Once the pipeline has finished successfully, find the link to visit your
- website from your project's **Settings > Pages**.
+1. After the pipeline has finished successfully, wait approximately 30 minutes
+ for your website to be visible. After waiting 30 minutes, find the link to
+ visit your website from your project's **Settings > Pages**. If the link
+ leads to a 404 page, wait a few minutes and try again.
-Your website is then visible on your domain, and you can modify yourfiles
+Your website is then visible on your domain and you can modify your files
as you wish. For every modification pushed to your repository, GitLab CI/CD
-will run a new pipeline to publish your changes to the server.
+will run a new pipeline to immediately publish your changes to the server.
_Advanced options:_
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index 456209c2180..78b6d202b32 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -107,11 +107,11 @@ in the pipelines settings page.
### Removing color codes
Some test coverage tools output with ANSI color codes that won't be
-parsed correctly by the regular expression and will cause coverage
-parsing to fail.
+parsed correctly by the regular expression and will cause coverage
+parsing to fail.
If your coverage tool doesn't provide an option to disable color
-codes in the output, you can pipe the output of the coverage tool through a
+codes in the output, you can pipe the output of the coverage tool through a
small one line script that will strip the color codes off.
For example:
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index 647250bd02a..1f7f85e9750 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -12,59 +12,66 @@ on a separate line in order to be properly detected and executed. Once executed,
> From [GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26672), an alert is displayed when a quick action is successfully applied.
-## Quick Actions for issues and merge requests
-
-The following quick actions are applicable to both issues and merge requests threads,
-discussions, and descriptions:
-
-| Command | Action | Issue | Merge request |
-|:---------------------------|:------------------------------ |:------|:--------------|
-| `/tableflip <Comment>` | Append the comment with `(╯°□°)╯︵ ┻━┻` | ✓ | ✓ |
-| `/shrug <Comment>` | Append the comment with `¯\_(ツ)_/¯` | ✓ | ✓ |
-| `/todo` | Add a To Do | ✓ | ✓ |
-| `/done` | Mark To Do as done | ✓ | ✓ |
-| `/subscribe` | Subscribe | ✓ | ✓ |
-| `/unsubscribe` | Unsubscribe | ✓ | ✓ |
-| `/close` | Close | ✓ | ✓ |
-| `/reopen` | Reopen | ✓ | ✓ |
-| `/title <New title>` | Change title | ✓ | ✓ |
-| `/award :emoji:` | Toggle emoji award | ✓ | ✓ |
-| `/assign me` | Assign yourself | ✓ | ✓ |
-| `/assign @user` | Assign one user | ✓ | ✓ |
-| `/assign @user1 @user2` | Assign multiple users **(STARTER)** | ✓ | ✓ |
-| `/unassign @user1 @user2` | Remove assignee(s) **(STARTER)** | ✓ | ✓ |
-| `/reassign @user1 @user2` | Change assignee **(STARTER)** | ✓ | ✓ |
-| `/unassign` | Remove current assignee | ✓ | ✓ |
-| `/milestone %milestone` | Set milestone | ✓ | ✓ |
-| `/remove_milestone` | Remove milestone | ✓ | ✓ |
-| `/label ~label1 ~label2` | Add label(s). Label names can also start without ~ but mixed syntax is not supported. | ✓ | ✓ |
-| `/unlabel ~label1 ~label2` | Remove all or specific label(s)| ✓ | ✓ |
-| `/relabel ~label1 ~label2` | Replace existing label(s) with those specified | ✓ | ✓ |
-| `/copy_metadata <#issue>` | Copy labels and milestone from another issue in the project | ✓ | ✓ |
-| `/copy_metadata <!merge_request>` | Copy labels and milestone from another merge request in the project | ✓ | ✓ |
-| `/estimate <1w 3d 2h 14m>` | Set time estimate | ✓ | ✓ |
-| `/remove_estimate` | Remove time estimate | ✓ | ✓ |
-| `/spend <time(1h 30m)> <date(YYYY-MM-DD)>` | Add spent time; optionally, specify the date that time was spent on | ✓ | ✓ |
-| `/spend <time(-1h 5m)> <date(YYYY-MM-DD)>` | Subtract spent time; optionally, specify the date that time was spent on | ✓ | ✓ |
-| `/remove_time_spent` | Remove time spent | ✓ | ✓ |
-| `/lock` | Lock the thread | ✓ | ✓ |
-| `/unlock` | Unlock the thread | ✓ | ✓ |
-| `/due <date>` | Set due date. Examples of valid `<date>` include `in 2 days`, `this Friday` and `December 31st`. | ✓ | |
-| `/remove_due_date` | Remove due date | ✓ | |
-| `/weight <value>` | Set weight. Valid options for `<value>` include `0`, `1`, `2`, etc. **(STARTER)** | ✓ | |
-| `/clear_weight` | Clears weight **(STARTER)** | ✓ | |
-| `/epic <epic>` | Add to epic `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic` or `epic-URL`. **(ULTIMATE)** | ✓ | |
-| `/remove_epic` | Removes from epic **(ULTIMATE)** | ✓ | |
-| `/promote` | Promote issue to epic **(ULTIMATE)** | ✓ | |
-| `/confidential` | Make confidential | ✓ | |
-| `/duplicate <#issue>` | Mark this issue as a duplicate of another issue | ✓ |
-| `/move <path/to/project>` | Move this issue to another project | ✓ | |
-| `/target_branch <Local branch Name>` | Set target branch | | ✓ |
-| `/wip` | Toggle the Work In Progress status | | ✓ |
-| `/approve` | Approve the merge request | | ✓ |
-| `/merge` | Merge (when pipeline succeeds) | | ✓ |
-| `/create_merge_request <branch name>` | Create a new merge request starting from the current issue | ✓ | |
-| `/relate #issue1 #issue2` | Mark issues as related **(STARTER)** | ✓ | |
+## Quick Actions for issues, merge requests and epics
+
+The following quick actions are applicable to descriptions, discussions and threads in:
+
+- Issues
+- Merge requests
+- Epics **(ULTIMATE)**
+
+| Command | Issue | Merge request | Epic | Action |
+|:--------------------------------------|:------|:--------------|:-----|:------ |
+| `/tableflip <comment>` | ✓ | ✓ | ✓ | Append the comment with `(╯°□°)╯︵ ┻━┻` |
+| `/shrug <comment>` | ✓ | ✓ | ✓ | Append the comment with `¯\_(ツ)_/¯` |
+| `/todo` | ✓ | ✓ | ✓ | Add a To Do |
+| `/done` | ✓ | ✓ | ✓ | Mark To Do as done |
+| `/subscribe` | ✓ | ✓ | ✓ | Subscribe |
+| `/unsubscribe` | ✓ | ✓ | ✓ | Unsubscribe |
+| `/close` | ✓ | ✓ | ✓ | Close |
+| `/reopen` | ✓ | ✓ | ✓ | Reopen |
+| `/title <new title>` | ✓ | ✓ | ✓ | Change title |
+| `/award :emoji:` | ✓ | ✓ | ✓ | Toggle emoji award |
+| `/assign me` | ✓ | ✓ | | Assign yourself |
+| `/assign @user` | ✓ | ✓ | | Assign one user |
+| `/assign @user1 @user2` | ✓ | ✓ | | Assign multiple users **(STARTER)** |
+| `/reassign @user1 @user2` | ✓ | ✓ | | Change assignee **(STARTER)** |
+| `/unassign` | ✓ | ✓ | | Remove current assignee |
+| `/unassign @user1 @user2` | ✓ | ✓ | | Remove assignee(s) **(STARTER)** |
+| `/milestone %milestone` | ✓ | ✓ | | Set milestone |
+| `/remove_milestone` | ✓ | ✓ | | Remove milestone |
+| `/label ~label1 ~label2` | ✓ | ✓ | ✓ | Add label(s). Label names can also start without `~` but mixed syntax is not supported |
+| `/relabel ~label1 ~label2` | ✓ | ✓ | ✓ | Replace existing label(s) with those specified |
+| `/unlabel ~label1 ~label2` | ✓ | ✓ | ✓ | Remove all or specific label(s) |
+| `/copy_metadata <#issue>` | ✓ | ✓ | | Copy labels and milestone from another issue in the project |
+| `/copy_metadata <!merge_request>` | ✓ | ✓ | | Copy labels and milestone from another merge request in the project |
+| `/estimate <<W>w <DD>d <hh>h <mm>m>` | ✓ | ✓ | | Set time estimate. For example, `/estimate 1w 3d 2h 14m` |
+| `/remove_estimate` | ✓ | ✓ | | Remove time estimate |
+| `/spend <time(<h>h <mm>m)> <date(<YYYY-MM-DD>)>` | ✓ | ✓ | | Add spent time; optionally specify the date that time was spent on. For example, `/spend time(1h 30m)` or `/spend time(1h 30m) date(2018-08-26)` |
+| `/spend <time(-<h>h <mm>m)> <date(<YYYY-MM-DD>)>` | ✓ | ✓ | | Subtract spent time; optionally specify the date that time was spent on. For example, `/spend time(-1h 30m)` or `/spend time(-1h 30m) date(2018-08-26)` |
+| `/remove_time_spent` | ✓ | ✓ | | Remove time spent |
+| `/lock` | ✓ | ✓ | | Lock the thread |
+| `/unlock` | ✓ | ✓ | | Unlock the thread |
+| `/due <date>` | ✓ | | | Set due date. Examples of valid `<date>` include `in 2 days`, `this Friday` and `December 31st` |
+| `/remove_due_date` | ✓ | | | Remove due date |
+| `/weight <value>` | ✓ | | | Set weight. Valid options for `<value>` include `0`, `1`, `2`, etc **(STARTER)** |
+| `/clear_weight` | ✓ | | | Clear weight **(STARTER)** |
+| `/epic <epic>` | ✓ | | | Add to epic `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. **(ULTIMATE)** |
+| `/remove_epic` | ✓ | | | Remove from epic **(ULTIMATE)** |
+| `/promote` | ✓ | | | Promote issue to epic **(ULTIMATE)** |
+| `/confidential` | ✓ | | | Make confidential |
+| `/duplicate <#issue>` | ✓ | | | Mark this issue as a duplicate of another issue |
+| `/create_merge_request <branch name>` | ✓ | | | Create a new merge request starting from the current issue |
+| `/relate #issue1 #issue2` | ✓ | | | Mark issues as related **(STARTER)** |
+| `/move <path/to/project>` | ✓ | | | Move this issue to another project |
+| `/target_branch <local branch name>` | | ✓ | | Set target branch |
+| `/wip` | | ✓ | | Toggle the Work In Progress status |
+| `/approve` | | ✓ | | Approve the merge request |
+| `/merge` | | ✓ | | Merge (when pipeline succeeds) |
+| `/child_epic <epic>` | | | ✓ | Add child epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. ([Introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ee/issues/7330)) **(ULTIMATE)** |
+| `/remove_child_epic <epic>` | | | ✓ | Remove child epic from `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. ([Introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ee/issues/7330)) **(ULTIMATE)** |
+| `/parent_epic <epic>` | | | ✓ | Set parent epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/issues/10556)) **(ULTIMATE)** |
+| `/remove_parent_epic` | | | ✓ | Remove parent epic from epic ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/issues/10556)) **(ULTIMATE)** |
## Autocomplete characters
@@ -93,30 +100,6 @@ The following quick actions are applicable for commit messages:
|:------------------------|:------------------------------------------|
| `/tag v1.2.3 <message>` | Tags this commit with an optional message |
-## Quick actions for Epics **(ULTIMATE)**
-
-The following quick actions are applicable for epics threads and description:
-
-| Command | Action |
-|:---------------------------|:----------------------------------------|
-| `/tableflip <Comment>` | Append the comment with `(╯°□°)╯︵ ┻━┻` |
-| `/shrug <Comment>` | Append the comment with `¯\_(ツ)_/¯` |
-| `/todo` | Add a To Do |
-| `/done` | Mark To Do as done |
-| `/subscribe` | Subscribe |
-| `/unsubscribe` | Unsubscribe |
-| `/close` | Close |
-| `/reopen` | Reopen |
-| `/title <New title>` | Change title |
-| `/award :emoji:` | Toggle emoji award |
-| `/label ~label1 ~label2` | Add label(s) |
-| `/unlabel ~label1 ~label2` | Remove all or specific label(s) |
-| `/relabel ~label1 ~label2` | Replace existing label(s) with those specified |
-| `/child_epic <epic>` | Adds child epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic` or `epic-URL`. ([Introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ee/issues/7330)) **(ULTIMATE)**|
-| `/remove_child_epic <epic>` | Removes child epic from `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic` or `epic-URL`. ([Introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ee/issues/7330)) **(ULTIMATE)** |
-| `/parent_epic <epic>` | Sets parent epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic` or `epic-URL`. ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/issues/10556)) **(ULTIMATE)** |
-| `/remove_parent_epic` | Removes parent epic from epic ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/issues/10556)) |
-
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index 4e3db95b6d6..58ccd8bf2ae 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -34,7 +34,7 @@ Issues or Merge Requests, both of which use Labels and Milestones, then you shou
#### Disabling email notifications
-You can disable all email notifications related to the project by selecting the
+You can disable all email notifications related to the project by selecting the
**Disable email notifications** checkbox. Only the project owner is allowed to change
this setting.
diff --git a/doc/user/reserved_names.md b/doc/user/reserved_names.md
index 68532ccee65..45ece5f048e 100644
--- a/doc/user/reserved_names.md
+++ b/doc/user/reserved_names.md
@@ -14,27 +14,27 @@ For a list of words that are not allowed to be used as group or project names, s
It is currently not possible to create a project with the following names:
-- \-
-- badges
-- blame
-- blob
-- builds
-- commits
-- create
-- create_dir
-- edit
-- environments/folders
-- files
-- find_file
-- gitlab-lfs/objects
-- info/lfs/objects
-- new
-- preview
-- raw
-- refs
-- tree
-- update
-- wikis
+- `\-`
+- `badges`
+- `blame`
+- `blob`
+- `builds`
+- `commits`
+- `create`
+- `create_dir`
+- `edit`
+- `environments/folders`
+- `files`
+- `find_file`
+- `gitlab-lfs/objects`
+- `info/lfs/objects`
+- `new`
+- `preview`
+- `raw`
+- `refs`
+- `tree`
+- `update`
+- `wikis`
## Reserved group names
diff --git a/doc/user/search/advanced_search_syntax.md b/doc/user/search/advanced_search_syntax.md
index c3d22e4fd29..d65dd32fe11 100644
--- a/doc/user/search/advanced_search_syntax.md
+++ b/doc/user/search/advanced_search_syntax.md
@@ -1,6 +1,7 @@
# Advanced Syntax Search **(STARTER ONLY)**
> **Notes:**
+>
> - Introduced in [GitLab Enterprise Starter][ee] 9.2
> - This is the user documentation. To install and configure Elasticsearch,
> visit the [administrator documentation](../../integration/elasticsearch.md).
diff --git a/doc/workflow/git_annex.md b/doc/workflow/git_annex.md
index cdc38a5b7eb..c3865aa953b 100644
--- a/doc/workflow/git_annex.md
+++ b/doc/workflow/git_annex.md
@@ -2,7 +2,7 @@
> **Warning:** GitLab has [completely
removed][deprecate-annex-issue] in GitLab 9.0 (2017/03/22).
-Read through the [migration guide from git-annex to git-lfs][guide].
+Read through the [migration guide from git-annex to Git LFS][guide].
The biggest limitation of Git, compared to some older centralized version
control systems, has been the maximum size of the repositories.
@@ -51,7 +51,7 @@ sudo yum install epel-release && sudo yum install git-annex
### Configuration for Omnibus packages
-For omnibus-gitlab packages, only one configuration setting is needed.
+For Omnibus GitLab packages, only one configuration setting is needed.
The Omnibus package will internally set the correct options in all locations.
1. In `/etc/gitlab/gitlab.rb` add the following line:
@@ -67,7 +67,7 @@ The Omnibus package will internally set the correct options in all locations.
There are 2 settings to enable git-annex on your GitLab server.
One is located in `config/gitlab.yml` of the GitLab repository and the other
-one is located in `config.yml` of gitlab-shell.
+one is located in `config.yml` of GitLab Shell.
1. In `config/gitlab.yml` add or edit the following lines:
@@ -76,7 +76,7 @@ one is located in `config.yml` of gitlab-shell.
git_annex_enabled: true
```
-1. In `config.yml` of gitlab-shell add or edit the following lines:
+1. In `config.yml` of GitLab Shell add or edit the following lines:
```yaml
git_annex_enabled: true
@@ -184,7 +184,7 @@ access files of projects you have access to (developer, maintainer, or owner rol
Internally GitLab uses [GitLab Shell] to handle SSH access and this was a great
integration point for `git-annex`.
-There is a setting in gitlab-shell so you can disable GitLab Annex support
+There is a setting in GitLab Shell so you can disable GitLab Annex support
if you want to.
## Troubleshooting tips
@@ -200,7 +200,7 @@ searching for your distribution.
Although there is no general guide for `git-annex` errors, there are a few tips
on how to go around the warnings.
-### git-annex-shell: Not a git-annex or gcrypt repository
+### `git-annex-shell: Not a git-annex or gcrypt repository`
This warning can appear on the initial `git annex sync --content` and is caused
by differences in `git-annex-shell`. You can read more about it
diff --git a/doc/workflow/releases.md b/doc/workflow/releases.md
index 00cddda24a4..1fd63a556c6 100644
--- a/doc/workflow/releases.md
+++ b/doc/workflow/releases.md
@@ -3,20 +3,20 @@
NOTE: In GitLab 11.7, we introduced the full fledged [Releases](../user/project/releases/index.md)
feature. You can still create release notes on this page, but the new method is preferred.
-You can add release notes to any git tag using the notes feature. Release notes
+You can add release notes to any Git tag using the notes feature. Release notes
behave like any other markdown form in GitLab so you can write text and
drag-n-drop files to it. Release notes are stored in GitLab's database.
There are several ways to add release notes:
-- In the interface, when you create a new git tag
-- In the interface, by adding a note to an existing git tag
+- In the interface, when you create a new Git tag
+- In the interface, by adding a note to an existing Git tag
- Using the GitLab API
## New tag page with release notes text area
![new_tag](releases/new_tag.png)
-## Tags page with button to add or edit release notes for existing git tag
+## Tags page with button to add or edit release notes for existing Git tag
![tags](releases/tags.png)
diff --git a/doc/workflow/time_tracking.md b/doc/workflow/time_tracking.md
index fad853f8a44..3d9f015b1fe 100644
--- a/doc/workflow/time_tracking.md
+++ b/doc/workflow/time_tracking.md
@@ -84,4 +84,4 @@ With this option enabled, `75h` is displayed instead of `1w 4d 3h`.
## Other interesting links
-- [Time Tracking landing page on about.gitlab.com](https://about.gitlab.com/solutions/time-tracking/)
+- [Time Tracking landing page in the GitLab handbook](https://about.gitlab.com/solutions/time-tracking/)
diff --git a/doc/workflow/timezone.md b/doc/workflow/timezone.md
index 60a4d0f19de..4101f891484 100644
--- a/doc/workflow/timezone.md
+++ b/doc/workflow/timezone.md
@@ -17,7 +17,7 @@ For Omnibus installations, run `gitlab-rake time:zones:all`.
NOTE: **Note:**
Currently, this rake task does not list timezones in TZInfo format required by GitLab Omnibus during a reconfigure: [#58672](https://gitlab.com/gitlab-org/gitlab-ce/issues/58672).
-## Changing time zone in omnibus installations
+## Changing time zone in Omnibus installations
GitLab defaults its time zone to UTC. It has a global timezone configuration parameter in `/etc/gitlab/gitlab.rb`.
diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb
index 6c1acc3963f..9125207167c 100644
--- a/lib/api/discussions.rb
+++ b/lib/api/discussions.rb
@@ -239,7 +239,7 @@ module API
# because notes are redacted if they point to projects that
# cannot be accessed by the user.
notes = prepare_notes_for_rendering(notes)
- notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
+ notes.select { |n| n.visible_for?(current_user) }
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 6f86adf6a5c..ba58e125568 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -1174,6 +1174,9 @@ module API
attributes.delete(:performance_bar_enabled)
attributes.delete(:allow_local_requests_from_hooks_and_services)
+ # let's not expose the secret key in a response
+ attributes.delete(:asset_proxy_secret_key)
+
attributes
end
@@ -1226,6 +1229,7 @@ module API
expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? }
expose :commit, using: Entities::Commit, if: lambda { |_, _| can_download_code? }
expose :upcoming_release?, as: :upcoming_release
+ expose :milestone, using: Entities::Milestone, if: -> (release, _) { release.milestone.present? }
expose :assets do
expose :assets_count, as: :count do |release, _|
@@ -1705,6 +1709,18 @@ module API
class ClusterGroup < Cluster
expose :group, using: Entities::BasicGroupDetails
end
+
+ module InternalPostReceive
+ class Message < Grape::Entity
+ expose :message
+ expose :type
+ end
+
+ class Response < Grape::Entity
+ expose :messages, using: Message
+ expose :reference_counter_decreased
+ end
+ end
end
end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index 9afe6c5b027..6b438235258 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -44,8 +44,6 @@ module API
end
def process_mr_push_options(push_options, project, user, changes)
- output = {}
-
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/61359')
service = ::MergeRequests::PushOptionsHandlerService.new(
@@ -56,15 +54,13 @@ module API
).execute
if service.errors.present?
- output[:warnings] = push_options_warning(service.errors.join("\n\n"))
+ push_options_warning(service.errors.join("\n\n"))
end
-
- output
end
def push_options_warning(warning)
options = Array.wrap(params[:push_options]).map { |p| "'#{p}'" }.join(' ')
- "Error encountered with push options #{options}: #{warning}"
+ "WARNINGS:\nError encountered with push options #{options}: #{warning}"
end
def redis_ping
diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb
index b2bf6bf7417..f445834323d 100644
--- a/lib/api/helpers/notes_helpers.rb
+++ b/lib/api/helpers/notes_helpers.rb
@@ -12,7 +12,7 @@ module API
end
def update_note(noteable, note_id)
- note = noteable.notes.find(params[:note_id])
+ note = noteable.notes.find(note_id)
authorize! :admin_note, note
@@ -61,8 +61,8 @@ module API
end
def get_note(noteable, note_id)
- note = noteable.notes.with_metadata.find(params[:note_id])
- can_read_note = !note.cross_reference_not_visible_for?(current_user)
+ note = noteable.notes.with_metadata.find(note_id)
+ can_read_note = note.visible_for?(current_user)
if can_read_note
present note, with: Entities::Note
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 224aaaaf006..088ea5bd79a 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -256,25 +256,26 @@ module API
post '/post_receive' do
status 200
- output = {} # Messages to gitlab-shell
+ response = Gitlab::InternalPostReceive::Response.new
user = identify(params[:identifier])
project = Gitlab::GlRepository.parse(params[:gl_repository]).first
push_options = Gitlab::PushOptions.new(params[:push_options])
+ response.reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease
+
PostReceive.perform_async(params[:gl_repository], params[:identifier],
params[:changes], push_options.as_json)
mr_options = push_options.get(:merge_request)
- output.merge!(process_mr_push_options(mr_options, project, user, params[:changes])) if mr_options.present?
+ if mr_options.present?
+ message = process_mr_push_options(mr_options, project, user, params[:changes])
+ response.add_alert_message(message)
+ end
broadcast_message = BroadcastMessage.current&.last&.message
- reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease
+ response.add_alert_message(broadcast_message)
- output.merge!(
- broadcast_message: broadcast_message,
- reference_counter_decreased: reference_counter_decreased,
- merge_request_urls: merge_request_urls
- )
+ response.add_merge_request_urls(merge_request_urls)
# A user is not guaranteed to be returned; an orphaned write deploy
# key could be used
@@ -282,11 +283,11 @@ module API
redirect_message = Gitlab::Checks::ProjectMoved.fetch_message(user.id, project.id)
project_created_message = Gitlab::Checks::ProjectCreated.fetch_message(user.id, project.id)
- output[:redirected_message] = redirect_message if redirect_message
- output[:project_created_message] = project_created_message if project_created_message
+ response.add_basic_message(redirect_message)
+ response.add_basic_message(project_created_message)
end
- output
+ present response, with: Entities::InternalPostReceive::Response
end
end
end
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 84563d66ee8..16fca9acccb 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -42,7 +42,7 @@ module API
# array returned, but this is really a edge-case.
notes = paginate(raw_notes)
notes = prepare_notes_for_rendering(notes)
- notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
+ notes = notes.select { |note| note.visible_for?(current_user) }
present notes, with: Entities::Note
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index a607df411a6..b4545295d54 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -51,16 +51,18 @@ module API
params do
requires :title, type: String, desc: 'The title of the snippet'
requires :file_name, type: String, desc: 'The file name of the snippet'
- requires :code, type: String, allow_blank: false, desc: 'The content of the snippet'
+ optional :code, type: String, allow_blank: false, desc: 'The content of the snippet (deprecated in favor of "content")'
+ optional :content, type: String, allow_blank: false, desc: 'The content of the snippet'
optional :description, type: String, desc: 'The description of a snippet'
requires :visibility, type: String,
values: Gitlab::VisibilityLevel.string_values,
desc: 'The visibility of the snippet'
+ mutually_exclusive :code, :content
end
post ":id/snippets" do
authorize! :create_project_snippet, user_project
- snippet_params = declared_params.merge(request: request, api: true)
- snippet_params[:content] = snippet_params.delete(:code)
+ snippet_params = declared_params(include_missing: false).merge(request: request, api: true)
+ snippet_params[:content] = snippet_params.delete(:code) if snippet_params[:code].present?
snippet = CreateSnippetService.new(user_project, current_user, snippet_params).execute
@@ -80,12 +82,14 @@ module API
requires :snippet_id, type: Integer, desc: 'The ID of a project snippet'
optional :title, type: String, desc: 'The title of the snippet'
optional :file_name, type: String, desc: 'The file name of the snippet'
- optional :code, type: String, allow_blank: false, desc: 'The content of the snippet'
+ optional :code, type: String, allow_blank: false, desc: 'The content of the snippet (deprecated in favor of "content")'
+ optional :content, type: String, allow_blank: false, desc: 'The content of the snippet'
optional :description, type: String, desc: 'The description of a snippet'
optional :visibility, type: String,
values: Gitlab::VisibilityLevel.string_values,
desc: 'The visibility of the snippet'
- at_least_one_of :title, :file_name, :code, :visibility_level
+ at_least_one_of :title, :file_name, :code, :content, :visibility_level
+ mutually_exclusive :code, :content
end
# rubocop: disable CodeReuse/ActiveRecord
put ":id/snippets/:snippet_id" do
diff --git a/lib/api/release/links.rb b/lib/api/release/links.rb
index 5d1b40e3bff..def36dc8529 100644
--- a/lib/api/release/links.rb
+++ b/lib/api/release/links.rb
@@ -5,7 +5,7 @@ module API
class Links < Grape::API
include PaginationParams
- RELEASE_ENDPOINT_REQUIREMETS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS
+ RELEASE_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS
.merge(tag_name: API::NO_SLASH_URL_PART_REGEX)
before { authorize! :read_release, user_project }
@@ -17,7 +17,7 @@ module API
params do
requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
end
- resource 'releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do
+ resource 'releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do
resource :assets do
desc 'Get a list of links of a release' do
detail 'This feature was introduced in GitLab 11.7.'
diff --git a/lib/api/releases.rb b/lib/api/releases.rb
index 7a3d804c30c..5a31581c4da 100644
--- a/lib/api/releases.rb
+++ b/lib/api/releases.rb
@@ -4,7 +4,7 @@ module API
class Releases < Grape::API
include PaginationParams
- RELEASE_ENDPOINT_REQUIREMETS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS
+ RELEASE_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS
.merge(tag_name: API::NO_SLASH_URL_PART_REGEX)
before { authorize_read_releases! }
@@ -33,7 +33,7 @@ module API
params do
requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
end
- get ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do
+ get ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do
authorize_download_code!
present release, with: Entities::Release, current_user: current_user
@@ -54,6 +54,7 @@ module API
requires :url, type: String
end
end
+ optional :milestone, type: String, desc: 'The title of the related milestone'
optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready. Defaults to the current time.'
end
post ':id/releases' do
@@ -79,8 +80,9 @@ module API
optional :name, type: String, desc: 'The name of the release'
optional :description, type: String, desc: 'Release notes with markdown support'
optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready.'
+ optional :milestone, type: String, desc: 'The title of the related milestone'
end
- put ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do
+ put ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do
authorize_update_release!
result = ::Releases::UpdateService
@@ -101,7 +103,7 @@ module API
params do
requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
end
- delete ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do
+ delete ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMENTS do
authorize_destroy_release!
result = ::Releases::DestroyService
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index c36ee5af63f..dd27ebab83d 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -36,6 +36,10 @@ module API
given akismet_enabled: ->(val) { val } do
requires :akismet_api_key, type: String, desc: 'Generate API key at http://www.akismet.com'
end
+ optional :asset_proxy_enabled, type: Boolean, desc: 'Enable proxying of assets'
+ optional :asset_proxy_url, type: String, desc: 'URL of the asset proxy server'
+ optional :asset_proxy_secret_key, type: String, desc: 'Shared secret with the asset proxy server'
+ optional :asset_proxy_whitelist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Assets that match these domain(s) will NOT be proxied. Wildcards allowed. Your GitLab installation URL is automatically whitelisted.'
optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)'
optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts"
optional :default_project_creation, type: Integer, values: ::Gitlab::Access.project_creation_values, desc: 'Determine if developers can create projects in the group'
@@ -104,6 +108,11 @@ module API
requires :recaptcha_site_key, type: String, desc: 'Generate site key at http://www.google.com/recaptcha'
requires :recaptcha_private_key, type: String, desc: 'Generate private key at http://www.google.com/recaptcha'
end
+ optional :login_recaptcha_protection_enabled, type: Boolean, desc: 'Helps prevent brute-force attacks'
+ given login_recaptcha_protection_enabled: ->(val) { val } do
+ requires :recaptcha_site_key, type: String, desc: 'Generate site key at http://www.google.com/recaptcha'
+ requires :recaptcha_private_key, type: String, desc: 'Generate private key at http://www.google.com/recaptcha'
+ end
optional :repository_checks_enabled, type: Boolean, desc: "GitLab will periodically run 'git fsck' in all project and wiki repositories to look for silent disk corruption issues."
optional :repository_storages, type: Array[String], desc: 'Storage paths for new projects'
optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to set up Two-factor authentication'
@@ -123,7 +132,7 @@ module API
optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.'
optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.'
optional :instance_statistics_visibility_private, type: Boolean, desc: 'When set to `true` Instance statistics will only be available to admins'
- optional :local_markdown_version, type: Integer, desc: "Local markdown version, increase this value when any cached markdown should be invalidated"
+ optional :local_markdown_version, type: Integer, desc: 'Local markdown version, increase this value when any cached markdown should be invalidated'
optional :allow_local_requests_from_hooks_and_services, type: Boolean, desc: 'Deprecated: Use :allow_local_requests_from_web_hooks_and_services instead. Allow requests to the local network from hooks and services.' # support legacy names, can be removed in v5
optional :snowplow_enabled, type: Grape::API::Boolean, desc: 'Enable Snowplow tracking'
given snowplow_enabled: ->(val) { val } do
diff --git a/lib/api/validations/types/comma_separated_to_array.rb b/lib/api/validations/types/comma_separated_to_array.rb
new file mode 100644
index 00000000000..b551878abd1
--- /dev/null
+++ b/lib/api/validations/types/comma_separated_to_array.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module API
+ module Validations
+ module Types
+ class CommaSeparatedToArray
+ def self.coerce
+ lambda do |value|
+ case value
+ when String
+ value.split(',').map(&:strip)
+ when Array
+ value.map { |v| v.to_s.split(',').map(&:strip) }.flatten
+ else
+ []
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index 52af28ce8ec..a0439089879 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -7,6 +7,14 @@ module Banzai
class AbstractReferenceFilter < ReferenceFilter
include CrossProjectReference
+ # REFERENCE_PLACEHOLDER is used for re-escaping HTML text except found
+ # reference (which we replace with placeholder during re-scaping). The
+ # random number helps ensure it's pretty close to unique. Since it's a
+ # transitory value (it never gets saved) we can initialize once, and it
+ # doesn't matter if it changes on a restart.
+ REFERENCE_PLACEHOLDER = "_reference_#{SecureRandom.hex(16)}_"
+ REFERENCE_PLACEHOLDER_PATTERN = %r{#{REFERENCE_PLACEHOLDER}(\d+)}.freeze
+
def self.object_class
# Implement in child class
# Example: MergeRequest
@@ -389,6 +397,14 @@ module Banzai
def escape_html_entities(text)
CGI.escapeHTML(text.to_s)
end
+
+ def escape_with_placeholders(text, placeholder_data)
+ escaped = escape_html_entities(text)
+
+ escaped.gsub(REFERENCE_PLACEHOLDER_PATTERN) do |match|
+ placeholder_data[$1.to_i]
+ end
+ end
end
end
end
diff --git a/lib/banzai/filter/asset_proxy_filter.rb b/lib/banzai/filter/asset_proxy_filter.rb
new file mode 100644
index 00000000000..0a9a52a73a1
--- /dev/null
+++ b/lib/banzai/filter/asset_proxy_filter.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ # Proxy's images/assets to another server. Reduces mixed content warnings
+ # as well as hiding the customer's IP address when requesting images.
+ # Copies the original img `src` to `data-canonical-src` then replaces the
+ # `src` with a new url to the proxy server.
+ class AssetProxyFilter < HTML::Pipeline::CamoFilter
+ def initialize(text, context = nil, result = nil)
+ super
+ end
+
+ def validate
+ needs(:asset_proxy, :asset_proxy_secret_key) if asset_proxy_enabled?
+ end
+
+ def asset_host_whitelisted?(host)
+ context[:asset_proxy_domain_regexp] ? context[:asset_proxy_domain_regexp].match?(host) : false
+ end
+
+ def self.transform_context(context)
+ context[:disable_asset_proxy] = !Gitlab.config.asset_proxy.enabled
+
+ unless context[:disable_asset_proxy]
+ context[:asset_proxy_enabled] = !context[:disable_asset_proxy]
+ context[:asset_proxy] = Gitlab.config.asset_proxy.url
+ context[:asset_proxy_secret_key] = Gitlab.config.asset_proxy.secret_key
+ context[:asset_proxy_domain_regexp] = Gitlab.config.asset_proxy.domain_regexp
+ end
+
+ context
+ end
+
+ # called during an initializer. Caching the values in Gitlab.config significantly increased
+ # performance, rather than querying Gitlab::CurrentSettings.current_application_settings
+ # over and over. However, this does mean that the Rails servers need to get restarted
+ # whenever the application settings are changed
+ def self.initialize_settings
+ application_settings = Gitlab::CurrentSettings.current_application_settings
+ Gitlab.config['asset_proxy'] ||= Settingslogic.new({})
+
+ if application_settings.respond_to?(:asset_proxy_enabled)
+ Gitlab.config.asset_proxy['enabled'] = application_settings.asset_proxy_enabled
+ Gitlab.config.asset_proxy['url'] = application_settings.asset_proxy_url
+ Gitlab.config.asset_proxy['secret_key'] = application_settings.asset_proxy_secret_key
+ Gitlab.config.asset_proxy['whitelist'] = application_settings.asset_proxy_whitelist || [Gitlab.config.gitlab.host]
+ Gitlab.config.asset_proxy['domain_regexp'] = compile_whitelist(Gitlab.config.asset_proxy.whitelist)
+ else
+ Gitlab.config.asset_proxy['enabled'] = ::ApplicationSetting.defaults[:asset_proxy_enabled]
+ end
+ end
+
+ def self.compile_whitelist(domain_list)
+ return if domain_list.empty?
+
+ escaped = domain_list.map { |domain| Regexp.escape(domain).gsub('\*', '.*?') }
+ Regexp.new("^(#{escaped.join('|')})$", Regexp::IGNORECASE)
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/commit_trailers_filter.rb b/lib/banzai/filter/commit_trailers_filter.rb
index f49c4b403db..02a47556151 100644
--- a/lib/banzai/filter/commit_trailers_filter.rb
+++ b/lib/banzai/filter/commit_trailers_filter.rb
@@ -88,7 +88,8 @@ module Banzai
user: user,
user_email: email,
css_class: 'avatar-inline',
- has_tooltip: false
+ has_tooltip: false,
+ only_path: false
)
link_href = user.nil? ? "mailto:#{email}" : urls.user_url(user)
diff --git a/lib/banzai/filter/external_link_filter.rb b/lib/banzai/filter/external_link_filter.rb
index 61ee3eac216..fb721fe12b1 100644
--- a/lib/banzai/filter/external_link_filter.rb
+++ b/lib/banzai/filter/external_link_filter.rb
@@ -14,10 +14,10 @@ module Banzai
# such as on `mailto:` links. Since we've been using it, do an
# initial parse for validity and then use Addressable
# for IDN support, etc
- uri = uri_strict(node['href'].to_s)
+ uri = uri_strict(node_src(node))
if uri
- node.set_attribute('href', uri.to_s)
- addressable_uri = addressable_uri(node['href'])
+ node.set_attribute(node_src_attribute(node), uri.to_s)
+ addressable_uri = addressable_uri(node_src(node))
else
addressable_uri = nil
end
@@ -35,6 +35,16 @@ module Banzai
private
+ # if this is a link to a proxied image, then `src` is already the correct
+ # proxied url, so work with the `data-canonical-src`
+ def node_src_attribute(node)
+ node['data-canonical-src'] ? 'data-canonical-src' : 'href'
+ end
+
+ def node_src(node)
+ node[node_src_attribute(node)]
+ end
+
def uri_strict(href)
URI.parse(href)
rescue URI::Error
@@ -72,7 +82,7 @@ module Banzai
return unless uri
return unless context[:emailable_links]
- unencoded_uri_str = Addressable::URI.unencode(node['href'])
+ unencoded_uri_str = Addressable::URI.unencode(node_src(node))
if unencoded_uri_str == node.content && idn?(uri)
node.content = uri.normalize
diff --git a/lib/banzai/filter/image_link_filter.rb b/lib/banzai/filter/image_link_filter.rb
index 01237303c27..ed0a01e6277 100644
--- a/lib/banzai/filter/image_link_filter.rb
+++ b/lib/banzai/filter/image_link_filter.rb
@@ -18,6 +18,9 @@ module Banzai
rel: 'noopener noreferrer'
)
+ # make sure the original non-proxied src carries over to the link
+ link['data-canonical-src'] = img['data-canonical-src'] if img['data-canonical-src']
+
link.children = img.clone
img.replace(link)
diff --git a/lib/banzai/filter/issuable_state_filter.rb b/lib/banzai/filter/issuable_state_filter.rb
index 8e2358694d4..9dfd77b1759 100644
--- a/lib/banzai/filter/issuable_state_filter.rb
+++ b/lib/banzai/filter/issuable_state_filter.rb
@@ -21,7 +21,8 @@ module Banzai
next if !can_read_cross_project? && cross_reference?(issuable)
if VISIBLE_STATES.include?(issuable.state) && issuable_reference?(node.inner_html, issuable)
- node.content += " (#{issuable.state})"
+ state = moved_issue?(issuable) ? s_("IssuableStatus|moved") : issuable.state
+ node.content += " (#{state})"
end
end
@@ -30,6 +31,10 @@ module Banzai
private
+ def moved_issue?(issuable)
+ issuable.instance_of?(Issue) && issuable.moved?
+ end
+
def issuable_reference?(text, issuable)
CGI.unescapeHTML(text) == issuable.reference_link_text(project || group)
end
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index 4892668fc22..a0789b7ca06 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -14,24 +14,24 @@ module Banzai
find_labels(parent_object).find(id)
end
- def self.references_in(text, pattern = Label.reference_pattern)
- unescape_html_entities(text).gsub(pattern) do |match|
- yield match, $~[:label_id].to_i, $~[:label_name], $~[:project], $~[:namespace], $~
- end
- end
-
def references_in(text, pattern = Label.reference_pattern)
- unescape_html_entities(text).gsub(pattern) do |match|
+ labels = {}
+ unescaped_html = unescape_html_entities(text).gsub(pattern) do |match|
namespace, project = $~[:namespace], $~[:project]
project_path = full_project_path(namespace, project)
label = find_label(project_path, $~[:label_id], $~[:label_name])
if label
- yield match, label.id, project, namespace, $~
+ labels[label.id] = yield match, label.id, project, namespace, $~
+ "#{REFERENCE_PLACEHOLDER}#{label.id}"
else
- escape_html_entities(match)
+ match
end
end
+
+ return text if labels.empty?
+
+ escape_with_placeholders(unescaped_html, labels)
end
def find_label(parent_ref, label_id, label_name)
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index 08969753d75..4c47ee4dba1 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -51,15 +51,21 @@ module Banzai
# default implementation.
return super(text, pattern) if pattern != Milestone.reference_pattern
- unescape_html_entities(text).gsub(pattern) do |match|
+ milestones = {}
+ unescaped_html = unescape_html_entities(text).gsub(pattern) do |match|
milestone = find_milestone($~[:project], $~[:namespace], $~[:milestone_iid], $~[:milestone_name])
if milestone
- yield match, milestone.id, $~[:project], $~[:namespace], $~
+ milestones[milestone.id] = yield match, milestone.id, $~[:project], $~[:namespace], $~
+ "#{REFERENCE_PLACEHOLDER}#{milestone.id}"
else
- escape_html_entities(match)
+ match
end
end
+
+ return text if milestones.empty?
+
+ escape_with_placeholders(unescaped_html, milestones)
end
def find_milestone(project_ref, namespace_ref, milestone_id, milestone_name)
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 86f18679496..2b734db5cfb 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -9,6 +9,7 @@ module Banzai
# Context options:
# :commit
# :group
+ # :current_user
# :project
# :project_wiki
# :ref
@@ -48,7 +49,7 @@ module Banzai
if html_attr.value.start_with?('/uploads/')
process_link_to_upload_attr(html_attr)
- elsif linkable_files?
+ elsif linkable_files? && repo_visible_to_user?
process_link_to_repository_attr(html_attr)
end
end
@@ -166,6 +167,10 @@ module Banzai
Gitlab.config.gitlab.relative_url_root.presence || '/'
end
+ def repo_visible_to_user?
+ project && Ability.allowed?(current_user, :download_code, project)
+ end
+
def ref
context[:ref] || project.default_branch
end
@@ -178,6 +183,10 @@ module Banzai
context[:project]
end
+ def current_user
+ context[:current_user]
+ end
+
def repository
@repository ||= project&.repository
end
diff --git a/lib/banzai/filter/video_link_filter.rb b/lib/banzai/filter/video_link_filter.rb
index 0fff104cf91..a278fcfdb47 100644
--- a/lib/banzai/filter/video_link_filter.rb
+++ b/lib/banzai/filter/video_link_filter.rb
@@ -23,6 +23,14 @@ module Banzai
"'.#{ext}' = substring(@src, string-length(@src) - #{ext.size})"
end
+ if context[:asset_proxy_enabled].present?
+ src_query.concat(
+ UploaderHelper::VIDEO_EXT.map do |ext|
+ "'.#{ext}' = substring(@data-canonical-src, string-length(@data-canonical-src) - #{ext.size})"
+ end
+ )
+ end
+
"descendant-or-self::img[not(ancestor::a) and (#{src_query.join(' or ')})]"
end
end
@@ -48,6 +56,13 @@ module Banzai
target: '_blank',
rel: 'noopener noreferrer',
title: "Download '#{element['title'] || element['alt']}'")
+
+ # make sure the original non-proxied src carries over
+ if element['data-canonical-src']
+ video['data-canonical-src'] = element['data-canonical-src']
+ link['data-canonical-src'] = element['data-canonical-src']
+ end
+
download_paragraph = doc.document.create_element('p')
download_paragraph.children = link
diff --git a/lib/banzai/pipeline/ascii_doc_pipeline.rb b/lib/banzai/pipeline/ascii_doc_pipeline.rb
index d25b74b23b2..82b99d3de4a 100644
--- a/lib/banzai/pipeline/ascii_doc_pipeline.rb
+++ b/lib/banzai/pipeline/ascii_doc_pipeline.rb
@@ -6,12 +6,17 @@ module Banzai
def self.filters
FilterArray[
Filter::AsciiDocSanitizationFilter,
+ Filter::AssetProxyFilter,
Filter::SyntaxHighlightFilter,
Filter::ExternalLinkFilter,
Filter::PlantumlFilter,
Filter::AsciiDocPostProcessingFilter
]
end
+
+ def self.transform_context(context)
+ Filter::AssetProxyFilter.transform_context(context)
+ end
end
end
end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index 2c1006f708a..f419e54c264 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -17,6 +17,7 @@ module Banzai
Filter::SpacedLinkFilter,
Filter::SanitizationFilter,
+ Filter::AssetProxyFilter,
Filter::SyntaxHighlightFilter,
Filter::MathFilter,
@@ -60,7 +61,7 @@ module Banzai
def self.transform_context(context)
context[:only_path] = true unless context.key?(:only_path)
- context
+ Filter::AssetProxyFilter.transform_context(context)
end
end
end
diff --git a/lib/banzai/pipeline/markup_pipeline.rb b/lib/banzai/pipeline/markup_pipeline.rb
index ceba082cd4f..c86d5f08ded 100644
--- a/lib/banzai/pipeline/markup_pipeline.rb
+++ b/lib/banzai/pipeline/markup_pipeline.rb
@@ -6,11 +6,16 @@ module Banzai
def self.filters
@filters ||= FilterArray[
Filter::SanitizationFilter,
+ Filter::AssetProxyFilter,
Filter::ExternalLinkFilter,
Filter::PlantumlFilter,
Filter::SyntaxHighlightFilter
]
end
+
+ def self.transform_context(context)
+ Filter::AssetProxyFilter.transform_context(context)
+ end
end
end
end
diff --git a/lib/banzai/pipeline/single_line_pipeline.rb b/lib/banzai/pipeline/single_line_pipeline.rb
index 72374207a8f..9aff6880f56 100644
--- a/lib/banzai/pipeline/single_line_pipeline.rb
+++ b/lib/banzai/pipeline/single_line_pipeline.rb
@@ -7,6 +7,7 @@ module Banzai
@filters ||= FilterArray[
Filter::HtmlEntityFilter,
Filter::SanitizationFilter,
+ Filter::AssetProxyFilter,
Filter::EmojiFilter,
Filter::AutolinkFilter,
@@ -29,6 +30,8 @@ module Banzai
end
def self.transform_context(context)
+ context = Filter::AssetProxyFilter.transform_context(context)
+
super(context).merge(
no_sourcepos: true
)
diff --git a/lib/gitlab/anonymous_session.rb b/lib/gitlab/anonymous_session.rb
new file mode 100644
index 00000000000..148b6d3310d
--- /dev/null
+++ b/lib/gitlab/anonymous_session.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class AnonymousSession
+ def initialize(remote_ip, session_id: nil)
+ @remote_ip = remote_ip
+ @session_id = session_id
+ end
+
+ def store_session_id_per_ip
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.pipelined do
+ redis.sadd(session_lookup_name, session_id)
+ redis.expire(session_lookup_name, 24.hours)
+ end
+ end
+ end
+
+ def stored_sessions
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.scard(session_lookup_name)
+ end
+ end
+
+ def cleanup_session_per_ip_entries
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.srem(session_lookup_name, session_id)
+ end
+ end
+
+ private
+
+ attr_reader :remote_ip, :session_id
+
+ def session_lookup_name
+ @session_lookup_name ||= "#{Gitlab::Redis::SharedState::IP_SESSIONS_LOOKUP_NAMESPACE}:#{remote_ip}"
+ end
+ end
+end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 6769bd95c2b..bdc46abeb9f 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -265,7 +265,8 @@ module Gitlab
:read_project,
:build_download_code,
:build_read_container_image,
- :build_create_container_image
+ :build_create_container_image,
+ :build_destroy_container_image
]
end
diff --git a/lib/gitlab/authorized_keys.rb b/lib/gitlab/authorized_keys.rb
index 3fe72f5fd43..820a78b653c 100644
--- a/lib/gitlab/authorized_keys.rb
+++ b/lib/gitlab/authorized_keys.rb
@@ -13,6 +13,24 @@ module Gitlab
@logger = logger
end
+ # Checks if the file is accessible or not
+ #
+ # @return [Boolean]
+ def accessible?
+ open_authorized_keys_file('r') { true }
+ rescue Errno::ENOENT, Errno::EACCES
+ false
+ end
+
+ # Creates the authorized_keys file if it doesn't exist
+ #
+ # @return [Boolean]
+ def create
+ open_authorized_keys_file(File::CREAT) { true }
+ rescue Errno::EACCES
+ false
+ end
+
# Add id and its key to the authorized_keys file
#
# @param [String] id identifier of key prefixed by `key-`
@@ -102,10 +120,14 @@ module Gitlab
[]
end
+ def file
+ @file ||= Gitlab.config.gitlab_shell.authorized_keys_file
+ end
+
private
def lock(timeout = 10)
- File.open("#{authorized_keys_file}.lock", "w+") do |f|
+ File.open("#{file}.lock", "w+") do |f|
f.flock File::LOCK_EX
Timeout.timeout(timeout) { yield }
ensure
@@ -114,7 +136,7 @@ module Gitlab
end
def open_authorized_keys_file(mode)
- File.open(authorized_keys_file, mode, 0o600) do |file|
+ File.open(file, mode, 0o600) do |file|
file.chmod(0o600)
yield file
end
@@ -141,9 +163,5 @@ module Gitlab
def strip(key)
key.split(/[ ]+/)[0, 2].join(' ')
end
-
- def authorized_keys_file
- Gitlab.config.gitlab_shell.authorized_keys_file
- end
end
end
diff --git a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
index 6ab4fca3854..f448d55f00a 100644
--- a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
+++ b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
@@ -43,7 +43,7 @@ module Gitlab
end
def create_namespace
- Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new(
+ Clusters::Kubernetes::CreateOrUpdateNamespaceService.new(
cluster: deployment_cluster,
kubernetes_namespace: kubernetes_namespace || build_namespace_record
).execute
diff --git a/lib/gitlab/ci/config/external/file/base.rb b/lib/gitlab/ci/config/external/file/base.rb
index 2ffbb214a92..c56d33544ba 100644
--- a/lib/gitlab/ci/config/external/file/base.rb
+++ b/lib/gitlab/ci/config/external/file/base.rb
@@ -26,6 +26,10 @@ module Gitlab
location.present?
end
+ def invalid_location_type?
+ !location.is_a?(String)
+ end
+
def invalid_extension?
location.nil? || !::File.basename(location).match?(YAML_WHITELIST_EXTENSION)
end
@@ -71,7 +75,9 @@ module Gitlab
end
def validate_location!
- if invalid_extension?
+ if invalid_location_type?
+ errors.push("Included file `#{location}` needs to be a string")
+ elsif invalid_extension?
errors.push("Included file `#{location}` does not have YAML extension!")
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/limit/job_activity.rb b/lib/gitlab/ci/pipeline/chain/limit/job_activity.rb
new file mode 100644
index 00000000000..31c218bf954
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/limit/job_activity.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Pipeline
+ module Chain
+ module Limit
+ class JobActivity < Chain::Base
+ def perform!
+ # to be overridden in EE
+ end
+
+ def break?
+ false # to be overridden in EE
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
index 3adc6a72874..3cdb7b5420c 100644
--- a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
@@ -25,8 +25,9 @@ code_quality:
expire_in: 1 week
dependencies: []
only:
- - branches
- - tags
+ refs:
+ - branches
+ - tags
except:
variables:
- $CODE_QUALITY_DISABLED
diff --git a/lib/gitlab/ci/templates/Pages/SwaggerUI.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/SwaggerUI.gitlab-ci.yml
new file mode 100644
index 00000000000..8fd08ea7995
--- /dev/null
+++ b/lib/gitlab/ci/templates/Pages/SwaggerUI.gitlab-ci.yml
@@ -0,0 +1,29 @@
+image: node:10-alpine
+
+# specify the location of the Open API Specification files within your project
+# and the filename of the specification that you would like to display by default
+variables:
+ DOCS_FOLDER: "api-docs"
+ SPEC_TO_DISPLAY: "my-project_specification_0.0.1.json"
+
+# These folders are cached between builds
+cache:
+ paths:
+ - ./node_modules
+
+# publishes all files from the $DOCS_FOLDER together with the static version of SwaggerUI
+# sets the specification file named in $SPEC_TO_DISPLAY to be displayed by default
+pages:
+ stage: deploy
+ before_script:
+ - npm install swagger-ui-dist@3.22.1
+ script:
+ - mkdir public
+ - cp -rp node_modules/swagger-ui-dist/* public
+ - cp -rp $DOCS_FOLDER/* public
+ - sed -i "s#https://petstore\.swagger\.io/v2/swagger\.json#$SPEC_TO_DISPLAY#g" public/index.html
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/lib/gitlab/correlation_id.rb b/lib/gitlab/correlation_id.rb
deleted file mode 100644
index 0f9bde4390e..00000000000
--- a/lib/gitlab/correlation_id.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module CorrelationId
- LOG_KEY = 'correlation_id'.freeze
-
- class << self
- def use_id(correlation_id, &blk)
- # always generate a id if null is passed
- correlation_id ||= new_id
-
- ids.push(correlation_id || new_id)
-
- begin
- yield(current_id)
- ensure
- ids.pop
- end
- end
-
- def current_id
- ids.last
- end
-
- def current_or_new_id
- current_id || new_id
- end
-
- private
-
- def ids
- Thread.current[:correlation_id] ||= []
- end
-
- def new_id
- SecureRandom.uuid
- end
- end
- end
-end
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index a12bbededc4..6ecd506d55b 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -13,6 +13,10 @@ module Gitlab
# FIXME: this should just be the max value of timestampz
MAX_TIMESTAMP_VALUE = Time.at((1 << 31) - 1).freeze
+ # The maximum number of characters for text fields, to avoid DoS attacks via parsing huge text fields
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/61974
+ MAX_TEXT_SIZE_LIMIT = 1_000_000
+
# Minimum schema version from which migrations are supported
# Migrations before this version may have been removed
MIN_SCHEMA_VERSION = 20190506135400
diff --git a/lib/gitlab/database_importers/self_monitoring/project/create_service.rb b/lib/gitlab/database_importers/self_monitoring/project/create_service.rb
index 3a170e8b5f8..5422a8631a0 100644
--- a/lib/gitlab/database_importers/self_monitoring/project/create_service.rb
+++ b/lib/gitlab/database_importers/self_monitoring/project/create_service.rb
@@ -45,20 +45,20 @@ module Gitlab
def validate_application_settings
return success if application_settings
- log_error(_('No application_settings found'))
+ log_error('No application_settings found')
error(_('No application_settings found'))
end
def validate_project_created
return success unless project_created?
- log_error(_('Project already created'))
+ log_error('Project already created')
error(_('Project already created'))
end
def validate_admins
unless instance_admins.any?
- log_error(_('No active admin user found'))
+ log_error('No active admin user found')
return error(_('No active admin user found'))
end
@@ -83,7 +83,7 @@ module Gitlab
def create_project
if project_created?
- log_info(_('Instance administration project already exists'))
+ log_info('Instance administration project already exists')
@project = application_settings.instance_administration_project
return success(project: project)
end
@@ -93,7 +93,7 @@ module Gitlab
if project.persisted?
success(project: project)
else
- log_error(_("Could not create instance administration project. Errors: %{errors}") % { errors: project.errors.full_messages })
+ log_error("Could not create instance administration project. Errors: %{errors}" % { errors: project.errors.full_messages })
error(_('Could not create project'))
end
end
@@ -106,7 +106,7 @@ module Gitlab
if result
success
else
- log_error(_("Could not save instance administration project ID, errors: %{errors}") % { errors: application_settings.errors.full_messages })
+ log_error("Could not save instance administration project ID, errors: %{errors}" % { errors: application_settings.errors.full_messages })
error(_('Could not save project ID'))
end
end
@@ -116,7 +116,7 @@ module Gitlab
errors = members.flat_map { |member| member.errors.full_messages }
if errors.any?
- log_error(_('Could not add admins as members to self-monitoring project. Errors: %{errors}') % { errors: errors })
+ log_error('Could not add admins as members to self-monitoring project. Errors: %{errors}' % { errors: errors })
error(_('Could not add admins as members'))
else
success
@@ -128,7 +128,7 @@ module Gitlab
return success unless prometheus_listen_address.present?
uri = parse_url(internal_prometheus_listen_address_uri)
- return error(_('Prometheus listen_address is not a valid URI')) unless uri
+ return error(_('Prometheus listen_address in config/gitlab.yml is not a valid URI')) unless uri
application_settings.add_to_outbound_local_requests_whitelist([uri.normalized_host])
result = application_settings.save
@@ -140,7 +140,7 @@ module Gitlab
Gitlab::CurrentSettings.expire_current_application_settings
success
else
- log_error(_("Could not add prometheus URL to whitelist, errors: %{errors}") % { errors: application_settings.errors.full_messages })
+ log_error("Could not add prometheus URL to whitelist, errors: %{errors}" % { errors: application_settings.errors.full_messages })
error(_('Could not add prometheus URL to whitelist'))
end
end
@@ -152,7 +152,7 @@ module Gitlab
service = project.find_or_initialize_service('prometheus')
unless service.update(prometheus_service_attributes)
- log_error(_('Could not save prometheus manual configuration for self-monitoring project. Errors: %{errors}') % { errors: service.errors.full_messages })
+ log_error('Could not save prometheus manual configuration for self-monitoring project. Errors: %{errors}' % { errors: service.errors.full_messages })
return error(_('Could not save prometheus manual configuration'))
end
@@ -175,15 +175,15 @@ module Gitlab
def prometheus_enabled?
Gitlab.config.prometheus.enable if Gitlab.config.prometheus
rescue Settingslogic::MissingSetting
- log_error(_('prometheus.enable is not present in gitlab.yml'))
+ log_error('prometheus.enable is not present in config/gitlab.yml')
false
end
def prometheus_listen_address
- Gitlab.config.prometheus.listen_address if Gitlab.config.prometheus
+ Gitlab.config.prometheus.listen_address.to_s if Gitlab.config.prometheus
rescue Settingslogic::MissingSetting
- log_error(_('prometheus.listen_address is not present in gitlab.yml'))
+ log_error('Prometheus listen_address is not present in config/gitlab.yml')
nil
end
@@ -228,9 +228,21 @@ module Gitlab
end
def internal_prometheus_listen_address_uri
- if prometheus_listen_address.starts_with?('http')
+ if prometheus_listen_address.starts_with?('0.0.0.0:')
+ # 0.0.0.0:9090
+ port = ':' + prometheus_listen_address.split(':').second
+ 'http://localhost' + port
+
+ elsif prometheus_listen_address.starts_with?(':')
+ # :9090
+ 'http://localhost' + prometheus_listen_address
+
+ elsif prometheus_listen_address.starts_with?('http')
+ # https://localhost:9090
prometheus_listen_address
+
else
+ # localhost:9090
'http://' + prometheus_listen_address
end
end
diff --git a/lib/gitlab/diff/suggestion.rb b/lib/gitlab/diff/suggestion.rb
index 4a3ac2106e2..b669e785721 100644
--- a/lib/gitlab/diff/suggestion.rb
+++ b/lib/gitlab/diff/suggestion.rb
@@ -46,7 +46,7 @@ module Gitlab
private
def line_break_chars(line)
- match = /\r\n|\r|\n/.match(line)
+ match = Gitlab::Regex.breakline_regex.match(line)
match[0] if match
end
end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index d65c0d3e78d..2ac99b1ff02 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -157,7 +157,7 @@ module Gitlab
# Keep track, separately, for the performance bar
self.query_time += duration
- if peek_enabled?
+ if Gitlab::PerformanceBar.enabled_for_request?
add_call_details(feature: "#{service}##{rpc}", duration: duration, request: request_hash, rpc: rpc,
backtrace: Gitlab::Profiler.clean_backtrace(caller))
end
@@ -335,17 +335,13 @@ module Gitlab
Gitlab::SafeRequestStore["gitaly_call_permitted"] = 0
end
- def self.peek_enabled?
- Gitlab::SafeRequestStore[:peek_enabled]
- end
-
def self.add_call_details(details)
Gitlab::SafeRequestStore['gitaly_call_details'] ||= []
Gitlab::SafeRequestStore['gitaly_call_details'] << details
end
def self.list_call_details
- return [] unless peek_enabled?
+ return [] unless Gitlab::PerformanceBar.enabled_for_request?
Gitlab::SafeRequestStore['gitaly_call_details'] || []
end
diff --git a/lib/gitlab/internal_post_receive/response.rb b/lib/gitlab/internal_post_receive/response.rb
new file mode 100644
index 00000000000..7e7ec2aa45c
--- /dev/null
+++ b/lib/gitlab/internal_post_receive/response.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module InternalPostReceive
+ class Response
+ attr_accessor :reference_counter_decreased
+ attr_reader :messages
+
+ Message = Struct.new(:message, :type) do
+ def self.basic(text)
+ new(text, :basic)
+ end
+
+ def self.alert(text)
+ new(text, :alert)
+ end
+ end
+
+ def initialize
+ @messages = []
+ @reference_counter_decreased = false
+ end
+
+ def add_merge_request_urls(urls_data)
+ urls_data.each do |url_data|
+ add_merge_request_url(url_data)
+ end
+ end
+
+ def add_merge_request_url(url_data)
+ message = if url_data[:new_merge_request]
+ "To create a merge request for #{url_data[:branch_name]}, visit:"
+ else
+ "View merge request for #{url_data[:branch_name]}:"
+ end
+
+ message += "\n #{url_data[:url]}"
+
+ add_basic_message(message)
+ end
+
+ def add_basic_message(text)
+ @messages << Message.basic(text) if text.present?
+ end
+
+ def add_alert_message(text)
+ @messages.unshift(Message.alert(text)) if text.present?
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/jira/http_client.rb b/lib/gitlab/jira/http_client.rb
new file mode 100644
index 00000000000..11a33a7b358
--- /dev/null
+++ b/lib/gitlab/jira/http_client.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Jira
+ # Gitlab JIRA HTTP client to be used with jira-ruby gem, this subclasses JIRA::HTTPClient.
+ # Uses Gitlab::HTTP to make requests to JIRA REST API.
+ # The parent class implementation can be found at: https://github.com/sumoheavy/jira-ruby/blob/v1.4.0/lib/jira/http_client.rb
+ class HttpClient < JIRA::HttpClient
+ extend ::Gitlab::Utils::Override
+
+ override :request
+ def request(*args)
+ result = make_request(*args)
+
+ raise JIRA::HTTPError.new(result) unless result.response.is_a?(Net::HTTPSuccess)
+
+ result
+ end
+
+ override :make_cookie_auth_request
+ def make_cookie_auth_request
+ body = {
+ username: @options.delete(:username),
+ password: @options.delete(:password)
+ }.to_json
+
+ make_request(:post, @options[:context_path] + '/rest/auth/1/session', body, { 'Content-Type' => 'application/json' })
+ end
+
+ override :make_request
+ def make_request(http_method, path, body = '', headers = {})
+ request_params = { headers: headers }
+ request_params[:body] = body if body.present?
+ request_params[:headers][:Cookie] = get_cookies if options[:use_cookies]
+ request_params[:timeout] = options[:read_timeout] if options[:read_timeout]
+ request_params[:base_uri] = uri.to_s
+ request_params.merge!(auth_params)
+
+ result = Gitlab::HTTP.public_send(http_method, path, **request_params) # rubocop:disable GitlabSecurity/PublicSend
+ @authenticated = result.response.is_a?(Net::HTTPOK)
+ store_cookies(result) if options[:use_cookies]
+
+ result
+ end
+
+ def auth_params
+ return {} unless @options[:username] && @options[:password]
+
+ {
+ basic_auth: {
+ username: @options[:username],
+ password: @options[:password]
+ }
+ }
+ end
+
+ private
+
+ def get_cookies
+ cookie_array = @cookies.values.map { |cookie| "#{cookie.name}=#{cookie.value[0]}" }
+ cookie_array += Array(@options[:additional_cookies]) if @options.key?(:additional_cookies)
+ cookie_array.join('; ') if cookie_array.any?
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/markdown_cache.rb b/lib/gitlab/markdown_cache.rb
index 0354c710a3f..03a2f62cbd9 100644
--- a/lib/gitlab/markdown_cache.rb
+++ b/lib/gitlab/markdown_cache.rb
@@ -3,8 +3,8 @@
module Gitlab
module MarkdownCache
# Increment this number every time the renderer changes its output
+ CACHE_COMMONMARK_VERSION = 17
CACHE_COMMONMARK_VERSION_START = 10
- CACHE_COMMONMARK_VERSION = 16
BaseError = Class.new(StandardError)
UnsupportedClassError = Class.new(BaseError)
diff --git a/lib/gitlab/metrics/samplers/ruby_sampler.rb b/lib/gitlab/metrics/samplers/ruby_sampler.rb
index 3bfa3da35e0..c38769f39a9 100644
--- a/lib/gitlab/metrics/samplers/ruby_sampler.rb
+++ b/lib/gitlab/metrics/samplers/ruby_sampler.rb
@@ -6,7 +6,7 @@ module Gitlab
module Metrics
module Samplers
class RubySampler < BaseSampler
- GC_REPORT_BUCKETS = [0.001, 0.002, 0.005, 0.01, 0.05, 0.1, 0.5].freeze
+ GC_REPORT_BUCKETS = [0.005, 0.01, 0.02, 0.04, 0.07, 0.1, 0.5].freeze
def initialize(interval)
GC::Profiler.clear
diff --git a/lib/gitlab/optimistic_locking.rb b/lib/gitlab/optimistic_locking.rb
index 0c0f46d3b77..d51d718c826 100644
--- a/lib/gitlab/optimistic_locking.rb
+++ b/lib/gitlab/optimistic_locking.rb
@@ -4,7 +4,8 @@ module Gitlab
module OptimisticLocking
module_function
- def retry_lock(subject, retries = 100, &block)
+ def retry_lock(subject, retries = nil, &block)
+ retries ||= 100
# TODO(Observability): We should be recording details of the number of retries and the duration of the total execution here
ActiveRecord::Base.transaction do
yield(subject)
diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb
index f96466b2b00..d9c28ff1181 100644
--- a/lib/gitlab/path_regex.rb
+++ b/lib/gitlab/path_regex.rb
@@ -132,7 +132,7 @@ module Gitlab
NO_SUFFIX_REGEX = /(?<!\.git|\.atom)/.freeze
NAMESPACE_FORMAT_REGEX = /(?:#{NAMESPACE_FORMAT_REGEX_JS})#{NO_SUFFIX_REGEX}/.freeze
PROJECT_PATH_FORMAT_REGEX = /(?:#{PATH_REGEX_STR})#{NO_SUFFIX_REGEX}/.freeze
- FULL_NAMESPACE_FORMAT_REGEX = %r{(#{NAMESPACE_FORMAT_REGEX}/)*#{NAMESPACE_FORMAT_REGEX}}.freeze
+ FULL_NAMESPACE_FORMAT_REGEX = %r{(#{NAMESPACE_FORMAT_REGEX}/){,#{Namespace::NUMBER_OF_ANCESTORS_ALLOWED}}#{NAMESPACE_FORMAT_REGEX}}.freeze
def root_namespace_route_regex
@root_namespace_route_regex ||= begin
diff --git a/lib/gitlab/performance_bar.rb b/lib/gitlab/performance_bar.rb
index 07439d8e011..68af290d069 100644
--- a/lib/gitlab/performance_bar.rb
+++ b/lib/gitlab/performance_bar.rb
@@ -6,7 +6,11 @@ module Gitlab
EXPIRY_TIME_L1_CACHE = 1.minute
EXPIRY_TIME_L2_CACHE = 5.minutes
- def self.enabled?(user = nil)
+ def self.enabled_for_request?
+ Gitlab::SafeRequestStore[:peek_enabled]
+ end
+
+ def self.enabled_for_user?(user = nil)
return true if Rails.env.development?
return true if user&.admin?
return false unless user && allowed_group_id
diff --git a/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled.rb b/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled.rb
index 2d997760c46..cddd4f18cc3 100644
--- a/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled.rb
+++ b/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled.rb
@@ -4,8 +4,8 @@
module Gitlab
module PerformanceBar
module RedisAdapterWhenPeekEnabled
- def save
- super unless ::Peek.request_id.blank?
+ def save(request_id)
+ super if ::Gitlab::PerformanceBar.enabled_for_request? && request_id.present?
end
end
end
diff --git a/lib/gitlab/performance_bar/with_top_level_warnings.rb b/lib/gitlab/performance_bar/with_top_level_warnings.rb
new file mode 100644
index 00000000000..fb5c5c5959d
--- /dev/null
+++ b/lib/gitlab/performance_bar/with_top_level_warnings.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module PerformanceBar
+ module WithTopLevelWarnings
+ def results
+ results = super
+
+ results.merge(has_warnings: has_warnings?(results))
+ end
+
+ def has_warnings?(results)
+ results[:data].any? do |_, value|
+ value[:warnings].present?
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/recaptcha.rb b/lib/gitlab/recaptcha.rb
index 772d743c9b0..f3cbe1db901 100644
--- a/lib/gitlab/recaptcha.rb
+++ b/lib/gitlab/recaptcha.rb
@@ -3,7 +3,7 @@
module Gitlab
module Recaptcha
def self.load_configurations!
- if Gitlab::CurrentSettings.recaptcha_enabled
+ if Gitlab::CurrentSettings.recaptcha_enabled || enabled_on_login?
::Recaptcha.configure do |config|
config.site_key = Gitlab::CurrentSettings.recaptcha_site_key
config.secret_key = Gitlab::CurrentSettings.recaptcha_private_key
@@ -16,5 +16,9 @@ module Gitlab
def self.enabled?
Gitlab::CurrentSettings.recaptcha_enabled
end
+
+ def self.enabled_on_login?
+ Gitlab::CurrentSettings.login_recaptcha_protection_enabled
+ end
end
end
diff --git a/lib/gitlab/redis/shared_state.rb b/lib/gitlab/redis/shared_state.rb
index 9066606ca21..270a19e780c 100644
--- a/lib/gitlab/redis/shared_state.rb
+++ b/lib/gitlab/redis/shared_state.rb
@@ -9,6 +9,7 @@ module Gitlab
SESSION_NAMESPACE = 'session:gitlab'.freeze
USER_SESSIONS_NAMESPACE = 'session:user:gitlab'.freeze
USER_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:user:gitlab'.freeze
+ IP_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:ip:gitlab'.freeze
DEFAULT_REDIS_SHARED_STATE_URL = 'redis://localhost:6382'.freeze
REDIS_SHARED_STATE_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_SHARED_STATE_CONFIG_FILE'.freeze
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index e6372a42dda..6636ffa448e 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -115,5 +115,9 @@ module Gitlab
def jira_transition_id_regex
@jira_transition_id_regex ||= /\d+/
end
+
+ def breakline_regex
+ @breakline_regex ||= /\r\n|\r|\n/
+ end
end
end
diff --git a/lib/gitlab/repository_cache_adapter.rb b/lib/gitlab/repository_cache_adapter.rb
index 75503ee1789..e40c366ed02 100644
--- a/lib/gitlab/repository_cache_adapter.rb
+++ b/lib/gitlab/repository_cache_adapter.rb
@@ -23,37 +23,6 @@ module Gitlab
end
end
- # Caches and strongly memoizes the method as a Redis Set.
- #
- # This only works for methods that do not take any arguments. The method
- # should return an Array of Strings to be cached.
- #
- # In addition to overriding the named method, a "name_include?" method is
- # defined. This uses the "SISMEMBER" query to efficiently check membership
- # without needing to load the entire set into memory.
- #
- # name - The name of the method to be cached.
- # fallback - A value to fall back to if the repository does not exist, or
- # in case of a Git error. Defaults to nil.
- def cache_method_as_redis_set(name, fallback: nil)
- uncached_name = alias_uncached_method(name)
-
- define_method(name) do
- cache_method_output_as_redis_set(name, fallback: fallback) do
- __send__(uncached_name) # rubocop:disable GitlabSecurity/PublicSend
- end
- end
-
- define_method("#{name}_include?") do |value|
- # If the cache isn't populated, we can't rely on it
- return redis_set_cache.include?(name, value) if redis_set_cache.exist?(name)
-
- # Since we have to pull all branch names to populate the cache, use
- # the data we already have to answer the query just this once
- __send__(name).include?(value) # rubocop:disable GitlabSecurity/PublicSend
- end
- end
-
# Caches truthy values from the method. All values are strongly memoized,
# and cached in RequestStore.
#
@@ -115,11 +84,6 @@ module Gitlab
raise NotImplementedError
end
- # RepositorySetCache to be used. Should be overridden by the including class
- def redis_set_cache
- raise NotImplementedError
- end
-
# List of cached methods. Should be overridden by the including class
def cached_methods
raise NotImplementedError
@@ -136,18 +100,6 @@ module Gitlab
end
end
- # Caches and strongly memoizes the supplied block as a Redis Set. The result
- # will be provided as a sorted array.
- #
- # name - The name of the method to be cached.
- # fallback - A value to fall back to if the repository does not exist, or
- # in case of a Git error. Defaults to nil.
- def cache_method_output_as_redis_set(name, fallback: nil, &block)
- memoize_method_output(name, fallback: fallback) do
- redis_set_cache.fetch(name, &block).sort
- end
- end
-
# Caches truthy values from the supplied block. All values are strongly
# memoized, and cached in RequestStore.
#
@@ -202,7 +154,6 @@ module Gitlab
clear_memoization(memoizable_name(name))
end
- expire_redis_set_method_caches(methods)
expire_request_store_method_caches(methods)
end
@@ -218,10 +169,6 @@ module Gitlab
end
end
- def expire_redis_set_method_caches(methods)
- methods.each { |name| redis_set_cache.expire(name) }
- end
-
# All cached repository methods depend on the existence of a Git repository,
# so if the repository doesn't exist, we already know not to call it.
def fallback_early?(method_name)
diff --git a/lib/gitlab/repository_set_cache.rb b/lib/gitlab/repository_set_cache.rb
deleted file mode 100644
index fb634328a95..00000000000
--- a/lib/gitlab/repository_set_cache.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-# frozen_string_literal: true
-
-# Interface to the Redis-backed cache store for keys that use a Redis set
-module Gitlab
- class RepositorySetCache
- attr_reader :repository, :namespace, :expires_in
-
- def initialize(repository, extra_namespace: nil, expires_in: 2.weeks)
- @repository = repository
- @namespace = "#{repository.full_path}:#{repository.project.id}"
- @namespace = "#{@namespace}:#{extra_namespace}" if extra_namespace
- @expires_in = expires_in
- end
-
- def cache_key(type)
- [type, namespace, 'set'].join(':')
- end
-
- def expire(key)
- with { |redis| redis.del(cache_key(key)) }
- end
-
- def exist?(key)
- with { |redis| redis.exists(cache_key(key)) }
- end
-
- def read(key)
- with { |redis| redis.smembers(cache_key(key)) }
- end
-
- def write(key, value)
- full_key = cache_key(key)
-
- with do |redis|
- redis.multi do
- redis.del(full_key)
-
- # Splitting into groups of 1000 prevents us from creating a too-long
- # Redis command
- value.in_groups_of(1000, false) { |subset| redis.sadd(full_key, subset) }
-
- redis.expire(full_key, expires_in)
- end
- end
-
- value
- end
-
- def fetch(key, &block)
- if exist?(key)
- read(key)
- else
- write(key, yield)
- end
- end
-
- def include?(key, value)
- with { |redis| redis.sismember(cache_key(key), value) }
- end
-
- private
-
- def with(&blk)
- Gitlab::Redis::Cache.with(&blk) # rubocop:disable CodeReuse/ActiveRecord
- end
- end
-end
diff --git a/lib/gitlab/rugged_instrumentation.rb b/lib/gitlab/rugged_instrumentation.rb
index 8bb8c547ae1..c2b55431547 100644
--- a/lib/gitlab/rugged_instrumentation.rb
+++ b/lib/gitlab/rugged_instrumentation.rb
@@ -27,19 +27,15 @@ module Gitlab
SafeRequestStore.active?
end
- def self.peek_enabled?
- SafeRequestStore[:peek_enabled]
- end
-
def self.add_call_details(details)
- return unless peek_enabled?
+ return unless Gitlab::PerformanceBar.enabled_for_request?
Gitlab::SafeRequestStore[:rugged_call_details] ||= []
Gitlab::SafeRequestStore[:rugged_call_details] << details
end
def self.list_call_details
- return [] unless peek_enabled?
+ return [] unless Gitlab::PerformanceBar.enabled_for_request?
Gitlab::SafeRequestStore[:rugged_call_details] || []
end
diff --git a/lib/gitlab/sanitizers/exif.rb b/lib/gitlab/sanitizers/exif.rb
index bb4e4ce7bbc..2f3d14ecebd 100644
--- a/lib/gitlab/sanitizers/exif.rb
+++ b/lib/gitlab/sanitizers/exif.rb
@@ -53,15 +53,18 @@ module Gitlab
end
# rubocop: disable CodeReuse/ActiveRecord
- def batch_clean(start_id: nil, stop_id: nil, dry_run: true, sleep_time: nil)
+ def batch_clean(start_id: nil, stop_id: nil, dry_run: true, sleep_time: nil, uploader: nil, since: nil)
relation = Upload.where('lower(path) like ? or lower(path) like ? or lower(path) like ?',
'%.jpg', '%.jpeg', '%.tiff')
+ relation = relation.where(uploader: uploader) if uploader
+ relation = relation.where('created_at > ?', since) if since
logger.info "running in dry run mode, no images will be rewritten" if dry_run
find_params = {
start: start_id.present? ? start_id.to_i : nil,
- finish: stop_id.present? ? stop_id.to_i : Upload.last&.id
+ finish: stop_id.present? ? stop_id.to_i : Upload.last&.id,
+ batch_size: 1000
}
relation.find_each(find_params) do |upload|
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 0fa17b3f559..9e813968093 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -165,16 +165,7 @@ module Gitlab
def add_key(key_id, key_content)
return unless self.authorized_keys_enabled?
- if shell_out_for_gitlab_keys?
- gitlab_shell_fast_execute([
- gitlab_shell_keys_path,
- 'add-key',
- key_id,
- strip_key(key_content)
- ])
- else
- gitlab_authorized_keys.add_key(key_id, key_content)
- end
+ gitlab_authorized_keys.add_key(key_id, key_content)
end
# Batch-add keys to authorized_keys
@@ -184,19 +175,7 @@ module Gitlab
def batch_add_keys(keys)
return unless self.authorized_keys_enabled?
- if shell_out_for_gitlab_keys?
- begin
- IO.popen("#{gitlab_shell_keys_path} batch-add-keys", 'w') do |io|
- add_keys_to_io(keys, io)
- end
-
- $?.success?
- rescue Error
- false
- end
- else
- gitlab_authorized_keys.batch_add_keys(keys)
- end
+ gitlab_authorized_keys.batch_add_keys(keys)
end
# Remove ssh key from authorized_keys
@@ -207,11 +186,7 @@ module Gitlab
def remove_key(id, _ = nil)
return unless self.authorized_keys_enabled?
- if shell_out_for_gitlab_keys?
- gitlab_shell_fast_execute([gitlab_shell_keys_path, 'rm-key', id])
- else
- gitlab_authorized_keys.rm_key(id)
- end
+ gitlab_authorized_keys.rm_key(id)
end
# Remove all ssh keys from gitlab shell
@@ -222,11 +197,7 @@ module Gitlab
def remove_all_keys
return unless self.authorized_keys_enabled?
- if shell_out_for_gitlab_keys?
- gitlab_shell_fast_execute([gitlab_shell_keys_path, 'clear'])
- else
- gitlab_authorized_keys.clear
- end
+ gitlab_authorized_keys.clear
end
# Remove ssh keys from gitlab shell that are not in the DB
@@ -341,14 +312,6 @@ module Gitlab
File.join(Gitlab.config.repositories.storages[storage].legacy_disk_path, dir_name)
end
- def gitlab_shell_projects_path
- File.join(gitlab_shell_path, 'bin', 'gitlab-projects')
- end
-
- def gitlab_shell_keys_path
- File.join(gitlab_shell_path, 'bin', 'gitlab-keys')
- end
-
def authorized_keys_enabled?
# Return true if nil to ensure the authorized_keys methods work while
# fixing the authorized_keys file during migration.
@@ -359,35 +322,6 @@ module Gitlab
private
- def shell_out_for_gitlab_keys?
- Gitlab.config.gitlab_shell.authorized_keys_file.blank?
- end
-
- def gitlab_shell_fast_execute(cmd)
- output, status = gitlab_shell_fast_execute_helper(cmd)
-
- return true if status.zero?
-
- Rails.logger.error("gitlab-shell failed with error #{status}: #{output}") # rubocop:disable Gitlab/RailsLogger
- false
- end
-
- def gitlab_shell_fast_execute_raise_error(cmd, vars = {})
- output, status = gitlab_shell_fast_execute_helper(cmd, vars)
-
- raise Error, output unless status.zero?
-
- true
- end
-
- def gitlab_shell_fast_execute_helper(cmd, vars = {})
- vars.merge!(ENV.to_h.slice(*GITLAB_SHELL_ENV_VARS))
-
- # Don't pass along the entire parent environment to prevent gitlab-shell
- # from wasting I/O by searching through GEM_PATH
- Bundler.with_original_env { Popen.popen(cmd, nil, vars) }
- end
-
def git_timeout
Gitlab.config.gitlab_shell.git_timeout
end
@@ -407,16 +341,8 @@ module Gitlab
def batch_read_key_ids(batch_size: 100, &block)
return unless self.authorized_keys_enabled?
- if shell_out_for_gitlab_keys?
- IO.popen("#{gitlab_shell_keys_path} list-key-ids") do |key_id_stream|
- key_id_stream.lazy.each_slice(batch_size) do |lines|
- yield(lines.map { |l| l.chomp.to_i })
- end
- end
- else
- gitlab_authorized_keys.list_key_ids.lazy.each_slice(batch_size) do |key_ids|
- yield(key_ids)
- end
+ gitlab_authorized_keys.list_key_ids.lazy.each_slice(batch_size) do |key_ids|
+ yield(key_ids)
end
end
diff --git a/lib/gitlab/sidekiq_middleware/metrics.rb b/lib/gitlab/sidekiq_middleware/metrics.rb
index 3dc9521ee8b..368f37a5d8c 100644
--- a/lib/gitlab/sidekiq_middleware/metrics.rb
+++ b/lib/gitlab/sidekiq_middleware/metrics.rb
@@ -35,7 +35,7 @@ module Gitlab
def init_metrics
{
- sidekiq_jobs_completion_seconds: ::Gitlab::Metrics.histogram(:sidekiq_jobs_completion_seconds, 'Seconds to complete sidekiq job', buckets: SIDEKIQ_LATENCY_BUCKETS),
+ sidekiq_jobs_completion_seconds: ::Gitlab::Metrics.histogram(:sidekiq_jobs_completion_seconds, 'Seconds to complete sidekiq job', {}, SIDEKIQ_LATENCY_BUCKETS),
sidekiq_jobs_failed_total: ::Gitlab::Metrics.counter(:sidekiq_jobs_failed_total, 'Sidekiq jobs failed'),
sidekiq_jobs_retried_total: ::Gitlab::Metrics.counter(:sidekiq_jobs_retried_total, 'Sidekiq jobs retried'),
sidekiq_running_jobs: ::Gitlab::Metrics.gauge(:sidekiq_running_jobs, 'Number of Sidekiq jobs running', {}, :livesum)
diff --git a/lib/gitlab/slash_commands/application_help.rb b/lib/gitlab/slash_commands/application_help.rb
index 0ea7554ba64..1a92346be15 100644
--- a/lib/gitlab/slash_commands/application_help.rb
+++ b/lib/gitlab/slash_commands/application_help.rb
@@ -3,12 +3,15 @@
module Gitlab
module SlashCommands
class ApplicationHelp < BaseCommand
- def initialize(params)
+ def initialize(project, params)
+ @project = project
@params = params
end
def execute
- Gitlab::SlashCommands::Presenters::Help.new(commands).present(trigger, params[:text])
+ Gitlab::SlashCommands::Presenters::Help
+ .new(project, commands)
+ .present(trigger, params[:text])
end
private
diff --git a/lib/gitlab/slash_commands/command.rb b/lib/gitlab/slash_commands/command.rb
index 7c963fcf38a..079b5916566 100644
--- a/lib/gitlab/slash_commands/command.rb
+++ b/lib/gitlab/slash_commands/command.rb
@@ -9,6 +9,7 @@ module Gitlab
Gitlab::SlashCommands::IssueNew,
Gitlab::SlashCommands::IssueSearch,
Gitlab::SlashCommands::IssueMove,
+ Gitlab::SlashCommands::IssueClose,
Gitlab::SlashCommands::Deploy,
Gitlab::SlashCommands::Run
]
@@ -21,7 +22,7 @@ module Gitlab
if command.allowed?(project, current_user)
command.new(project, chat_name, params).execute(match)
else
- Gitlab::SlashCommands::Presenters::Access.new.access_denied
+ Gitlab::SlashCommands::Presenters::Access.new.access_denied(project)
end
else
Gitlab::SlashCommands::Help.new(project, chat_name, params)
diff --git a/lib/gitlab/slash_commands/help.rb b/lib/gitlab/slash_commands/help.rb
index dbe15baa3d7..3eff64192ab 100644
--- a/lib/gitlab/slash_commands/help.rb
+++ b/lib/gitlab/slash_commands/help.rb
@@ -19,7 +19,9 @@ module Gitlab
end
def execute(commands, text)
- Gitlab::SlashCommands::Presenters::Help.new(commands).present(trigger, text)
+ Gitlab::SlashCommands::Presenters::Help
+ .new(project, commands)
+ .present(trigger, text)
end
def trigger
diff --git a/lib/gitlab/slash_commands/issue_close.rb b/lib/gitlab/slash_commands/issue_close.rb
new file mode 100644
index 00000000000..5fcc86e91c4
--- /dev/null
+++ b/lib/gitlab/slash_commands/issue_close.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SlashCommands
+ class IssueClose < IssueCommand
+ def self.match(text)
+ /\Aissue\s+close\s+#{Issue.reference_prefix}?(?<iid>\d+)/.match(text)
+ end
+
+ def self.help_message
+ "issue close <id>"
+ end
+
+ def self.allowed?(project, user)
+ can?(user, :update_issue, project)
+ end
+
+ def execute(match)
+ issue = find_by_iid(match[:iid])
+
+ return not_found unless issue
+ return presenter(issue).already_closed if issue.closed?
+
+ close_issue(issue: issue)
+
+ presenter(issue).present
+ end
+
+ private
+
+ def close_issue(issue:)
+ Issues::CloseService.new(project, current_user).execute(issue)
+ end
+
+ def presenter(issue)
+ Gitlab::SlashCommands::Presenters::IssueClose.new(issue)
+ end
+
+ def not_found
+ Gitlab::SlashCommands::Presenters::Access.new.not_found
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/slash_commands/presenters/access.rb b/lib/gitlab/slash_commands/presenters/access.rb
index fa163cb098e..b1bfaa6cb59 100644
--- a/lib/gitlab/slash_commands/presenters/access.rb
+++ b/lib/gitlab/slash_commands/presenters/access.rb
@@ -4,8 +4,15 @@ module Gitlab
module SlashCommands
module Presenters
class Access < Presenters::Base
- def access_denied
- ephemeral_response(text: "Whoops! This action is not allowed. This incident will be [reported](https://xkcd.com/838/).")
+ def access_denied(project)
+ ephemeral_response(text: <<~MESSAGE)
+ You are not allowed to perform the given chatops command. Most
+ likely you do not have access to the GitLab project for this chatops
+ integration.
+
+ The GitLab project for this chatops integration can be found at
+ #{url_for(project)}.
+ MESSAGE
end
def not_found
@@ -22,20 +29,6 @@ module Gitlab
ephemeral_response(text: message)
end
-
- def unknown_command(commands)
- ephemeral_response(text: help_message(trigger))
- end
-
- private
-
- def help_message(trigger)
- header_with_list("Command not found, these are the commands you can use", full_commands(trigger))
- end
-
- def full_commands(trigger)
- @resource.map { |command| "#{trigger} #{command.help_message}" }
- end
end
end
end
diff --git a/lib/gitlab/slash_commands/presenters/help.rb b/lib/gitlab/slash_commands/presenters/help.rb
index 480d7aa6a30..342dae456a8 100644
--- a/lib/gitlab/slash_commands/presenters/help.rb
+++ b/lib/gitlab/slash_commands/presenters/help.rb
@@ -4,6 +4,11 @@ module Gitlab
module SlashCommands
module Presenters
class Help < Presenters::Base
+ def initialize(project, commands)
+ @project = project
+ @commands = commands
+ end
+
def present(trigger, text)
ephemeral_response(text: help_message(trigger, text))
end
@@ -11,17 +16,72 @@ module Gitlab
private
def help_message(trigger, text)
- return "No commands available :thinking_face:" unless @resource.present?
+ unless @commands.present?
+ return <<~MESSAGE
+ This chatops integration does not have any commands that can be
+ executed.
+
+ #{footer}
+ MESSAGE
+ end
if text.start_with?('help')
- header_with_list("Available commands", full_commands(trigger))
+ <<~MESSAGE
+ #{full_commands_message(trigger)}
+
+ #{help_footer}
+ MESSAGE
else
- header_with_list("Unknown command, these commands are available", full_commands(trigger))
+ <<~MESSAGE
+ The specified command is not valid.
+
+ #{full_commands_message(trigger)}
+
+ #{help_footer}
+ MESSAGE
end
end
- def full_commands(trigger)
- @resource.map { |command| "#{trigger} #{command.help_message}" }
+ def help_footer
+ message = @project ? project_info : ''
+ message += <<~MESSAGE
+ *Documentation*
+
+ For more information about GitLab chatops, refer to its
+ documentation: https://docs.gitlab.com/ce/ci/chatops/README.html.
+ MESSAGE
+
+ message
+ end
+
+ def project_info
+ <<~MESSAGE
+ *Project*
+
+ The GitLab project for this chatops integration can be found at
+ #{url_for(@project)}.
+
+ MESSAGE
+ end
+
+ def full_commands_message(trigger)
+ list = @commands
+ .map { |command| "#{trigger} #{command.help_message}" }
+ .join("\n")
+
+ <<~MESSAGE
+ *Available commands*
+
+ The following commands are available for this chatops integration:
+
+ #{list}
+
+ If available, the `run` command is used for running GitLab CI jobs
+ defined in this project's `.gitlab-ci.yml` file. For example, if a
+ job called "help" is defined you can run it like so:
+
+ `#{trigger} run help`
+ MESSAGE
end
end
end
diff --git a/lib/gitlab/slash_commands/presenters/issue_base.rb b/lib/gitlab/slash_commands/presenters/issue_base.rb
index b6db103b82b..08cb82274fd 100644
--- a/lib/gitlab/slash_commands/presenters/issue_base.rb
+++ b/lib/gitlab/slash_commands/presenters/issue_base.rb
@@ -40,6 +40,14 @@ module Gitlab
]
end
+ def project_link
+ "[#{project.full_name}](#{project.web_url})"
+ end
+
+ def author_profile_link
+ "[#{author.to_reference}](#{url_for(author)})"
+ end
+
private
attr_reader :resource
diff --git a/lib/gitlab/slash_commands/presenters/issue_close.rb b/lib/gitlab/slash_commands/presenters/issue_close.rb
new file mode 100644
index 00000000000..b3f24f4296a
--- /dev/null
+++ b/lib/gitlab/slash_commands/presenters/issue_close.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SlashCommands
+ module Presenters
+ class IssueClose < Presenters::Base
+ include Presenters::IssueBase
+
+ def present
+ if @resource.confidential?
+ ephemeral_response(close_issue)
+ else
+ in_channel_response(close_issue)
+ end
+ end
+
+ def already_closed
+ ephemeral_response(text: "Issue #{@resource.to_reference} is already closed.")
+ end
+
+ private
+
+ def close_issue
+ {
+ attachments: [
+ {
+ title: "#{@resource.title} · #{@resource.to_reference}",
+ title_link: resource_url,
+ author_name: author.name,
+ author_icon: author.avatar_url,
+ fallback: "Closed issue #{@resource.to_reference}: #{@resource.title}",
+ pretext: pretext,
+ color: color(@resource),
+ fields: fields,
+ mrkdwn_in: [
+ :title,
+ :pretext,
+ :fields
+ ]
+ }
+ ]
+ }
+ end
+
+ def pretext
+ "I closed an issue on #{author_profile_link}'s behalf: *#{@resource.to_reference}* in #{project_link}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/slash_commands/presenters/issue_new.rb b/lib/gitlab/slash_commands/presenters/issue_new.rb
index ac78745ae70..1424a4ac381 100644
--- a/lib/gitlab/slash_commands/presenters/issue_new.rb
+++ b/lib/gitlab/slash_commands/presenters/issue_new.rb
@@ -36,15 +36,7 @@ module Gitlab
end
def pretext
- "I created an issue on #{author_profile_link}'s behalf: **#{@resource.to_reference}** in #{project_link}"
- end
-
- def project_link
- "[#{project.full_name}](#{project.web_url})"
- end
-
- def author_profile_link
- "[#{author.to_reference}](#{url_for(author)})"
+ "I created an issue on #{author_profile_link}'s behalf: *#{@resource.to_reference}* in #{project_link}"
end
end
end
diff --git a/lib/gitlab/tracing.rb b/lib/gitlab/tracing.rb
index 29517591c51..7732d7c9d9c 100644
--- a/lib/gitlab/tracing.rb
+++ b/lib/gitlab/tracing.rb
@@ -30,7 +30,7 @@ module Gitlab
# Avoid using `format` since it can throw TypeErrors
# which we want to avoid on unsanitised env var input
tracing_url_template.to_s
- .gsub(/\{\{\s*correlation_id\s*\}\}/, Gitlab::CorrelationId.current_id.to_s)
+ .gsub(/\{\{\s*correlation_id\s*\}\}/, Labkit::Correlation::CorrelationId.current_id.to_s)
.gsub(/\{\{\s*service\s*\}\}/, Gitlab.process_name)
end
end
diff --git a/lib/gitlab/visibility_level_checker.rb b/lib/gitlab/visibility_level_checker.rb
new file mode 100644
index 00000000000..f15f1486a4e
--- /dev/null
+++ b/lib/gitlab/visibility_level_checker.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+# Gitlab::VisibilityLevelChecker verifies that:
+# - Current @project.visibility_level is not restricted
+# - Override visibility param is not restricted
+# - @see https://docs.gitlab.com/ce/api/project_import_export.html#import-a-file
+#
+# @param current_user [User] Current user object to verify visibility level against
+# @param project [Project] Current project that is being created/imported
+# @param project_params [Hash] Supplementary project params (e.g. import
+# params containing visibility override)
+#
+# @example
+# user = User.find(2)
+# project = Project.last
+# project_params = {:import_data=>{:data=>{:override_params=>{"visibility"=>"public"}}}}
+# level_checker = Gitlab::VisibilityLevelChecker.new(user, project, project_params: project_params)
+#
+# project_visibility = level_checker.level_restricted?
+# => #<Gitlab::VisibilityEvaluationResult:0x00007fbe16ee33c0 @restricted=true, @visibility_level=20>
+#
+# if project_visibility.restricted?
+# deny_visibility_level(project, project_visibility.visibility_level)
+# end
+#
+# @return [VisibilityEvaluationResult] Visibility evaluation result. Responds to:
+# #restricted - boolean indicating if level is restricted
+# #visibility_level - integer of restricted visibility level
+#
+module Gitlab
+ class VisibilityLevelChecker
+ def initialize(current_user, project, project_params: {})
+ @current_user = current_user
+ @project = project
+ @project_params = project_params
+ end
+
+ def level_restricted?
+ return VisibilityEvaluationResult.new(true, override_visibility_level_value) if override_visibility_restricted?
+ return VisibilityEvaluationResult.new(true, project.visibility_level) if project_visibility_restricted?
+
+ VisibilityEvaluationResult.new(false, nil)
+ end
+
+ private
+
+ attr_reader :current_user, :project, :project_params
+
+ def override_visibility_restricted?
+ return unless import_data
+ return unless override_visibility_level
+ return if Gitlab::VisibilityLevel.allowed_for?(current_user, override_visibility_level_value)
+
+ true
+ end
+
+ def project_visibility_restricted?
+ return if Gitlab::VisibilityLevel.allowed_for?(current_user, project.visibility_level)
+
+ true
+ end
+
+ def import_data
+ @import_data ||= project_params[:import_data]
+ end
+
+ def override_visibility_level
+ @override_visibility_level ||= import_data.deep_symbolize_keys.dig(:data, :override_params, :visibility)
+ end
+
+ def override_visibility_level_value
+ @override_visibility_level_value ||= Gitlab::VisibilityLevel.level_value(override_visibility_level)
+ end
+ end
+
+ class VisibilityEvaluationResult
+ attr_reader :visibility_level
+
+ def initialize(restricted, visibility_level)
+ @restricted = restricted
+ @visibility_level = visibility_level
+ end
+
+ def restricted?
+ @restricted
+ end
+ end
+end
diff --git a/lib/peek/views/active_record.rb b/lib/peek/views/active_record.rb
index 2d78818630d..1bb3ddb964a 100644
--- a/lib/peek/views/active_record.rb
+++ b/lib/peek/views/active_record.rb
@@ -3,13 +3,31 @@
module Peek
module Views
class ActiveRecord < DetailedView
+ DEFAULT_THRESHOLDS = {
+ calls: 100,
+ duration: 3000,
+ individual_call: 1000
+ }.freeze
+
+ THRESHOLDS = {
+ production: {
+ calls: 100,
+ duration: 15000,
+ individual_call: 5000
+ }
+ }.freeze
+
+ def self.thresholds
+ @thresholds ||= THRESHOLDS.fetch(Rails.env.to_sym, DEFAULT_THRESHOLDS)
+ end
+
private
def setup_subscribers
super
subscribe('sql.active_record') do |_, start, finish, _, data|
- if Gitlab::SafeRequestStore.store[:peek_enabled]
+ if Gitlab::PerformanceBar.enabled_for_request?
unless data[:cached]
detail_store << {
duration: finish - start,
diff --git a/lib/peek/views/detailed_view.rb b/lib/peek/views/detailed_view.rb
index f4ca1cb5075..4f3eddaf11b 100644
--- a/lib/peek/views/detailed_view.rb
+++ b/lib/peek/views/detailed_view.rb
@@ -3,11 +3,16 @@
module Peek
module Views
class DetailedView < View
+ def self.thresholds
+ {}
+ end
+
def results
{
- duration: formatted_duration,
+ duration: format_duration(duration),
calls: calls,
- details: details
+ details: details,
+ warnings: warnings
}
end
@@ -18,30 +23,48 @@ module Peek
private
def duration
- detail_store.map { |entry| entry[:duration] }.sum # rubocop:disable CodeReuse/ActiveRecord
+ detail_store.map { |entry| entry[:duration] }.sum * 1000 # rubocop:disable CodeReuse/ActiveRecord
end
def calls
detail_store.count
end
+ def details
+ call_details
+ .sort { |a, b| b[:duration] <=> a[:duration] }
+ .map(&method(:format_call_details))
+ end
+
+ def warnings
+ [
+ warning_for(calls, self.class.thresholds[:calls], label: "#{key} calls"),
+ warning_for(duration, self.class.thresholds[:duration], label: "#{key} duration")
+ ].flatten.compact
+ end
+
def call_details
detail_store
end
def format_call_details(call)
- call.merge(duration: (call[:duration] * 1000).round(3))
- end
+ duration = (call[:duration] * 1000).round(3)
- def details
- call_details
- .sort { |a, b| b[:duration] <=> a[:duration] }
- .map(&method(:format_call_details))
+ call.merge(duration: duration,
+ warnings: warning_for(duration, self.class.thresholds[:individual_call]))
end
- def formatted_duration
- ms = duration * 1000
+ def warning_for(actual, threshold, label: nil)
+ if threshold && actual > threshold
+ prefix = "#{label}: " if label
+
+ ["#{prefix}#{actual} over #{threshold}"]
+ else
+ []
+ end
+ end
+ def format_duration(ms)
if ms >= 1000
"%.2fms" % ms
else
diff --git a/lib/peek/views/gitaly.rb b/lib/peek/views/gitaly.rb
index 6ad6ddfd89d..7dc00b16cc0 100644
--- a/lib/peek/views/gitaly.rb
+++ b/lib/peek/views/gitaly.rb
@@ -3,10 +3,28 @@
module Peek
module Views
class Gitaly < DetailedView
+ DEFAULT_THRESHOLDS = {
+ calls: 30,
+ duration: 1000,
+ individual_call: 500
+ }.freeze
+
+ THRESHOLDS = {
+ production: {
+ calls: 30,
+ duration: 1000,
+ individual_call: 500
+ }
+ }.freeze
+
+ def self.thresholds
+ @thresholds ||= THRESHOLDS.fetch(Rails.env.to_sym, DEFAULT_THRESHOLDS)
+ end
+
private
def duration
- ::Gitlab::GitalyClient.query_time
+ ::Gitlab::GitalyClient.query_time * 1000
end
def calls
diff --git a/lib/peek/views/redis_detailed.rb b/lib/peek/views/redis_detailed.rb
index f36f581d5e9..84041b6be73 100644
--- a/lib/peek/views/redis_detailed.rb
+++ b/lib/peek/views/redis_detailed.rb
@@ -16,7 +16,7 @@ module Gitlab
private
def add_call_details(duration, args)
- return unless peek_enabled?
+ return unless Gitlab::PerformanceBar.enabled_for_request?
# redis-rb passes an array (e.g. [:get, key])
return unless args.length == 1
@@ -27,10 +27,6 @@ module Gitlab
}
end
- def peek_enabled?
- Gitlab::SafeRequestStore.store[:peek_enabled]
- end
-
def detail_store
::Gitlab::SafeRequestStore['redis_call_details'] ||= []
end
diff --git a/lib/peek/views/rugged.rb b/lib/peek/views/rugged.rb
index 18b3f422852..3ed54a010f8 100644
--- a/lib/peek/views/rugged.rb
+++ b/lib/peek/views/rugged.rb
@@ -12,7 +12,7 @@ module Peek
private
def duration
- ::Gitlab::RuggedInstrumentation.query_time
+ ::Gitlab::RuggedInstrumentation.query_time_ms
end
def calls
diff --git a/lib/system_check/app/authorized_keys_permission_check.rb b/lib/system_check/app/authorized_keys_permission_check.rb
new file mode 100644
index 00000000000..1246a6875a3
--- /dev/null
+++ b/lib/system_check/app/authorized_keys_permission_check.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module SystemCheck
+ module App
+ class AuthorizedKeysPermissionCheck < SystemCheck::BaseCheck
+ set_name 'Is authorized keys file accessible?'
+ set_skip_reason 'skipped (authorized keys not enabled)'
+
+ def skip?
+ !authorized_keys_enabled?
+ end
+
+ def check?
+ authorized_keys.accessible?
+ end
+
+ def repair!
+ authorized_keys.create
+ end
+
+ def show_error
+ try_fixing_it([
+ "sudo chmod 700 #{File.dirname(authorized_keys.file)}",
+ "touch #{authorized_keys.file}",
+ "sudo chmod 600 #{authorized_keys.file}"
+ ])
+ fix_and_rerun
+ end
+
+ private
+
+ def authorized_keys_enabled?
+ Gitlab::CurrentSettings.current_application_settings.authorized_keys_enabled
+ end
+
+ def authorized_keys
+ @authorized_keys ||= Gitlab::AuthorizedKeys.new
+ end
+ end
+ end
+end
diff --git a/lib/system_check/incoming_email/foreman_configured_check.rb b/lib/system_check/incoming_email/foreman_configured_check.rb
deleted file mode 100644
index 944913087da..00000000000
--- a/lib/system_check/incoming_email/foreman_configured_check.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-module SystemCheck
- module IncomingEmail
- class ForemanConfiguredCheck < SystemCheck::BaseCheck
- set_name 'Foreman configured correctly?'
-
- def check?
- path = Rails.root.join('Procfile')
-
- File.exist?(path) && File.read(path) =~ /^mail_room:/
- end
-
- def show_error
- try_fixing_it(
- 'Enable mail_room in your Procfile.'
- )
- for_more_information(
- 'doc/administration/reply_by_email.md'
- )
- fix_and_rerun
- end
- end
- end
-end
diff --git a/lib/system_check/incoming_email_check.rb b/lib/system_check/incoming_email_check.rb
index 155b6547595..e0e1147711c 100644
--- a/lib/system_check/incoming_email_check.rb
+++ b/lib/system_check/incoming_email_check.rb
@@ -14,8 +14,6 @@ module SystemCheck
if Rails.env.production?
checks << SystemCheck::IncomingEmail::InitdConfiguredCheck
checks << SystemCheck::IncomingEmail::MailRoomRunningCheck
- else
- checks << SystemCheck::IncomingEmail::ForemanConfiguredCheck
end
SystemCheck.run('Reply by email', checks)
diff --git a/lib/system_check/rake_task/app_task.rb b/lib/system_check/rake_task/app_task.rb
index cc32feb8604..e98cee510ff 100644
--- a/lib/system_check/rake_task/app_task.rb
+++ b/lib/system_check/rake_task/app_task.rb
@@ -30,7 +30,8 @@ module SystemCheck
SystemCheck::App::RubyVersionCheck,
SystemCheck::App::GitVersionCheck,
SystemCheck::App::GitUserDefaultSSHConfigCheck,
- SystemCheck::App::ActiveUsersCheck
+ SystemCheck::App::ActiveUsersCheck,
+ SystemCheck::App::AuthorizedKeysPermissionCheck
]
end
end
diff --git a/lib/tasks/gitlab/uploads/sanitize.rake b/lib/tasks/gitlab/uploads/sanitize.rake
index 12cf5302555..4f23a0a5d82 100644
--- a/lib/tasks/gitlab/uploads/sanitize.rake
+++ b/lib/tasks/gitlab/uploads/sanitize.rake
@@ -2,7 +2,7 @@ namespace :gitlab do
namespace :uploads do
namespace :sanitize do
desc 'GitLab | Uploads | Remove EXIF from images.'
- task :remove_exif, [:start_id, :stop_id, :dry_run, :sleep_time] => :environment do |task, args|
+ task :remove_exif, [:start_id, :stop_id, :dry_run, :sleep_time, :uploader, :since] => :environment do |task, args|
args.with_defaults(dry_run: 'true')
args.with_defaults(sleep_time: 0.3)
@@ -11,7 +11,9 @@ namespace :gitlab do
sanitizer = Gitlab::Sanitizers::Exif.new(logger: logger)
sanitizer.batch_clean(start_id: args.start_id, stop_id: args.stop_id,
dry_run: args.dry_run != 'false',
- sleep_time: args.sleep_time.to_f)
+ sleep_time: args.sleep_time.to_f,
+ uploader: args.uploader,
+ since: args.since)
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 12138d2db3a..629daccf006 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1104,9 +1104,6 @@ msgstr ""
msgid "An error occurred while parsing recent searches"
msgstr ""
-msgid "An error occurred while rendering KaTeX"
-msgstr ""
-
msgid "An error occurred while rendering preview broadcast message"
msgstr ""
@@ -1516,6 +1513,18 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
+msgid "Autocomplete"
+msgstr ""
+
+msgid "Autocomplete description"
+msgstr ""
+
+msgid "Autocomplete hint"
+msgstr ""
+
+msgid "Autocomplete usage hint"
+msgstr ""
+
msgid "Automatic certificate management using %{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end}"
msgstr ""
@@ -1951,7 +1960,10 @@ msgstr ""
msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
msgstr ""
-msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
+msgid "CICD|You must add a %{base_domain_link_start}base domain%{link_end} to your %{kubernetes_cluster_link_start}Kubernetes cluster%{link_end} in order for your deployment strategy to work."
+msgstr ""
+
+msgid "CICD|You must add a %{kubernetes_cluster_link_start}Kubernetes cluster integration%{link_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
msgid "CICD|group enabled"
@@ -2974,6 +2986,9 @@ msgstr ""
msgid "ComboSearch is not defined"
msgstr ""
+msgid "Command"
+msgstr ""
+
msgid "Command line instructions"
msgstr ""
@@ -3362,15 +3377,9 @@ msgstr ""
msgid "Could not add admins as members"
msgstr ""
-msgid "Could not add admins as members to self-monitoring project. Errors: %{errors}"
-msgstr ""
-
msgid "Could not add prometheus URL to whitelist"
msgstr ""
-msgid "Could not add prometheus URL to whitelist, errors: %{errors}"
-msgstr ""
-
msgid "Could not authorize chat nickname. Try again!"
msgstr ""
@@ -3386,9 +3395,6 @@ msgstr ""
msgid "Could not create group"
msgstr ""
-msgid "Could not create instance administration project. Errors: %{errors}"
-msgstr ""
-
msgid "Could not create project"
msgstr ""
@@ -3407,18 +3413,12 @@ msgstr ""
msgid "Could not revoke personal access token %{personal_access_token_name}."
msgstr ""
-msgid "Could not save instance administration project ID, errors: %{errors}"
-msgstr ""
-
msgid "Could not save project ID"
msgstr ""
msgid "Could not save prometheus manual configuration"
msgstr ""
-msgid "Could not save prometheus manual configuration for self-monitoring project. Errors: %{errors}"
-msgstr ""
-
msgid "Coverage"
msgstr ""
@@ -3611,9 +3611,15 @@ msgstr ""
msgid "Customize how Google Code email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Customize icon"
+msgstr ""
+
msgid "Customize language and region related settings."
msgstr ""
+msgid "Customize name"
+msgstr ""
+
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -3964,6 +3970,9 @@ msgstr ""
msgid "Description:"
msgstr ""
+msgid "Descriptive label"
+msgstr ""
+
msgid "Deselect all"
msgstr ""
@@ -4075,6 +4084,9 @@ msgstr ""
msgid "Dismiss Cycle Analytics introduction box"
msgstr ""
+msgid "Display name"
+msgstr ""
+
msgid "Do you want to customize how Google Code email addresses and usernames are imported into GitLab?"
msgstr ""
@@ -4120,6 +4132,9 @@ msgstr ""
msgid "Download export"
msgstr ""
+msgid "Download image"
+msgstr ""
+
msgid "Download source code"
msgstr ""
@@ -4330,7 +4345,7 @@ msgstr ""
msgid "Enable or disable version check and usage ping."
msgstr ""
-msgid "Enable reCAPTCHA or Akismet and set IP limits."
+msgid "Enable reCAPTCHA or Akismet and set IP limits. For reCAPTCHA, we currently only support %{recaptcha_v2_link_start}v2%{recaptcha_v2_link_end}"
msgstr ""
msgid "Enable shared Runners"
@@ -5322,9 +5337,6 @@ msgstr ""
msgid "GitLab.com import"
msgstr ""
-msgid "GitLab’s issue tracker"
-msgstr ""
-
msgid "Gitaly"
msgstr ""
@@ -5700,7 +5712,10 @@ msgstr ""
msgid "Help page text and support page url."
msgstr ""
-msgid "Helps prevent bots from creating accounts. We currently only support %{recaptcha_v2_link_start}reCAPTCHA v2%{recaptcha_v2_link_end}"
+msgid "Helps prevent bots from brute-force attacks."
+msgstr ""
+
+msgid "Helps prevent bots from creating accounts."
msgstr ""
msgid "Hide archived projects"
@@ -6068,9 +6083,6 @@ msgstr ""
msgid "Instance Statistics visibility"
msgstr ""
-msgid "Instance administration project already exists"
-msgstr ""
-
msgid "Instance administrators group already exists"
msgstr ""
@@ -6110,6 +6122,9 @@ msgstr ""
msgid "Invalid Login or password"
msgstr ""
+msgid "Invalid URL"
+msgstr ""
+
msgid "Invalid date"
msgstr ""
@@ -6158,6 +6173,9 @@ msgstr ""
msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
msgstr ""
+msgid "IssuableStatus|moved"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -6221,6 +6239,9 @@ msgstr ""
msgid "Issues, merge requests, pushes, and comments."
msgstr ""
+msgid "It may take up to 30 minutes before the site is available after the first deployment."
+msgstr ""
+
msgid "It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected."
msgstr ""
@@ -6565,9 +6586,6 @@ msgstr ""
msgid "Learn more"
msgstr ""
-msgid "Learn more about %{issue_boards_url}, to keep track of issues in multiple lists, using labels, assignees, and milestones. If you’re missing something from issue boards, please create an issue on %{gitlab_issues_url}."
-msgstr ""
-
msgid "Learn more about Auto DevOps"
msgstr ""
@@ -6834,6 +6852,36 @@ msgstr ""
msgid "Marks this issue as a duplicate of %{duplicate_reference}."
msgstr ""
+msgid "MattermostService|Add to Mattermost"
+msgstr ""
+
+msgid "MattermostService|Command trigger word"
+msgstr ""
+
+msgid "MattermostService|Fill in the word that works best for your team."
+msgstr ""
+
+msgid "MattermostService|Request URL"
+msgstr ""
+
+msgid "MattermostService|Request method"
+msgstr ""
+
+msgid "MattermostService|Response icon"
+msgstr ""
+
+msgid "MattermostService|Response username"
+msgstr ""
+
+msgid "MattermostService|See list of available commands in Mattermost after setting up this service, by entering"
+msgstr ""
+
+msgid "MattermostService|Suggestions:"
+msgstr ""
+
+msgid "MattermostService|This service allows users to perform common operations on this project by entering slash commands in Mattermost."
+msgstr ""
+
msgid "Max access level"
msgstr ""
@@ -7011,6 +7059,9 @@ msgstr ""
msgid "Messages"
msgstr ""
+msgid "Method"
+msgstr ""
+
msgid "Metrics"
msgstr ""
@@ -7574,6 +7625,9 @@ msgstr ""
msgid "Note: Consider asking your GitLab administrator to configure %{github_integration_link}, which will allow login via GitHub and allow importing repositories without generating a Personal Access Token."
msgstr ""
+msgid "Note: the container registry is always visible when a project is public"
+msgstr ""
+
msgid "NoteForm|Note"
msgstr ""
@@ -7930,6 +7984,9 @@ msgstr ""
msgid "Perform advanced options such as changing path, transferring, or removing the group."
msgstr ""
+msgid "Perform common operations on GitLab project"
+msgstr ""
+
msgid "Performance optimization"
msgstr ""
@@ -8128,6 +8185,9 @@ msgstr ""
msgid "Pipeline|Coverage"
msgstr ""
+msgid "Pipeline|Detached merge request pipeline"
+msgstr ""
+
msgid "Pipeline|Duration"
msgstr ""
@@ -8137,6 +8197,12 @@ msgstr ""
msgid "Pipeline|Key"
msgstr ""
+msgid "Pipeline|Merge train pipeline"
+msgstr ""
+
+msgid "Pipeline|Merged result pipeline"
+msgstr ""
+
msgid "Pipeline|Pipeline"
msgstr ""
@@ -8887,6 +8953,39 @@ msgstr ""
msgid "ProjectSelect|Search for project"
msgstr ""
+msgid "ProjectService|%{service_title}: status off"
+msgstr ""
+
+msgid "ProjectService|%{service_title}: status on"
+msgstr ""
+
+msgid "ProjectService|Integrations"
+msgstr ""
+
+msgid "ProjectService|Last edit"
+msgstr ""
+
+msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
+msgstr ""
+
+msgid "ProjectService|Project services"
+msgstr ""
+
+msgid "ProjectService|Project services allow you to integrate GitLab with other applications"
+msgstr ""
+
+msgid "ProjectService|Service"
+msgstr ""
+
+msgid "ProjectService|Services"
+msgstr ""
+
+msgid "ProjectService|Settings"
+msgstr ""
+
+msgid "ProjectService|To set up this service:"
+msgstr ""
+
msgid "ProjectSettings|Additional merge request capabilities that influence how and when merges will be performed"
msgstr ""
@@ -9091,7 +9190,7 @@ msgstr ""
msgid "ProjectsNew|Want to house several dependent projects under the same namespace? %{link_start}Create a group.%{link_end}"
msgstr ""
-msgid "Prometheus listen_address is not a valid URI"
+msgid "Prometheus listen_address in config/gitlab.yml is not a valid URI"
msgstr ""
msgid "PrometheusService|%{exporters} with %{metrics} were found"
@@ -9992,9 +10091,57 @@ msgstr ""
msgid "SearchCodeResults|of %{link_to_project}"
msgstr ""
+msgid "SearchResults|Showing %{count} %{scope} for \"%{term}\""
+msgstr ""
+
msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
msgstr ""
+msgid "SearchResults|comment"
+msgid_plural "SearchResults|comments"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|commit"
+msgid_plural "SearchResults|commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|issue"
+msgid_plural "SearchResults|issues"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|merge request"
+msgid_plural "SearchResults|merge requests"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|milestone"
+msgid_plural "SearchResults|milestones"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|project"
+msgid_plural "SearchResults|projects"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|result"
+msgid_plural "SearchResults|results"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|snippet"
+msgid_plural "SearchResults|snippets"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "SearchResults|user"
+msgid_plural "SearchResults|users"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Secret"
msgstr ""
@@ -10414,6 +10561,21 @@ msgstr ""
msgid "Size and domain settings for static websites"
msgstr ""
+msgid "SlackService|2. Paste the <strong>Token</strong> into the field below"
+msgstr ""
+
+msgid "SlackService|3. Select the <strong>Active</strong> checkbox, press <strong>Save changes</strong> and start using GitLab inside Slack!"
+msgstr ""
+
+msgid "SlackService|Fill in the word that works best for your team."
+msgstr ""
+
+msgid "SlackService|See list of available commands in Slack after setting up this service, by entering"
+msgstr ""
+
+msgid "SlackService|This service allows users to perform common operations on this project by entering slash commands in Slack."
+msgstr ""
+
msgid "Slower but makes sure the project workspace is pristine as it clones the repository from scratch for every job"
msgstr ""
@@ -10720,6 +10882,9 @@ msgstr ""
msgid "Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging."
msgstr ""
+msgid "Star labels to start sorting by priority"
+msgstr ""
+
msgid "Star toggle failed. Try again later."
msgstr ""
@@ -10987,6 +11152,9 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestions:"
+msgstr ""
+
msgid "Sunday"
msgstr ""
@@ -11394,9 +11562,6 @@ msgstr ""
msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time."
msgstr ""
-msgid "The tabs below will be removed in a future version"
-msgstr ""
-
msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
msgstr ""
@@ -11589,6 +11754,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This feature is in development. Please disable the `job_log_json` feature flag"
+msgstr ""
+
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -12043,6 +12211,9 @@ msgstr ""
msgid "To see all the user's personal access tokens you must impersonate them first."
msgstr ""
+msgid "To set up this service:"
+msgstr ""
+
msgid "To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there."
msgstr ""
@@ -13434,6 +13605,9 @@ msgstr ""
msgid "Your new personal access token has been created."
msgstr ""
+msgid "Your pages are served under:"
+msgstr ""
+
msgid "Your password reset token has expired."
msgstr ""
@@ -13646,9 +13820,6 @@ msgstr ""
msgid "issue"
msgstr ""
-msgid "issue boards"
-msgstr ""
-
msgid "it is stored externally"
msgstr ""
@@ -13676,6 +13847,12 @@ msgstr ""
msgid "manual"
msgstr ""
+msgid "math|The math in this entry is taking too long to render and may not be displayed as expected. For performance reasons, math blocks are also limited to %{maxChars} characters. Consider splitting up large formulae, splitting math blocks among multiple entries, or using an image instead."
+msgstr ""
+
+msgid "math|There was an error rendering this math block"
+msgstr ""
+
msgid "merge request"
msgid_plural "merge requests"
msgstr[0] ""
@@ -13985,12 +14162,6 @@ msgstr ""
msgid "project avatar"
msgstr ""
-msgid "prometheus.enable is not present in gitlab.yml"
-msgstr ""
-
-msgid "prometheus.listen_address is not present in gitlab.yml"
-msgstr ""
-
msgid "quick actions"
msgstr ""
diff --git a/package.json b/package.json
index 50cd5f36d20..782bb3a96a7 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"scripts": {
- "check-dependencies": "yarn check --integrity",
+ "check-dependencies": "scripts/frontend/check_dependencies.sh",
"clean": "rm -rf public/assets tmp/cache/*-loader",
"dev-server": "NODE_OPTIONS=\"--max-old-space-size=3584\" nodemon -w 'config/webpack.config.js' --exec 'webpack-dev-server --config config/webpack.config.js'",
"eslint": "eslint --max-warnings 0 --report-unused-disable-directives --ext .js,.vue .",
@@ -36,9 +36,9 @@
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-syntax-import-meta": "^7.2.0",
"@babel/preset-env": "^7.5.5",
- "@gitlab/svgs": "^1.70.0",
- "@gitlab/ui": "5.19.0",
- "@gitlab/visual-review-tools": "^1.0.0",
+ "@gitlab/svgs": "^1.71.0",
+ "@gitlab/ui": "5.20.2",
+ "@gitlab/visual-review-tools": "1.0.1",
"apollo-cache-inmemory": "^1.5.1",
"apollo-client": "^2.5.1",
"apollo-link": "^1.2.11",
@@ -96,7 +96,7 @@
"jszip-utils": "^0.0.2",
"katex": "^0.10.0",
"marked": "^0.3.12",
- "mermaid": "^8.2.4",
+ "mermaid": "^8.2.6",
"monaco-editor": "^0.15.6",
"monaco-editor-webpack-plugin": "^1.7.0",
"mousetrap": "^1.4.6",
diff --git a/qa/qa.rb b/qa/qa.rb
index 2c11df1f15a..8b38011486b 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -73,6 +73,7 @@ module QA
end
module Repository
+ autoload :Commit, 'qa/resource/repository/commit'
autoload :Push, 'qa/resource/repository/push'
autoload :ProjectPush, 'qa/resource/repository/project_push'
autoload :WikiPush, 'qa/resource/repository/wiki_push'
@@ -346,6 +347,10 @@ module QA
module Issuable
autoload :Common, 'qa/page/component/issuable/common'
end
+
+ module WebIDE
+ autoload :Alert, 'qa/page/component/web_ide/alert'
+ end
end
end
diff --git a/qa/qa/page/admin/menu.rb b/qa/qa/page/admin/menu.rb
index a520fb546c8..5a18ebd7af8 100644
--- a/qa/qa/page/admin/menu.rb
+++ b/qa/qa/page/admin/menu.rb
@@ -11,12 +11,21 @@ module QA
element :admin_settings_repository_item
element :admin_settings_general_item
element :admin_settings_metrics_and_profiling_item
+ element :admin_settings_preferences_link
end
view 'app/views/layouts/nav/sidebar/_admin.html.haml' do
element :integration_settings_link
end
+ def go_to_preferences_settings
+ hover_settings do
+ within_submenu do
+ click_element :admin_settings_preferences_link
+ end
+ end
+ end
+
def go_to_repository_settings
hover_settings do
within_submenu do
@@ -83,3 +92,5 @@ module QA
end
end
end
+
+QA::Page::Admin::Menu.prepend_if_ee('QA::EE::Page::Admin::Menu')
diff --git a/qa/qa/page/component/web_ide/alert.rb b/qa/qa/page/component/web_ide/alert.rb
new file mode 100644
index 00000000000..0f0623d5ebf
--- /dev/null
+++ b/qa/qa/page/component/web_ide/alert.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module WebIDE
+ module Alert
+ def self.prepended(page)
+ page.module_eval do
+ view 'app/assets/javascripts/ide/components/error_message.vue' do
+ element :flash_alert
+ end
+ end
+ end
+
+ def has_no_alert?(message = nil)
+ return has_no_element?(:flash_alert) if message.nil?
+
+ within_element(:flash_alert) do
+ has_no_text?(message)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/dashboard/projects.rb b/qa/qa/page/dashboard/projects.rb
index 0c23d7cffbb..378ac793f7b 100644
--- a/qa/qa/page/dashboard/projects.rb
+++ b/qa/qa/page/dashboard/projects.rb
@@ -29,3 +29,5 @@ module QA
end
end
end
+
+QA::Page::Dashboard::Projects.prepend_if_ee('QA::EE::Page::Dashboard::Projects')
diff --git a/qa/qa/page/file/show.rb b/qa/qa/page/file/show.rb
index 92f9181f99d..f5f44909f25 100644
--- a/qa/qa/page/file/show.rb
+++ b/qa/qa/page/file/show.rb
@@ -32,3 +32,5 @@ module QA
end
end
end
+
+QA::Page::File::Show.prepend_if_ee('QA::EE::Page::File::Show')
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 6a415b56e50..72f8e1c3ef0 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -187,3 +187,5 @@ module QA
end
end
end
+
+QA::Page::MergeRequest::Show.prepend_if_ee('QA::EE::Page::MergeRequest::Show')
diff --git a/qa/qa/page/profile/menu.rb b/qa/qa/page/profile/menu.rb
index 2d503499e13..99a795a23ef 100644
--- a/qa/qa/page/profile/menu.rb
+++ b/qa/qa/page/profile/menu.rb
@@ -34,3 +34,5 @@ module QA
end
end
end
+
+QA::Page::Profile::Menu.prepend_if_ee('QA::EE::Page::Profile::Menu')
diff --git a/qa/qa/page/project/commit/show.rb b/qa/qa/page/project/commit/show.rb
index 9770b8a657c..ba09dd1b92a 100644
--- a/qa/qa/page/project/commit/show.rb
+++ b/qa/qa/page/project/commit/show.rb
@@ -9,6 +9,7 @@ module QA
element :options_button
element :email_patches
element :plain_diff
+ element :commit_sha_content
end
def select_email_patches
@@ -20,6 +21,10 @@ module QA
click_element :options_button
click_element :plain_diff
end
+
+ def commit_sha
+ find_element(:commit_sha_content).text
+ end
end
end
end
diff --git a/qa/qa/page/project/issue/index.rb b/qa/qa/page/project/issue/index.rb
index c4383951ec4..f74366f6967 100644
--- a/qa/qa/page/project/issue/index.rb
+++ b/qa/qa/page/project/issue/index.rb
@@ -9,11 +9,21 @@ module QA
element :issue_link, 'link_to issue.title' # rubocop:disable QA/ElementWithPattern
end
+ view 'app/views/shared/issuable/_nav.html.haml' do
+ element :closed_issues_link
+ end
+
def click_issue_link(title)
click_link(title)
end
+
+ def click_closed_issues_link
+ click_element :closed_issues_link
+ end
end
end
end
end
end
+
+QA::Page::Project::Issue::Index.prepend_if_ee('QA::EE::Page::Project::Issue::Index')
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index 45dad9bc0ae..52929ece9ed 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -37,6 +37,10 @@ module QA
element :dropdown_menu_labels
end
+ view 'app/views/shared/issuable/_close_reopen_button.html.haml' do
+ element :reopen_issue_button
+ end
+
# Adds a comment to an issue
# attachment option should be an absolute path
def comment(text, attachment: nil, filter: :all_activities)
@@ -108,3 +112,5 @@ module QA
end
end
end
+
+QA::Page::Project::Issue::Show.prepend_if_ee('QA::EE::Page::Project::Issue::Show')
diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb
index 838d59b59cb..a9226927741 100644
--- a/qa/qa/page/project/menu.rb
+++ b/qa/qa/page/project/menu.rb
@@ -39,3 +39,5 @@ module QA
end
end
end
+
+QA::Page::Project::Menu.prepend_if_ee('QA::EE::Page::Project::SubMenus::SecurityCompliance')
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 64aab9be056..d0e8011d82d 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -73,3 +73,5 @@ module QA
end
end
end
+
+QA::Page::Project::New.prepend_if_ee('QA::EE::Page::Project::New')
diff --git a/qa/qa/page/project/operations/kubernetes/show.rb b/qa/qa/page/project/operations/kubernetes/show.rb
index eb30e0ea02a..fa276f15b8a 100644
--- a/qa/qa/page/project/operations/kubernetes/show.rb
+++ b/qa/qa/page/project/operations/kubernetes/show.rb
@@ -53,3 +53,5 @@ module QA
end
end
end
+
+QA::Page::Project::Operations::Kubernetes::Show.prepend_if_ee('QA::EE::Page::Project::Operations::Kubernetes::Show')
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index 284d0957eb8..3dca47a57e9 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -60,3 +60,5 @@ module QA::Page
end
end
end
+
+QA::Page::Project::Pipeline::Show.prepend_if_ee('QA::EE::Page::Project::Pipeline::Show')
diff --git a/qa/qa/page/project/settings/main.rb b/qa/qa/page/project/settings/main.rb
index dbbe62e3b1d..a196fc0123a 100644
--- a/qa/qa/page/project/settings/main.rb
+++ b/qa/qa/page/project/settings/main.rb
@@ -41,3 +41,5 @@ module QA
end
end
end
+
+QA::Page::Project::Settings::Main.prepend_if_ee('QA::EE::Page::Project::Settings::Main')
diff --git a/qa/qa/page/project/settings/mirroring_repositories.rb b/qa/qa/page/project/settings/mirroring_repositories.rb
index 831166f6373..e3afaceda80 100644
--- a/qa/qa/page/project/settings/mirroring_repositories.rb
+++ b/qa/qa/page/project/settings/mirroring_repositories.rb
@@ -89,3 +89,5 @@ module QA
end
end
end
+
+QA::Page::Project::Settings::MirroringRepositories.prepend_if_ee('QA::EE::Page::Project::Settings::MirroringRepositories')
diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb
index 903b0979614..1e707f1d315 100644
--- a/qa/qa/page/project/settings/protected_branches.rb
+++ b/qa/qa/page/project/settings/protected_branches.rb
@@ -73,3 +73,5 @@ module QA
end
end
end
+
+QA::Page::Project::Settings::ProtectedBranches.prepend_if_ee('QA::EE::Page::Project::Settings::ProtectedBranches')
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index 9fd668f812b..549992f271b 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -131,3 +131,5 @@ module QA
end
end
end
+
+QA::Page::Project::Show.prepend_if_ee('QA::EE::Page::Project::Show')
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
index 37bca97fec7..4d26cadcdfe 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -5,6 +5,7 @@ module QA
module Project
module WebIDE
class Edit < Page::Base
+ prepend Page::Component::WebIDE::Alert
include Page::Component::DropdownFilter
view 'app/assets/javascripts/ide/components/activity_bar.vue' do
@@ -34,6 +35,10 @@ module QA
element :dropdown_filter_input
end
+ view 'app/assets/javascripts/ide/components/commit_sidebar/actions.vue' do
+ element :commit_to_current_branch_radio
+ end
+
view 'app/assets/javascripts/ide/components/commit_sidebar/form.vue' do
element :begin_commit_button
element :commit_button
@@ -104,7 +109,7 @@ module QA
# animation is still in process even when the buttons have the
# expected visibility.
commit_success_msg_shown = retry_until do
- uncheck_element :start_new_mr_checkbox
+ click_element :commit_to_current_branch_radio
click_element :commit_button
wait(reload: false) do
@@ -119,3 +124,5 @@ module QA
end
end
end
+
+QA::Page::Project::WebIDE::Edit.prepend_if_ee('QA::EE::Page::Component::WebIDE::WebTerminalPanel')
diff --git a/qa/qa/page/settings/common.rb b/qa/qa/page/settings/common.rb
index bede3fde105..2d7b41c76e1 100644
--- a/qa/qa/page/settings/common.rb
+++ b/qa/qa/page/settings/common.rb
@@ -11,7 +11,7 @@ module QA
within_element(element_name) do
# Because it is possible to click the button before the JS toggle code is bound
wait(reload: false) do
- click_button 'Expand' unless has_css?('button', text: 'Collapse')
+ click_button 'Expand' unless has_css?('button', text: 'Collapse', wait: 1)
has_content?('Collapse')
end
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 93a82094776..4a29a14c5c2 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -15,6 +15,7 @@ module QA
attribute :add_name_uuid
attribute :description
attribute :standalone
+ attribute :runners_token
attribute :group do
Group.fabricate!
diff --git a/qa/qa/resource/repository/commit.rb b/qa/qa/resource/repository/commit.rb
new file mode 100644
index 00000000000..61c2ad6bfc0
--- /dev/null
+++ b/qa/qa/resource/repository/commit.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ module Repository
+ class Commit < Base
+ attr_accessor :author_email,
+ :author_name,
+ :branch,
+ :commit_message,
+ :file_path,
+ :sha
+
+ attribute :project do
+ Project.fabricate! do |resource|
+ resource.name = 'project-with-commit'
+ end
+ end
+
+ def initialize
+ @commit_message = 'QA Test - Commit message'
+ end
+
+ def files=(files)
+ if !files.is_a?(Array) ||
+ files.empty? ||
+ files.any? { |file| !file.has_key?(:file_path) || !file.has_key?(:content) }
+ raise ArgumentError, "Please provide an array of hashes e.g.: [{file_path: 'file1', content: 'foo'}]"
+ end
+
+ @files = files
+ end
+
+ def resource_web_url(resource)
+ super
+ rescue ResourceURLMissingError
+ # this particular resource does not expose a web_url property
+ end
+
+ def api_get_path
+ "#{api_post_path}/#{@sha}"
+ end
+
+ def api_post_path
+ "/projects/#{CGI.escape(project.path_with_namespace)}/repository/commits"
+ end
+
+ def api_post_body
+ {
+ branch: @branch || "master",
+ author_email: @author_email || Runtime::User.default_email,
+ author_name: @author_name || Runtime::User.username,
+ commit_message: commit_message,
+ actions: actions
+ }
+ end
+
+ def actions
+ @files.map do |file|
+ file.merge({ action: "create" })
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/runner.rb b/qa/qa/resource/runner.rb
index 3344ad3360a..9c2e138bade 100644
--- a/qa/qa/resource/runner.rb
+++ b/qa/qa/resource/runner.rb
@@ -6,9 +6,10 @@ module QA
module Resource
class Runner < Base
attr_writer :name, :tags, :image
+ attr_accessor :config
attribute :project do
- Project.fabricate! do |resource|
+ Project.fabricate_via_api! do |resource|
resource.name = 'project-with-ci-cd'
resource.description = 'Project with CI/CD Pipelines'
end
@@ -26,24 +27,27 @@ module QA
@image || 'gitlab/gitlab-runner:alpine'
end
- def fabricate!
- project.visit!
-
- Page::Project::Menu.perform(&:go_to_ci_cd_settings)
-
+ def fabricate_via_api!
Service::Runner.new(name).tap do |runner|
- Page::Project::Settings::CICD.perform do |settings|
- settings.expand_runners_settings do |runners|
- runner.pull
- runner.token = runners.registration_token
- runner.address = runners.coordinator_address
- runner.tags = tags
- runner.image = image
- runner.register!
- end
- end
+ runner.pull
+ runner.token = project.runners_token
+ runner.address = Runtime::Scenario.gitlab_address
+ runner.tags = tags
+ runner.image = image
+ runner.config = config if config
+ runner.run_untagged = true
+ runner.register!
end
end
+
+ def api_get_path
+ end
+
+ def api_post_path
+ end
+
+ def api_post_body
+ end
end
end
end
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
index 2987bb1a213..197008ed8bf 100644
--- a/qa/qa/runtime/browser.rb
+++ b/qa/qa/runtime/browser.rb
@@ -68,7 +68,7 @@ module QA
options = Selenium::WebDriver.const_get(QA::Runtime::Env.browser.capitalize)::Options.new
if QA::Runtime::Env.browser == :chrome
- options.add_argument("window-size=1240,1680")
+ options.add_argument("window-size=1480,2200")
# Chrome won't work properly in a Docker container in sandbox mode
options.add_argument("no-sandbox")
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index b184eeb1701..594e5712ab2 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -233,3 +233,5 @@ module QA
end
end
end
+
+QA::Runtime::Env.extend_if_ee('QA::EE::Runtime::Env')
diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb
index 632a0f5f2a9..99497cbe0ad 100644
--- a/qa/qa/scenario/test/sanity/selectors.rb
+++ b/qa/qa/scenario/test/sanity/selectors.rb
@@ -56,3 +56,5 @@ module QA
end
end
end
+
+QA::Scenario::Test::Sanity::Selectors.prepend_if_ee('QA::EE::Scenario::Test::Sanity::Selectors')
diff --git a/qa/qa/service/runner.rb b/qa/qa/service/runner.rb
index 03b234f7a56..6fc5984b12a 100644
--- a/qa/qa/service/runner.rb
+++ b/qa/qa/service/runner.rb
@@ -7,13 +7,25 @@ module QA
class Runner
include Service::Shellout
- attr_accessor :token, :address, :tags, :image
+ attr_accessor :token, :address, :tags, :image, :run_untagged
+ attr_writer :config
def initialize(name)
@image = 'gitlab/gitlab-runner:alpine'
@name = name || "qa-runner-#{SecureRandom.hex(4)}"
@network = Runtime::Scenario.attributes[:network] || 'test'
@tags = %w[qa test]
+ @run_untagged = false
+ end
+
+ def config
+ @config ||= <<~END
+ concurrent = 1
+ check_interval = 0
+
+ [session_server]
+ session_timeout = 1800
+ END
end
def network
@@ -32,19 +44,30 @@ module QA
shell <<~CMD.tr("\n", ' ')
docker run -d --rm --entrypoint=/bin/sh
--network #{network} --name #{@name}
+ -p 8093:8093
-e CI_SERVER_URL=#{@address}
-e REGISTER_NON_INTERACTIVE=true
-e REGISTRATION_TOKEN=#{@token}
-e RUNNER_EXECUTOR=shell
-e RUNNER_TAG_LIST=#{@tags.join(',')}
-e RUNNER_NAME=#{@name}
- #{@image} -c 'gitlab-runner register && gitlab-runner run'
+ #{@image} -c "#{register_command}"
CMD
end
def remove!
shell "docker rm -f #{@name}"
end
+
+ private
+
+ def register_command
+ <<~CMD
+ printf '#{config.chomp.gsub(/\n/, "\\n").gsub('"', '\"')}' > /etc/gitlab-runner/config.toml &&
+ gitlab-runner register --run-untagged=#{@run_untagged} &&
+ gitlab-runner run
+ CMD
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb
new file mode 100644
index 00000000000..7b6a3579af0
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Plan' do
+ describe 'Close issue' do
+ let(:issue_title) { 'issue title' }
+ let(:commit_message) { 'Closes' }
+
+ before do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ issue = Resource::Issue.fabricate_via_api! do |issue|
+ issue.title = issue_title
+ end
+
+ @project = issue.project
+ @issue_id = issue.api_response[:iid]
+
+ # Initial commit should be pushed because
+ # the very first commit to the project doesn't close the issue
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/38965
+ push_commit('Initial commit')
+ end
+
+ it 'user closes an issue by pushing commit' do
+ push_commit("#{commit_message} ##{@issue_id}", false)
+
+ @project.visit!
+ Page::Project::Show.perform do |show|
+ show.click_commit(commit_message)
+ end
+ commit_sha = Page::Project::Commit::Show.perform(&:commit_sha)
+
+ Page::Project::Menu.perform(&:click_issues)
+ Page::Project::Issue::Index.perform do |index|
+ index.click_closed_issues_link
+ index.click_issue_link(issue_title)
+ end
+
+ Page::Project::Issue::Show.perform do |show|
+ expect(show).to have_element(:reopen_issue_button)
+ expect(show).to have_content("closed via commit #{commit_sha}")
+ end
+ end
+
+ def push_commit(commit_message, new_branch = true)
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.commit_message = commit_message
+ push.new_branch = new_branch
+ push.file_content = commit_message
+ push.project = @project
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
index 33744236dd4..900ddcb7f59 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
@@ -15,11 +15,11 @@ module QA
Resource::Runner.fabricate! do |runner|
runner.name = executor
- end
+ end.project.visit!
+ Page::Project::Menu.perform(&:go_to_ci_cd_settings)
Page::Project::Settings::CICD.perform do |settings|
sleep 5 # Runner should register within 5 seconds
- settings.refresh
settings.expand_runners_settings do |page|
expect(page).to have_content(executor)
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
index f6411d8c5ad..141166f6971 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
@@ -17,7 +17,7 @@ module QA
@repository_location = @project.repository_ssh_location
- Resource::Runner.fabricate_via_browser_ui! do |resource|
+ Resource::Runner.fabricate_via_api! do |resource|
resource.project = @project
resource.name = @runner_name
resource.tags = %w[qa docker]
diff --git a/rubocop/cop/inject_enterprise_edition_module.rb b/rubocop/cop/inject_enterprise_edition_module.rb
index e0e1b2d6c7d..6f007e667f2 100644
--- a/rubocop/cop/inject_enterprise_edition_module.rb
+++ b/rubocop/cop/inject_enterprise_edition_module.rb
@@ -24,7 +24,7 @@ module RuboCop
# We use `match?` here instead of RuboCop's AST matching, as this makes
# it far easier to handle nested constants such as `EE::Foo::Bar::Baz`.
- line.match?(/(\s|\()('|")?(::)?EE::/)
+ line.match?(/(\s|\()('|")?(::)?(QA::)?EE::/)
end
def on_send(node)
diff --git a/scripts/frontend/check_dependencies.sh b/scripts/frontend/check_dependencies.sh
new file mode 100755
index 00000000000..64e7cf9da52
--- /dev/null
+++ b/scripts/frontend/check_dependencies.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+if ! yarn check --integrity 2>&1 > /dev/null
+then
+ echo
+ echo " $(tput setaf 1)yarn check --integrity$(tput sgr0) failed!"
+ echo " Your dependencies probably don't match the yarn.lock file."
+ echo " Please run $(tput setaf 2)yarn install$(tput sgr0) and try again."
+ echo
+ exit 1
+fi
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index 4935c1342a3..fc5b57451de 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -164,7 +164,7 @@ function create_application_secret() {
function download_chart() {
echoinfo "Downloading the GitLab chart..." true
- curl -o gitlab.tar.bz2 "https://gitlab.com/charts/gitlab/-/archive/${GITLAB_HELM_CHART_REF}/gitlab-${GITLAB_HELM_CHART_REF}.tar.bz2"
+ curl -o gitlab.tar.bz2 "https://gitlab.com/gitlab-org/charts/gitlab/-/archive/${GITLAB_HELM_CHART_REF}/gitlab-${GITLAB_HELM_CHART_REF}.tar.bz2"
tar -xjf gitlab.tar.bz2
cd "gitlab-${GITLAB_HELM_CHART_REF}"
diff --git a/spec/controllers/boards/lists_controller_spec.rb b/spec/controllers/boards/lists_controller_spec.rb
index 418ca6f3210..1e8a8145b35 100644
--- a/spec/controllers/boards/lists_controller_spec.rb
+++ b/spec/controllers/boards/lists_controller_spec.rb
@@ -30,6 +30,21 @@ describe Boards::ListsController do
expect(json_response.length).to eq 3
end
+ it 'avoids n+1 queries when serializing lists' do
+ list_1 = create(:list, board: board)
+ list_1.update_preferences_for(user, { collapsed: true })
+
+ control_count = ActiveRecord::QueryRecorder.new { read_board_list user: user, board: board }.count
+
+ list_2 = create(:list, board: board)
+ list_2.update_preferences_for(user, { collapsed: true })
+
+ list_3 = create(:list, board: board)
+ list_3.update_preferences_for(user, { collapsed: true })
+
+ expect { read_board_list user: user, board: board }.not_to exceed_query_limit(control_count)
+ end
+
context 'with unauthorized user' do
let(:unauth_user) { create(:user) }
@@ -154,6 +169,22 @@ describe Boards::ListsController do
end
end
+ context 'with collapsed preference' do
+ it 'saves collapsed preference for user' do
+ save_setting user: user, board: board, list: planning, setting: { collapsed: true }
+
+ expect(planning.preferences_for(user).collapsed).to eq(true)
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'saves not collapsed preference for user' do
+ save_setting user: user, board: board, list: planning, setting: { collapsed: false }
+
+ expect(planning.preferences_for(user).collapsed).to eq(false)
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
def move(user:, board:, list:, position:)
sign_in(user)
@@ -166,6 +197,19 @@ describe Boards::ListsController do
patch :update, params: params, as: :json
end
+
+ def save_setting(user:, board:, list:, setting: {})
+ sign_in(user)
+
+ params = { namespace_id: project.namespace.to_param,
+ project_id: project,
+ board_id: board.to_param,
+ id: list.to_param,
+ list: setting,
+ format: :json }
+
+ patch :update, params: params, as: :json
+ end
end
describe 'DELETE destroy' do
diff --git a/spec/controllers/groups/runners_controller_spec.rb b/spec/controllers/groups/runners_controller_spec.rb
index 91f9e2c7832..14b0cf959b3 100644
--- a/spec/controllers/groups/runners_controller_spec.rb
+++ b/spec/controllers/groups/runners_controller_spec.rb
@@ -3,73 +3,202 @@
require 'spec_helper'
describe Groups::RunnersController do
- let(:user) { create(:user) }
- let(:group) { create(:group) }
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
let(:runner) { create(:ci_runner, :group, groups: [group]) }
-
- let(:params) do
- {
- group_id: group,
- id: runner
- }
- end
+ let(:params) { { group_id: group, id: runner } }
before do
sign_in(user)
- group.add_maintainer(user)
+ end
+
+ describe '#show' do
+ context 'when user is owner' do
+ before do
+ group.add_owner(user)
+ end
+
+ it 'renders show with 200 status code' do
+ get :show, params: { group_id: group, id: runner }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to render_template(:show)
+ end
+ end
+
+ context 'when user is not owner' do
+ before do
+ group.add_maintainer(user)
+ end
+
+ it 'renders a 404' do
+ get :show, params: { group_id: group, id: runner }
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
+ describe '#edit' do
+ context 'when user is owner' do
+ before do
+ group.add_owner(user)
+ end
+
+ it 'renders show with 200 status code' do
+ get :edit, params: { group_id: group, id: runner }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to render_template(:edit)
+ end
+ end
+
+ context 'when user is not owner' do
+ before do
+ group.add_maintainer(user)
+ end
+
+ it 'renders a 404' do
+ get :edit, params: { group_id: group, id: runner }
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
end
describe '#update' do
- it 'updates the runner and ticks the queue' do
- new_desc = runner.description.swapcase
+ context 'when user is an owner' do
+ before do
+ group.add_owner(user)
+ end
- expect do
- post :update, params: params.merge(runner: { description: new_desc } )
- end.to change { runner.ensure_runner_queue_value }
+ it 'updates the runner, ticks the queue, and redirects' do
+ new_desc = runner.description.swapcase
- runner.reload
+ expect do
+ post :update, params: params.merge(runner: { description: new_desc } )
+ end.to change { runner.ensure_runner_queue_value }
- expect(response).to have_gitlab_http_status(302)
- expect(runner.description).to eq(new_desc)
+ expect(response).to have_gitlab_http_status(302)
+ expect(runner.reload.description).to eq(new_desc)
+ end
+ end
+
+ context 'when user is not an owner' do
+ before do
+ group.add_maintainer(user)
+ end
+
+ it 'rejects the update and responds 404' do
+ old_desc = runner.description
+
+ expect do
+ post :update, params: params.merge(runner: { description: old_desc.swapcase } )
+ end.not_to change { runner.ensure_runner_queue_value }
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(runner.reload.description).to eq(old_desc)
+ end
end
end
describe '#destroy' do
- it 'destroys the runner' do
- delete :destroy, params: params
+ context 'when user is an owner' do
+ before do
+ group.add_owner(user)
+ end
+
+ it 'destroys the runner and redirects' do
+ delete :destroy, params: params
+
+ expect(response).to have_gitlab_http_status(302)
+ expect(Ci::Runner.find_by(id: runner.id)).to be_nil
+ end
+ end
+
+ context 'when user is not an owner' do
+ before do
+ group.add_maintainer(user)
+ end
+
+ it 'responds 404 and does not destroy the runner' do
+ delete :destroy, params: params
- expect(response).to have_gitlab_http_status(302)
- expect(Ci::Runner.find_by(id: runner.id)).to be_nil
+ expect(response).to have_gitlab_http_status(404)
+ expect(Ci::Runner.find_by(id: runner.id)).to be_present
+ end
end
end
describe '#resume' do
- it 'marks the runner as active and ticks the queue' do
- runner.update(active: false)
+ context 'when user is an owner' do
+ before do
+ group.add_owner(user)
+ end
- expect do
- post :resume, params: params
- end.to change { runner.ensure_runner_queue_value }
+ it 'marks the runner as active, ticks the queue, and redirects' do
+ runner.update(active: false)
- runner.reload
+ expect do
+ post :resume, params: params
+ end.to change { runner.ensure_runner_queue_value }
- expect(response).to have_gitlab_http_status(302)
- expect(runner.active).to eq(true)
+ expect(response).to have_gitlab_http_status(302)
+ expect(runner.reload.active).to eq(true)
+ end
+ end
+
+ context 'when user is not an owner' do
+ before do
+ group.add_maintainer(user)
+ end
+
+ it 'responds 404 and does not activate the runner' do
+ runner.update(active: false)
+
+ expect do
+ post :resume, params: params
+ end.not_to change { runner.ensure_runner_queue_value }
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(runner.reload.active).to eq(false)
+ end
end
end
describe '#pause' do
- it 'marks the runner as inactive and ticks the queue' do
- runner.update(active: true)
+ context 'when user is an owner' do
+ before do
+ group.add_owner(user)
+ end
+
+ it 'marks the runner as inactive, ticks the queue, and redirects' do
+ runner.update(active: true)
+
+ expect do
+ post :pause, params: params
+ end.to change { runner.ensure_runner_queue_value }
+
+ expect(response).to have_gitlab_http_status(302)
+ expect(runner.reload.active).to eq(false)
+ end
+ end
+
+ context 'when user is not an owner' do
+ before do
+ group.add_maintainer(user)
+ end
- expect do
- post :pause, params: params
- end.to change { runner.ensure_runner_queue_value }
+ it 'responds 404 and does not update the runner or queue' do
+ runner.update(active: true)
- runner.reload
+ expect do
+ post :pause, params: params
+ end.not_to change { runner.ensure_runner_queue_value }
- expect(response).to have_gitlab_http_status(302)
- expect(runner.active).to eq(false)
+ expect(response).to have_gitlab_http_status(404)
+ expect(runner.reload.active).to eq(true)
+ end
end
end
end
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 45aebd1554c..17964c78e8d 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Projects::BlobController do
include ProjectForksHelper
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 187c7864ad7..608131dcbc8 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1084,16 +1084,41 @@ describe Projects::IssuesController do
end
it "deletes the issue" do
- delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid, destroy_confirm: true }
expect(response).to have_gitlab_http_status(302)
expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./)
end
+ it "deletes the issue" do
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid, destroy_confirm: true }
+
+ expect(response).to have_gitlab_http_status(302)
+ expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./)
+ end
+
+ it "prevents deletion if destroy_confirm is not set" do
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original
+
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
+
+ expect(response).to have_gitlab_http_status(302)
+ expect(controller).to set_flash[:notice].to('Destroy confirmation not provided for issue')
+ end
+
+ it "prevents deletion in JSON format if destroy_confirm is not set" do
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original
+
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid, format: 'json' }
+
+ expect(response).to have_gitlab_http_status(422)
+ expect(json_response).to eq({ 'errors' => 'Destroy confirmation not provided for issue' })
+ end
+
it 'delegates the update of the todos count cache to TodoService' do
expect_any_instance_of(TodoService).to receive(:destroy_target).with(issue).once
- delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid, destroy_confirm: true }
end
end
end
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index f076a5e769f..bd3e66efd58 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -12,6 +12,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
before do
stub_feature_flags(ci_enable_live_trace: true)
+ stub_feature_flags(job_log_json: false)
stub_not_protect_default_branch
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 11b1eaf11b7..d0370dfaeee 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -573,16 +573,34 @@ describe Projects::MergeRequestsController do
end
it "deletes the merge request" do
- delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid }
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid, destroy_confirm: true }
expect(response).to have_gitlab_http_status(302)
expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./)
end
+ it "prevents deletion if destroy_confirm is not set" do
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original
+
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid }
+
+ expect(response).to have_gitlab_http_status(302)
+ expect(controller).to set_flash[:notice].to('Destroy confirmation not provided for merge request')
+ end
+
+ it "prevents deletion in JSON format if destroy_confirm is not set" do
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original
+
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid, format: 'json' }
+
+ expect(response).to have_gitlab_http_status(422)
+ expect(json_response).to eq({ 'errors' => 'Destroy confirmation not provided for merge request' })
+ end
+
it 'delegates the update of the todos count cache to TodoService' do
expect_any_instance_of(TodoService).to receive(:destroy_target).with(merge_request).once
- delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid }
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid, destroy_confirm: true }
end
end
end
@@ -719,19 +737,63 @@ describe Projects::MergeRequestsController do
end
describe 'GET test_reports' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_diffs,
+ :with_merge_request_pipeline,
+ target_project: project,
+ source_project: project
+ )
+ end
+
subject do
- get :test_reports,
- params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: merge_request.iid
- },
- format: :json
+ get :test_reports, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: merge_request.iid
+ },
+ format: :json
end
before do
allow_any_instance_of(MergeRequest)
- .to receive(:compare_test_reports).and_return(comparison_status)
+ .to receive(:compare_test_reports)
+ .and_return(comparison_status)
+
+ allow_any_instance_of(MergeRequest)
+ .to receive(:actual_head_pipeline)
+ .and_return(merge_request.all_pipelines.take)
+ end
+
+ describe 'permissions on a public project with private CI/CD' do
+ let(:project) { create :project, :repository, :public, :builds_private }
+ let(:comparison_status) { { status: :parsed, data: { summary: 1 } } }
+
+ context 'while signed out' do
+ before do
+ sign_out(user)
+ end
+
+ it 'responds with a 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(response.body).to be_blank
+ end
+ end
+
+ context 'while signed in as an unrelated user' do
+ before do
+ sign_in(create(:user))
+ end
+
+ it 'responds with a 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(response.body).to be_blank
+ end
+ end
end
context 'when comparison is being processed' do
@@ -1052,17 +1114,39 @@ describe Projects::MergeRequestsController do
let(:status) { pipeline.detailed_status(double('user')) }
- before do
+ it 'returns a detailed head_pipeline status in json' do
get_pipeline_status
- end
- it 'return a detailed head_pipeline status in json' do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['text']).to eq status.text
expect(json_response['label']).to eq status.label
expect(json_response['icon']).to eq status.icon
expect(json_response['favicon']).to match_asset_path "/assets/ci_favicons/#{status.favicon}.png"
end
+
+ context 'with project member visibility on a public project' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository, :public, :builds_private) }
+
+ it 'returns pipeline data to project members' do
+ project.add_developer(user)
+
+ get_pipeline_status
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['text']).to eq status.text
+ expect(json_response['label']).to eq status.label
+ expect(json_response['icon']).to eq status.icon
+ expect(json_response['favicon']).to match_asset_path "/assets/ci_favicons/#{status.favicon}.png"
+ end
+
+ it 'returns blank OK response to non-project-members' do
+ get_pipeline_status
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_empty
+ end
+ end
end
context 'when head_pipeline does not exist' do
@@ -1070,7 +1154,7 @@ describe Projects::MergeRequestsController do
get_pipeline_status
end
- it 'return empty' do
+ it 'returns blank OK response' do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_empty
end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 4500c412521..4db77921f24 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -212,40 +212,232 @@ describe Projects::NotesController do
describe 'POST create' do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.source_project }
+ let(:note_text) { 'some note' }
let(:request_params) do
{
- note: { note: 'some note', noteable_id: merge_request.id, noteable_type: 'MergeRequest' },
+ note: { note: note_text, noteable_id: merge_request.id, noteable_type: 'MergeRequest' },
namespace_id: project.namespace,
project_id: project,
merge_request_diff_head_sha: 'sha',
target_type: 'merge_request',
target_id: merge_request.id
- }
+ }.merge(extra_request_params)
+ end
+ let(:extra_request_params) { {} }
+
+ let(:project_visibility) { Gitlab::VisibilityLevel::PUBLIC }
+ let(:merge_requests_access_level) { ProjectFeature::ENABLED }
+
+ def create!
+ post :create, params: request_params
end
before do
+ project.update_attribute(:visibility_level, project_visibility)
+ project.project_feature.update(merge_requests_access_level: merge_requests_access_level)
sign_in(user)
- project.add_developer(user)
end
- it "returns status 302 for html" do
- post :create, params: request_params
+ describe 'making the creation request' do
+ before do
+ create!
+ end
+
+ context 'the project is publically available' do
+ context 'for HTML' do
+ it "returns status 302" do
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
+ context 'for JSON' do
+ let(:extra_request_params) { { format: :json } }
+
+ it "returns status 200 for json" do
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+ end
- expect(response).to have_gitlab_http_status(302)
+ context 'the project is a private project' do
+ let(:project_visibility) { Gitlab::VisibilityLevel::PRIVATE }
+
+ [{}, { format: :json }].each do |extra|
+ context "format is #{extra[:format]}" do
+ let(:extra_request_params) { extra }
+
+ it "returns status 404" do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+ end
end
- it "returns status 200 for json" do
- post :create, params: request_params.merge(format: :json)
+ context 'the user is a developer on a private project' do
+ let(:project_visibility) { Gitlab::VisibilityLevel::PRIVATE }
- expect(response).to have_gitlab_http_status(200)
+ before do
+ project.add_developer(user)
+ end
+
+ context 'HTML requests' do
+ it "returns status 302 (redirect)" do
+ create!
+
+ expect(response).to have_gitlab_http_status(302)
+ end
+ end
+
+ context 'JSON requests' do
+ let(:extra_request_params) { { format: :json } }
+
+ it "returns status 200" do
+ create!
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ context 'the return_discussion param is set' do
+ let(:extra_request_params) { { format: :json, return_discussion: 'true' } }
+
+ it 'returns discussion JSON when the return_discussion param is set' do
+ create!
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to have_key 'discussion'
+ expect(json_response.dig('discussion', 'notes', 0, 'note')).to eq(request_params[:note][:note])
+ end
+ end
+
+ context 'when creating a note with quick actions' do
+ context 'with commands that return changes' do
+ let(:note_text) { "/award :thumbsup:\n/estimate 1d\n/spend 3h" }
+ let(:extra_request_params) { { format: :json } }
+
+ it 'includes changes in commands_changes ' do
+ create!
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['commands_changes']).to include('emoji_award', 'time_estimate', 'spend_time')
+ expect(json_response['commands_changes']).not_to include('target_project', 'title')
+ end
+ end
+
+ context 'with commands that do not return changes' do
+ let(:issue) { create(:issue, project: project) }
+ let(:other_project) { create(:project) }
+ let(:note_text) { "/move #{other_project.full_path}\n/title AAA" }
+ let(:extra_request_params) { { format: :json, target_id: issue.id, target_type: 'issue' } }
+
+ before do
+ other_project.add_developer(user)
+ end
+
+ it 'does not include changes in commands_changes' do
+ create!
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['commands_changes']).not_to include('target_project', 'title')
+ end
+ end
+ end
end
- it 'returns discussion JSON when the return_discussion param is set' do
- post :create, params: request_params.merge(format: :json, return_discussion: 'true')
+ context 'when the internal project prohibits non-members from accessing merge requests' do
+ let(:project_visibility) { Gitlab::VisibilityLevel::INTERNAL }
+ let(:merge_requests_access_level) { ProjectFeature::PRIVATE }
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to have_key 'discussion'
- expect(json_response['discussion']['notes'][0]['note']).to eq(request_params[:note][:note])
+ it "prevents a non-member user from creating a note on one of the project's merge requests" do
+ create!
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ context 'when the user is a team member' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'can add comments' do
+ expect { create! }.to change { project.notes.count }.by(1)
+ end
+ end
+
+ # Illustration of the attack vector for posting comments to discussions that should
+ # be inaccessible.
+ #
+ # This relies on posting a note to a commit that is not necessarily even in the
+ # merge request, with a value of :in_reply_to_discussion_id that points to a
+ # discussion on a merge_request that should not be accessible.
+ context 'when the request includes a :in_reply_to_discussion_id designed to fool us' do
+ let(:commit) { create(:commit, project: project) }
+
+ let(:existing_comment) do
+ create(:note_on_commit,
+ note: 'first',
+ project: project,
+ commit_id: merge_request.commit_shas.first)
+ end
+
+ let(:discussion) { existing_comment.discussion }
+
+ # see !60465 for details of the structure of this request
+ let(:request_params) do
+ { "utf8" => "✓",
+ "authenticity_token" => "1",
+ "view" => "inline",
+ "line_type" => "",
+ "merge_request_diff_head_sha" => "",
+ "in_reply_to_discussion_id" => discussion.id,
+ "note_project_id" => project.id,
+ "project_id" => project.id,
+ "namespace_id" => project.namespace,
+ "target_type" => "commit",
+ "target_id" => commit.id,
+ "note" => {
+ "noteable_type" => "",
+ "noteable_id" => "",
+ "commit_id" => "",
+ "type" => "",
+ "line_code" => "",
+ "position" => "",
+ "note" => "ThisReplyWillGoToMergeRequest"
+ } }
+ end
+
+ it 'prevents the request from adding notes to the spoofed discussion' do
+ expect { create! }.not_to change { discussion.notes.count }
+ end
+
+ it 'returns an error to the user' do
+ create!
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
+ context 'when the public project prohibits non-members from accessing merge requests' do
+ let(:project_visibility) { Gitlab::VisibilityLevel::PUBLIC }
+ let(:merge_requests_access_level) { ProjectFeature::PRIVATE }
+
+ it "prevents a non-member user from creating a note on one of the project's merge requests" do
+ create!
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ context 'when the user is a team member' do
+ before do
+ project.add_developer(user)
+ create!
+ end
+
+ it 'can add comments' do
+ expect(response).to be_redirect
+ end
+ end
end
context 'when merge_request_diff_head_sha present' do
@@ -262,7 +454,7 @@ describe Projects::NotesController do
end
it "returns status 302 for html" do
- post :create, params: request_params
+ create!
expect(response).to have_gitlab_http_status(302)
end
@@ -285,7 +477,7 @@ describe Projects::NotesController do
end
context 'when creating a commit comment from an MR fork' do
- let(:project) { create(:project, :repository) }
+ let(:project) { create(:project, :repository, :public) }
let(:forked_project) do
fork_project(project, nil, repository: true)
@@ -299,45 +491,59 @@ describe Projects::NotesController do
create(:note_on_commit, note: 'a note', project: forked_project, commit_id: merge_request.commit_shas.first)
end
- def post_create(extra_params = {})
- post :create, params: {
+ let(:note_project_id) do
+ forked_project.id
+ end
+
+ let(:request_params) do
+ {
note: { note: 'some other note', noteable_id: merge_request.id },
namespace_id: project.namespace,
project_id: project,
target_type: 'merge_request',
target_id: merge_request.id,
- note_project_id: forked_project.id,
+ note_project_id: note_project_id,
in_reply_to_discussion_id: existing_comment.discussion_id
- }.merge(extra_params)
+ }
+ end
+
+ let(:fork_visibility) { Gitlab::VisibilityLevel::PUBLIC }
+
+ before do
+ forked_project.update_attribute(:visibility_level, fork_visibility)
end
context 'when the note_project_id is not correct' do
- it 'returns a 404' do
- post_create(note_project_id: Project.maximum(:id).succ)
+ let(:note_project_id) do
+ project.id && Project.maximum(:id).succ
+ end
+ it 'returns a 404' do
+ create!
expect(response).to have_gitlab_http_status(404)
end
end
context 'when the user has no access to the fork' do
- it 'returns a 404' do
- post_create
+ let(:fork_visibility) { Gitlab::VisibilityLevel::PRIVATE }
+ it 'returns a 404' do
+ create!
expect(response).to have_gitlab_http_status(404)
end
end
context 'when the user has access to the fork' do
- let(:discussion) { forked_project.notes.find_discussion(existing_comment.discussion_id) }
+ let!(:discussion) { forked_project.notes.find_discussion(existing_comment.discussion_id) }
+ let(:fork_visibility) { Gitlab::VisibilityLevel::PUBLIC }
- before do
- forked_project.add_developer(user)
-
- existing_comment
+ it 'is successful' do
+ create!
+ expect(response).to have_gitlab_http_status(302)
end
it 'creates the note' do
- expect { post_create }.to change { forked_project.notes.count }.by(1)
+ expect { create! }.to change { forked_project.notes.count }.by(1)
end
end
end
@@ -346,11 +552,6 @@ describe Projects::NotesController do
let(:locked_issue) { create(:issue, :locked, project: project) }
let(:issue) {create(:issue, project: project)}
- before do
- project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
- project.project_member(user).destroy
- end
-
it 'uses target_id and ignores noteable_id' do
request_params = {
note: { note: 'some note', noteable_type: 'Issue', noteable_id: locked_issue.id },
@@ -368,7 +569,6 @@ describe Projects::NotesController do
context 'when the merge request discussion is locked' do
before do
- project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
merge_request.update_attribute(:discussion_locked, true)
end
@@ -382,6 +582,10 @@ describe Projects::NotesController do
end
context 'when a user is a team member' do
+ before do
+ project.add_developer(user)
+ end
+
it 'returns 302 status for html' do
post :create, params: request_params
@@ -400,10 +604,6 @@ describe Projects::NotesController do
end
context 'when a user is not a team member' do
- before do
- project.project_member(user).destroy
- end
-
it 'returns 404 status' do
post :create, params: request_params
@@ -415,37 +615,6 @@ describe Projects::NotesController do
end
end
end
-
- context 'when creating a note with quick actions' do
- context 'with commands that return changes' do
- let(:note_text) { "/award :thumbsup:\n/estimate 1d\n/spend 3h" }
-
- it 'includes changes in commands_changes ' do
- post :create, params: request_params.merge(note: { note: note_text }, format: :json)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['commands_changes']).to include('emoji_award', 'time_estimate', 'spend_time')
- expect(json_response['commands_changes']).not_to include('target_project', 'title')
- end
- end
-
- context 'with commands that do not return changes' do
- let(:issue) { create(:issue, project: project) }
- let(:other_project) { create(:project) }
- let(:note_text) { "/move #{other_project.full_path}\n/title AAA" }
-
- before do
- other_project.add_developer(user)
- end
-
- it 'does not include changes in commands_changes' do
- post :create, params: request_params.merge(note: { note: note_text }, target_type: 'issue', target_id: issue.id, format: :json)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['commands_changes']).not_to include('target_project', 'title')
- end
- end
- end
end
describe 'PUT update' do
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 563b61962cf..180d997a8e8 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -11,6 +11,7 @@ describe Projects::ServicesController do
before do
sign_in(user)
project.add_maintainer(user)
+ allow(Gitlab::UrlBlocker).to receive(:validate!).and_return([URI.parse('http://example.com'), nil])
end
describe '#test' do
@@ -56,6 +57,8 @@ describe Projects::ServicesController do
stub_request(:get, 'http://example.com/rest/api/2/serverInfo')
.to_return(status: 200, body: '{}')
+ expect(Gitlab::HTTP).to receive(:get).with("/rest/api/2/serverInfo", any_args).and_call_original
+
put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params }
expect(response.status).to eq(200)
@@ -66,6 +69,8 @@ describe Projects::ServicesController do
stub_request(:get, 'http://example.com/rest/api/2/serverInfo')
.to_return(status: 200, body: '{}')
+ expect(Gitlab::HTTP).to receive(:get).with("/rest/api/2/serverInfo", any_args).and_call_original
+
put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params }
expect(response.status).to eq(200)
diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb
index 89857a9d21b..fafcd6927cd 100644
--- a/spec/controllers/sent_notifications_controller_spec.rb
+++ b/spec/controllers/sent_notifications_controller_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe SentNotificationsController do
let(:user) { create(:user) }
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index 9c4ddce5409..68b7bf61231 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -100,16 +100,8 @@ describe SessionsController do
end
end
- context 'when reCAPTCHA is enabled' do
- let(:user) { create(:user) }
- let(:user_params) { { login: user.username, password: user.password } }
-
- before do
- stub_application_setting(recaptcha_enabled: true)
- request.headers[described_class::CAPTCHA_HEADER] = 1
- end
-
- it 'displays an error when the reCAPTCHA is not solved' do
+ context 'with reCAPTCHA' do
+ def unsuccesful_login(user_params, sesion_params: {})
# Without this, `verify_recaptcha` arbitrarily returns true in test env
Recaptcha.configuration.skip_verify_env.delete('test')
counter = double(:counter)
@@ -119,14 +111,10 @@ describe SessionsController do
.with(:failed_login_captcha_total, anything)
.and_return(counter)
- post(:create, params: { user: user_params })
-
- expect(response).to render_template(:new)
- expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
- expect(subject.current_user).to be_nil
+ post(:create, params: { user: user_params }, session: sesion_params)
end
- it 'successfully logs in a user when reCAPTCHA is solved' do
+ def succesful_login(user_params, sesion_params: {})
# Avoid test ordering issue and ensure `verify_recaptcha` returns true
Recaptcha.configuration.skip_verify_env << 'test'
counter = double(:counter)
@@ -137,9 +125,80 @@ describe SessionsController do
.and_return(counter)
expect(Gitlab::Metrics).to receive(:counter).and_call_original
- post(:create, params: { user: user_params })
+ post(:create, params: { user: user_params }, session: sesion_params)
+ end
- expect(subject.current_user).to eq user
+ context 'when reCAPTCHA is enabled' do
+ let(:user) { create(:user) }
+ let(:user_params) { { login: user.username, password: user.password } }
+
+ before do
+ stub_application_setting(recaptcha_enabled: true)
+ request.headers[described_class::CAPTCHA_HEADER] = 1
+ end
+
+ it 'displays an error when the reCAPTCHA is not solved' do
+ # Without this, `verify_recaptcha` arbitrarily returns true in test env
+
+ unsuccesful_login(user_params)
+
+ expect(response).to render_template(:new)
+ expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
+ expect(subject.current_user).to be_nil
+ end
+
+ it 'successfully logs in a user when reCAPTCHA is solved' do
+ succesful_login(user_params)
+
+ expect(subject.current_user).to eq user
+ end
+ end
+
+ context 'when reCAPTCHA login protection is enabled' do
+ let(:user) { create(:user) }
+ let(:user_params) { { login: user.username, password: user.password } }
+
+ before do
+ stub_application_setting(login_recaptcha_protection_enabled: true)
+ end
+
+ context 'when user tried to login 5 times' do
+ it 'displays an error when the reCAPTCHA is not solved' do
+ unsuccesful_login(user_params, sesion_params: { failed_login_attempts: 6 })
+
+ expect(response).to render_template(:new)
+ expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
+ expect(subject.current_user).to be_nil
+ end
+
+ it 'successfully logs in a user when reCAPTCHA is solved' do
+ succesful_login(user_params, sesion_params: { failed_login_attempts: 6 })
+
+ expect(subject.current_user).to eq user
+ end
+ end
+
+ context 'when there are more than 5 anonymous session with the same IP' do
+ before do
+ allow(Gitlab::AnonymousSession).to receive_message_chain(:new, :stored_sessions).and_return(6)
+ end
+
+ it 'displays an error when the reCAPTCHA is not solved' do
+ unsuccesful_login(user_params)
+
+ expect(response).to render_template(:new)
+ expect(flash[:alert]).to include 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
+ expect(subject.current_user).to be_nil
+ end
+
+ it 'successfully logs in a user when reCAPTCHA is solved' do
+ expect(Gitlab::AnonymousSession).to receive_message_chain(:new, :cleanup_session_per_ip_entries)
+
+ succesful_login(user_params)
+
+ expect(subject.current_user).to eq user
+ end
+ end
end
end
end
@@ -348,4 +407,17 @@ describe SessionsController do
expect(controller.stored_location_for(:redirect)).to eq(search_path)
end
end
+
+ context 'when login fails' do
+ before do
+ set_devise_mapping(context: @request)
+ @request.env["warden.options"] = { action: 'unauthenticated' }
+ end
+
+ it 'does increment failed login counts for session' do
+ get(:new, params: { user: { login: 'failed' } })
+
+ expect(session[:failed_login_attempts]).to eq(1)
+ end
+ end
end
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index 0876502a899..5f4a6bf8ee7 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -21,8 +21,20 @@ shared_examples 'content publicly cached' do
end
describe UploadsController do
+ include WorkhorseHelpers
+
let!(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
+ describe 'POST #authorize' do
+ it_behaves_like 'handle uploads authorize' do
+ let(:uploader_class) { PersonalFileUploader }
+ let(:model) { create(:personal_snippet, :public) }
+ let(:params) do
+ { model: 'personal_snippet', id: model.id }
+ end
+ end
+ end
+
describe 'POST create' do
let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
let(:txt) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') }
@@ -636,4 +648,10 @@ describe UploadsController do
end
end
end
+
+ def post_authorize(verified: true)
+ request.headers.merge!(workhorse_internal_api_request_header) if verified
+
+ post :authorize, params: { model: 'personal_snippet', id: model.id }, format: :json
+ end
end
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index 232890b1bba..52af470efac 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -30,7 +30,7 @@ describe 'Database schema' do
draft_notes: %w[discussion_id commit_id],
emails: %w[user_id],
events: %w[target_id],
- epics: %w[updated_by_id last_edited_by_id start_date_sourcing_milestone_id due_date_sourcing_milestone_id],
+ epics: %w[updated_by_id last_edited_by_id start_date_sourcing_milestone_id due_date_sourcing_milestone_id state_id],
forked_project_links: %w[forked_from_project_id],
geo_event_log: %w[hashed_storage_attachments_event_id],
geo_job_artifact_deleted_events: %w[job_artifact_id],
diff --git a/spec/factories/group_members.rb b/spec/factories/group_members.rb
index 4c875935d82..a93f13395a2 100644
--- a/spec/factories/group_members.rb
+++ b/spec/factories/group_members.rb
@@ -24,5 +24,9 @@ FactoryBot.define do
trait(:ldap) do
ldap true
end
+
+ trait :blocked do
+ after(:build) { |group_member, _| group_member.user.block! }
+ end
end
end
diff --git a/spec/factories/milestone_releases.rb b/spec/factories/milestone_releases.rb
new file mode 100644
index 00000000000..08e109480ab
--- /dev/null
+++ b/spec/factories/milestone_releases.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :milestone_release do
+ milestone
+ release
+
+ before(:create, :build) do |mr|
+ project = create(:project)
+ mr.milestone.project = project
+ mr.release.project = project
+ end
+ end
+end
diff --git a/spec/factories/project_members.rb b/spec/factories/project_members.rb
index 6dcac0400ca..723fa6058fe 100644
--- a/spec/factories/project_members.rb
+++ b/spec/factories/project_members.rb
@@ -17,5 +17,9 @@ FactoryBot.define do
invite_token 'xxx'
invite_email 'email@email.com'
end
+
+ trait :blocked do
+ after(:build) { |project_member, _| project_member.user.block! }
+ end
end
end
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index b2c8bdab013..57e58513529 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -39,6 +39,14 @@ FactoryBot.define do
avatar { fixture_file_upload('spec/fixtures/dk.png') }
end
+ trait :with_sign_ins do
+ sign_in_count 3
+ current_sign_in_at { Time.now }
+ last_sign_in_at { FFaker::Time.between(10.days.ago, 1.day.ago) }
+ current_sign_in_ip '127.0.0.1'
+ last_sign_in_ip '127.0.0.1'
+ end
+
trait :two_factor_via_otp do
before(:create) do |user|
user.otp_required_for_login = true
diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
index ca8874a24f9..4c3c0904a06 100644
--- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb
+++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Admin disables Git access protocol', :js do
include StubENV
diff --git a/spec/features/admin/admin_disables_two_factor_spec.rb b/spec/features/admin/admin_disables_two_factor_spec.rb
index 6e6a4964541..7227141168e 100644
--- a/spec/features/admin/admin_disables_two_factor_spec.rb
+++ b/spec/features/admin/admin_disables_two_factor_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Admin disables 2FA for a user' do
it 'successfully', :js do
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index ddd87404003..eb59de2e132 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -263,6 +263,7 @@ describe 'Admin updates settings' do
page.within('.as-spam') do
check 'Enable reCAPTCHA'
+ check 'Enable reCAPTCHA for login'
fill_in 'reCAPTCHA Site Key', with: 'key'
fill_in 'reCAPTCHA Private Key', with: 'key'
fill_in 'IPs per user', with: 15
@@ -271,6 +272,7 @@ describe 'Admin updates settings' do
expect(page).to have_content "Application settings saved successfully"
expect(current_settings.recaptcha_enabled).to be true
+ expect(current_settings.login_recaptcha_protection_enabled).to be true
expect(current_settings.unique_ips_limit_per_user).to eq(15)
end
end
diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb
index 8c1ec183286..3e8197588ed 100644
--- a/spec/features/admin/admin_uses_repository_checks_spec.rb
+++ b/spec/features/admin/admin_uses_repository_checks_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Admin uses repository checks' do
include StubENV
diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb
index 6e477a93293..0412dc2b69c 100644
--- a/spec/features/boards/add_issues_modal_spec.rb
+++ b/spec/features/boards/add_issues_modal_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Issue Boards add issue modal', :js do
let(:project) { create(:project, :public) }
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 902ecdcd3e8..57dc9de62fb 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Issue Boards', :js do
include DragTo
diff --git a/spec/features/boards/issue_ordering_spec.rb b/spec/features/boards/issue_ordering_spec.rb
index c8ea202169c..62abd914fcb 100644
--- a/spec/features/boards/issue_ordering_spec.rb
+++ b/spec/features/boards/issue_ordering_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Issue Boards', :js do
include DragTo
diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb
index 5f3a2c409ed..6074c559701 100644
--- a/spec/features/boards/keyboard_shortcut_spec.rb
+++ b/spec/features/boards/keyboard_shortcut_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Issue Boards shortcut', :js do
context 'issues are enabled' do
diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb
index 93de2750466..70bc067f79d 100644
--- a/spec/features/boards/modal_filter_spec.rb
+++ b/spec/features/boards/modal_filter_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Issue Boards add issue modal filtering', :js do
let(:project) { create(:project, :public) }
diff --git a/spec/features/boards/multiple_boards_spec.rb b/spec/features/boards/multiple_boards_spec.rb
index 9a2b7a80498..2389707be9c 100644
--- a/spec/features/boards/multiple_boards_spec.rb
+++ b/spec/features/boards/multiple_boards_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Multiple Issue Boards', :js do
set(:user) { create(:user) }
diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb
index abbec0ea810..36743650270 100644
--- a/spec/features/boards/new_issue_spec.rb
+++ b/spec/features/boards/new_issue_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Issue Boards new issue', :js do
let(:project) { create(:project, :public) }
diff --git a/spec/features/boards/reload_boards_on_browser_back_spec.rb b/spec/features/boards/reload_boards_on_browser_back_spec.rb
index 752c8c1052d..6528b8f58bb 100644
--- a/spec/features/boards/reload_boards_on_browser_back_spec.rb
+++ b/spec/features/boards/reload_boards_on_browser_back_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Ensure Boards do not show stale data on browser back', :js do
let(:project) {create(:project, :public)}
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 7ee3a839293..8497eaf102f 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Issue Boards', :js do
include BoardHelpers
diff --git a/spec/features/boards/sub_group_project_spec.rb b/spec/features/boards/sub_group_project_spec.rb
index 264e5e85505..4384a1a9379 100644
--- a/spec/features/boards/sub_group_project_spec.rb
+++ b/spec/features/boards/sub_group_project_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Sub-group project issue boards', :js do
let(:group) { create(:group) }
diff --git a/spec/features/commits/user_uses_quick_actions_spec.rb b/spec/features/commits/user_uses_quick_actions_spec.rb
index 4b7e7465df1..70ea920b7ec 100644
--- a/spec/features/commits/user_uses_quick_actions_spec.rb
+++ b/spec/features/commits/user_uses_quick_actions_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Commit > User uses quick actions', :js do
include Spec::Support::Helpers::Features::NotesHelpers
diff --git a/spec/features/dashboard/todos/target_state_spec.rb b/spec/features/dashboard/todos/target_state_spec.rb
index 931fc16a5eb..0ea1f43e34b 100644
--- a/spec/features/dashboard/todos/target_state_spec.rb
+++ b/spec/features/dashboard/todos/target_state_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Dashboard > Todo target states' do
let(:user) { create(:user) }
diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb
index a7ccc6f7d7b..00fa85930b1 100644
--- a/spec/features/global_search_spec.rb
+++ b/spec/features/global_search_spec.rb
@@ -16,8 +16,7 @@ describe 'Global search' do
it 'increases usage ping searches counter' do
expect(Gitlab::UsageDataCounters::SearchCounter).to receive(:increment_navbar_searches_count)
- fill_in "search", with: "foobar"
- click_button "Go"
+ submit_search('foobar')
end
describe 'I search through the issues and I see pagination' do
@@ -27,10 +26,9 @@ describe 'Global search' do
end
it "has a pagination" do
- fill_in "search", with: "initial"
- click_button "Go"
+ submit_search('initial')
+ select_search_scope('Issues')
- select_filter("Issues")
expect(page).to have_selector('.gl-pagination .next')
end
end
diff --git a/spec/features/groups/board_sidebar_spec.rb b/spec/features/groups/board_sidebar_spec.rb
index 9f597efa7b7..ed874141ef4 100644
--- a/spec/features/groups/board_sidebar_spec.rb
+++ b/spec/features/groups/board_sidebar_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Group Issue Boards', :js do
include BoardHelpers
diff --git a/spec/features/groups/board_spec.rb b/spec/features/groups/board_spec.rb
index ca33dbb7a33..f95e2e91cd7 100644
--- a/spec/features/groups/board_spec.rb
+++ b/spec/features/groups/board_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Group Boards' do
let(:group) { create(:group) }
diff --git a/spec/features/groups/clusters/user_spec.rb b/spec/features/groups/clusters/user_spec.rb
index 84a8691a7f2..8891866c1f8 100644
--- a/spec/features/groups/clusters/user_spec.rb
+++ b/spec/features/groups/clusters/user_spec.rb
@@ -13,7 +13,7 @@ describe 'User Cluster', :js do
gitlab_sign_in(user)
allow(Groups::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 }
- allow_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
+ allow_any_instance_of(Clusters::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
allow_any_instance_of(Clusters::Cluster).to receive(:retrieve_connection_status).and_return(:connected)
end
diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb
index 7f0155b63e0..17738905e8d 100644
--- a/spec/features/groups/milestone_spec.rb
+++ b/spec/features/groups/milestone_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Group milestones' do
let(:group) { create(:group) }
diff --git a/spec/features/groups/user_browse_projects_group_page_spec.rb b/spec/features/groups/user_browse_projects_group_page_spec.rb
index 075bc1128ca..68221e3fefe 100644
--- a/spec/features/groups/user_browse_projects_group_page_spec.rb
+++ b/spec/features/groups/user_browse_projects_group_page_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'User browse group projects page' do
let(:user) { create :user }
diff --git a/spec/features/instance_statistics/cohorts_spec.rb b/spec/features/instance_statistics/cohorts_spec.rb
index 61294ec9af2..3940e8fa389 100644
--- a/spec/features/instance_statistics/cohorts_spec.rb
+++ b/spec/features/instance_statistics/cohorts_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Cohorts page' do
before do
diff --git a/spec/features/instance_statistics/instance_statistics_spec.rb b/spec/features/instance_statistics/instance_statistics_spec.rb
index 40d0f1db207..0fb78c6eef8 100644
--- a/spec/features/instance_statistics/instance_statistics_spec.rb
+++ b/spec/features/instance_statistics/instance_statistics_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Cohorts page', :js do
before do
diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb
index dc68e633e27..30c516459c5 100644
--- a/spec/features/issuables/issuable_list_spec.rb
+++ b/spec/features/issuables/issuable_list_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'issuable list' do
let(:project) { create(:project) }
diff --git a/spec/features/issuables/markdown_references/internal_references_spec.rb b/spec/features/issuables/markdown_references/internal_references_spec.rb
index e84629dc17f..f3b534bca49 100644
--- a/spec/features/issuables/markdown_references/internal_references_spec.rb
+++ b/spec/features/issuables/markdown_references/internal_references_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require "rails_helper"
+require 'spec_helper'
describe "Internal references", :js do
include Spec::Support::Helpers::Features::NotesHelpers
diff --git a/spec/features/issuables/markdown_references/jira_spec.rb b/spec/features/issuables/markdown_references/jira_spec.rb
index aecdeffe4a4..8085918f533 100644
--- a/spec/features/issuables/markdown_references/jira_spec.rb
+++ b/spec/features/issuables/markdown_references/jira_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require "rails_helper"
+require 'spec_helper'
describe "Jira", :js do
let(:user) { create(:user) }
diff --git a/spec/features/issuables/user_sees_sidebar_spec.rb b/spec/features/issuables/user_sees_sidebar_spec.rb
index d1e10c70414..52040eb8cbb 100644
--- a/spec/features/issuables/user_sees_sidebar_spec.rb
+++ b/spec/features/issuables/user_sees_sidebar_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Issue Sidebar on Mobile' do
include MobileHelpers
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index e8b828bd264..57d04f0bf40 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Issues > Labels bulk assignment' do
let(:user) { create(:user) }
diff --git a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
index f2e482faf5f..7d3df711555 100644
--- a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
+++ b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Resolving all open threads in a merge request from an issue', :js do
let(:user) { create(:user) }
diff --git a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
index 08c98358544..8f4083533b3 100644
--- a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
+++ b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Resolve an open thread in a merge request by creating an issue', :js do
let(:user) { create(:user) }
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index 2ca551cec77..e1177bedd2d 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Dropdown assignee', :js do
include FilteredSearchHelpers
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
index b2905fc1dde..bd22eb1056b 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Dropdown author', :js do
include FilteredSearchHelpers
diff --git a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb
index 45344aeb8f5..7ec3d215fb1 100644
--- a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Dropdown emoji', :js do
include FilteredSearchHelpers
diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
index 34313553b34..1c56902a27d 100644
--- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Dropdown hint', :js do
include FilteredSearchHelpers
diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
index 8429fe44c43..9a782950bf6 100644
--- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Dropdown milestone', :js do
include FilteredSearchHelpers
diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb
index 0fe03160e7a..e97314e02e6 100644
--- a/spec/features/issues/filtered_search/search_bar_spec.rb
+++ b/spec/features/issues/filtered_search/search_bar_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Search bar', :js do
include FilteredSearchHelpers
diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb
index b20add1ea33..f8035ef4b85 100644
--- a/spec/features/issues/filtered_search/visual_tokens_spec.rb
+++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Visual tokens', :js do
include FilteredSearchHelpers
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index e840ac79916..8d7b6be5ea2 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'New/edit issue', :js do
include ActionView::Helpers::JavaScriptHelper
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 566441dc6f6..cc834df367b 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'GFM autocomplete', :js do
let(:issue_xss_title) { 'This will execute alert<img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;' }
diff --git a/spec/features/issues/group_label_sidebar_spec.rb b/spec/features/issues/group_label_sidebar_spec.rb
index 746c55b6a26..fe6d95e1039 100644
--- a/spec/features/issues/group_label_sidebar_spec.rb
+++ b/spec/features/issues/group_label_sidebar_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Group label on issue' do
it 'renders link to the project issues page' do
diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb
index 8f2e5b237ea..a1b53718577 100644
--- a/spec/features/issues/issue_detail_spec.rb
+++ b/spec/features/issues/issue_detail_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Issue Detail', :js do
let(:user) { create(:user) }
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index b88d1bbeae5..9baba80bf06 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Issue Sidebar' do
include MobileHelpers
diff --git a/spec/features/issues/keyboard_shortcut_spec.rb b/spec/features/issues/keyboard_shortcut_spec.rb
index e1325e63e6b..c5d53cd1cd0 100644
--- a/spec/features/issues/keyboard_shortcut_spec.rb
+++ b/spec/features/issues/keyboard_shortcut_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Issues shortcut', :js do
context 'New Issue shortcut' do
diff --git a/spec/features/issues/markdown_toolbar_spec.rb b/spec/features/issues/markdown_toolbar_spec.rb
index b80b47a4c99..d174fdcb25e 100644
--- a/spec/features/issues/markdown_toolbar_spec.rb
+++ b/spec/features/issues/markdown_toolbar_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Issue markdown toolbar', :js do
let(:project) { create(:project, :public) }
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index 9e0be7d8935..1122504248f 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'issue move to another project' do
let(:user) { create(:user) }
diff --git a/spec/features/issues/resource_label_events_spec.rb b/spec/features/issues/resource_label_events_spec.rb
index 3d380c183ec..b367bbe2c99 100644
--- a/spec/features/issues/resource_label_events_spec.rb
+++ b/spec/features/issues/resource_label_events_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'List issue resource label events', :js do
let(:user) { create(:user) }
diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb
index 85c72c42f45..0d009f47fff 100644
--- a/spec/features/issues/spam_issues_spec.rb
+++ b/spec/features/issues/spam_issues_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'New issue', :js do
include StubENV
diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb
index 9282c31751a..4bb96ad069c 100644
--- a/spec/features/issues/todo_spec.rb
+++ b/spec/features/issues/todo_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Manually create a todo item from issue', :js do
let!(:project) { create(:project) }
diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb
index 0e27d0231d0..45a0b1932a2 100644
--- a/spec/features/issues/update_issues_spec.rb
+++ b/spec/features/issues/update_issues_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Multiple issue updating from issues#index', :js do
let!(:project) { create(:project) }
diff --git a/spec/features/issues/user_comments_on_issue_spec.rb b/spec/features/issues/user_comments_on_issue_spec.rb
index bdaaea5bf7f..829f945c47f 100644
--- a/spec/features/issues/user_comments_on_issue_spec.rb
+++ b/spec/features/issues/user_comments_on_issue_spec.rb
@@ -55,6 +55,23 @@ describe "User comments on issue", :js do
expect(page.find('svg.mermaid')).to have_content escaped_content
end
+
+ it 'opens autocomplete menu for quick actions and have `/label` first choice' do
+ project.add_maintainer(user)
+ create(:label, project: project, title: 'label')
+
+ page.within '.timeline-content-form' do
+ find('#note-body').native.send_keys('/l')
+ end
+
+ wait_for_requests
+
+ expect(page).to have_selector('.atwho-container')
+
+ page.within '.atwho-container #at-view-commands' do
+ expect(find('li', match: :first)).to have_content('/label')
+ end
+ end
end
context "when editing comments" do
diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
index 878a73718d7..be31c45b373 100644
--- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
+++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'User creates branch and merge request on issue page', :js do
let(:membership_level) { :developer }
diff --git a/spec/features/issues/user_creates_confidential_merge_request_spec.rb b/spec/features/issues/user_creates_confidential_merge_request_spec.rb
index 4a32b7e2b73..24089bdeb81 100644
--- a/spec/features/issues/user_creates_confidential_merge_request_spec.rb
+++ b/spec/features/issues/user_creates_confidential_merge_request_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'User creates confidential merge request on issue page', :js do
include ProjectForksHelper
diff --git a/spec/features/issues/user_sees_breadcrumb_links_spec.rb b/spec/features/issues/user_sees_breadcrumb_links_spec.rb
index db8d3c5dd53..f31d730c337 100644
--- a/spec/features/issues/user_sees_breadcrumb_links_spec.rb
+++ b/spec/features/issues/user_sees_breadcrumb_links_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'New issue breadcrumb' do
let(:project) { create(:project) }
diff --git a/spec/features/issues/user_uses_quick_actions_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb
index 8fec92031a9..26979e943d0 100644
--- a/spec/features/issues/user_uses_quick_actions_spec.rb
+++ b/spec/features/issues/user_uses_quick_actions_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
# These are written as feature specs because they cover more specific test scenarios
# than the ones described on spec/services/notes/create_service_spec.rb for quick actions,
diff --git a/spec/features/markdown/math_spec.rb b/spec/features/markdown/math_spec.rb
index 68d99b4241a..76eef66c517 100644
--- a/spec/features/markdown/math_spec.rb
+++ b/spec/features/markdown/math_spec.rb
@@ -34,7 +34,9 @@ describe 'Math rendering', :js do
visit project_issue_path(project, issue)
- expect(page).to have_selector('.katex-error', text: "\href{javascript:alert('xss');}{xss}")
- expect(page).to have_selector('.katex-html a', text: 'Gitlab')
+ page.within '.description > .md' do
+ expect(page).to have_selector('.katex-error')
+ expect(page).to have_selector('.katex-html a', text: 'Gitlab')
+ end
end
end
diff --git a/spec/features/merge_request/user_assigns_themselves_spec.rb b/spec/features/merge_request/user_assigns_themselves_spec.rb
index d7918a9e9d7..549d6e50337 100644
--- a/spec/features/merge_request/user_assigns_themselves_spec.rb
+++ b/spec/features/merge_request/user_assigns_themselves_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User assigns themselves' do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/merge_request/user_awards_emoji_spec.rb b/spec/features/merge_request/user_awards_emoji_spec.rb
index 5e9b232760b..8aa90107251 100644
--- a/spec/features/merge_request/user_awards_emoji_spec.rb
+++ b/spec/features/merge_request/user_awards_emoji_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User awards emoji', :js do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/merge_request/user_creates_mr_spec.rb b/spec/features/merge_request/user_creates_mr_spec.rb
index 267097bd466..665bc352c0f 100644
--- a/spec/features/merge_request/user_creates_mr_spec.rb
+++ b/spec/features/merge_request/user_creates_mr_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User creates MR' do
include ProjectForksHelper
diff --git a/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb
index 3213efcb60b..895cbb8f02b 100644
--- a/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb
+++ b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request < User customizes merge commit message', :js do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/merge_request/user_locks_discussion_spec.rb b/spec/features/merge_request/user_locks_discussion_spec.rb
index 365e9b60d90..0eaaf32dc31 100644
--- a/spec/features/merge_request/user_locks_discussion_spec.rb
+++ b/spec/features/merge_request/user_locks_discussion_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User locks discussion', :js do
let(:user) { create(:user) }
diff --git a/spec/features/merge_request/user_merges_immediately_spec.rb b/spec/features/merge_request/user_merges_immediately_spec.rb
index 2f01971c2e9..1188d3b2ceb 100644
--- a/spec/features/merge_request/user_merges_immediately_spec.rb
+++ b/spec/features/merge_request/user_merges_immediately_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge requests > User merges immediately', :js do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb
index 3f9c27bbf9d..4afbf30ece4 100644
--- a/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User merges only if pipeline succeeds', :js do
let(:merge_request) { create(:merge_request_with_diffs) }
diff --git a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
index 93938ac221a..ffc12ffdbaf 100644
--- a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User merges when pipeline succeeds', :js do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/merge_request/user_posts_diff_notes_spec.rb b/spec/features/merge_request/user_posts_diff_notes_spec.rb
index abae6ffbd71..8b16760606c 100644
--- a/spec/features/merge_request/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_diff_notes_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User posts diff notes', :js do
include MergeRequestDiffHelpers
diff --git a/spec/features/merge_request/user_posts_notes_spec.rb b/spec/features/merge_request/user_posts_notes_spec.rb
index 1fad9eac1bd..435b3cd2555 100644
--- a/spec/features/merge_request/user_posts_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_notes_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User posts notes', :js do
include NoteInteractionHelpers
diff --git a/spec/features/merge_request/user_resolves_conflicts_spec.rb b/spec/features/merge_request/user_resolves_conflicts_spec.rb
index fb089a9ffcb..dd1ec17ae51 100644
--- a/spec/features/merge_request/user_resolves_conflicts_spec.rb
+++ b/spec/features/merge_request/user_resolves_conflicts_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User resolves conflicts', :js do
let(:project) { create(:project, :repository) }
diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
index b8e4852a748..8b41ef86791 100644
--- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
+++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User resolves diff notes and threads', :js do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
index 23b37c218e1..48c3ed7178d 100644
--- a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
+++ b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User scrolls to note on load', :js do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
index fae81758086..baef831c40e 100644
--- a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User sees avatars on diff notes', :js do
include NoteInteractionHelpers
diff --git a/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb b/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb
index e1f0ddc4c6a..592ad3aae9b 100644
--- a/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb
+++ b/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'New merge request breadcrumb' do
let(:project) { create(:project, :repository) }
diff --git a/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb b/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb
index 93ae408a173..f54161fbaec 100644
--- a/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb
+++ b/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User sees check out branch modal', :js do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb b/spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb
index 8a2614c53af..1d62f7f0702 100644
--- a/spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb
+++ b/spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User cherry-picks', :js do
let(:group) { create(:group) }
diff --git a/spec/features/merge_request/user_sees_closing_issues_message_spec.rb b/spec/features/merge_request/user_sees_closing_issues_message_spec.rb
index 52163571175..f77ea82649c 100644
--- a/spec/features/merge_request/user_sees_closing_issues_message_spec.rb
+++ b/spec/features/merge_request/user_sees_closing_issues_message_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User sees closing issues message', :js do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb b/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb
index f46921af1cd..224261dec00 100644
--- a/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb
+++ b/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User sees deleted target branch', :js do
let(:merge_request) { create(:merge_request) }
diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
index 14fbfc2fd3f..87fb3f5b3e7 100644
--- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User sees deployment widget', :js do
describe 'when merge request has associated environments' do
diff --git a/spec/features/merge_request/user_sees_diff_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb
index 32429bac5f9..8eeed7b0843 100644
--- a/spec/features/merge_request/user_sees_diff_spec.rb
+++ b/spec/features/merge_request/user_sees_diff_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User sees diff', :js do
include ProjectForksHelper
diff --git a/spec/features/merge_request/user_sees_discussions_spec.rb b/spec/features/merge_request/user_sees_discussions_spec.rb
index a11baf1dca3..48f5814322a 100644
--- a/spec/features/merge_request/user_sees_discussions_spec.rb
+++ b/spec/features/merge_request/user_sees_discussions_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User sees threads', :js do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/merge_request/user_sees_empty_state_spec.rb b/spec/features/merge_request/user_sees_empty_state_spec.rb
index f1ad5c2dffc..88eba976d62 100644
--- a/spec/features/merge_request/user_sees_empty_state_spec.rb
+++ b/spec/features/merge_request/user_sees_empty_state_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User sees empty state' do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb b/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb
index 53cafb60dfb..4cc129e5d5f 100644
--- a/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User sees merge button depending on unresolved threads', :js do
let(:project) { create(:project, :repository) }
diff --git a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
index 28f88718ec1..dd5662d83f2 100644
--- a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User sees pipelines triggered by merge request', :js do
include ProjectForksHelper
diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index b5d0240e9de..3f2a676462b 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User sees merge widget', :js do
include ProjectForksHelper
diff --git a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
index eb89d616b14..4d801c6ff11 100644
--- a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request < User sees mini pipeline graph', :js do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb b/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb
index 80ee35f2a91..b4fb844b943 100644
--- a/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb
+++ b/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User sees MR from deleted forked project', :js do
include ProjectForksHelper
diff --git a/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb b/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb
index d9dc32d0594..db0d632cdf2 100644
--- a/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb
+++ b/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
# This test serves as a regression test for a bug that caused an error
# message to be shown by JavaScript when the source branch was deleted.
diff --git a/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb b/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb
index 195592531f2..0391794649c 100644
--- a/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb
+++ b/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User sees notes from forked project', :js do
include ProjectForksHelper
diff --git a/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb b/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb
index 68c9e0b123d..3e15a9c136b 100644
--- a/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb
+++ b/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User sees pipelines from forked project', :js do
include ProjectForksHelper
diff --git a/spec/features/merge_request/user_sees_pipelines_spec.rb b/spec/features/merge_request/user_sees_pipelines_spec.rb
index e057f59e00c..f04317a59ee 100644
--- a/spec/features/merge_request/user_sees_pipelines_spec.rb
+++ b/spec/features/merge_request/user_sees_pipelines_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User sees pipelines', :js do
describe 'pipeline tab' do
diff --git a/spec/features/merge_request/user_sees_system_notes_spec.rb b/spec/features/merge_request/user_sees_system_notes_spec.rb
index 26cdd5ba21c..0482458d5ac 100644
--- a/spec/features/merge_request/user_sees_system_notes_spec.rb
+++ b/spec/features/merge_request/user_sees_system_notes_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User sees system notes', :js do
let(:public_project) { create(:project, :public, :repository) }
diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb
index dae5845adec..62abcff7bda 100644
--- a/spec/features/merge_request/user_sees_versions_spec.rb
+++ b/spec/features/merge_request/user_sees_versions_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User sees versions', :js do
let(:merge_request) do
diff --git a/spec/features/merge_request/user_sees_wip_help_message_spec.rb b/spec/features/merge_request/user_sees_wip_help_message_spec.rb
index 46209237faf..1179303171c 100644
--- a/spec/features/merge_request/user_sees_wip_help_message_spec.rb
+++ b/spec/features/merge_request/user_sees_wip_help_message_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User sees WIP help message' do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb
index a1f027cbb1d..6262f1ce055 100644
--- a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb
+++ b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User selects branches for new MR', :js do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
index 61b9904dde5..4db067a4e41 100644
--- a/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
+++ b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User toggles whitespace changes', :js do
let(:merge_request) { create(:merge_request) }
diff --git a/spec/features/merge_request/user_uses_quick_actions_spec.rb b/spec/features/merge_request/user_uses_quick_actions_spec.rb
index 3948c0c5c89..318f8812263 100644
--- a/spec/features/merge_request/user_uses_quick_actions_spec.rb
+++ b/spec/features/merge_request/user_uses_quick_actions_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
# These are written as feature specs because they cover more specific test scenarios
# than the ones described on spec/services/notes/create_service_spec.rb for quick actions,
diff --git a/spec/features/merge_requests/filters_generic_behavior_spec.rb b/spec/features/merge_requests/filters_generic_behavior_spec.rb
index 404e591cf4b..58aad1b7e91 100644
--- a/spec/features/merge_requests/filters_generic_behavior_spec.rb
+++ b/spec/features/merge_requests/filters_generic_behavior_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge Requests > Filters generic behavior', :js do
include FilteredSearchHelpers
diff --git a/spec/features/merge_requests/user_filters_by_assignees_spec.rb b/spec/features/merge_requests/user_filters_by_assignees_spec.rb
index b896b6392f1..00bd8455ae1 100644
--- a/spec/features/merge_requests/user_filters_by_assignees_spec.rb
+++ b/spec/features/merge_requests/user_filters_by_assignees_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge Requests > User filters by assignees', :js do
include FilteredSearchHelpers
diff --git a/spec/features/merge_requests/user_filters_by_labels_spec.rb b/spec/features/merge_requests/user_filters_by_labels_spec.rb
index 9fb149ca58d..fd2b4b23f96 100644
--- a/spec/features/merge_requests/user_filters_by_labels_spec.rb
+++ b/spec/features/merge_requests/user_filters_by_labels_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge Requests > User filters by labels', :js do
include FilteredSearchHelpers
diff --git a/spec/features/merge_requests/user_filters_by_milestones_spec.rb b/spec/features/merge_requests/user_filters_by_milestones_spec.rb
index 2fb3c86ea94..e0ee69d7a5b 100644
--- a/spec/features/merge_requests/user_filters_by_milestones_spec.rb
+++ b/spec/features/merge_requests/user_filters_by_milestones_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge Requests > User filters by milestones', :js do
include FilteredSearchHelpers
diff --git a/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb b/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb
index 3dd0f93ddfa..bc6e2ac5132 100644
--- a/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb
+++ b/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge requests > User filters by multiple criteria', :js do
include FilteredSearchHelpers
diff --git a/spec/features/merge_requests/user_filters_by_target_branch_spec.rb b/spec/features/merge_requests/user_filters_by_target_branch_spec.rb
index b5969f656b0..0d03c5eae31 100644
--- a/spec/features/merge_requests/user_filters_by_target_branch_spec.rb
+++ b/spec/features/merge_requests/user_filters_by_target_branch_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge Requests > User filters by target branch', :js do
include FilteredSearchHelpers
diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
index 69d4650d1ac..f145bdd044b 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge requests > User lists merge requests' do
include MergeRequestHelpers
diff --git a/spec/features/merge_requests/user_mass_updates_spec.rb b/spec/features/merge_requests/user_mass_updates_spec.rb
index 48c92a87c63..4d3461bf1ae 100644
--- a/spec/features/merge_requests/user_mass_updates_spec.rb
+++ b/spec/features/merge_requests/user_mass_updates_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge requests > User mass updates', :js do
let(:project) { create(:project, :repository) }
diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb
index 6d5acc894b1..bfff33f3956 100644
--- a/spec/features/milestone_spec.rb
+++ b/spec/features/milestone_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Milestone' do
let(:group) { create(:group, :public) }
diff --git a/spec/features/milestones/user_creates_milestone_spec.rb b/spec/features/milestones/user_creates_milestone_spec.rb
index 73f4f187501..5c93ddcf6f8 100644
--- a/spec/features/milestones/user_creates_milestone_spec.rb
+++ b/spec/features/milestones/user_creates_milestone_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require "rails_helper"
+require 'spec_helper'
describe "User creates milestone", :js do
set(:user) { create(:user) }
diff --git a/spec/features/milestones/user_deletes_milestone_spec.rb b/spec/features/milestones/user_deletes_milestone_spec.rb
index 5666c1dd507..7c1d88f7798 100644
--- a/spec/features/milestones/user_deletes_milestone_spec.rb
+++ b/spec/features/milestones/user_deletes_milestone_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require "rails_helper"
+require 'spec_helper'
describe "User deletes milestone", :js do
let(:user) { create(:user) }
diff --git a/spec/features/milestones/user_edits_milestone_spec.rb b/spec/features/milestones/user_edits_milestone_spec.rb
index f8e96eac3ea..b41b8f3282f 100644
--- a/spec/features/milestones/user_edits_milestone_spec.rb
+++ b/spec/features/milestones/user_edits_milestone_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require "rails_helper"
+require 'spec_helper'
describe "User edits milestone", :js do
set(:user) { create(:user) }
diff --git a/spec/features/milestones/user_promotes_milestone_spec.rb b/spec/features/milestones/user_promotes_milestone_spec.rb
index acceb5cad62..7678b6cbfa5 100644
--- a/spec/features/milestones/user_promotes_milestone_spec.rb
+++ b/spec/features/milestones/user_promotes_milestone_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'User promotes milestone' do
set(:group) { create(:group) }
diff --git a/spec/features/milestones/user_sees_breadcrumb_links_spec.rb b/spec/features/milestones/user_sees_breadcrumb_links_spec.rb
index f58242759cc..92445735328 100644
--- a/spec/features/milestones/user_sees_breadcrumb_links_spec.rb
+++ b/spec/features/milestones/user_sees_breadcrumb_links_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'New project milestone breadcrumb' do
let(:project) { create(:project) }
diff --git a/spec/features/milestones/user_views_milestone_spec.rb b/spec/features/milestones/user_views_milestone_spec.rb
index aa0cdf66b75..71abb195ad1 100644
--- a/spec/features/milestones/user_views_milestone_spec.rb
+++ b/spec/features/milestones/user_views_milestone_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require "rails_helper"
+require 'spec_helper'
describe "User views milestone" do
set(:user) { create(:user) }
diff --git a/spec/features/milestones/user_views_milestones_spec.rb b/spec/features/milestones/user_views_milestones_spec.rb
index 6868791f584..0b51ca12997 100644
--- a/spec/features/milestones/user_views_milestones_spec.rb
+++ b/spec/features/milestones/user_views_milestones_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require "rails_helper"
+require 'spec_helper'
describe "User views milestones" do
set(:user) { create(:user) }
diff --git a/spec/features/profiles/account_spec.rb b/spec/features/profiles/account_spec.rb
index 741e41adbf1..c6db15065f2 100644
--- a/spec/features/profiles/account_spec.rb
+++ b/spec/features/profiles/account_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Profile > Account', :js do
let(:user) { create(:user, username: 'foo') }
diff --git a/spec/features/profiles/active_sessions_spec.rb b/spec/features/profiles/active_sessions_spec.rb
index 709cca7d178..a5c2d15f598 100644
--- a/spec/features/profiles/active_sessions_spec.rb
+++ b/spec/features/profiles/active_sessions_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
let(:user) do
diff --git a/spec/features/profiles/chat_names_spec.rb b/spec/features/profiles/chat_names_spec.rb
index 0219dacbc38..4c447faf77e 100644
--- a/spec/features/profiles/chat_names_spec.rb
+++ b/spec/features/profiles/chat_names_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Profile > Chat' do
let(:user) { create(:user) }
diff --git a/spec/features/profiles/emails_spec.rb b/spec/features/profiles/emails_spec.rb
index 40e2988730b..4d2cd0f8b56 100644
--- a/spec/features/profiles/emails_spec.rb
+++ b/spec/features/profiles/emails_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Profile > Emails' do
let(:user) { create(:user) }
diff --git a/spec/features/profiles/gpg_keys_spec.rb b/spec/features/profiles/gpg_keys_spec.rb
index 4237f037c27..07e87f36c65 100644
--- a/spec/features/profiles/gpg_keys_spec.rb
+++ b/spec/features/profiles/gpg_keys_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Profile > GPG Keys' do
let(:user) { create(:user, email: GpgHelpers::User2.emails.first) }
diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb
index c1b142c4e12..3fc0fd76d2e 100644
--- a/spec/features/profiles/keys_spec.rb
+++ b/spec/features/profiles/keys_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Profile > SSH Keys' do
let(:user) { create(:user) }
diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb
index 1ab7742b36e..0905ab0aef8 100644
--- a/spec/features/profiles/user_edit_profile_spec.rb
+++ b/spec/features/profiles/user_edit_profile_spec.rb
@@ -49,6 +49,23 @@ describe 'User edit profile' do
end
end
+ describe 'when I change my email' do
+ before do
+ user.send_reset_password_instructions
+ end
+
+ it 'clears the reset password token' do
+ expect(user.reset_password_token?).to be true
+
+ fill_in 'user_email', with: 'new-email@example.com'
+ submit_settings
+
+ user.reload
+ expect(user.confirmation_token).not_to be_nil
+ expect(user.reset_password_token?).to be false
+ end
+ end
+
context 'user avatar' do
before do
attach_file(:user_avatar, Rails.root.join('spec', 'fixtures', 'banana_sample.gif'))
diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb
index 3899aab8170..84f2e3e09ae 100644
--- a/spec/features/projects/clusters/user_spec.rb
+++ b/spec/features/projects/clusters/user_spec.rb
@@ -13,7 +13,7 @@ describe 'User Cluster', :js do
gitlab_sign_in(user)
allow(Projects::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 }
- allow_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
+ allow_any_instance_of(Clusters::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
allow_any_instance_of(Clusters::Cluster).to receive(:retrieve_connection_status).and_return(:connected)
end
diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
index 1199a3bd226..3d9fcfe0f62 100644
--- a/spec/features/projects/commit/mini_pipeline_graph_spec.rb
+++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Mini Pipeline Graph in Commit View', :js do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/projects/files/user_searches_for_files_spec.rb b/spec/features/projects/files/user_searches_for_files_spec.rb
index e82f54fbe50..ff7547bce83 100644
--- a/spec/features/projects/files/user_searches_for_files_spec.rb
+++ b/spec/features/projects/files/user_searches_for_files_spec.rb
@@ -18,8 +18,7 @@ describe 'Projects > Files > User searches for files' do
end
it 'does not show any result' do
- fill_in('search', with: 'coffee')
- click_button('Go')
+ submit_search('coffee')
expect(page).to have_content("We couldn't find any")
end
@@ -50,8 +49,7 @@ describe 'Projects > Files > User searches for files' do
it 'shows found files' do
expect(page).to have_selector('.tree-controls .shortcuts-find-file')
- fill_in('search', with: 'coffee')
- click_button('Go')
+ submit_search('coffee')
expect(page).to have_content('coffee')
expect(page).to have_content('CONTRIBUTING.md')
diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb
index 1b277e17b0c..4d8a4812123 100644
--- a/spec/features/projects/jobs/user_browses_job_spec.rb
+++ b/spec/features/projects/jobs/user_browses_job_spec.rb
@@ -10,6 +10,8 @@ describe 'User browses a job', :js do
let!(:build) { create(:ci_build, :success, :trace_artifact, :coverage, pipeline: pipeline) }
before do
+ stub_feature_flags(job_log_json: false)
+
project.add_maintainer(user)
project.enable_ci
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 8ed420300af..d1783de0330 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -20,6 +20,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
before do
project.add_role(user, user_access_level)
sign_in(user)
+ stub_feature_flags(job_log_json: false)
end
describe "GET /:project/jobs" do
@@ -609,6 +610,14 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
expect(find('.js-job-deployment-link')['href']).to include(second_deployment.deployable.project.path, second_deployment.deployable_id.to_s)
end
+
+ context 'when deployment does not have a deployable' do
+ let!(:second_deployment) { create(:deployment, :success, environment: environment, deployable: nil) }
+
+ it 'has an empty href' do
+ expect(find('.js-job-deployment-link')['href']).to be_empty
+ end
+ end
end
context 'job failed to deploy' do
diff --git a/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb b/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb
index 35c84204910..68a924e4fad 100644
--- a/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb
+++ b/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'New project label breadcrumb' do
let(:project) { create(:project) }
diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb
index c4b3ddb2088..d55e9d12801 100644
--- a/spec/features/projects/pages_spec.rb
+++ b/spec/features/projects/pages_spec.rb
@@ -30,6 +30,12 @@ shared_examples 'pages settings editing' do
expect(page).to have_content('Access pages')
end
+ it 'renders first deployment warning' do
+ visit project_pages_path(project)
+
+ expect(page).to have_content('It may take up to 30 minutes before the site is available after the first deployment.')
+ end
+
context 'when support for external domains is disabled' do
it 'renders message that support is disabled' do
visit project_pages_path(project)
diff --git a/spec/features/projects/settings/lfs_settings_spec.rb b/spec/features/projects/settings/lfs_settings_spec.rb
index 56606df5a78..5fa3b9bba55 100644
--- a/spec/features/projects/settings/lfs_settings_spec.rb
+++ b/spec/features/projects/settings/lfs_settings_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Projects > Settings > LFS settings' do
let(:project) { create(:project) }
diff --git a/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb b/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb
index 9c77a08718e..3e9bfed1e47 100644
--- a/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb
+++ b/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Repository Settings > User sees revoke deploy token modal', :js do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb
index 6f176c260a2..70dc6c966ba 100644
--- a/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb
+++ b/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Projects > Show > Developer views empty project instructions' do
let(:project) { create(:project, :empty_repo) }
diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb
index 430883fdf29..891b780a100 100644
--- a/spec/features/projects/snippets/create_snippet_spec.rb
+++ b/spec/features/projects/snippets/create_snippet_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Projects > Snippets > Create Snippet', :js do
include DropzoneHelper
diff --git a/spec/features/projects/tags/user_edits_tags_spec.rb b/spec/features/projects/tags/user_edits_tags_spec.rb
index ebb2844d17f..63f97eeb4e0 100644
--- a/spec/features/projects/tags/user_edits_tags_spec.rb
+++ b/spec/features/projects/tags/user_edits_tags_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Project > Tags', :js do
include DropzoneHelper
diff --git a/spec/features/read_only_spec.rb b/spec/features/read_only_spec.rb
index 3af4b51b9b1..619d34ebed4 100644
--- a/spec/features/read_only_spec.rb
+++ b/spec/features/read_only_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'read-only message' do
set(:user) { create(:user) }
diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb
index 4ca79ccaea8..9451ee6eb15 100644
--- a/spec/features/search/user_searches_for_code_spec.rb
+++ b/spec/features/search/user_searches_for_code_spec.rb
@@ -6,21 +6,6 @@ describe 'User searches for code' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, namespace: user.namespace) }
- def submit_search(search, with_send_keys: false)
- page.within('.search') do
- field = find_field('search')
- field.fill_in(with: search)
-
- if with_send_keys
- field.send_keys(:enter)
- else
- click_button("Go")
- end
- end
-
- click_link('Code')
- end
-
context 'when signed in' do
before do
project.add_maintainer(user)
@@ -31,7 +16,9 @@ describe 'User searches for code' do
visit(project_path(project))
submit_search('application.js')
+ select_search_scope('Code')
+ expect(page).to have_selector('.results', text: 'application.js')
expect(page).to have_selector('.file-content .code')
expect(page).to have_selector("span.line[lang='javascript']")
end
@@ -52,9 +39,7 @@ describe 'User searches for code' do
fill_in('dashboard_search', with: 'rspec')
find('.btn-search').click
- page.within('.results') do
- expect(find(:css, '.search-results')).to have_content('Update capybara, rspec-rails, poltergeist to recent versions')
- end
+ expect(page).to have_selector('.results', text: 'Update capybara, rspec-rails, poltergeist to recent versions')
end
it 'search mutiple words with refs switching' do
@@ -64,16 +49,12 @@ describe 'User searches for code' do
fill_in('dashboard_search', with: search)
find('.btn-search').click
- page.within('.results') do
- expect(find('.search-results')).to have_content(expected_result)
- end
+ expect(page).to have_selector('.results', text: expected_result)
find('.js-project-refs-dropdown').click
find('.dropdown-page-one .dropdown-content').click_link('v1.0.0')
- page.within('.results') do
- expect(find(:css, '.search-results')).to have_content(expected_result)
- end
+ expect(page).to have_selector('.results', text: expected_result)
expect(find_field('dashboard_search').value).to eq(search)
end
@@ -84,7 +65,9 @@ describe 'User searches for code' do
before do
visit(project_tree_path(project, ref_name))
- submit_search('gitlab-grack', with_send_keys: true)
+
+ submit_search('gitlab-grack')
+ select_search_scope('Code')
end
it 'shows ref switcher in code result summary' do
@@ -104,22 +87,27 @@ describe 'User searches for code' do
end
it 'search result changes when refs switched' do
- expect(find('.search-results')).not_to have_content('path = gitlab-grack')
+ expect(find('.results')).not_to have_content('path = gitlab-grack')
+
find('.js-project-refs-dropdown').click
find('.dropdown-page-one .dropdown-content').click_link('master')
- expect(find('.search-results')).to have_content('path = gitlab-grack')
+
+ expect(page).to have_selector('.results', text: 'path = gitlab-grack')
end
end
it 'no ref switcher shown in issue result summary', :js do
issue = create(:issue, title: 'test', project: project)
visit(project_tree_path(project))
- submit_search('test', with_send_keys: true)
+
+ submit_search('test')
+ select_search_scope('Code')
+
expect(page).to have_selector('.js-project-refs-dropdown')
- page.within('.search-filter') do
- click_link('Issues')
- end
- expect(find(:css, '.search-results')).to have_link(issue.title)
+
+ select_search_scope('Issues')
+
+ expect(find(:css, '.results')).to have_link(issue.title)
expect(page).not_to have_selector('.js-project-refs-dropdown')
end
end
@@ -133,10 +121,9 @@ describe 'User searches for code' do
it 'finds code' do
submit_search('rspec')
+ select_search_scope('Code')
- page.within('.results') do
- expect(find(:css, '.search-results')).to have_content('Update capybara, rspec-rails, poltergeist to recent versions')
- end
+ expect(page).to have_selector('.results', text: 'Update capybara, rspec-rails, poltergeist to recent versions')
end
end
end
diff --git a/spec/features/search/user_searches_for_comments_spec.rb b/spec/features/search/user_searches_for_comments_spec.rb
index 2ce3fa4735f..0a203a5bf2d 100644
--- a/spec/features/search/user_searches_for_comments_spec.rb
+++ b/spec/features/search/user_searches_for_comments_spec.rb
@@ -18,15 +18,13 @@ describe 'User searches for comments' do
let(:comment) { create(:note_on_commit, author: user, project: project, commit_id: 12345678, note: 'Bug here') }
it 'finds a commit' do
- page.within('.search') do
- fill_in('search', with: comment.note)
- click_button('Go')
- end
-
- click_link('Comments')
+ submit_search(comment.note)
+ select_search_scope('Comments')
- expect(page).to have_text('Commit deleted')
- expect(page).to have_text('12345678')
+ page.within('.results') do
+ expect(page).to have_content('Commit deleted')
+ expect(page).to have_content('12345678')
+ end
end
end
end
@@ -36,14 +34,10 @@ describe 'User searches for comments' do
let(:comment) { create(:note, noteable: snippet, author: user, note: 'Supercalifragilisticexpialidocious', project: project) }
it 'finds a snippet' do
- page.within('.search') do
- fill_in('search', with: comment.note)
- click_button('Go')
- end
-
- click_link('Comments')
+ submit_search(comment.note)
+ select_search_scope('Comments')
- expect(page).to have_link(snippet.title)
+ expect(page).to have_selector('.results', text: snippet.title)
end
end
end
diff --git a/spec/features/search/user_searches_for_commits_spec.rb b/spec/features/search/user_searches_for_commits_spec.rb
index 81c299752ea..958f12d3b84 100644
--- a/spec/features/search/user_searches_for_commits_spec.rb
+++ b/spec/features/search/user_searches_for_commits_spec.rb
@@ -16,15 +16,13 @@ describe 'User searches for commits' do
context 'when searching by SHA' do
it 'finds a commit and redirects to its page' do
- fill_in('search', with: sha)
- click_button('Search')
+ submit_search(sha)
expect(page).to have_current_path(project_commit_path(project, sha))
end
it 'finds a commit in uppercase and redirects to its page' do
- fill_in('search', with: sha.upcase)
- click_button('Search')
+ submit_search(sha.upcase)
expect(page).to have_current_path(project_commit_path(project, sha))
end
@@ -34,16 +32,14 @@ describe 'User searches for commits' do
it 'finds a commit and holds on /search page' do
create_commit('Message referencing another sha: "deadbeef"', project, user, 'master')
- fill_in('search', with: 'deadbeef')
- click_button('Search')
+ submit_search('deadbeef')
expect(page).to have_current_path('/search', ignore_query: true)
end
it 'finds multiple commits' do
- fill_in('search', with: 'See merge request')
- click_button('Search')
- click_link('Commits')
+ submit_search('See merge request')
+ select_search_scope('Commits')
expect(page).to have_selector('.commit-row-description', count: 9)
end
diff --git a/spec/features/search/user_searches_for_issues_spec.rb b/spec/features/search/user_searches_for_issues_spec.rb
index f0fcf6df70c..ae718cec7af 100644
--- a/spec/features/search/user_searches_for_issues_spec.rb
+++ b/spec/features/search/user_searches_for_issues_spec.rb
@@ -21,13 +21,11 @@ describe 'User searches for issues', :js do
it 'finds an issue' do
fill_in('dashboard_search', with: issue1.title)
find('.btn-search').click
-
- page.within('.search-filter') do
- click_link('Issues')
- end
+ select_search_scope('Issues')
page.within('.results') do
- expect(find(:css, '.search-results')).to have_link(issue1.title).and have_no_link(issue2.title)
+ expect(page).to have_link(issue1.title)
+ expect(page).not_to have_link(issue2.title)
end
end
@@ -41,13 +39,11 @@ describe 'User searches for issues', :js do
fill_in('dashboard_search', with: issue1.title)
find('.btn-search').click
-
- page.within('.search-filter') do
- click_link('Issues')
- end
+ select_search_scope('Issues')
page.within('.results') do
- expect(find(:css, '.search-results')).to have_link(issue1.title).and have_no_link(issue2.title)
+ expect(page).to have_link(issue1.title)
+ expect(page).not_to have_link(issue2.title)
end
end
end
@@ -65,13 +61,11 @@ describe 'User searches for issues', :js do
it 'finds an issue' do
fill_in('dashboard_search', with: issue1.title)
find('.btn-search').click
-
- page.within('.search-filter') do
- click_link('Issues')
- end
+ select_search_scope('Issues')
page.within('.results') do
- expect(find(:css, '.search-results')).to have_link(issue1.title).and have_no_link(issue2.title)
+ expect(page).to have_link(issue1.title)
+ expect(page).not_to have_link(issue2.title)
end
end
end
diff --git a/spec/features/search/user_searches_for_merge_requests_spec.rb b/spec/features/search/user_searches_for_merge_requests_spec.rb
index d005b87cdfe..0139ac26816 100644
--- a/spec/features/search/user_searches_for_merge_requests_spec.rb
+++ b/spec/features/search/user_searches_for_merge_requests_spec.rb
@@ -20,13 +20,11 @@ describe 'User searches for merge requests', :js do
it 'finds a merge request' do
fill_in('dashboard_search', with: merge_request1.title)
find('.btn-search').click
-
- page.within('.search-filter') do
- click_link('Merge requests')
- end
+ select_search_scope('Merge requests')
page.within('.results') do
- expect(find(:css, '.search-results')).to have_link(merge_request1.title).and have_no_link(merge_request2.title)
+ expect(page).to have_link(merge_request1.title)
+ expect(page).not_to have_link(merge_request2.title)
end
end
@@ -40,13 +38,11 @@ describe 'User searches for merge requests', :js do
fill_in('dashboard_search', with: merge_request1.title)
find('.btn-search').click
-
- page.within('.search-filter') do
- click_link('Merge requests')
- end
+ select_search_scope('Merge requests')
page.within('.results') do
- expect(find(:css, '.search-results')).to have_link(merge_request1.title).and have_no_link(merge_request2.title)
+ expect(page).to have_link(merge_request1.title)
+ expect(page).not_to have_link(merge_request2.title)
end
end
end
diff --git a/spec/features/search/user_searches_for_milestones_spec.rb b/spec/features/search/user_searches_for_milestones_spec.rb
index 00964ab4f1d..0714cfcc309 100644
--- a/spec/features/search/user_searches_for_milestones_spec.rb
+++ b/spec/features/search/user_searches_for_milestones_spec.rb
@@ -20,13 +20,11 @@ describe 'User searches for milestones', :js do
it 'finds a milestone' do
fill_in('dashboard_search', with: milestone1.title)
find('.btn-search').click
-
- page.within('.search-filter') do
- click_link('Milestones')
- end
+ select_search_scope('Milestones')
page.within('.results') do
- expect(find(:css, '.search-results')).to have_link(milestone1.title).and have_no_link(milestone2.title)
+ expect(page).to have_link(milestone1.title)
+ expect(page).not_to have_link(milestone2.title)
end
end
@@ -40,13 +38,11 @@ describe 'User searches for milestones', :js do
fill_in('dashboard_search', with: milestone1.title)
find('.btn-search').click
-
- page.within('.search-filter') do
- click_link('Milestones')
- end
+ select_search_scope('Milestones')
page.within('.results') do
- expect(find(:css, '.search-results')).to have_link(milestone1.title).and have_no_link(milestone2.title)
+ expect(page).to have_link(milestone1.title)
+ expect(page).not_to have_link(milestone2.title)
end
end
end
diff --git a/spec/features/search/user_searches_for_projects_spec.rb b/spec/features/search/user_searches_for_projects_spec.rb
index 082c1ae8e4a..b194ac32ff6 100644
--- a/spec/features/search/user_searches_for_projects_spec.rb
+++ b/spec/features/search/user_searches_for_projects_spec.rb
@@ -20,8 +20,7 @@ describe 'User searches for projects' do
it 'preserves the group being searched in' do
visit(search_path(group_id: project.namespace.id))
- fill_in('search', with: 'foo')
- click_button('Search')
+ submit_search('foo')
expect(find('#group_id', visible: false).value).to eq(project.namespace.id.to_s)
end
@@ -29,8 +28,7 @@ describe 'User searches for projects' do
it 'preserves the project being searched in' do
visit(search_path(project_id: project.id))
- fill_in('search', with: 'foo')
- click_button('Search')
+ submit_search('foo')
expect(find('#project_id', visible: false).value).to eq(project.id.to_s)
end
diff --git a/spec/features/search/user_searches_for_users_spec.rb b/spec/features/search/user_searches_for_users_spec.rb
index e10c1afc0b8..6f2c5d48018 100644
--- a/spec/features/search/user_searches_for_users_spec.rb
+++ b/spec/features/search/user_searches_for_users_spec.rb
@@ -3,83 +3,81 @@
require 'spec_helper'
describe 'User searches for users' do
- context 'when on the dashboard' do
- it 'finds the user', :js do
- create(:user, username: 'gob_bluth', name: 'Gob Bluth')
+ let(:user1) { create(:user, username: 'gob_bluth', name: 'Gob Bluth') }
+ let(:user2) { create(:user, username: 'michael_bluth', name: 'Michael Bluth') }
+ let(:user3) { create(:user, username: 'gob_2018', name: 'George Oscar Bluth') }
- sign_in(create(:user))
+ before do
+ sign_in(user1)
+ end
+ context 'when on the dashboard' do
+ it 'finds the user', :js do
visit dashboard_projects_path
- fill_in 'search', with: 'gob'
- find('#search').send_keys(:enter)
+ submit_search('gob')
+ select_search_scope('Users')
- expect(page).to have_content('Users 1')
-
- click_on('Users 1')
-
- expect(page).to have_content('Gob Bluth')
- expect(page).to have_content('@gob_bluth')
+ page.within('.results') do
+ expect(page).to have_content('Gob Bluth')
+ expect(page).to have_content('@gob_bluth')
+ end
end
end
context 'when on the project page' do
- it 'finds the user belonging to the project' do
- project = create(:project)
+ let(:project) { create(:project) }
- user1 = create(:user, username: 'gob_bluth', name: 'Gob Bluth')
+ before do
create(:project_member, :developer, user: user1, project: project)
-
- user2 = create(:user, username: 'michael_bluth', name: 'Michael Bluth')
create(:project_member, :developer, user: user2, project: project)
+ user3
+ end
- create(:user, username: 'gob_2018', name: 'George Oscar Bluth')
-
- sign_in(user1)
-
- visit projects_path(project)
+ it 'finds the user belonging to the project' do
+ visit project_path(project)
- fill_in 'search', with: 'gob'
- click_button 'Go'
+ submit_search('gob')
+ select_search_scope('Users')
- expect(page).to have_content('Gob Bluth')
- expect(page).to have_content('@gob_bluth')
+ page.within('.results') do
+ expect(page).to have_content('Gob Bluth')
+ expect(page).to have_content('@gob_bluth')
- expect(page).not_to have_content('Michael Bluth')
- expect(page).not_to have_content('@michael_bluth')
+ expect(page).not_to have_content('Michael Bluth')
+ expect(page).not_to have_content('@michael_bluth')
- expect(page).not_to have_content('George Oscar Bluth')
- expect(page).not_to have_content('@gob_2018')
+ expect(page).not_to have_content('George Oscar Bluth')
+ expect(page).not_to have_content('@gob_2018')
+ end
end
end
context 'when on the group page' do
- it 'finds the user belonging to the group' do
- group = create(:group)
+ let(:group) { create(:group) }
- user1 = create(:user, username: 'gob_bluth', name: 'Gob Bluth')
+ before do
create(:group_member, :developer, user: user1, group: group)
-
- user2 = create(:user, username: 'michael_bluth', name: 'Michael Bluth')
create(:group_member, :developer, user: user2, group: group)
+ user3
+ end
- create(:user, username: 'gob_2018', name: 'George Oscar Bluth')
-
- sign_in(user1)
-
+ it 'finds the user belonging to the group' do
visit group_path(group)
- fill_in 'search', with: 'gob'
- click_button 'Go'
+ submit_search('gob')
+ select_search_scope('Users')
- expect(page).to have_content('Gob Bluth')
- expect(page).to have_content('@gob_bluth')
+ page.within('.results') do
+ expect(page).to have_content('Gob Bluth')
+ expect(page).to have_content('@gob_bluth')
- expect(page).not_to have_content('Michael Bluth')
- expect(page).not_to have_content('@michael_bluth')
+ expect(page).not_to have_content('Michael Bluth')
+ expect(page).not_to have_content('@michael_bluth')
- expect(page).not_to have_content('George Oscar Bluth')
- expect(page).not_to have_content('@gob_2018')
+ expect(page).not_to have_content('George Oscar Bluth')
+ expect(page).not_to have_content('@gob_2018')
+ end
end
end
end
diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb
index 0a5abfbf46a..1ae37447bdc 100644
--- a/spec/features/search/user_searches_for_wiki_pages_spec.rb
+++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb
@@ -26,13 +26,10 @@ describe 'User searches for wiki pages', :js do
fill_in('dashboard_search', with: search_term)
find('.btn-search').click
-
- page.within('.search-filter') do
- click_link('Wiki')
- end
+ select_search_scope('Wiki')
page.within('.results') do
- expect(find(:css, '.search-results')).to have_link(wiki_page.title, href: project_wiki_path(project, wiki_page.slug))
+ expect(page).to have_link(wiki_page.title, href: project_wiki_path(project, wiki_page.slug))
end
end
end
diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb
index 5006631cc14..7e7c09e4a13 100644
--- a/spec/features/search/user_uses_header_search_field_spec.rb
+++ b/spec/features/search/user_uses_header_search_field_spec.rb
@@ -19,8 +19,7 @@ describe 'User uses header search field', :js do
end
it 'starts searching by pressing the enter key' do
- fill_in('search', with: 'gitlab')
- find('#search').native.send_keys(:enter)
+ submit_search('gitlab')
page.within('.page-title') do
expect(page).to have_content('Search')
@@ -101,8 +100,7 @@ describe 'User uses header search field', :js do
before do
create(:issue, project: project, title: 'project issue')
- fill_in('search', with: 'project')
- find('#search').send_keys(:enter)
+ submit_search('project')
end
it 'displays result counts for all categories' do
diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb
index f4f3872aa09..d6575ec9de1 100644
--- a/spec/features/security/group/internal_access_spec.rb
+++ b/spec/features/security/group/internal_access_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Internal Group access' do
include AccessMatchers
diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb
index 9cef8ef777c..2dc863a6e73 100644
--- a/spec/features/security/group/private_access_spec.rb
+++ b/spec/features/security/group/private_access_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Private Group access' do
include AccessMatchers
diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb
index bbe74f0dab0..4066a19fce2 100644
--- a/spec/features/security/group/public_access_spec.rb
+++ b/spec/features/security/group/public_access_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Public Group access' do
include AccessMatchers
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 42c747c674f..d089fa718d2 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -7,6 +7,10 @@ describe "Internal Project Access" do
set(:project) { create(:project, :internal, :repository) }
+ before do
+ stub_feature_flags(job_log_json: false)
+ end
+
describe "Project should be internal" do
describe '#internal?' do
subject { project.internal? }
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index a86d240b7d6..b868cd595cb 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -7,6 +7,10 @@ describe "Private Project Access" do
set(:project) { create(:project, :private, :repository, public_builds: false) }
+ before do
+ stub_feature_flags(job_log_json: false)
+ end
+
describe "Project should be private" do
describe '#private?' do
subject { project.private? }
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 8d7f8c84358..8db2f2d69e5 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -7,6 +7,10 @@ describe "Public Project Access" do
set(:project) { create(:project, :public, :repository) }
+ before do
+ stub_feature_flags(job_log_json: false)
+ end
+
describe "Project should be public" do
describe '#public?' do
subject { project.public? }
diff --git a/spec/features/snippets/explore_spec.rb b/spec/features/snippets/explore_spec.rb
index b48a5691e96..57e91fea709 100644
--- a/spec/features/snippets/explore_spec.rb
+++ b/spec/features/snippets/explore_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Explore Snippets' do
let!(:public_snippet) { create(:personal_snippet, :public) }
diff --git a/spec/features/snippets/internal_snippet_spec.rb b/spec/features/snippets/internal_snippet_spec.rb
index 8454a347382..4ef3b0e5e7a 100644
--- a/spec/features/snippets/internal_snippet_spec.rb
+++ b/spec/features/snippets/internal_snippet_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Internal Snippets', :js do
let(:internal_snippet) { create(:personal_snippet, :internal) }
diff --git a/spec/features/snippets/public_snippets_spec.rb b/spec/features/snippets/public_snippets_spec.rb
index e32a9292e22..a0db00cfc67 100644
--- a/spec/features/snippets/public_snippets_spec.rb
+++ b/spec/features/snippets/public_snippets_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Public Snippets', :js do
it 'Unauthenticated user should see public snippets' do
diff --git a/spec/features/snippets/search_snippets_spec.rb b/spec/features/snippets/search_snippets_spec.rb
index 4a8c5f9b1fe..dce790e5708 100644
--- a/spec/features/snippets/search_snippets_spec.rb
+++ b/spec/features/snippets/search_snippets_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Search Snippets' do
it 'User searches for snippets by title' do
@@ -10,12 +10,8 @@ describe 'Search Snippets' do
sign_in private_snippet.author
visit dashboard_snippets_path
- page.within '.search' do
- fill_in 'search', with: 'Middle'
- click_button 'Go'
- end
-
- click_link 'Titles and Filenames'
+ submit_search('Middle')
+ select_search_scope('Titles and Filenames')
expect(page).to have_link(public_snippet.title)
expect(page).to have_link(private_snippet.title)
@@ -45,11 +41,7 @@ describe 'Search Snippets' do
sign_in create(:user)
visit dashboard_snippets_path
-
- page.within '.search' do
- fill_in 'search', with: 'line seven'
- click_button 'Go'
- end
+ submit_search('line seven')
expect(page).to have_content('line seven')
diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb
index a4a5407d1f7..52ec5eddd5c 100644
--- a/spec/features/snippets/user_creates_snippet_spec.rb
+++ b/spec/features/snippets/user_creates_snippet_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'User creates snippet', :js do
include DropzoneHelper
diff --git a/spec/features/snippets/user_deletes_snippet_spec.rb b/spec/features/snippets/user_deletes_snippet_spec.rb
index 9773aca849a..217419a220a 100644
--- a/spec/features/snippets/user_deletes_snippet_spec.rb
+++ b/spec/features/snippets/user_deletes_snippet_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'User deletes snippet' do
let(:user) { create(:user) }
diff --git a/spec/features/snippets/user_edits_snippet_spec.rb b/spec/features/snippets/user_edits_snippet_spec.rb
index 5ff12c37aff..51d9baf44bc 100644
--- a/spec/features/snippets/user_edits_snippet_spec.rb
+++ b/spec/features/snippets/user_edits_snippet_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'User edits snippet', :js do
include DropzoneHelper
diff --git a/spec/features/snippets/user_snippets_spec.rb b/spec/features/snippets/user_snippets_spec.rb
index 4e9215db945..c6daa19d5e6 100644
--- a/spec/features/snippets/user_snippets_spec.rb
+++ b/spec/features/snippets/user_snippets_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'User Snippets' do
let(:author) { create(:user) }
diff --git a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
index 40d864e0002..d9d9d7e4b04 100644
--- a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
+++ b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'User uploads avatar to group' do
it 'they see the new avatar' do
diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
index 4c694365e3b..fc31d7aa3d1 100644
--- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
+++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'User uploads avatar to profile' do
let!(:user) { create(:user) }
diff --git a/spec/features/uploads/user_uploads_file_to_note_spec.rb b/spec/features/uploads/user_uploads_file_to_note_spec.rb
index 6bf8e8ea74f..30b5cf267ae 100644
--- a/spec/features/uploads/user_uploads_file_to_note_spec.rb
+++ b/spec/features/uploads/user_uploads_file_to_note_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'User uploads file to note' do
include DropzoneHelper
diff --git a/spec/features/user_can_display_performance_bar_spec.rb b/spec/features/user_can_display_performance_bar_spec.rb
index b2036108d42..154e948015f 100644
--- a/spec/features/user_can_display_performance_bar_spec.rb
+++ b/spec/features/user_can_display_performance_bar_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'User can display performance bar', :js do
shared_examples 'performance bar cannot be displayed' do
diff --git a/spec/features/user_sees_revert_modal_spec.rb b/spec/features/user_sees_revert_modal_spec.rb
index 35828b5f086..24b4f8dd4aa 100644
--- a/spec/features/user_sees_revert_modal_spec.rb
+++ b/spec/features/user_sees_revert_modal_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User sees revert modal', :js do
let(:project) { create(:project, :public, :repository) }
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index 8e4db2ca840..d1f3b3f4076 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -624,6 +624,14 @@ describe 'Login' do
end
end
+ describe 'Client helper classes and flags' do
+ it 'adds client browser and platform classes to page body' do
+ visit root_path
+ expect(find('body')[:class]).to include('gl-browser-generic')
+ expect(find('body')[:class]).to include('gl-platform-other')
+ end
+ end
+
context 'when terms are enforced' do
let(:user) { create(:user) }
diff --git a/spec/finders/members_finder_spec.rb b/spec/finders/members_finder_spec.rb
index 4203f58fe81..6920fb4e572 100644
--- a/spec/finders/members_finder_spec.rb
+++ b/spec/finders/members_finder_spec.rb
@@ -17,11 +17,10 @@ describe MembersFinder, '#execute' do
result = described_class.new(project, user2).execute
- expect(result.to_a).to match_array([member1, member2, member3])
+ expect(result).to contain_exactly(member1, member2, member3)
end
- it 'includes nested group members if asked' do
- project = create(:project, namespace: group)
+ it 'includes nested group members if asked', :nested_groups do
nested_group.request_access(user1)
member1 = group.add_maintainer(user2)
member2 = nested_group.add_maintainer(user3)
@@ -29,7 +28,28 @@ describe MembersFinder, '#execute' do
result = described_class.new(project, user2).execute(include_descendants: true)
- expect(result.to_a).to match_array([member1, member2, member3])
+ expect(result).to contain_exactly(member1, member2, member3)
+ end
+
+ it 'returns the members.access_level when the user is invited', :nested_groups do
+ member_invite = create(:project_member, :invited, project: project, invite_email: create(:user).email)
+ member1 = group.add_maintainer(user2)
+
+ result = described_class.new(project, user2).execute(include_descendants: true)
+
+ expect(result).to contain_exactly(member1, member_invite)
+ expect(result.last.access_level).to eq(member_invite.access_level)
+ end
+
+ it 'returns the highest access_level for the user', :nested_groups do
+ member1 = project.add_guest(user1)
+ group.add_developer(user1)
+ nested_group.add_reporter(user1)
+
+ result = described_class.new(project, user1).execute(include_descendants: true)
+
+ expect(result).to contain_exactly(member1)
+ expect(result.first.access_level).to eq(Gitlab::Access::DEVELOPER)
end
context 'when include_invited_groups_members == true' do
@@ -37,8 +57,8 @@ describe MembersFinder, '#execute' do
set(:linked_group) { create(:group, :public, :access_requestable) }
set(:nested_linked_group) { create(:group, parent: linked_group) }
- set(:linked_group_member) { linked_group.add_developer(user1) }
- set(:nested_linked_group_member) { nested_linked_group.add_developer(user2) }
+ set(:linked_group_member) { linked_group.add_guest(user1) }
+ set(:nested_linked_group_member) { nested_linked_group.add_guest(user2) }
it 'includes all the invited_groups members including members inherited from ancestor groups' do
create(:project_group_link, project: project, group: nested_linked_group)
@@ -60,5 +80,17 @@ describe MembersFinder, '#execute' do
expect(subject).to contain_exactly(linked_group_member)
end
+
+ context 'when the user is a member of invited group and ancestor groups' do
+ it 'returns the highest access_level for the user limited by project_group_link.group_access', :nested_groups do
+ create(:project_group_link, project: project, group: nested_linked_group, group_access: Gitlab::Access::REPORTER)
+ nested_linked_group.add_developer(user1)
+
+ result = subject
+
+ expect(result).to contain_exactly(linked_group_member, nested_linked_group_member)
+ expect(result.first.access_level).to eq(Gitlab::Access::REPORTER)
+ end
+ end
end
end
diff --git a/spec/fixtures/api/schemas/entities/merge_request_noteable.json b/spec/fixtures/api/schemas/entities/merge_request_noteable.json
new file mode 100644
index 00000000000..88b0fecc24c
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/merge_request_noteable.json
@@ -0,0 +1,28 @@
+{
+ "type": "object",
+ "properties" : {
+ "merge_params": { "type": ["object", "null"] },
+ "state": { "type": "string" },
+ "source_branch": { "type": "string" },
+ "target_branch": { "type": "string" },
+ "diff_head_sha": { "type": "string" },
+ "create_note_path": { "type": ["string", "null"] },
+ "preview_note_path": { "type": ["string", "null"] },
+ "create_issue_to_resolve_discussions_path": { "type": ["string", "null"] },
+ "new_blob_path": { "type": ["string", "null"] },
+ "can_receive_suggestion": { "type": "boolean" },
+ "current_user": {
+ "type": "object",
+ "required": [
+ "can_create_note",
+ "can_update"
+ ],
+ "properties": {
+ "can_create_note": { "type": "boolean" },
+ "can_update": { "type": "boolean" }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/entities/merge_request_poll_widget.json b/spec/fixtures/api/schemas/entities/merge_request_poll_widget.json
index 2052892dfa3..1eda0e12920 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_poll_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_poll_widget.json
@@ -24,22 +24,20 @@
"ci_status": { "type": ["string", "null"] },
"cancel_auto_merge_path": { "type": ["string", "null"] },
"test_reports_path": { "type": ["string", "null"] },
- "can_receive_suggestion": { "type": "boolean" },
"create_issue_to_resolve_discussions_path": { "type": ["string", "null"] },
"current_user": {
"type": "object",
"required": [
"can_remove_source_branch",
"can_revert_on_current_merge_request",
- "can_cherry_pick_on_current_merge_request"
+ "can_cherry_pick_on_current_merge_request",
+ "can_create_issue"
],
"properties": {
"can_remove_source_branch": { "type": "boolean" },
"can_revert_on_current_merge_request": { "type": ["boolean", "null"] },
"can_cherry_pick_on_current_merge_request": { "type": ["boolean", "null"] },
- "can_create_note": { "type": "boolean" },
- "can_create_issue": { "type": "boolean" },
- "can_update": { "type": "boolean" }
+ "can_create_issue": { "type": "boolean" }
},
"additionalProperties": false
},
diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json
index 779a47222b7..e2df7952d8f 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json
@@ -5,7 +5,6 @@
{ "$ref": "merge_request_poll_widget.json" },
{
"properties" : {
- "merge_params": { "type": ["object", "null"] },
"source_project_full_path": { "type": ["string", "null"]},
"target_project_full_path": { "type": ["string", "null"]},
"email_patches_path": { "type": "string" },
@@ -13,9 +12,7 @@
"merge_request_basic_path": { "type": "string" },
"merge_request_widget_path": { "type": "string" },
"merge_request_cached_widget_path": { "type": "string" },
- "create_note_path": { "type": ["string", "null"] },
"commit_change_content_path": { "type": "string" },
- "preview_note_path": { "type": ["string", "null"] },
"conflicts_docs_path": { "type": ["string", "null"] },
"merge_request_pipelines_docs_path": { "type": ["string", "null"] },
"ci_environments_status_path": { "type": "string" },
diff --git a/spec/fixtures/api/schemas/pipeline.json b/spec/fixtures/api/schemas/pipeline.json
index b6e30c40f13..d9ffad8fbab 100644
--- a/spec/fixtures/api/schemas/pipeline.json
+++ b/spec/fixtures/api/schemas/pipeline.json
@@ -97,6 +97,10 @@
"id": "/properties/details/properties/finished_at",
"type": "string"
},
+ "name": {
+ "id": "/properties/details/properties/name",
+ "type": "string"
+ },
"manual_actions": {
"id": "/properties/details/properties/manual_actions",
"items": {},
@@ -323,6 +327,10 @@
"id": "/properties/web_url",
"type": "string"
},
+ "merge_request_event_type": {
+ "id": "/properties/merge_request_event_type",
+ "type": "string"
+ },
"user": {
"id": "/properties/user",
"properties": {
diff --git a/spec/fixtures/api/schemas/public_api/v4/release.json b/spec/fixtures/api/schemas/public_api/v4/release.json
index ec3fa59cdb1..078b1c0b982 100644
--- a/spec/fixtures/api/schemas/public_api/v4/release.json
+++ b/spec/fixtures/api/schemas/public_api/v4/release.json
@@ -15,6 +15,7 @@
"author": {
"oneOf": [{ "type": "null" }, { "$ref": "user/basic.json" }]
},
+ "milestone": { "type": "string" },
"assets": {
"required": ["count", "links", "sources"],
"properties": {
diff --git a/spec/frontend/branches/divergence_graph_spec.js b/spec/frontend/branches/divergence_graph_spec.js
index 8283bc966e4..adf39a2216a 100644
--- a/spec/frontend/branches/divergence_graph_spec.js
+++ b/spec/frontend/branches/divergence_graph_spec.js
@@ -25,13 +25,25 @@ describe('Divergence graph', () => {
mock.restore();
});
- it('calls axos get with list of branch names', () =>
+ it('calls axios get with list of branch names', () =>
init('/-/diverging_counts').then(() => {
expect(axios.get).toHaveBeenCalledWith('/-/diverging_counts', {
params: { names: ['master', 'test/hello-world'] },
});
}));
+ describe('no branches listed', () => {
+ beforeEach(() => {
+ document.body.innerHTML = `<div></div>`;
+ });
+
+ it('avoids requesting diverging commit counts', () => {
+ expect(axios.get).not.toHaveBeenCalledWith('/-/diverging_counts');
+
+ init('/-/diverging_counts');
+ });
+ });
+
it('creates Vue components', () =>
init('/-/diverging_counts').then(() => {
expect(document.querySelector('[data-name="master"]').innerHTML).not.toEqual('');
diff --git a/spec/frontend/clusters/components/application_row_spec.js b/spec/frontend/clusters/components/application_row_spec.js
index 9f127ccb690..41da4125a20 100644
--- a/spec/frontend/clusters/components/application_row_spec.js
+++ b/spec/frontend/clusters/components/application_row_spec.js
@@ -371,7 +371,7 @@ describe('Application Row', () => {
it('contains a link to the chart repo if application has been updated', () => {
const version = '0.1.45';
- const chartRepo = 'https://gitlab.com/charts/gitlab-runner';
+ const chartRepo = 'https://gitlab.com/gitlab-org/charts/gitlab-runner';
vm = mountComponent(ApplicationRow, {
...DEFAULT_APPLICATION_STATE,
status: APPLICATION_STATUS.INSTALLED,
diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js
index f2cc413512d..c168bce7a4e 100644
--- a/spec/frontend/clusters/stores/clusters_store_spec.js
+++ b/spec/frontend/clusters/stores/clusters_store_spec.js
@@ -86,7 +86,7 @@ describe('Clusters Store', () => {
requestReason: null,
version: mockResponseData.applications[2].version,
updateAvailable: mockResponseData.applications[2].update_available,
- chartRepo: 'https://gitlab.com/charts/gitlab-runner',
+ chartRepo: 'https://gitlab.com/gitlab-org/charts/gitlab-runner',
installed: false,
installFailed: false,
updateFailed: false,
diff --git a/spec/frontend/jobs/store/utils_spec.js b/spec/frontend/jobs/store/utils_spec.js
new file mode 100644
index 00000000000..9e81558f8c2
--- /dev/null
+++ b/spec/frontend/jobs/store/utils_spec.js
@@ -0,0 +1,60 @@
+import linesParser from '~/jobs/store/utils';
+
+describe('linesParser', () => {
+ const mockData = [
+ {
+ offset: 1001,
+ content: [{ text: ' on docker-auto-scale-com 8a6210b8' }],
+ },
+ {
+ offset: 1002,
+ content: [
+ {
+ text:
+ 'Using Docker executor with image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33',
+ },
+ ],
+ sections: ['prepare-executor'],
+ section_header: true,
+ },
+ {
+ offset: 1003,
+ content: [{ text: 'Starting service postgres:9.6.14 ...' }],
+ sections: ['prepare-executor'],
+ },
+ {
+ offset: 1004,
+ content: [{ text: 'Pulling docker image postgres:9.6.14 ...', style: 'term-fg-l-green' }],
+ sections: ['prepare-executor'],
+ },
+ ];
+
+ let result;
+
+ beforeEach(() => {
+ result = linesParser(mockData);
+ });
+
+ describe('regular line', () => {
+ it('adds a lineNumber property with correct index', () => {
+ expect(result[0].lineNumber).toEqual(0);
+ expect(result[1].line.lineNumber).toEqual(1);
+ });
+ });
+
+ describe('collpasible section', () => {
+ it('adds a `isClosed` property', () => {
+ expect(result[1].isClosed).toEqual(true);
+ });
+
+ it('adds a `isHeader` property', () => {
+ expect(result[1].isHeader).toEqual(true);
+ });
+
+ it('creates a lines array property with the content of the collpasible section', () => {
+ expect(result[1].lines.length).toEqual(2);
+ expect(result[1].lines[0].content).toEqual(mockData[2].content);
+ expect(result[1].lines[1].content).toEqual(mockData[3].content);
+ });
+ });
+});
diff --git a/spec/frontend/mocks/ce/lib/utils/axios_utils.js b/spec/frontend/mocks/ce/lib/utils/axios_utils.js
index b4065626b09..a3783b91f95 100644
--- a/spec/frontend/mocks/ce/lib/utils/axios_utils.js
+++ b/spec/frontend/mocks/ce/lib/utils/axios_utils.js
@@ -6,9 +6,10 @@ axios.isMock = true;
axios.defaults.adapter = config => {
const message =
`Unexpected unmocked request: ${JSON.stringify(config, null, 2)}\n` +
- 'Consider using the `axios-mock-adapter` in tests.';
+ 'Consider using the `axios-mock-adapter` module in tests.';
const error = new Error(message);
error.config = config;
+ global.fail(error);
throw error;
};
diff --git a/spec/frontend/mocks/mocks_helper_spec.js b/spec/frontend/mocks/mocks_helper_spec.js
index b8bb02c2f43..82e88b712c0 100644
--- a/spec/frontend/mocks/mocks_helper_spec.js
+++ b/spec/frontend/mocks/mocks_helper_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable global-require, promise/catch-or-return */
+/* eslint-disable global-require */
import path from 'path';
@@ -126,9 +126,8 @@ describe('mocks_helper.js', () => {
it('survives jest.isolateModules()', done => {
jest.isolateModules(() => {
const axios2 = require('~/lib/utils/axios_utils').default;
- expect(axios2.get('http://gitlab.com'))
- .rejects.toThrow('Unexpected unmocked request')
- .then(done);
+ expect(axios2.isMock).toBe(true);
+ done();
});
});
diff --git a/spec/frontend/mocks/node/jquery.js b/spec/frontend/mocks/node/jquery.js
index 34a25772f67..5c82f65406e 100644
--- a/spec/frontend/mocks/node/jquery.js
+++ b/spec/frontend/mocks/node/jquery.js
@@ -4,9 +4,11 @@ const $ = jest.requireActual('jquery');
// Fail tests for unmocked requests
$.ajax = () => {
- throw new Error(
+ const err = new Error(
'Unexpected unmocked jQuery.ajax() call! Make sure to mock jQuery.ajax() in tests.',
);
+ global.fail(err);
+ throw err;
};
// jquery is not an ES6 module
diff --git a/spec/frontend/mocks_spec.js b/spec/frontend/mocks_spec.js
index 2d2324120fd..a4a1fdea396 100644
--- a/spec/frontend/mocks_spec.js
+++ b/spec/frontend/mocks_spec.js
@@ -3,11 +3,22 @@ import axios from '~/lib/utils/axios_utils';
describe('Mock auto-injection', () => {
describe('mocks', () => {
- it('~/lib/utils/axios_utils', () =>
- expect(axios.get('http://gitlab.com')).rejects.toThrow('Unexpected unmocked request'));
+ let failMock;
+ beforeEach(() => {
+ failMock = jest.spyOn(global, 'fail').mockImplementation();
+ });
+
+ it('~/lib/utils/axios_utils', done => {
+ expect(axios.get('http://gitlab.com')).rejects.toThrow('Unexpected unmocked request');
+ setImmediate(() => {
+ expect(failMock).toHaveBeenCalledTimes(1);
+ done();
+ });
+ });
it('jQuery.ajax()', () => {
expect($.ajax).toThrow('Unexpected unmocked');
+ expect(failMock).toHaveBeenCalledTimes(1);
});
});
});
diff --git a/spec/frontend/notes/components/note_app_spec.js b/spec/frontend/notes/components/note_app_spec.js
index ff833d2c899..02fd30d5a15 100644
--- a/spec/frontend/notes/components/note_app_spec.js
+++ b/spec/frontend/notes/components/note_app_spec.js
@@ -133,32 +133,31 @@ describe('note_app', () => {
);
});
- it('should not render form when commenting is disabled', () => {
- wrapper.destroy();
+ it('should render form comment button as disabled', () => {
+ expect(wrapper.find('.js-note-new-discussion').attributes('disabled')).toEqual('disabled');
+ });
- store.state.commentsDisabled = true;
- wrapper = mountComponent();
- return waitForDiscussionsRequest().then(() => {
- expect(wrapper.find('.js-main-target-form').exists()).toBe(false);
- });
+ it('updates discussions badge', () => {
+ expect(document.querySelector('.js-discussions-count').textContent).toEqual('2');
});
+ });
- it('should render discussion filter note `commentsDisabled` is true', () => {
- wrapper.destroy();
+ describe('render with comments disabled', () => {
+ beforeEach(() => {
+ setFixtures('<div class="js-discussions-count"></div>');
+ Vue.http.interceptors.push(mockData.individualNoteInterceptor);
store.state.commentsDisabled = true;
wrapper = mountComponent();
- return waitForDiscussionsRequest().then(() => {
- expect(wrapper.find('.js-discussion-filter-note').exists()).toBe(true);
- });
+ return waitForDiscussionsRequest();
});
- it('should render form comment button as disabled', () => {
- expect(wrapper.find('.js-note-new-discussion').attributes('disabled')).toEqual('disabled');
+ it('should not render form when commenting is disabled', () => {
+ expect(wrapper.find('.js-main-target-form').exists()).toBe(false);
});
- it('updates discussions badge', () => {
- expect(document.querySelector('.js-discussions-count').textContent).toEqual('2');
+ it('should render discussion filter note `commentsDisabled` is true', () => {
+ expect(wrapper.find('.js-discussion-filter-note').exists()).toBe(true);
});
});
diff --git a/spec/frontend/project_find_file_spec.js b/spec/frontend/project_find_file_spec.js
new file mode 100644
index 00000000000..8102033139f
--- /dev/null
+++ b/spec/frontend/project_find_file_spec.js
@@ -0,0 +1,77 @@
+import MockAdapter from 'axios-mock-adapter';
+import $ from 'jquery';
+import ProjectFindFile from '~/project_find_file';
+import axios from '~/lib/utils/axios_utils';
+import { TEST_HOST } from 'helpers/test_constants';
+
+const BLOB_URL_TEMPLATE = `${TEST_HOST}/namespace/project/blob/master`;
+const FILE_FIND_URL = `${TEST_HOST}/namespace/project/files/master?format=json`;
+const FIND_TREE_URL = `${TEST_HOST}/namespace/project/tree/master`;
+const TEMPLATE = `<div class="file-finder-holder tree-holder js-file-finder" data-blob-url-template="${BLOB_URL_TEMPLATE}" data-file-find-url="${FILE_FIND_URL}" data-find-tree-url="${FIND_TREE_URL}">
+ <input class="file-finder-input" id="file_find" />
+ <div class="tree-content-holder">
+ <div class="table-holder">
+ <table class="files-slider tree-table">
+ <tbody />
+ </table>
+ </div>
+ </div>
+</div>`;
+
+describe('ProjectFindFile', () => {
+ let element;
+ let mock;
+
+ const getProjectFindFileInstance = () =>
+ new ProjectFindFile(element, {
+ url: FILE_FIND_URL,
+ treeUrl: FIND_TREE_URL,
+ blobUrlTemplate: BLOB_URL_TEMPLATE,
+ });
+
+ const findFiles = () =>
+ element
+ .find('.tree-table tr')
+ .toArray()
+ .map(el => ({
+ text: el.textContent,
+ href: el.querySelector('a').href,
+ }));
+
+ beforeEach(() => {
+ // Create a mock adapter for stubbing axios API requests
+ mock = new MockAdapter(axios);
+
+ element = $(TEMPLATE);
+ });
+
+ afterEach(() => {
+ // Reset the mock adapter
+ mock.restore();
+ });
+
+ it('loads and renders elements from remote server', done => {
+ const files = [
+ 'fileA.txt',
+ 'fileB.txt',
+ 'fi#leC.txt',
+ 'folderA/fileD.txt',
+ 'folder#B/fileE.txt',
+ 'folde?rC/fil#F.txt',
+ ];
+ mock.onGet(FILE_FIND_URL).replyOnce(200, files);
+
+ getProjectFindFileInstance(); // This triggers a load / axios call + subsequent render in the constructor
+
+ setImmediate(() => {
+ expect(findFiles()).toEqual(
+ files.map(text => ({
+ text,
+ href: `${BLOB_URL_TEMPLATE}/${encodeURIComponent(text)}`,
+ })),
+ );
+
+ done();
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
new file mode 100644
index 00000000000..1f4d1e17ea0
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
@@ -0,0 +1,55 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
+import AutoMergeFailedComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue';
+import eventHub from '~/vue_merge_request_widget/event_hub';
+
+describe('MRWidgetAutoMergeFailed', () => {
+ let wrapper;
+ const mergeError = 'This is the merge error';
+ const findButton = () => wrapper.find('button');
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(AutoMergeFailedComponent, {
+ sync: false,
+ propsData: { ...props },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent({
+ mr: { mergeError },
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders failed message', () => {
+ expect(wrapper.text()).toContain('This merge request failed to be merged automatically');
+ });
+
+ it('renders merge error provided', () => {
+ expect(wrapper.text()).toContain(mergeError);
+ });
+
+ it('render refresh button', () => {
+ expect(findButton().text()).toEqual('Refresh');
+ });
+
+ it('emits event and shows loading icon when button is clicked', () => {
+ jest.spyOn(eventHub, '$emit');
+ findButton().trigger('click');
+
+ expect(eventHub.$emit.mock.calls[0][0]).toBe('MRWidgetUpdateRequested');
+
+ return wrapper.vm.$nextTick(() => {
+ expect(findButton().attributes('disabled')).toEqual('disabled');
+ expect(
+ findButton()
+ .find(GlLoadingIcon)
+ .exists(),
+ ).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/changed_file_icon_spec.js b/spec/frontend/vue_shared/components/changed_file_icon_spec.js
index 806602877ef..d0586f9e63f 100644
--- a/spec/frontend/vue_shared/components/changed_file_icon_spec.js
+++ b/spec/frontend/vue_shared/components/changed_file_icon_spec.js
@@ -106,12 +106,10 @@ describe('Changed file icon', () => {
expect(findIcon().props('size')).toBe(size);
});
- // NOTE: It looks like 'showStagedIcon' behavior is backwards to what the name suggests
- // https://gitlab.com/gitlab-org/gitlab-ce/issues/66071
it.each`
showStagedIcon | iconName | desc
- ${false} | ${'file-modified-solid'} | ${'with showStagedIcon false, renders staged icon'}
- ${true} | ${'file-modified'} | ${'with showStagedIcon true, renders regular icon'}
+ ${true} | ${'file-modified-solid'} | ${'with showStagedIcon true, renders staged icon'}
+ ${false} | ${'file-modified'} | ${'with showStagedIcon false, renders regular icon'}
`('$desc', ({ showStagedIcon, iconName }) => {
factory({
file: stagedFile(),
diff --git a/spec/frontend/vue_shared/components/file_icon_spec.js b/spec/frontend/vue_shared/components/file_icon_spec.js
new file mode 100644
index 00000000000..328eec0a80a
--- /dev/null
+++ b/spec/frontend/vue_shared/components/file_icon_spec.js
@@ -0,0 +1,75 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
+import FileIcon from '~/vue_shared/components/file_icon.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+
+describe('File Icon component', () => {
+ let wrapper;
+ const findIcon = () => wrapper.find('svg');
+ const getIconName = () =>
+ findIcon()
+ .find('use')
+ .element.getAttribute('xlink:href')
+ .replace(`${gon.sprite_file_icons}#`, '');
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(FileIcon, {
+ sync: false,
+ propsData: { ...props },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should render a span element and an icon', () => {
+ createComponent({
+ fileName: 'test.js',
+ });
+
+ expect(wrapper.element.tagName).toEqual('SPAN');
+ expect(findIcon().exists()).toBeDefined();
+ });
+
+ it.each`
+ fileName | iconName
+ ${'test.js'} | ${'javascript'}
+ ${'test.png'} | ${'image'}
+ ${'webpack.js'} | ${'webpack'}
+ `('should render a $iconName icon based on file ending', ({ fileName, iconName }) => {
+ createComponent({ fileName });
+ expect(getIconName()).toBe(iconName);
+ });
+
+ it('should render a standard folder icon', () => {
+ createComponent({
+ fileName: 'js',
+ folder: true,
+ });
+
+ expect(findIcon().exists()).toBe(false);
+ expect(wrapper.find(Icon).props('cssClasses')).toContain('folder-icon');
+ });
+
+ it('should render a loading icon', () => {
+ createComponent({
+ fileName: 'test.js',
+ loading: true,
+ });
+
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ });
+
+ it('should add a special class and a size class', () => {
+ const size = 120;
+ createComponent({
+ fileName: 'test.js',
+ cssClasses: 'extraclasses',
+ size,
+ });
+
+ expect(findIcon().classes()).toContain(`s${size}`);
+ expect(findIcon().classes()).toContain('extraclasses');
+ });
+});
diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb
index 94998d302f9..9330e75af11 100644
--- a/spec/helpers/avatars_helper_spec.rb
+++ b/spec/helpers/avatars_helper_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe AvatarsHelper do
include UploadHelpers
@@ -324,5 +324,47 @@ describe AvatarsHelper do
)
end
end
+
+ context 'with only_path parameter set to false' do
+ let(:user_with_avatar) { create(:user, :with_avatar, username: 'foobar') }
+
+ context 'with user parameter' do
+ let(:options) { { user: user_with_avatar, only_path: false } }
+
+ it 'will return avatar with a full path' do
+ is_expected.to eq tag(
+ :img,
+ alt: "#{user_with_avatar.name}'s avatar",
+ src: avatar_icon_for_user(user_with_avatar, 16, only_path: false),
+ data: { container: 'body' },
+ class: "avatar s16 has-tooltip",
+ title: user_with_avatar.name
+ )
+ end
+ end
+
+ context 'with user_name and user_email' do
+ let(:options) { { user_email: user_with_avatar.email, user_name: user_with_avatar.username, only_path: false } }
+
+ it 'will return avatar with a full path' do
+ is_expected.to eq tag(
+ :img,
+ alt: "#{user_with_avatar.username}'s avatar",
+ src: avatar_icon_for_email(user_with_avatar.email, 16, only_path: false),
+ data: { container: 'body' },
+ class: "avatar s16 has-tooltip",
+ title: user_with_avatar.username
+ )
+ end
+ end
+ end
+
+ context 'with unregistered email address' do
+ let(:options) { { user_email: "unregistered_email@example.com" } }
+
+ it 'will return default alt text for avatar' do
+ expect(subject).to include("default avatar")
+ end
+ end
end
end
diff --git a/spec/helpers/commits_helper_spec.rb b/spec/helpers/commits_helper_spec.rb
index 824b3ab4fc1..9e53bc05a48 100644
--- a/spec/helpers/commits_helper_spec.rb
+++ b/spec/helpers/commits_helper_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe CommitsHelper do
describe 'commit_author_link' do
diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb
index d25f0c6de4a..a14ae2cde4b 100644
--- a/spec/helpers/emails_helper_spec.rb
+++ b/spec/helpers/emails_helper_spec.rb
@@ -6,30 +6,62 @@ describe EmailsHelper do
let(:merge_request) { create(:merge_request) }
let(:merge_request_presenter) { merge_request.present }
- context "and format is text" do
- it "returns plain text" do
- expect(closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
+ context 'when user can read merge request' do
+ let(:user) { create(:user) }
+
+ before do
+ merge_request.project.add_developer(user)
+ self.instance_variable_set(:@recipient, user)
+ self.instance_variable_set(:@project, merge_request.project)
+ end
+
+ context "and format is text" do
+ it "returns plain text" do
+ expect(helper.closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
+ end
end
- end
- context "and format is HTML" do
- it "returns HTML" do
- expect(closure_reason_text(merge_request, format: :html)).to eq("via merge request #{link_to(merge_request.to_reference, merge_request_presenter.web_url)}")
+ context "and format is HTML" do
+ it "returns HTML" do
+ expect(helper.closure_reason_text(merge_request, format: :html)).to eq("via merge request #{link_to(merge_request.to_reference, merge_request_presenter.web_url)}")
+ end
+ end
+
+ context "and format is unknown" do
+ it "returns plain text" do
+ expect(helper.closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
+ end
end
end
- context "and format is unknown" do
- it "returns plain text" do
- expect(closure_reason_text(merge_request, format: :text)).to eq("via merge request #{merge_request.to_reference} (#{merge_request_presenter.web_url})")
+ context 'when user cannot read merge request' do
+ it "does not have link to merge request" do
+ expect(helper.closure_reason_text(merge_request)).to be_empty
end
end
end
context 'when given a String' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
let(:closed_via) { "5a0eb6fd7e0f133044378c662fcbbc0d0c16dbfa" }
- it "returns plain text" do
- expect(closure_reason_text(closed_via)).to eq("via #{closed_via}")
+ context 'when user can read commits' do
+ before do
+ project.add_developer(user)
+ self.instance_variable_set(:@recipient, user)
+ self.instance_variable_set(:@project, project)
+ end
+
+ it "returns plain text" do
+ expect(closure_reason_text(closed_via)).to eq("via #{closed_via}")
+ end
+ end
+
+ context 'when user cannot read commits' do
+ it "returns plain text" do
+ expect(closure_reason_text(closed_via)).to be_empty
+ end
end
end
diff --git a/spec/helpers/form_helper_spec.rb b/spec/helpers/form_helper_spec.rb
index 18cf0031d5f..a70d8333f30 100644
--- a/spec/helpers/form_helper_spec.rb
+++ b/spec/helpers/form_helper_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe FormHelper do
describe 'form_errors' do
diff --git a/spec/helpers/import_helper_spec.rb b/spec/helpers/import_helper_spec.rb
index 6e8c13db9fe..8a1b1e859b1 100644
--- a/spec/helpers/import_helper_spec.rb
+++ b/spec/helpers/import_helper_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe ImportHelper do
describe '#sanitize_project_name' do
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 4f1cab38f34..1d57aaa0da5 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -278,4 +278,14 @@ describe LabelsHelper do
it { is_expected.to eq('Subscribe at group level') }
end
end
+
+ describe '#label_tooltip_title' do
+ let(:html) { '<img src="example.png">This is an image</img>' }
+ let(:label_with_html_content) { create(:label, title: 'test', description: html) }
+
+ it 'removes HTML' do
+ tooltip = label_tooltip_title(label_with_html_content)
+ expect(tooltip).to eq('This is an image')
+ end
+ end
end
diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb
index bf50763d06f..3b08fc511a3 100644
--- a/spec/helpers/page_layout_helper_spec.rb
+++ b/spec/helpers/page_layout_helper_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe PageLayoutHelper do
describe 'page_description' do
diff --git a/spec/helpers/profiles_helper_spec.rb b/spec/helpers/profiles_helper_spec.rb
index 8e336469c27..da2dc229c35 100644
--- a/spec/helpers/profiles_helper_spec.rb
+++ b/spec/helpers/profiles_helper_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe ProfilesHelper do
describe '#commit_email_select_options' do
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index a70bfc2adc7..d2a4ce6540d 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -503,7 +503,7 @@ describe ProjectsHelper do
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:enabled_git_access_protocol) { 'ssh' }
allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return('git@localhost:')
- expect(helper.push_to_create_project_command(user)).to eq('git push --set-upstream git@localhost:john/$(git rev-parse --show-toplevel | xargs basename).git $(git rev-parse --abbrev-ref HEAD)')
+ expect(helper.push_to_create_project_command(user)).to eq("git push --set-upstream #{Gitlab.config.gitlab.user}@localhost:john/$(git rev-parse --show-toplevel | xargs basename).git $(git rev-parse --abbrev-ref HEAD)")
end
end
@@ -549,6 +549,42 @@ describe ProjectsHelper do
end
end
+ describe '#git_user_email' do
+ context 'not logged-in' do
+ before do
+ allow(helper).to receive(:current_user).and_return(nil)
+ end
+
+ it 'returns your@email.com' do
+ expect(helper.send(:git_user_email)).to eq('your@email.com')
+ end
+ end
+
+ context 'user logged in' do
+ let(:user) { create(:user) }
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+ end
+
+ context 'user has no configured commit email' do
+ it 'returns the primary email' do
+ expect(helper.send(:git_user_email)).to eq(user.email)
+ end
+ end
+
+ context 'user has a configured commit email' do
+ before do
+ confirmed_email = create(:email, :confirmed, user: user)
+ user.update(commit_email: confirmed_email)
+ end
+
+ it 'returns the commit email' do
+ expect(helper.send(:git_user_email)).to eq(user.commit_email)
+ end
+ end
+ end
+ end
+
describe 'show_xcode_link' do
let!(:project) { create(:project) }
let(:mac_ua) { 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36' }
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 2ab72679ee7..e1dc589236b 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -6,7 +6,7 @@ describe SearchHelper do
str
end
- describe 'search_autocomplete_source' do
+ describe 'search_autocomplete_opts' do
context "with no current user" do
before do
allow(self).to receive(:current_user).and_return(nil)
@@ -99,6 +99,47 @@ describe SearchHelper do
end
end
+ describe 'search_entries_info' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:scope, :label) do
+ 'commits' | 'commit'
+ 'issues' | 'issue'
+ 'merge_requests' | 'merge request'
+ 'milestones' | 'milestone'
+ 'projects' | 'project'
+ 'snippet_titles' | 'snippet'
+ 'users' | 'user'
+
+ 'blobs' | 'result'
+ 'snippet_blobs' | 'result'
+ 'wiki_blobs' | 'result'
+
+ 'notes' | 'comment'
+ end
+
+ with_them do
+ it 'uses the correct singular label' do
+ collection = Kaminari.paginate_array([:foo]).page(1).per(10)
+
+ expect(search_entries_info(collection, scope, 'foo')).to eq("Showing 1 #{label} for \"foo\"")
+ end
+
+ it 'uses the correct plural label' do
+ collection = Kaminari.paginate_array([:foo] * 23).page(1).per(10)
+
+ expect(search_entries_info(collection, scope, 'foo')).to eq("Showing 1 - 10 of 23 #{label.pluralize} for \"foo\"")
+ end
+ end
+
+ it 'raises an error for unrecognized scopes' do
+ expect do
+ collection = Kaminari.paginate_array([:foo]).page(1).per(10)
+ search_entries_info(collection, 'unknown', 'foo')
+ end.to raise_error(RuntimeError)
+ end
+ end
+
describe 'search_filter_input_options' do
context 'project' do
before do
diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb
index a6623bc7941..9165e91ddcd 100644
--- a/spec/helpers/users_helper_spec.rb
+++ b/spec/helpers/users_helper_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe UsersHelper do
include TermsHelper
diff --git a/spec/initializers/asset_proxy_setting_spec.rb b/spec/initializers/asset_proxy_setting_spec.rb
new file mode 100644
index 00000000000..42e4d4aa594
--- /dev/null
+++ b/spec/initializers/asset_proxy_setting_spec.rb
@@ -0,0 +1,13 @@
+require 'spec_helper'
+
+describe 'Asset proxy settings initialization' do
+ describe '#asset_proxy' do
+ it 'defaults to disabled' do
+ expect(Banzai::Filter::AssetProxyFilter).to receive(:initialize_settings)
+
+ require_relative '../../config/initializers/asset_proxy_settings'
+
+ expect(Gitlab.config.asset_proxy.enabled).to be_falsey
+ end
+ end
+end
diff --git a/spec/initializers/rest-client-hostname_override_spec.rb b/spec/initializers/rest-client-hostname_override_spec.rb
new file mode 100644
index 00000000000..3707e001d41
--- /dev/null
+++ b/spec/initializers/rest-client-hostname_override_spec.rb
@@ -0,0 +1,147 @@
+require 'spec_helper'
+
+describe 'rest-client dns rebinding protection' do
+ include StubRequests
+
+ context 'when local requests are not allowed' do
+ it 'allows an external request with http' do
+ request_stub = stub_full_request('http://example.com', ip_address: '93.184.216.34')
+
+ RestClient.get('http://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'allows an external request with https' do
+ request_stub = stub_full_request('https://example.com', ip_address: '93.184.216.34')
+
+ RestClient.get('https://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'raises error when it is a request that resolves to a local address' do
+ stub_full_request('https://example.com', ip_address: '172.16.0.0')
+
+ expect { RestClient.get('https://example.com') }
+ .to raise_error(ArgumentError,
+ "URL 'https://example.com' is blocked: Requests to the local network are not allowed")
+ end
+
+ it 'raises error when it is a request that resolves to a localhost address' do
+ stub_full_request('https://example.com', ip_address: '127.0.0.1')
+
+ expect { RestClient.get('https://example.com') }
+ .to raise_error(ArgumentError,
+ "URL 'https://example.com' is blocked: Requests to localhost are not allowed")
+ end
+
+ it 'raises error when it is a request to local address' do
+ expect { RestClient.get('http://172.16.0.0') }
+ .to raise_error(ArgumentError,
+ "URL 'http://172.16.0.0' is blocked: Requests to the local network are not allowed")
+ end
+
+ it 'raises error when it is a request to localhost address' do
+ expect { RestClient.get('http://127.0.0.1') }
+ .to raise_error(ArgumentError,
+ "URL 'http://127.0.0.1' is blocked: Requests to localhost are not allowed")
+ end
+ end
+
+ context 'when port different from URL scheme is used' do
+ it 'allows the request' do
+ request_stub = stub_full_request('https://example.com:8080', ip_address: '93.184.216.34')
+
+ RestClient.get('https://example.com:8080/')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'raises error when it is a request to local address' do
+ expect { RestClient.get('https://172.16.0.0:8080') }
+ .to raise_error(ArgumentError,
+ "URL 'https://172.16.0.0:8080' is blocked: Requests to the local network are not allowed")
+ end
+
+ it 'raises error when it is a request to localhost address' do
+ expect { RestClient.get('https://127.0.0.1:8080') }
+ .to raise_error(ArgumentError,
+ "URL 'https://127.0.0.1:8080' is blocked: Requests to localhost are not allowed")
+ end
+ end
+
+ context 'when DNS rebinding protection is disabled' do
+ before do
+ stub_application_setting(dns_rebinding_protection_enabled: false)
+ end
+
+ it 'allows the request' do
+ request_stub = stub_request(:get, 'https://example.com')
+
+ RestClient.get('https://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+ end
+
+ context 'when http(s) proxy environment variable is set' do
+ before do
+ stub_env('https_proxy' => 'https://my.proxy')
+ end
+
+ it 'allows the request' do
+ request_stub = stub_request(:get, 'https://example.com')
+
+ RestClient.get('https://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+ end
+
+ context 'when local requests are allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
+ end
+
+ it 'allows an external request' do
+ request_stub = stub_full_request('https://example.com', ip_address: '93.184.216.34')
+
+ RestClient.get('https://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'allows an external request that resolves to a local address' do
+ request_stub = stub_full_request('https://example.com', ip_address: '172.16.0.0')
+
+ RestClient.get('https://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'allows an external request that resolves to a localhost address' do
+ request_stub = stub_full_request('https://example.com', ip_address: '127.0.0.1')
+
+ RestClient.get('https://example.com/')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'allows a local address request' do
+ request_stub = stub_request(:get, 'http://172.16.0.0')
+
+ RestClient.get('http://172.16.0.0')
+
+ expect(request_stub).to have_been_requested
+ end
+
+ it 'allows a localhost address request' do
+ request_stub = stub_request(:get, 'http://127.0.0.1')
+
+ RestClient.get('http://127.0.0.1')
+
+ expect(request_stub).to have_been_requested
+ end
+ end
+end
diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js
index bb7abe52eae..05e6ea1394d 100644
--- a/spec/javascripts/boards/issue_spec.js
+++ b/spec/javascripts/boards/issue_spec.js
@@ -1,6 +1,6 @@
/* global ListIssue */
-import Vue from 'vue';
+import axios from '~/lib/utils/axios_utils';
import '~/boards/models/label';
import '~/boards/models/assignee';
import '~/boards/models/issue';
@@ -175,7 +175,7 @@ describe('Issue model', () => {
describe('update', () => {
it('passes assignee ids when there are assignees', done => {
- spyOn(Vue.http, 'patch').and.callFake((url, data) => {
+ spyOn(axios, 'patch').and.callFake((url, data) => {
expect(data.issue.assignee_ids).toEqual([1]);
done();
return Promise.resolve();
@@ -185,7 +185,7 @@ describe('Issue model', () => {
});
it('passes assignee ids of [0] when there are no assignees', done => {
- spyOn(Vue.http, 'patch').and.callFake((url, data) => {
+ spyOn(axios, 'patch').and.callFake((url, data) => {
expect(data.issue.assignee_ids).toEqual([0]);
done();
return Promise.resolve();
diff --git a/spec/javascripts/environments/environment_item_spec.js b/spec/javascripts/environments/environment_item_spec.js
index 388d7063d13..f9ee4648128 100644
--- a/spec/javascripts/environments/environment_item_spec.js
+++ b/spec/javascripts/environments/environment_item_spec.js
@@ -106,6 +106,7 @@ describe('Environment item', () => {
play_path: '/play',
},
],
+ deployed_at: '2016-11-29T18:11:58.430Z',
},
has_stop_action: true,
environment_path: 'root/ci-folders/environments/31',
@@ -139,9 +140,7 @@ describe('Environment item', () => {
it('should render last deployment date', () => {
const timeagoInstance = new timeago(); // eslint-disable-line
- const formatedDate = timeagoInstance.format(
- environment.last_deployment.deployable.created_at,
- );
+ const formatedDate = timeagoInstance.format(environment.last_deployment.deployed_at);
expect(
component.$el.querySelector('.environment-created-date-timeago').textContent,
diff --git a/spec/javascripts/flash_spec.js b/spec/javascripts/flash_spec.js
index aecab331ead..bd8608b6bac 100644
--- a/spec/javascripts/flash_spec.js
+++ b/spec/javascripts/flash_spec.js
@@ -25,14 +25,6 @@ describe('Flash', () => {
'<script>alert("a");</script>',
);
});
-
- it('adds container classes when inside content wrapper', () => {
- el.innerHTML = createFlashEl('testing', 'alert', true);
-
- expect(el.querySelector('.flash-text').classList.contains('container-fluid')).toBeTruthy();
-
- expect(el.querySelector('.flash-text').classList.contains('container-limited')).toBeTruthy();
- });
});
describe('hideFlash', () => {
@@ -171,9 +163,7 @@ describe('Flash', () => {
it('adds container classes when inside content-wrapper', () => {
flash('test');
- expect(document.querySelector('.flash-text').className).toBe(
- 'flash-text container-fluid container-limited limit-container-width',
- );
+ expect(document.querySelector('.flash-text').className).toBe('flash-text');
});
it('does not add container when outside of content-wrapper', () => {
diff --git a/spec/javascripts/groups/components/app_spec.js b/spec/javascripts/groups/components/app_spec.js
index 31873311e16..23b2564d3f9 100644
--- a/spec/javascripts/groups/components/app_spec.js
+++ b/spec/javascripts/groups/components/app_spec.js
@@ -1,3 +1,4 @@
+import '~/flash';
import $ from 'jquery';
import Vue from 'vue';
@@ -333,7 +334,7 @@ describe('AppComponent', () => {
it('hides modal confirmation leave group and remove group item from tree', done => {
const notice = `You left the "${childGroupItem.fullName}" group.`;
- spyOn(vm.service, 'leaveGroup').and.returnValue(returnServicePromise({ notice }));
+ spyOn(vm.service, 'leaveGroup').and.returnValue(Promise.resolve({ data: { notice } }));
spyOn(vm.store, 'removeGroup').and.callThrough();
spyOn(window, 'Flash');
spyOn($, 'scrollTo');
diff --git a/spec/javascripts/groups/service/groups_service_spec.js b/spec/javascripts/groups/service/groups_service_spec.js
index 339e5131615..45db962a1ef 100644
--- a/spec/javascripts/groups/service/groups_service_spec.js
+++ b/spec/javascripts/groups/service/groups_service_spec.js
@@ -1,11 +1,8 @@
-import Vue from 'vue';
-import VueResource from 'vue-resource';
+import axios from '~/lib/utils/axios_utils';
import GroupsService from '~/groups/service/groups_service';
import { mockEndpoint, mockParentGroupItem } from '../mock_data';
-Vue.use(VueResource);
-
describe('GroupsService', () => {
let service;
@@ -15,8 +12,8 @@ describe('GroupsService', () => {
describe('getGroups', () => {
it('should return promise for `GET` request on provided endpoint', () => {
- spyOn(service.groups, 'get').and.stub();
- const queryParams = {
+ spyOn(axios, 'get').and.stub();
+ const params = {
page: 2,
filter: 'git',
sort: 'created_asc',
@@ -25,21 +22,21 @@ describe('GroupsService', () => {
service.getGroups(55, 2, 'git', 'created_asc', true);
- expect(service.groups.get).toHaveBeenCalledWith({ parent_id: 55 });
+ expect(axios.get).toHaveBeenCalledWith(mockEndpoint, { params: { parent_id: 55 } });
service.getGroups(null, 2, 'git', 'created_asc', true);
- expect(service.groups.get).toHaveBeenCalledWith(queryParams);
+ expect(axios.get).toHaveBeenCalledWith(mockEndpoint, { params });
});
});
describe('leaveGroup', () => {
it('should return promise for `DELETE` request on provided endpoint', () => {
- spyOn(Vue.http, 'delete').and.stub();
+ spyOn(axios, 'delete').and.stub();
service.leaveGroup(mockParentGroupItem.leavePath);
- expect(Vue.http.delete).toHaveBeenCalledWith(mockParentGroupItem.leavePath);
+ expect(axios.delete).toHaveBeenCalledWith(mockParentGroupItem.leavePath);
});
});
});
diff --git a/spec/javascripts/issue_show/components/edit_actions_spec.js b/spec/javascripts/issue_show/components/edit_actions_spec.js
index d92c54ea83f..2ab74ae4e10 100644
--- a/spec/javascripts/issue_show/components/edit_actions_spec.js
+++ b/spec/javascripts/issue_show/components/edit_actions_spec.js
@@ -104,7 +104,7 @@ describe('Edit Actions components', () => {
spyOn(window, 'confirm').and.returnValue(true);
vm.$el.querySelector('.btn-danger').click();
- expect(eventHub.$emit).toHaveBeenCalledWith('delete.issuable');
+ expect(eventHub.$emit).toHaveBeenCalledWith('delete.issuable', { destroy_confirm: true });
});
it('shows loading icon after clicking delete button', done => {
diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js
deleted file mode 100644
index 1e49a955815..00000000000
--- a/spec/javascripts/monitoring/charts/area_spec.js
+++ /dev/null
@@ -1,265 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { createStore } from '~/monitoring/stores';
-import { GlLink } from '@gitlab/ui';
-import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
-import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper';
-import Area from '~/monitoring/components/charts/area.vue';
-import * as types from '~/monitoring/stores/mutation_types';
-import { TEST_HOST } from 'spec/test_constants';
-import MonitoringMock, { deploymentData } from '../mock_data';
-
-describe('Area component', () => {
- const mockSha = 'mockSha';
- const mockWidgets = 'mockWidgets';
- const mockSvgPathContent = 'mockSvgPathContent';
- const projectPath = `${TEST_HOST}/path/to/project`;
- const commitUrl = `${projectPath}/commit/${mockSha}`;
- let mockGraphData;
- let areaChart;
- let spriteSpy;
- let store;
-
- beforeEach(() => {
- store = createStore();
- store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data);
- store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
-
- [mockGraphData] = store.state.monitoringDashboard.groups[0].metrics;
-
- areaChart = shallowMount(Area, {
- propsData: {
- graphData: mockGraphData,
- containerWidth: 0,
- deploymentData: store.state.monitoringDashboard.deploymentData,
- projectPath,
- },
- slots: {
- default: mockWidgets,
- },
- store,
- });
-
- spriteSpy = spyOnDependency(Area, 'getSvgIconPathContent').and.callFake(
- () => new Promise(resolve => resolve(mockSvgPathContent)),
- );
- });
-
- afterEach(() => {
- areaChart.destroy();
- });
-
- it('renders chart title', () => {
- expect(areaChart.find({ ref: 'graphTitle' }).text()).toBe(mockGraphData.title);
- });
-
- it('contains graph widgets from slot', () => {
- expect(areaChart.find({ ref: 'graphWidgets' }).text()).toBe(mockWidgets);
- });
-
- describe('wrapped components', () => {
- describe('GitLab UI area chart', () => {
- let glAreaChart;
-
- beforeEach(() => {
- glAreaChart = areaChart.find(GlAreaChart);
- });
-
- it('is a Vue instance', () => {
- expect(glAreaChart.isVueInstance()).toBe(true);
- });
-
- it('receives data properties needed for proper chart render', () => {
- const props = glAreaChart.props();
-
- expect(props.data).toBe(areaChart.vm.chartData);
- expect(props.option).toBe(areaChart.vm.chartOptions);
- expect(props.formatTooltipText).toBe(areaChart.vm.formatTooltipText);
- expect(props.thresholds).toBe(areaChart.vm.thresholds);
- });
-
- it('recieves a tooltip title', () => {
- const mockTitle = 'mockTitle';
- areaChart.vm.tooltip.title = mockTitle;
-
- expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipTitle', mockTitle)).toBe(true);
- });
-
- describe('when tooltip is showing deployment data', () => {
- beforeEach(() => {
- areaChart.vm.tooltip.isDeployment = true;
- });
-
- it('uses deployment title', () => {
- expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipTitle', 'Deployed')).toBe(
- true,
- );
- });
-
- it('renders clickable commit sha in tooltip content', () => {
- areaChart.vm.tooltip.sha = mockSha;
- areaChart.vm.tooltip.commitUrl = commitUrl;
-
- const commitLink = areaChart.find(GlLink);
-
- expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true);
- expect(commitLink.attributes('href')).toEqual(commitUrl);
- });
- });
- });
- });
-
- describe('methods', () => {
- describe('formatTooltipText', () => {
- const mockDate = deploymentData[0].created_at;
- const generateSeriesData = type => ({
- seriesData: [
- {
- seriesName: areaChart.vm.chartData[0].name,
- componentSubType: type,
- value: [mockDate, 5.55555],
- seriesIndex: 0,
- },
- ],
- value: mockDate,
- });
-
- describe('when series is of line type', () => {
- beforeEach(() => {
- areaChart.vm.formatTooltipText(generateSeriesData('line'));
- });
-
- it('formats tooltip title', () => {
- expect(areaChart.vm.tooltip.title).toBe('31 May 2017, 9:23PM');
- });
-
- it('formats tooltip content', () => {
- const name = 'Core Usage';
- const value = '5.556';
- const seriesLabel = areaChart.find(GlChartSeriesLabel);
-
- expect(seriesLabel.vm.color).toBe('');
- expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true);
- expect(areaChart.vm.tooltip.content).toEqual([{ name, value, color: undefined }]);
- expect(
- shallowWrapperContainsSlotText(areaChart.find(GlAreaChart), 'tooltipContent', value),
- ).toBe(true);
- });
- });
-
- describe('when series is of scatter type', () => {
- beforeEach(() => {
- areaChart.vm.formatTooltipText(generateSeriesData('scatter'));
- });
-
- it('formats tooltip title', () => {
- expect(areaChart.vm.tooltip.title).toBe('31 May 2017, 9:23PM');
- });
-
- it('formats tooltip sha', () => {
- expect(areaChart.vm.tooltip.sha).toBe('f5bcd1d9');
- });
- });
- });
-
- describe('setSvg', () => {
- const mockSvgName = 'mockSvgName';
-
- beforeEach(() => {
- areaChart.vm.setSvg(mockSvgName);
- });
-
- it('gets svg path content', () => {
- expect(spriteSpy).toHaveBeenCalledWith(mockSvgName);
- });
-
- it('sets svg path content', done => {
- areaChart.vm.$nextTick(() => {
- expect(areaChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`);
- done();
- });
- });
- });
-
- describe('onResize', () => {
- const mockWidth = 233;
-
- beforeEach(() => {
- spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({
- width: mockWidth,
- }));
- areaChart.vm.onResize();
- });
-
- it('sets area chart width', () => {
- expect(areaChart.vm.width).toBe(mockWidth);
- });
- });
- });
-
- describe('computed', () => {
- describe('chartData', () => {
- let chartData;
- const seriesData = () => chartData[0];
-
- beforeEach(() => {
- ({ chartData } = areaChart.vm);
- });
-
- it('utilizes all data points', () => {
- expect(chartData.length).toBe(1);
- expect(seriesData().data.length).toBe(297);
- });
-
- it('creates valid data', () => {
- const { data } = seriesData();
-
- expect(
- data.filter(([time, value]) => new Date(time).getTime() > 0 && typeof value === 'number')
- .length,
- ).toBe(data.length);
- });
-
- it('formats line width correctly', () => {
- expect(chartData[0].lineStyle.width).toBe(2);
- });
- });
-
- describe('chartOptions', () => {
- describe('dataZoom', () => {
- it('contains an svg object within an array to properly render icon', () => {
- const dataZoomObject = [{}];
-
- expect(areaChart.vm.chartOptions.dataZoom).toEqual(dataZoomObject);
- });
- });
-
- describe('yAxis formatter', () => {
- let format;
-
- beforeEach(() => {
- format = areaChart.vm.chartOptions.yAxis.axisLabel.formatter;
- });
-
- it('rounds to 3 decimal places', () => {
- expect(format(0.88888)).toBe('0.889');
- });
- });
- });
-
- describe('scatterSeries', () => {
- it('utilizes deployment data', () => {
- expect(areaChart.vm.scatterSeries.data).toEqual([
- ['2017-05-31T21:23:37.881Z', 0],
- ['2017-05-30T20:08:04.629Z', 0],
- ['2017-05-30T17:42:38.409Z', 0],
- ]);
- });
- });
-
- describe('yAxisLabel', () => {
- it('constructs a label for the chart y-axis', () => {
- expect(areaChart.vm.yAxisLabel).toBe('CPU');
- });
- });
- });
-});
diff --git a/spec/javascripts/monitoring/components/dashboard_spec.js b/spec/javascripts/monitoring/components/dashboard_spec.js
index f3ec7520c6f..15e41e2fe93 100644
--- a/spec/javascripts/monitoring/components/dashboard_spec.js
+++ b/spec/javascripts/monitoring/components/dashboard_spec.js
@@ -378,7 +378,9 @@ describe('Dashboard', () => {
});
});
- describe('link to chart', () => {
+ // https://gitlab.com/gitlab-org/gitlab-ce/issues/66922
+ // eslint-disable-next-line jasmine/no-disabled-tests
+ xdescribe('link to chart', () => {
let wrapper;
const currentDashboard = 'TEST_DASHBOARD';
localVue.use(GlToast);
diff --git a/spec/javascripts/releases/components/release_block_spec.js b/spec/javascripts/releases/components/release_block_spec.js
index f761a18e326..fdf23f3f69d 100644
--- a/spec/javascripts/releases/components/release_block_spec.js
+++ b/spec/javascripts/releases/components/release_block_spec.js
@@ -88,6 +88,10 @@ describe('Release block', () => {
vm.$destroy();
});
+ it("renders the block with an id equal to the release's tag name", () => {
+ expect(vm.$el.id).toBe('18.04');
+ });
+
it('renders release name', () => {
expect(vm.$el.textContent).toContain(release.name);
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
deleted file mode 100644
index 55a11a72551..00000000000
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import Vue from 'vue';
-import autoMergeFailedComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue';
-import eventHub from '~/vue_merge_request_widget/event_hub';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('MRWidgetAutoMergeFailed', () => {
- let vm;
- const mergeError = 'This is the merge error';
-
- beforeEach(() => {
- const Component = Vue.extend(autoMergeFailedComponent);
- vm = mountComponent(Component, {
- mr: { mergeError },
- });
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('renders failed message', () => {
- expect(vm.$el.textContent).toContain('This merge request failed to be merged automatically');
- });
-
- it('renders merge error provided', () => {
- expect(vm.$el.innerText).toContain(mergeError);
- });
-
- it('render refresh button', () => {
- expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Refresh');
- });
-
- it('emits event and shows loading icon when button is clicked', done => {
- spyOn(eventHub, '$emit');
- vm.$el.querySelector('button').click();
-
- expect(eventHub.$emit.calls.argsFor(0)[0]).toEqual('MRWidgetUpdateRequested');
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
- expect(vm.$el.querySelector('button .loading-container span').classList).toContain(
- 'gl-spinner',
- );
- done();
- });
- });
-});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index 53e1f077610..2bb2319cc60 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -6,7 +6,7 @@ import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit
import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { MWPS_MERGE_STRATEGY, ATMTWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
+import { MWPS_MERGE_STRATEGY, MTWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
const commitMessage = 'This is the commit message';
const squashCommitMessage = 'This is the squash commit message';
@@ -164,7 +164,7 @@ describe('ReadyToMerge', () => {
});
it('returns info class for pending status', () => {
- Vue.set(vm.mr, 'availableAutoMergeStrategies', [ATMTWPS_MERGE_STRATEGY]);
+ Vue.set(vm.mr, 'availableAutoMergeStrategies', [MTWPS_MERGE_STRATEGY]);
expect(vm.mergeButtonClass).toEqual(inActionClass);
});
diff --git a/spec/javascripts/vue_shared/components/file_icon_spec.js b/spec/javascripts/vue_shared/components/file_icon_spec.js
deleted file mode 100644
index 1f61e19fa84..00000000000
--- a/spec/javascripts/vue_shared/components/file_icon_spec.js
+++ /dev/null
@@ -1,92 +0,0 @@
-import Vue from 'vue';
-import fileIcon from '~/vue_shared/components/file_icon.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('File Icon component', () => {
- let vm;
- let FileIcon;
-
- beforeEach(() => {
- FileIcon = Vue.extend(fileIcon);
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('should render a span element with an svg', () => {
- vm = mountComponent(FileIcon, {
- fileName: 'test.js',
- });
-
- expect(vm.$el.tagName).toEqual('SPAN');
- expect(vm.$el.querySelector('span > svg')).toBeDefined();
- });
-
- it('should render a javascript icon based on file ending', () => {
- vm = mountComponent(FileIcon, {
- fileName: 'test.js',
- });
-
- expect(vm.$el.firstChild.firstChild.getAttribute('xlink:href')).toBe(
- `${gon.sprite_file_icons}#javascript`,
- );
- });
-
- it('should render a image icon based on file ending', () => {
- vm = mountComponent(FileIcon, {
- fileName: 'test.png',
- });
-
- expect(vm.$el.firstChild.firstChild.getAttribute('xlink:href')).toBe(
- `${gon.sprite_file_icons}#image`,
- );
- });
-
- it('should render a webpack icon based on file namer', () => {
- vm = mountComponent(FileIcon, {
- fileName: 'webpack.js',
- });
-
- expect(vm.$el.firstChild.firstChild.getAttribute('xlink:href')).toBe(
- `${gon.sprite_file_icons}#webpack`,
- );
- });
-
- it('should render a standard folder icon', () => {
- vm = mountComponent(FileIcon, {
- fileName: 'js',
- folder: true,
- });
-
- expect(vm.$el.querySelector('span > svg > use').getAttribute('xlink:href')).toBe(
- `${gon.sprite_file_icons}#folder`,
- );
- });
-
- it('should render a loading icon', () => {
- vm = mountComponent(FileIcon, {
- fileName: 'test.js',
- loading: true,
- });
-
- const { classList } = vm.$el.querySelector('.loading-container span');
-
- expect(classList.contains('gl-spinner')).toEqual(true);
- });
-
- it('should add a special class and a size class', () => {
- vm = mountComponent(FileIcon, {
- fileName: 'test.js',
- cssClasses: 'extraclasses',
- size: 120,
- });
-
- const { classList } = vm.$el.firstChild;
- const containsSizeClass = classList.contains('s120');
- const containsCustomClass = classList.contains('extraclasses');
-
- expect(containsSizeClass).toBe(true);
- expect(containsCustomClass).toBe(true);
- });
-});
diff --git a/spec/lib/banzai/filter/asset_proxy_filter_spec.rb b/spec/lib/banzai/filter/asset_proxy_filter_spec.rb
new file mode 100644
index 00000000000..b7f45421b2a
--- /dev/null
+++ b/spec/lib/banzai/filter/asset_proxy_filter_spec.rb
@@ -0,0 +1,95 @@
+require 'spec_helper'
+
+describe Banzai::Filter::AssetProxyFilter do
+ include FilterSpecHelper
+
+ def image(path)
+ %(<img src="#{path}" />)
+ end
+
+ it 'does not replace if disabled' do
+ stub_asset_proxy_setting(enabled: false)
+
+ context = described_class.transform_context({})
+ src = 'http://example.com/test.png'
+ doc = filter(image(src), context)
+
+ expect(doc.at_css('img')['src']).to eq src
+ end
+
+ context 'during initialization' do
+ after do
+ Gitlab.config.asset_proxy['enabled'] = false
+ end
+
+ it '#initialize_settings' do
+ stub_application_setting(asset_proxy_enabled: true)
+ stub_application_setting(asset_proxy_secret_key: 'shared-secret')
+ stub_application_setting(asset_proxy_url: 'https://assets.example.com')
+ stub_application_setting(asset_proxy_whitelist: %w(gitlab.com *.mydomain.com))
+
+ described_class.initialize_settings
+
+ expect(Gitlab.config.asset_proxy.enabled).to be_truthy
+ expect(Gitlab.config.asset_proxy.secret_key).to eq 'shared-secret'
+ expect(Gitlab.config.asset_proxy.url).to eq 'https://assets.example.com'
+ expect(Gitlab.config.asset_proxy.whitelist).to eq %w(gitlab.com *.mydomain.com)
+ expect(Gitlab.config.asset_proxy.domain_regexp).to eq /^(gitlab\.com|.*?\.mydomain\.com)$/i
+ end
+ end
+
+ context 'when properly configured' do
+ before do
+ stub_asset_proxy_setting(enabled: true)
+ stub_asset_proxy_setting(secret_key: 'shared-secret')
+ stub_asset_proxy_setting(url: 'https://assets.example.com')
+ stub_asset_proxy_setting(whitelist: %W(gitlab.com *.mydomain.com #{Gitlab.config.gitlab.host}))
+ stub_asset_proxy_setting(domain_regexp: described_class.compile_whitelist(Gitlab.config.asset_proxy.whitelist))
+ @context = described_class.transform_context({})
+ end
+
+ it 'replaces img src' do
+ src = 'http://example.com/test.png'
+ new_src = 'https://assets.example.com/08df250eeeef1a8cf2c761475ac74c5065105612/687474703a2f2f6578616d706c652e636f6d2f746573742e706e67'
+ doc = filter(image(src), @context)
+
+ expect(doc.at_css('img')['src']).to eq new_src
+ expect(doc.at_css('img')['data-canonical-src']).to eq src
+ end
+
+ it 'skips internal images' do
+ src = "#{Gitlab.config.gitlab.url}/test.png"
+ doc = filter(image(src), @context)
+
+ expect(doc.at_css('img')['src']).to eq src
+ end
+
+ it 'skip relative urls' do
+ src = "/test.png"
+ doc = filter(image(src), @context)
+
+ expect(doc.at_css('img')['src']).to eq src
+ end
+
+ it 'skips single domain' do
+ src = "http://gitlab.com/test.png"
+ doc = filter(image(src), @context)
+
+ expect(doc.at_css('img')['src']).to eq src
+ end
+
+ it 'skips single domain and ignores url in query string' do
+ src = "http://gitlab.com/test.png?url=http://example.com/test.png"
+ doc = filter(image(src), @context)
+
+ expect(doc.at_css('img')['src']).to eq src
+ end
+
+ it 'skips wildcarded domain' do
+ src = "http://images.mydomain.com/test.png"
+ doc = filter(image(src), @context)
+
+ expect(doc.at_css('img')['src']).to eq src
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
index 807f1b8bbd3..8e955ec3b6b 100644
--- a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
+++ b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Banzai::Filter::BlockquoteFenceFilter do
include FilterSpecHelper
diff --git a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
index bcb74be1034..192d00805e0 100644
--- a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
@@ -189,5 +189,26 @@ describe Banzai::Filter::CommitTrailersFilter do
expect_to_have_user_link_with_avatar(doc, user: user, trailer: trailer)
expect(doc.text).to include(commit_body)
end
+
+ context 'with Gitlab-hosted avatars in commit trailers' do
+ # Because commit trailers are contained within markdown,
+ # any path-only link will automatically be prefixed
+ # with the path of its repository.
+ # See: "build_relative_path" in "lib/banzai/filter/relative_link_filter.rb"
+ let(:user_with_avatar) { create(:user, :with_avatar, username: 'foobar') }
+
+ it 'returns a full path for avatar urls' do
+ _, message_html = build_commit_message(
+ trailer: trailer,
+ name: user_with_avatar.name,
+ email: user_with_avatar.email
+ )
+
+ doc = filter(message_html)
+ expected = "#{Gitlab.config.gitlab.url}#{user_with_avatar.avatar_url}"
+
+ expect(doc.css('img')[0].attr('src')).to start_with expected
+ end
+ end
end
end
diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb
index 59fea5766ee..4b2500b31f7 100644
--- a/spec/lib/banzai/filter/external_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_link_filter_spec.rb
@@ -156,6 +156,18 @@ describe Banzai::Filter::ExternalLinkFilter do
expect(doc_email.to_html).to include('http://xn--example-6p25f.com/</a>')
end
end
+
+ context 'autolinked image' do
+ let(:html) { %q(<a href="https://assets.example.com/6d8b/634c" data-canonical-src="http://exa%F0%9F%98%84mple.com/test.png"><img src="http://exa%F0%9F%98%84mple.com/test.png" data-canonical-src="http://exa%F0%9F%98%84mple.com/test.png"></a>) }
+ let(:doc) { filter(html) }
+
+ it_behaves_like 'an external link with rel attribute'
+
+ it 'adds a toolip with punycode' do
+ expect(doc.to_html).to include('class="has-tooltip"')
+ expect(doc.to_html).to include('title="http://xn--example-6p25f.com/test.png"')
+ end
+ end
end
context 'for links that look malicious' do
diff --git a/spec/lib/banzai/filter/front_matter_filter_spec.rb b/spec/lib/banzai/filter/front_matter_filter_spec.rb
index 90b383dbcff..1b5b1770615 100644
--- a/spec/lib/banzai/filter/front_matter_filter_spec.rb
+++ b/spec/lib/banzai/filter/front_matter_filter_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Banzai::Filter::FrontMatterFilter do
include FilterSpecHelper
diff --git a/spec/lib/banzai/filter/image_link_filter_spec.rb b/spec/lib/banzai/filter/image_link_filter_spec.rb
index 7b0cb675551..011e3a1e2da 100644
--- a/spec/lib/banzai/filter/image_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/image_link_filter_spec.rb
@@ -28,4 +28,11 @@ describe Banzai::Filter::ImageLinkFilter do
doc = filter(%Q(<p>test #{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')} inline</p>))
expect(doc.to_html).to match %r{^<p>test <a[^>]*><img[^>]*></a> inline</p>$}
end
+
+ it 'keep the data-canonical-src' do
+ doc = filter(%q(<img src="http://assets.example.com/6cd/4d7" data-canonical-src="http://example.com/test.png" />))
+
+ expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href']
+ expect(doc.at_css('img')['data-canonical-src']).to eq doc.at_css('a')['data-canonical-src']
+ end
end
diff --git a/spec/lib/banzai/filter/issuable_state_filter_spec.rb b/spec/lib/banzai/filter/issuable_state_filter_spec.rb
index 9f6dcded56f..cb431df7551 100644
--- a/spec/lib/banzai/filter/issuable_state_filter_spec.rb
+++ b/spec/lib/banzai/filter/issuable_state_filter_spec.rb
@@ -131,6 +131,14 @@ describe Banzai::Filter::IssuableStateFilter do
expect(doc.css('a').last.text).to eq("#{closed_issue.to_reference} (closed)")
end
+
+ it 'appends state to moved issue references' do
+ moved_issue = create(:issue, :closed, project: project, moved_to: create_issue(:opened))
+ link = create_link(moved_issue.to_reference, issue: moved_issue.id, reference_type: 'issue')
+ doc = filter(link, context)
+
+ expect(doc.css('a').last.text).to eq("#{moved_issue.to_reference} (moved)")
+ end
end
context 'for merge request references' do
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index 213a5459118..35e99d2586e 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -10,6 +10,11 @@ describe Banzai::Filter::LabelReferenceFilter do
let(:label) { create(:label, project: project) }
let(:reference) { label.to_reference }
+ it_behaves_like 'HTML text with references' do
+ let(:resource) { label }
+ let(:resource_text) { resource.title }
+ end
+
it 'requires project context' do
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index 3f021adc756..ab0c2c383c5 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -329,6 +329,10 @@ describe Banzai::Filter::MilestoneReferenceFilter do
it_behaves_like 'cross-project / same-namespace complete reference'
it_behaves_like 'cross project shorthand reference'
it_behaves_like 'references with HTML entities'
+ it_behaves_like 'HTML text with references' do
+ let(:resource) { milestone }
+ let(:resource_text) { "#{resource.class.reference_prefix}#{resource.title}" }
+ end
end
shared_context 'group milestones' do
@@ -340,6 +344,10 @@ describe Banzai::Filter::MilestoneReferenceFilter do
it_behaves_like 'String-based multi-word references in quotes'
it_behaves_like 'referencing a milestone in a link href'
it_behaves_like 'references with HTML entities'
+ it_behaves_like 'HTML text with references' do
+ let(:resource) { milestone }
+ let(:resource_text) { "#{resource.class.reference_prefix}#{resource.title}" }
+ end
it 'does not support references by IID' do
doc = reference_filter("See #{Milestone.reference_prefix}#{milestone.iid}")
diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb
index 69f9c1ae829..927d226c400 100644
--- a/spec/lib/banzai/filter/project_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb
@@ -26,10 +26,18 @@ describe Banzai::Filter::ProjectReferenceFilter do
expect(reference_filter(act).to_html).to eq(CGI.escapeHTML(exp))
end
- it 'fails fast for long invalid string' do
- expect do
- Timeout.timeout(5.seconds) { reference_filter("A" * 50000).to_html }
- end.not_to raise_error
+ context 'when invalid reference strings are very long' do
+ shared_examples_for 'fails fast' do |ref_string|
+ it 'fails fast for long strings' do
+ # took well under 1 second in CI https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/3267#note_172824
+ expect do
+ Timeout.timeout(3.seconds) { reference_filter(ref_string).to_html }
+ end.not_to raise_error
+ end
+ end
+
+ it_behaves_like 'fails fast', 'A' * 50000
+ it_behaves_like 'fails fast', '/a' * 50000
end
it 'allows references with text after the > character' do
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index ecb83b6cb66..f8b3748c663 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -7,6 +7,7 @@ describe Banzai::Filter::RelativeLinkFilter do
contexts.reverse_merge!({
commit: commit,
project: project,
+ current_user: user,
group: group,
project_wiki: project_wiki,
ref: ref,
@@ -33,7 +34,8 @@ describe Banzai::Filter::RelativeLinkFilter do
%(<div>#{element}</div>)
end
- let(:project) { create(:project, :repository) }
+ let(:project) { create(:project, :repository, :public) }
+ let(:user) { create(:user) }
let(:group) { nil }
let(:project_path) { project.full_path }
let(:ref) { 'markdown' }
@@ -75,6 +77,11 @@ describe Banzai::Filter::RelativeLinkFilter do
include_examples :preserve_unchanged
end
+ context 'without project repository access' do
+ let(:project) { create(:project, :repository, repository_access_level: ProjectFeature::PRIVATE) }
+ include_examples :preserve_unchanged
+ end
+
it 'does not raise an exception on invalid URIs' do
act = link("://foo")
expect { filter(act) }.not_to raise_error
@@ -282,51 +289,63 @@ describe Banzai::Filter::RelativeLinkFilter do
let(:relative_path) { "/#{project.full_path}#{upload_path}" }
context 'to a project upload' do
- context 'with an absolute URL' do
- let(:absolute_path) { Gitlab.config.gitlab.url + relative_path }
- let(:only_path) { false }
+ shared_examples 'rewrite project uploads' do
+ context 'with an absolute URL' do
+ let(:absolute_path) { Gitlab.config.gitlab.url + relative_path }
+ let(:only_path) { false }
- it 'rewrites the link correctly' do
+ it 'rewrites the link correctly' do
+ doc = filter(link(upload_path))
+
+ expect(doc.at_css('a')['href']).to eq(absolute_path)
+ end
+ end
+
+ it 'rebuilds relative URL for a link' do
doc = filter(link(upload_path))
+ expect(doc.at_css('a')['href']).to eq(relative_path)
- expect(doc.at_css('a')['href']).to eq(absolute_path)
+ doc = filter(nested(link(upload_path)))
+ expect(doc.at_css('a')['href']).to eq(relative_path)
end
- end
- it 'rebuilds relative URL for a link' do
- doc = filter(link(upload_path))
- expect(doc.at_css('a')['href']).to eq(relative_path)
+ it 'rebuilds relative URL for an image' do
+ doc = filter(image(upload_path))
+ expect(doc.at_css('img')['src']).to eq(relative_path)
- doc = filter(nested(link(upload_path)))
- expect(doc.at_css('a')['href']).to eq(relative_path)
- end
+ doc = filter(nested(image(upload_path)))
+ expect(doc.at_css('img')['src']).to eq(relative_path)
+ end
- it 'rebuilds relative URL for an image' do
- doc = filter(image(upload_path))
- expect(doc.at_css('img')['src']).to eq(relative_path)
+ it 'does not modify absolute URL' do
+ doc = filter(link('http://example.com'))
+ expect(doc.at_css('a')['href']).to eq 'http://example.com'
+ end
- doc = filter(nested(image(upload_path)))
- expect(doc.at_css('img')['src']).to eq(relative_path)
- end
+ it 'supports unescaped Unicode filenames' do
+ path = '/uploads/한글.png'
+ doc = filter(link(path))
- it 'does not modify absolute URL' do
- doc = filter(link('http://example.com'))
- expect(doc.at_css('a')['href']).to eq 'http://example.com'
- end
+ expect(doc.at_css('a')['href']).to eq("/#{project.full_path}/uploads/%ED%95%9C%EA%B8%80.png")
+ end
- it 'supports unescaped Unicode filenames' do
- path = '/uploads/한글.png'
- doc = filter(link(path))
+ it 'supports escaped Unicode filenames' do
+ path = '/uploads/한글.png'
+ escaped = Addressable::URI.escape(path)
+ doc = filter(image(escaped))
- expect(doc.at_css('a')['href']).to eq("/#{project.full_path}/uploads/%ED%95%9C%EA%B8%80.png")
+ expect(doc.at_css('img')['src']).to eq("/#{project.full_path}/uploads/%ED%95%9C%EA%B8%80.png")
+ end
end
- it 'supports escaped Unicode filenames' do
- path = '/uploads/한글.png'
- escaped = Addressable::URI.escape(path)
- doc = filter(image(escaped))
+ context 'without project repository access' do
+ let(:project) { create(:project, :repository, repository_access_level: ProjectFeature::PRIVATE) }
+
+ it_behaves_like 'rewrite project uploads'
+ end
- expect(doc.at_css('img')['src']).to eq("/#{project.full_path}/uploads/%ED%95%9C%EA%B8%80.png")
+ context 'with project repository access' do
+ it_behaves_like 'rewrite project uploads'
end
end
diff --git a/spec/lib/banzai/filter/video_link_filter_spec.rb b/spec/lib/banzai/filter/video_link_filter_spec.rb
index 483e806624c..cd932f502f3 100644
--- a/spec/lib/banzai/filter/video_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/video_link_filter_spec.rb
@@ -49,4 +49,26 @@ describe Banzai::Filter::VideoLinkFilter do
expect(element['src']).to eq '/path/my_image.jpg'
end
end
+
+ context 'when asset proxy is enabled' do
+ it 'uses the correct src' do
+ stub_asset_proxy_setting(enabled: true)
+
+ proxy_src = 'https://assets.example.com/6d8b63'
+ canonical_src = 'http://example.com/test.mp4'
+ image = %(<img src="#{proxy_src}" data-canonical-src="#{canonical_src}" />)
+ container = filter(image, asset_proxy_enabled: true).children.first
+
+ expect(container['class']).to eq 'video-container'
+
+ video, paragraph = container.children
+
+ expect(video['src']).to eq proxy_src
+ expect(video['data-canonical-src']).to eq canonical_src
+
+ link = paragraph.children.first
+
+ expect(link['href']).to eq proxy_src
+ end
+ end
end
diff --git a/spec/lib/banzai/pipeline/description_pipeline_spec.rb b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
index d032ec71e45..5ecd3df5151 100644
--- a/spec/lib/banzai/pipeline/description_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Banzai::Pipeline::DescriptionPipeline do
def parse(html)
diff --git a/spec/lib/banzai/pipeline/email_pipeline_spec.rb b/spec/lib/banzai/pipeline/email_pipeline_spec.rb
index eea25320f3d..77186861225 100644
--- a/spec/lib/banzai/pipeline/email_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/email_pipeline_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Banzai::Pipeline::EmailPipeline do
describe '.filters' do
diff --git a/spec/lib/banzai/pipeline/full_pipeline_spec.rb b/spec/lib/banzai/pipeline/full_pipeline_spec.rb
index 2b4d1b58676..f63b86d1451 100644
--- a/spec/lib/banzai/pipeline/full_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/full_pipeline_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Banzai::Pipeline::FullPipeline do
describe 'References' do
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index 0a3e0962452..7eb63fea413 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Banzai::Pipeline::GfmPipeline do
describe 'integration between parsing regular and external issue references' do
@@ -142,4 +142,48 @@ describe Banzai::Pipeline::GfmPipeline do
expect(output).to include(Gitlab::Routing.url_helpers.milestone_path(milestone))
end
end
+
+ describe 'asset proxy' do
+ let(:project) { create(:project, :public) }
+ let(:image) { '![proxy](http://example.com/test.png)' }
+ let(:proxy) { 'https://assets.example.com/08df250eeeef1a8cf2c761475ac74c5065105612/687474703a2f2f6578616d706c652e636f6d2f746573742e706e67' }
+ let(:version) { Gitlab::CurrentSettings.current_application_settings.local_markdown_version }
+
+ before do
+ stub_asset_proxy_setting(enabled: true)
+ stub_asset_proxy_setting(secret_key: 'shared-secret')
+ stub_asset_proxy_setting(url: 'https://assets.example.com')
+ stub_asset_proxy_setting(whitelist: %W(gitlab.com *.mydomain.com #{Gitlab.config.gitlab.host}))
+ stub_asset_proxy_setting(domain_regexp: Banzai::Filter::AssetProxyFilter.compile_whitelist(Gitlab.config.asset_proxy.whitelist))
+ end
+
+ it 'replaces a lazy loaded img src' do
+ output = described_class.to_html(image, project: project)
+ doc = Nokogiri::HTML.fragment(output)
+ result = doc.css('img').first
+
+ expect(result['data-src']).to eq(proxy)
+ end
+
+ it 'autolinks images to the proxy' do
+ output = described_class.to_html(image, project: project)
+ doc = Nokogiri::HTML.fragment(output)
+ result = doc.css('a').first
+
+ expect(result['href']).to eq(proxy)
+ expect(result['data-canonical-src']).to eq('http://example.com/test.png')
+ end
+
+ it 'properly adds tooltips to link for IDN images' do
+ image = '![proxy](http://exa😄mple.com/test.png)'
+ proxy = 'https://assets.example.com/6d8b634c412a23c6bfe1b2963f174febf5635ddd/687474703a2f2f6578612546302539462539382538346d706c652e636f6d2f746573742e706e67'
+ output = described_class.to_html(image, project: project)
+ doc = Nokogiri::HTML.fragment(output)
+ result = doc.css('a').first
+
+ expect(result['href']).to eq(proxy)
+ expect(result['data-canonical-src']).to eq('http://exa%F0%9F%98%84mple.com/test.png')
+ expect(result['title']).to eq 'http://xn--example-6p25f.com/test.png'
+ end
+ end
end
diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
index 015af20f220..4a485fbc2bd 100644
--- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Banzai::Pipeline::WikiPipeline do
describe 'TableOfContents' do
diff --git a/spec/lib/gitlab/anonymous_session_spec.rb b/spec/lib/gitlab/anonymous_session_spec.rb
new file mode 100644
index 00000000000..94daa0f2470
--- /dev/null
+++ b/spec/lib/gitlab/anonymous_session_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::AnonymousSession, :clean_gitlab_redis_shared_state do
+ let(:default_session_id) { '6919a6f1bb119dd7396fadc38fd18d0d' }
+ let(:additional_session_id) { '7919a6f1bb119dd7396fadc38fd18d0d' }
+
+ subject { new_anonymous_session }
+
+ def new_anonymous_session(session_id = default_session_id)
+ described_class.new('127.0.0.1', session_id: session_id)
+ end
+
+ describe '#store_session_id_per_ip' do
+ it 'adds session id to proper key' do
+ subject.store_session_id_per_ip
+
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to eq [default_session_id]
+ end
+ end
+
+ it 'adds expiration time to key' do
+ Timecop.freeze do
+ subject.store_session_id_per_ip
+
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis.ttl("session:lookup:ip:gitlab:127.0.0.1")).to eq(24.hours.to_i)
+ end
+ end
+ end
+
+ it 'adds id only once' do
+ subject.store_session_id_per_ip
+ subject.store_session_id_per_ip
+
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to eq [default_session_id]
+ end
+ end
+
+ context 'when there is already one session' do
+ it 'adds session id to proper key' do
+ subject.store_session_id_per_ip
+ new_anonymous_session(additional_session_id).store_session_id_per_ip
+
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to contain_exactly(default_session_id, additional_session_id)
+ end
+ end
+ end
+ end
+
+ describe '#stored_sessions' do
+ it 'returns all anonymous sessions per ip' do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.sadd("session:lookup:ip:gitlab:127.0.0.1", default_session_id)
+ redis.sadd("session:lookup:ip:gitlab:127.0.0.1", additional_session_id)
+ end
+
+ expect(subject.stored_sessions).to eq(2)
+ end
+ end
+
+ it 'removes obsolete lookup through ip entries' do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.sadd("session:lookup:ip:gitlab:127.0.0.1", default_session_id)
+ redis.sadd("session:lookup:ip:gitlab:127.0.0.1", additional_session_id)
+ end
+
+ subject.cleanup_session_per_ip_entries
+
+ Gitlab::Redis::SharedState.with do |redis|
+ expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to eq [additional_session_id]
+ end
+ end
+end
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 098c33f9cb1..0365d63ea9c 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -587,7 +587,8 @@ describe Gitlab::Auth do
:read_project,
:build_download_code,
:build_read_container_image,
- :build_create_container_image
+ :build_create_container_image,
+ :build_destroy_container_image
]
end
diff --git a/spec/lib/gitlab/authorized_keys_spec.rb b/spec/lib/gitlab/authorized_keys_spec.rb
index 42bc509eeef..adf36cf1050 100644
--- a/spec/lib/gitlab/authorized_keys_spec.rb
+++ b/spec/lib/gitlab/authorized_keys_spec.rb
@@ -5,10 +5,81 @@ require 'spec_helper'
describe Gitlab::AuthorizedKeys do
let(:logger) { double('logger').as_null_object }
- subject { described_class.new(logger) }
+ subject(:authorized_keys) { described_class.new(logger) }
+
+ describe '#accessible?' do
+ subject { authorized_keys.accessible? }
+
+ context 'authorized_keys file exists' do
+ before do
+ create_authorized_keys_fixture
+ end
+
+ after do
+ delete_authorized_keys_file
+ end
+
+ context 'can open file' do
+ it { is_expected.to be_truthy }
+ end
+
+ context 'cannot open file' do
+ before do
+ allow(File).to receive(:open).and_raise(Errno::EACCES)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ context 'authorized_keys file does not exist' do
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#create' do
+ subject { authorized_keys.create }
+
+ context 'authorized_keys file exists' do
+ before do
+ create_authorized_keys_fixture
+ end
+
+ after do
+ delete_authorized_keys_file
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'authorized_keys file does not exist' do
+ after do
+ delete_authorized_keys_file
+ end
+
+ it 'creates authorized_keys file' do
+ expect(subject).to be_truthy
+ expect(File.exist?(tmp_authorized_keys_path)).to be_truthy
+ end
+ end
+
+ context 'cannot create file' do
+ before do
+ allow(File).to receive(:open).and_raise(Errno::EACCES)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
describe '#add_key' do
+ let(:id) { 'key-741' }
+
+ subject { authorized_keys.add_key(id, key) }
+
context 'authorized_keys file exists' do
+ let(:key) { 'ssh-rsa AAAAB3NzaDAxx2E trailing garbage' }
+
before do
create_authorized_keys_fixture
end
@@ -21,19 +92,20 @@ describe Gitlab::AuthorizedKeys do
auth_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-741\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaDAxx2E"
expect(logger).to receive(:info).with('Adding key (key-741): ssh-rsa AAAAB3NzaDAxx2E')
- expect(subject.add_key('key-741', 'ssh-rsa AAAAB3NzaDAxx2E trailing garbage'))
- .to be_truthy
+ expect(subject).to be_truthy
expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{auth_line}\n")
end
end
context 'authorized_keys file does not exist' do
+ let(:key) { 'ssh-rsa AAAAB3NzaDAxx2E' }
+
before do
delete_authorized_keys_file
end
it 'creates the file' do
- expect(subject.add_key('key-741', 'ssh-rsa AAAAB3NzaDAxx2E')).to be_truthy
+ expect(subject).to be_truthy
expect(File.exist?(tmp_authorized_keys_path)).to be_truthy
end
end
@@ -47,6 +119,8 @@ describe Gitlab::AuthorizedKeys do
]
end
+ subject { authorized_keys.batch_add_keys(keys) }
+
context 'authorized_keys file exists' do
before do
create_authorized_keys_fixture
@@ -62,7 +136,7 @@ describe Gitlab::AuthorizedKeys do
expect(logger).to receive(:info).with('Adding key (key-12): ssh-dsa ASDFASGADG')
expect(logger).to receive(:info).with('Adding key (key-123): ssh-rsa GFDGDFSGSDFG')
- expect(subject.batch_add_keys(keys)).to be_truthy
+ expect(subject).to be_truthy
expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{auth_line1}\n#{auth_line2}\n")
end
@@ -70,7 +144,7 @@ describe Gitlab::AuthorizedKeys do
let(:keys) { [double(shell_id: 'key-123', key: "ssh-rsa A\tSDFA\nSGADG")] }
it "doesn't add keys" do
- expect(subject.batch_add_keys(keys)).to be_falsey
+ expect(subject).to be_falsey
expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n")
end
end
@@ -82,16 +156,28 @@ describe Gitlab::AuthorizedKeys do
end
it 'creates the file' do
- expect(subject.batch_add_keys(keys)).to be_truthy
+ expect(subject).to be_truthy
expect(File.exist?(tmp_authorized_keys_path)).to be_truthy
end
end
end
describe '#rm_key' do
+ let(:key) { 'key-741' }
+
+ subject { authorized_keys.rm_key(key) }
+
context 'authorized_keys file exists' do
+ let(:other_line) { "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-742\",options ssh-rsa AAAAB3NzaDAxx2E" }
+ let(:delete_line) { "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-741\",options ssh-rsa AAAAB3NzaDAxx2E" }
+
before do
create_authorized_keys_fixture
+
+ File.open(tmp_authorized_keys_path, 'a') do |auth_file|
+ auth_file.puts delete_line
+ auth_file.puts other_line
+ end
end
after do
@@ -99,16 +185,10 @@ describe Gitlab::AuthorizedKeys do
end
it "removes the right line" do
- other_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-742\",options ssh-rsa AAAAB3NzaDAxx2E"
- delete_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-741\",options ssh-rsa AAAAB3NzaDAxx2E"
erased_line = delete_line.gsub(/./, '#')
- File.open(tmp_authorized_keys_path, 'a') do |auth_file|
- auth_file.puts delete_line
- auth_file.puts other_line
- end
expect(logger).to receive(:info).with('Removing key (key-741)')
- expect(subject.rm_key('key-741')).to be_truthy
+ expect(subject).to be_truthy
expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{erased_line}\n#{other_line}\n")
end
end
@@ -118,13 +198,13 @@ describe Gitlab::AuthorizedKeys do
delete_authorized_keys_file
end
- it 'returns false' do
- expect(subject.rm_key('key-741')).to be_falsey
- end
+ it { is_expected.to be_falsey }
end
end
describe '#clear' do
+ subject { authorized_keys.clear }
+
context 'authorized_keys file exists' do
before do
create_authorized_keys_fixture
@@ -134,9 +214,7 @@ describe Gitlab::AuthorizedKeys do
delete_authorized_keys_file
end
- it "returns true" do
- expect(subject.clear).to be_truthy
- end
+ it { is_expected.to be_truthy }
end
context 'authorized_keys file does not exist' do
@@ -144,13 +222,13 @@ describe Gitlab::AuthorizedKeys do
delete_authorized_keys_file
end
- it "still returns true" do
- expect(subject.clear).to be_truthy
- end
+ it { is_expected.to be_truthy }
end
end
describe '#list_key_ids' do
+ subject { authorized_keys.list_key_ids }
+
context 'authorized_keys file exists' do
before do
create_authorized_keys_fixture(
@@ -163,9 +241,7 @@ describe Gitlab::AuthorizedKeys do
delete_authorized_keys_file
end
- it 'returns array of key IDs' do
- expect(subject.list_key_ids).to eq([1, 2, 3, 9000])
- end
+ it { is_expected.to eq([1, 2, 3, 9000]) }
end
context 'authorized_keys file does not exist' do
@@ -173,9 +249,7 @@ describe Gitlab::AuthorizedKeys do
delete_authorized_keys_file
end
- it 'returns an empty array' do
- expect(subject.list_key_ids).to be_empty
- end
+ it { is_expected.to be_empty }
end
end
diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
index ad4fa4fe03a..c1f6871a568 100644
--- a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable, :migration, schema: 20190315191339 do
let(:namespaces) { table(:namespaces) }
diff --git a/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb b/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb
index 740781f1aa5..fd35ddc49a2 100644
--- a/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb
+++ b/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::BackgroundMigration::ResetMergeStatus, :migration, schema: 20190528180441 do
let(:namespaces) { table(:namespaces) }
diff --git a/spec/lib/gitlab/checks/project_created_spec.rb b/spec/lib/gitlab/checks/project_created_spec.rb
index 14cb5e6ec66..373fef2a240 100644
--- a/spec/lib/gitlab/checks/project_created_spec.rb
+++ b/spec/lib/gitlab/checks/project_created_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/checks/project_moved_spec.rb b/spec/lib/gitlab/checks/project_moved_spec.rb
index 3ca977aa48d..3de397760b4 100644
--- a/spec/lib/gitlab/checks/project_moved_spec.rb
+++ b/spec/lib/gitlab/checks/project_moved_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
index 775550f2acc..c7a5ac783b3 100644
--- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
+++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
@@ -87,7 +87,7 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
.with(cluster, environment: deployment.environment)
.and_return(namespace_builder)
- expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService)
+ expect(Clusters::Kubernetes::CreateOrUpdateNamespaceService)
.to receive(:new)
.with(cluster: cluster, kubernetes_namespace: kubernetes_namespace)
.and_return(service)
@@ -107,7 +107,7 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
it 'creates a namespace using the tokenless record' do
expect(Clusters::BuildKubernetesNamespaceService).not_to receive(:new)
- expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService)
+ expect(Clusters::Kubernetes::CreateOrUpdateNamespaceService)
.to receive(:new)
.with(cluster: cluster, kubernetes_namespace: kubernetes_namespace)
.and_return(service)
@@ -123,7 +123,7 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
end
it 'does not create a namespace' do
- expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:new)
+ expect(Clusters::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:new)
subject
end
diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
index dd536a241bd..af995f4869a 100644
--- a/spec/lib/gitlab/ci/config/external/file/base_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb
@@ -41,6 +41,12 @@ describe Gitlab::Ci::Config::External::File::Base do
end
describe '#valid?' do
+ context 'when location is not a string' do
+ let(:location) { %w(some/file.txt other/file.txt) }
+
+ it { is_expected.not_to be_valid }
+ end
+
context 'when location is not a YAML file' do
let(:location) { 'some/file.txt' }
diff --git a/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb b/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
index eed2a1b7b48..e6321d48e11 100644
--- a/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
+++ b/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::DatabaseImporters::CommonMetrics::Importer do
subject { described_class.new }
diff --git a/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb b/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb
index 94f544e59b3..e4e8a85e7bc 100644
--- a/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb
+++ b/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::DatabaseImporters::CommonMetrics::PrometheusMetric do
it 'group enum equals ::PrometheusMetric' do
diff --git a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
index b25f0f6b887..aab6fbcbbd1 100644
--- a/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
+++ b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
@@ -130,7 +130,7 @@ describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
end
it 'returns error when saving project ID fails' do
- allow(application_setting).to receive(:update) { false }
+ allow(application_setting).to receive(:save) { false }
expect { result }.to raise_error(StandardError, 'Could not save project ID')
end
@@ -176,14 +176,28 @@ describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
end
context 'with non default prometheus address' do
+ let(:listen_address) { 'https://localhost:9090' }
+
let(:prometheus_settings) do
{
enable: true,
- listen_address: 'https://localhost:9090'
+ listen_address: listen_address
}
end
it_behaves_like 'has prometheus service', 'https://localhost:9090'
+
+ context 'with :9090 symbol' do
+ let(:listen_address) { :':9090' }
+
+ it_behaves_like 'has prometheus service', 'http://localhost:9090'
+ end
+
+ context 'with 0.0.0.0:9090' do
+ let(:listen_address) { '0.0.0.0:9090' }
+
+ it_behaves_like 'has prometheus service', 'http://localhost:9090'
+ end
end
context 'when prometheus setting is not present in gitlab.yml' do
diff --git a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
index 3eb5db51224..6d61edaa870 100644
--- a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::DependencyLinker::CartfileLinker do
describe '.support?' do
diff --git a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
index 6bef6f57e64..cc1f09628ef 100644
--- a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::DependencyLinker::ComposerJsonLinker do
describe '.support?' do
diff --git a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
index 6ecdb0d1247..c6b6dfa77cb 100644
--- a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::DependencyLinker::GemfileLinker do
describe '.support?' do
diff --git a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
index 256fe58925c..c1cbfa31684 100644
--- a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::DependencyLinker::GemspecLinker do
describe '.support?' do
diff --git a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
index f620d1b590c..9f8542a76c9 100644
--- a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::DependencyLinker::GodepsJsonLinker do
describe '.support?' do
diff --git a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
index 71e9381eaad..eb0c5e0675a 100644
--- a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::DependencyLinker::PackageJsonLinker do
describe '.support?' do
diff --git a/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb b/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb
index 9bfb1b13a2b..5b69ef5af24 100644
--- a/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::DependencyLinker::Parser::Gemfile do
describe '#parse' do
diff --git a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
index eb81bc07760..77326e73505 100644
--- a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::DependencyLinker::PodfileLinker do
describe '.support?' do
diff --git a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
index 938726dd434..d522a08cdd9 100644
--- a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::DependencyLinker::PodspecJsonLinker do
describe '.support?' do
diff --git a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
index 540eb2fadfe..baabd0c0460 100644
--- a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::DependencyLinker::PodspecLinker do
describe '.support?' do
diff --git a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
index 957a87985a2..04ac5f10479 100644
--- a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::DependencyLinker::RequirementsTxtLinker do
describe '.support?' do
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
index 98e46d62ca0..3ea3334caf0 100644
--- a/spec/lib/gitlab/dependency_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::DependencyLinker do
describe '.link' do
diff --git a/spec/lib/gitlab/favicon_spec.rb b/spec/lib/gitlab/favicon_spec.rb
index dce56bbd2c4..63c26e29d73 100644
--- a/spec/lib/gitlab/favicon_spec.rb
+++ b/spec/lib/gitlab/favicon_spec.rb
@@ -1,14 +1,16 @@
-require 'rails_helper'
+require 'spec_helper'
RSpec.describe Gitlab::Favicon, :request_store do
+ include RailsHelpers
+
describe '.main' do
it 'defaults to favicon.png' do
- allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production'))
+ stub_rails_env('production')
expect(described_class.main).to match_asset_path '/assets/favicon.png'
end
it 'has blue favicon for development', unless: Gitlab.ee? do
- allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('development'))
+ stub_rails_env('development')
expect(described_class.main).to match_asset_path '/assets/favicon-blue.png'
end
@@ -24,7 +26,7 @@ RSpec.describe Gitlab::Favicon, :request_store do
context 'asset host' do
before do
- allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('production'))
+ stub_rails_env('production')
end
it 'returns a relative url when the asset host is not configured' do
diff --git a/spec/lib/gitlab/file_markdown_link_builder_spec.rb b/spec/lib/gitlab/file_markdown_link_builder_spec.rb
index feb2776c5d0..d9e2e162ae8 100644
--- a/spec/lib/gitlab/file_markdown_link_builder_spec.rb
+++ b/spec/lib/gitlab/file_markdown_link_builder_spec.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::FileMarkdownLinkBuilder do
let(:custom_class) do
diff --git a/spec/lib/gitlab/file_type_detection_spec.rb b/spec/lib/gitlab/file_type_detection_spec.rb
index 5e9b8988cc8..22ec7d414e8 100644
--- a/spec/lib/gitlab/file_type_detection_spec.rb
+++ b/spec/lib/gitlab/file_type_detection_spec.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::FileTypeDetection do
def upload_fixture(filename)
diff --git a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
index 1a4168f7317..474240cf620 100644
--- a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
+++ b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
@@ -35,7 +35,7 @@ describe Gitlab::Git::RuggedImpl::UseRugged, :seed_helper do
let(:args) { ['refs/heads/master', 1] }
before do
- allow(Gitlab::RuggedInstrumentation).to receive(:peek_enabled?).and_return(true)
+ allow(Gitlab::PerformanceBar).to receive(:enabled_for_request?).and_return(true)
end
it 'instruments Rugged call' do
diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb
index 47e6f5d4220..a3d7e42733d 100644
--- a/spec/lib/gitlab/gpg/commit_spec.rb
+++ b/spec/lib/gitlab/gpg/commit_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::Gpg::Commit do
describe '#signature' do
diff --git a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
index 1a2c6ef25c4..1dfca0b056c 100644
--- a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
+++ b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
describe '#run' do
diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb
index 48bbd7f854c..77d318c9b23 100644
--- a/spec/lib/gitlab/gpg_spec.rb
+++ b/spec/lib/gitlab/gpg_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::Gpg do
describe '.fingerprints_from_key' do
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 3c6b17c10ec..47ba7eff8ed 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -26,6 +26,7 @@ issues:
events:
- author
- project
+- group
- target
- push_event_payload
notes:
@@ -62,6 +63,8 @@ milestone:
- participants
- events
- boards
+- milestone_release
+- release
snippets:
- author
- project
@@ -72,6 +75,8 @@ releases:
- author
- project
- links
+- milestone_release
+- milestone
links:
- release
project_members:
@@ -483,3 +488,7 @@ lists:
- milestone
- board
- label
+- list_user_preferences
+milestone_releases:
+- milestone
+- release
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index d6e1fbaa979..0aef4887c75 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -396,6 +396,27 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(project.lfs_enabled).to be_falsey
end
+
+ it 'overrides project feature access levels' do
+ access_level_keys = project.project_feature.attributes.keys.select { |a| a =~ /_access_level/ }
+
+ # `pages_access_level` is not included, since it is not available in the public API
+ # and has a dependency on project's visibility level
+ # see ProjectFeature model
+ access_level_keys.delete('pages_access_level')
+
+ disabled_access_levels = Hash[access_level_keys.collect { |item| [item, 'disabled'] }]
+
+ project.create_import_data(data: { override_params: disabled_access_levels })
+
+ restored_project_json
+
+ aggregate_failures do
+ access_level_keys.each do |key|
+ expect(project.public_send(key)).to eq(ProjectFeature::DISABLED)
+ end
+ end
+ end
end
context 'with a project that has a group' do
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index f0545176a90..516e62c4728 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -33,6 +33,7 @@ Event:
- target_type
- target_id
- project_id
+- group_id
- created_at
- updated_at
- action
diff --git a/spec/lib/gitlab/internal_post_receive/response_spec.rb b/spec/lib/gitlab/internal_post_receive/response_spec.rb
new file mode 100644
index 00000000000..f43762c9248
--- /dev/null
+++ b/spec/lib/gitlab/internal_post_receive/response_spec.rb
@@ -0,0 +1,121 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::InternalPostReceive::Response do
+ subject { described_class.new }
+
+ describe '#add_merge_request_urls' do
+ context 'when there are urls_data' do
+ it 'adds a message for each merge request URL' do
+ urls_data = [
+ { new_merge_request: false, branch_name: 'foo', url: 'http://example.com/foo/bar/merge_requests/1' },
+ { new_merge_request: true, branch_name: 'bar', url: 'http://example.com/foo/bar/merge_requests/new?merge_request%5Bsource_branch%5D=bar' }
+ ]
+
+ subject.add_merge_request_urls(urls_data)
+
+ expected = [a_kind_of(described_class::Message), a_kind_of(described_class::Message)]
+ expect(subject.messages).to match(expected)
+ end
+ end
+ end
+
+ describe '#add_merge_request_url' do
+ context 'when :new_merge_request is false' do
+ it 'adds a basic message to view the existing merge request' do
+ url_data = { new_merge_request: false, branch_name: 'foo', url: 'http://example.com/foo/bar/merge_requests/1' }
+
+ subject.add_merge_request_url(url_data)
+
+ message = <<~MESSAGE.strip
+ View merge request for foo:
+ http://example.com/foo/bar/merge_requests/1
+ MESSAGE
+
+ expect(subject.messages.first.message).to eq(message)
+ expect(subject.messages.first.type).to eq(:basic)
+ end
+ end
+
+ context 'when :new_merge_request is true' do
+ it 'adds a basic message to create a new merge request' do
+ url_data = { new_merge_request: true, branch_name: 'bar', url: 'http://example.com/foo/bar/merge_requests/new?merge_request%5Bsource_branch%5D=bar' }
+
+ subject.add_merge_request_url(url_data)
+
+ message = <<~MESSAGE.strip
+ To create a merge request for bar, visit:
+ http://example.com/foo/bar/merge_requests/new?merge_request%5Bsource_branch%5D=bar
+ MESSAGE
+
+ expect(subject.messages.first.message).to eq(message)
+ expect(subject.messages.first.type).to eq(:basic)
+ end
+ end
+ end
+
+ describe '#add_basic_message' do
+ context 'when text is present' do
+ it 'adds a basic message' do
+ subject.add_basic_message('hello')
+
+ expect(subject.messages.first.message).to eq('hello')
+ expect(subject.messages.first.type).to eq(:basic)
+ end
+ end
+
+ context 'when text is blank' do
+ it 'does not add a message' do
+ subject.add_basic_message(' ')
+
+ expect(subject.messages).to be_blank
+ end
+ end
+ end
+
+ describe '#add_alert_message' do
+ context 'when text is present' do
+ it 'adds a alert message' do
+ subject.add_alert_message('hello')
+
+ expect(subject.messages.first.message).to eq('hello')
+ expect(subject.messages.first.type).to eq(:alert)
+ end
+ end
+
+ context 'when text is blank' do
+ it 'does not add a message' do
+ subject.add_alert_message(' ')
+
+ expect(subject.messages).to be_blank
+ end
+ end
+ end
+
+ describe '#reference_counter_decreased' do
+ context 'initially' do
+ it 'reference_counter_decreased is set to false' do
+ expect(subject.reference_counter_decreased).to eq(false)
+ end
+ end
+ end
+
+ describe '#reference_counter_decreased=' do
+ context 'when the argument is truthy' do
+ it 'reference_counter_decreased is truthy' do
+ subject.reference_counter_decreased = true
+
+ expect(subject.reference_counter_decreased).to be_truthy
+ end
+ end
+
+ context 'when the argument is falsey' do
+ it 'reference_counter_decreased is falsey' do
+ subject.reference_counter_decreased = false
+
+ expect(subject.reference_counter_decreased).to be_falsey
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
index 7395b095454..f7f510f01db 100644
--- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:files) { { 'ca.pem': 'some file content' } }
diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
index fce2aded786..64cadcc011c 100644
--- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::Kubernetes::Helm::Pod do
describe '#generate' do
diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
index f49d4e23e39..e5d688aa391 100644
--- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb
+++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
@@ -3,6 +3,7 @@
require 'spec_helper'
describe Gitlab::Kubernetes::KubeClient do
+ include StubRequests
include KubernetesHelpers
let(:api_url) { 'https://kubernetes.example.com/prefix' }
@@ -14,6 +15,17 @@ describe Gitlab::Kubernetes::KubeClient do
stub_kubeclient_discover(api_url)
end
+ def method_call(client, method_name)
+ case method_name
+ when /\A(get_|delete_)/
+ client.public_send(method_name)
+ when /\A(create_|update_)/
+ client.public_send(method_name, {})
+ else
+ raise "Unknown method name #{method_name}"
+ end
+ end
+
shared_examples 'a Kubeclient' do
it 'is a Kubeclient::Client' do
is_expected.to be_an_instance_of Kubeclient::Client
@@ -25,28 +37,30 @@ describe Gitlab::Kubernetes::KubeClient do
end
shared_examples 'redirection not allowed' do |method_name|
- before do
- redirect_url = 'https://not-under-our-control.example.com/api/v1/pods'
+ context 'api_url is redirected' do
+ before do
+ redirect_url = 'https://not-under-our-control.example.com/api/v1/pods'
- stub_request(:get, %r{\A#{api_url}/})
- .to_return(status: 302, headers: { location: redirect_url })
+ stub_request(:get, %r{\A#{api_url}/})
+ .to_return(status: 302, headers: { location: redirect_url })
- stub_request(:get, redirect_url)
- .to_return(status: 200, body: '{}')
- end
+ stub_request(:get, redirect_url)
+ .to_return(status: 200, body: '{}')
+ end
- it 'does not follow redirects' do
- method_call = -> do
- case method_name
- when /\A(get_|delete_)/
- client.public_send(method_name)
- when /\A(create_|update_)/
- client.public_send(method_name, {})
- else
- raise "Unknown method name #{method_name}"
- end
+ it 'does not follow redirects' do
+ expect { method_call(client, method_name) }.to raise_error(Kubeclient::HttpError)
end
- expect { method_call.call }.to raise_error(Kubeclient::HttpError)
+ end
+ end
+
+ shared_examples 'dns rebinding not allowed' do |method_name|
+ it 'does not allow DNS rebinding' do
+ stub_dns(api_url, ip_address: '8.8.8.8')
+ client
+
+ stub_dns(api_url, ip_address: '192.168.2.120')
+ expect { method_call(client, method_name) }.to raise_error(ArgumentError, /is blocked/)
end
end
@@ -160,6 +174,7 @@ describe Gitlab::Kubernetes::KubeClient do
].each do |method|
describe "##{method}" do
include_examples 'redirection not allowed', method
+ include_examples 'dns rebinding not allowed', method
it 'delegates to the core client' do
expect(client).to delegate_method(method).to(:core_client)
@@ -185,6 +200,7 @@ describe Gitlab::Kubernetes::KubeClient do
].each do |method|
describe "##{method}" do
include_examples 'redirection not allowed', method
+ include_examples 'dns rebinding not allowed', method
it 'delegates to the rbac client' do
expect(client).to delegate_method(method).to(:rbac_client)
@@ -203,6 +219,7 @@ describe Gitlab::Kubernetes::KubeClient do
describe '#get_deployments' do
include_examples 'redirection not allowed', 'get_deployments'
+ include_examples 'dns rebinding not allowed', 'get_deployments'
it 'delegates to the extensions client' do
expect(client).to delegate_method(:get_deployments).to(:extensions_client)
diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb
index f52095bf633..16595102375 100644
--- a/spec/lib/gitlab/middleware/go_spec.rb
+++ b/spec/lib/gitlab/middleware/go_spec.rb
@@ -202,7 +202,7 @@ describe Gitlab::Middleware::Go do
def expect_response_with_path(response, protocol, path)
repository_url = case protocol
when :ssh
- "ssh://git@#{Gitlab.config.gitlab.host}/#{path}.git"
+ "ssh://#{Gitlab.config.gitlab.user}@#{Gitlab.config.gitlab.host}/#{path}.git"
when :http, nil
"http://#{Gitlab.config.gitlab.host}/#{path}.git"
end
diff --git a/spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb b/spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb
new file mode 100644
index 00000000000..3b92261f0fe
--- /dev/null
+++ b/spec/lib/gitlab/performance_bar/with_top_level_warnings_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'rspec-parameterized'
+
+describe Gitlab::PerformanceBar::WithTopLevelWarnings do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { Module.new }
+
+ before do
+ subject.singleton_class.prepend(described_class)
+ end
+
+ describe '#has_warnings?' do
+ where(:has_warnings, :results) do
+ false | { data: {} }
+ false | { data: { gitaly: { warnings: [] } } }
+ true | { data: { gitaly: { warnings: [1] } } }
+ true | { data: { gitaly: { warnings: [] }, redis: { warnings: [1] } } }
+ end
+
+ with_them do
+ it do
+ expect(subject.has_warnings?(results)).to eq(has_warnings)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/performance_bar_spec.rb b/spec/lib/gitlab/performance_bar_spec.rb
index 8d8ac2aebbe..816db49d94a 100644
--- a/spec/lib/gitlab/performance_bar_spec.rb
+++ b/spec/lib/gitlab/performance_bar_spec.rb
@@ -6,14 +6,14 @@ describe Gitlab::PerformanceBar do
shared_examples 'allowed user IDs are cached' do
before do
# Warm the caches
- described_class.enabled?(user)
+ described_class.enabled_for_user?(user)
end
it 'caches the allowed user IDs in cache', :use_clean_rails_memory_store_caching do
expect do
expect(described_class.l1_cache_backend).to receive(:fetch).and_call_original
expect(described_class.l2_cache_backend).not_to receive(:fetch)
- expect(described_class.enabled?(user)).to be_truthy
+ expect(described_class.enabled_for_user?(user)).to be_truthy
end.not_to exceed_query_limit(0)
end
@@ -22,7 +22,7 @@ describe Gitlab::PerformanceBar do
expect do
expect(described_class.l1_cache_backend).to receive(:fetch).and_call_original
expect(described_class.l2_cache_backend).to receive(:fetch).and_call_original
- expect(described_class.enabled?(user)).to be_truthy
+ expect(described_class.enabled_for_user?(user)).to be_truthy
end.not_to exceed_query_limit(0)
end
end
@@ -32,7 +32,7 @@ describe Gitlab::PerformanceBar do
expect do
expect(described_class.l1_cache_backend).to receive(:fetch).and_call_original
expect(described_class.l2_cache_backend).to receive(:fetch).and_call_original
- expect(described_class.enabled?(user)).to be_truthy
+ expect(described_class.enabled_for_user?(user)).to be_truthy
end.not_to exceed_query_limit(2)
end
end
@@ -41,7 +41,7 @@ describe Gitlab::PerformanceBar do
it { expect(described_class.l1_cache_backend).to eq(Gitlab::ThreadMemoryCache.cache_backend) }
it { expect(described_class.l2_cache_backend).to eq(Rails.cache) }
- describe '.enabled?' do
+ describe '.enabled_for_user?' do
let(:user) { create(:user) }
before do
@@ -49,24 +49,24 @@ describe Gitlab::PerformanceBar do
end
it 'returns false when given user is nil' do
- expect(described_class.enabled?(nil)).to be_falsy
+ expect(described_class.enabled_for_user?(nil)).to be_falsy
end
it 'returns true when given user is an admin' do
user = build_stubbed(:user, :admin)
- expect(described_class.enabled?(user)).to be_truthy
+ expect(described_class.enabled_for_user?(user)).to be_truthy
end
it 'returns false when allowed_group_id is nil' do
expect(described_class).to receive(:allowed_group_id).and_return(nil)
- expect(described_class.enabled?(user)).to be_falsy
+ expect(described_class.enabled_for_user?(user)).to be_falsy
end
context 'when allowed group ID does not exist' do
it 'returns false' do
- expect(described_class.enabled?(user)).to be_falsy
+ expect(described_class.enabled_for_user?(user)).to be_falsy
end
end
@@ -79,7 +79,7 @@ describe Gitlab::PerformanceBar do
context 'when user is not a member of the allowed group' do
it 'returns false' do
- expect(described_class.enabled?(user)).to be_falsy
+ expect(described_class.enabled_for_user?(user)).to be_falsy
end
it_behaves_like 'allowed user IDs are cached'
@@ -91,7 +91,7 @@ describe Gitlab::PerformanceBar do
end
it 'returns true' do
- expect(described_class.enabled?(user)).to be_truthy
+ expect(described_class.enabled_for_user?(user)).to be_truthy
end
it_behaves_like 'allowed user IDs are cached'
@@ -108,7 +108,7 @@ describe Gitlab::PerformanceBar do
end
it 'returns the nested group' do
- expect(described_class.enabled?(user)).to be_truthy
+ expect(described_class.enabled_for_user?(user)).to be_truthy
end
end
@@ -118,7 +118,7 @@ describe Gitlab::PerformanceBar do
end
it 'returns false' do
- expect(described_class.enabled?(user)).to be_falsy
+ expect(described_class.enabled_for_user?(user)).to be_falsy
end
end
end
diff --git a/spec/lib/gitlab/prometheus/metric_group_spec.rb b/spec/lib/gitlab/prometheus/metric_group_spec.rb
index a45dd0af91e..787f14daf47 100644
--- a/spec/lib/gitlab/prometheus/metric_group_spec.rb
+++ b/spec/lib/gitlab/prometheus/metric_group_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::Prometheus::MetricGroup do
describe '.common_metrics' do
diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb
index fd1338b55a6..808eb865a21 100644
--- a/spec/lib/gitlab/repository_cache_adapter_spec.rb
+++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb
@@ -6,7 +6,6 @@ describe Gitlab::RepositoryCacheAdapter do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:cache) { repository.send(:cache) }
- let(:redis_set_cache) { repository.send(:redis_set_cache) }
describe '#cache_method_output', :use_clean_rails_memory_store_caching do
let(:fallback) { 10 }
@@ -209,11 +208,9 @@ describe Gitlab::RepositoryCacheAdapter do
describe '#expire_method_caches' do
it 'expires the caches of the given methods' do
expect(cache).to receive(:expire).with(:rendered_readme)
- expect(cache).to receive(:expire).with(:branch_names)
- expect(redis_set_cache).to receive(:expire).with(:rendered_readme)
- expect(redis_set_cache).to receive(:expire).with(:branch_names)
+ expect(cache).to receive(:expire).with(:gitignore)
- repository.expire_method_caches(%i(rendered_readme branch_names))
+ repository.expire_method_caches(%i(rendered_readme gitignore))
end
it 'does not expire caches for non-existent methods' do
diff --git a/spec/lib/gitlab/repository_set_cache_spec.rb b/spec/lib/gitlab/repository_set_cache_spec.rb
deleted file mode 100644
index 87e51f801e5..00000000000
--- a/spec/lib/gitlab/repository_set_cache_spec.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
- let(:project) { create(:project) }
- let(:repository) { project.repository }
- let(:namespace) { "#{repository.full_path}:#{project.id}" }
- let(:cache) { described_class.new(repository) }
-
- describe '#cache_key' do
- subject { cache.cache_key(:foo) }
-
- it 'includes the namespace' do
- is_expected.to eq("foo:#{namespace}:set")
- end
-
- context 'with a given namespace' do
- let(:extra_namespace) { 'my:data' }
- let(:cache) { described_class.new(repository, extra_namespace: extra_namespace) }
-
- it 'includes the full namespace' do
- is_expected.to eq("foo:#{namespace}:#{extra_namespace}:set")
- end
- end
- end
-
- describe '#expire' do
- it 'expires the given key from the cache' do
- cache.write(:foo, ['value'])
-
- expect(cache.read(:foo)).to contain_exactly('value')
- expect(cache.expire(:foo)).to eq(1)
- expect(cache.read(:foo)).to be_empty
- end
- end
-
- describe '#exist?' do
- it 'checks whether the key exists' do
- expect(cache.exist?(:foo)).to be(false)
-
- cache.write(:foo, ['value'])
-
- expect(cache.exist?(:foo)).to be(true)
- end
- end
-
- describe '#fetch' do
- let(:blk) { -> { ['block value'] } }
-
- subject { cache.fetch(:foo, &blk) }
-
- it 'fetches the key from the cache when filled' do
- cache.write(:foo, ['value'])
-
- is_expected.to contain_exactly('value')
- end
-
- it 'writes the value of the provided block when empty' do
- cache.expire(:foo)
-
- is_expected.to contain_exactly('block value')
- expect(cache.read(:foo)).to contain_exactly('block value')
- end
- end
-
- describe '#include?' do
- it 'checks inclusion in the Redis set' do
- cache.write(:foo, ['value'])
-
- expect(cache.include?(:foo, 'value')).to be(true)
- expect(cache.include?(:foo, 'bar')).to be(false)
- end
- end
-end
diff --git a/spec/lib/gitlab/sanitizers/exif_spec.rb b/spec/lib/gitlab/sanitizers/exif_spec.rb
index 22c5f27dc6d..f882dbbdb5c 100644
--- a/spec/lib/gitlab/sanitizers/exif_spec.rb
+++ b/spec/lib/gitlab/sanitizers/exif_spec.rb
@@ -7,7 +7,9 @@ describe Gitlab::Sanitizers::Exif do
describe '#batch_clean' do
context 'with image uploads' do
- let!(:uploads) { create_list(:upload, 3, :with_file, :issuable_upload) }
+ set(:upload1) { create(:upload, :with_file, :issuable_upload) }
+ set(:upload2) { create(:upload, :with_file, :personal_snippet_upload) }
+ set(:upload3) { create(:upload, :with_file, created_at: 3.days.ago) }
it 'processes all uploads if range ID is not set' do
expect(sanitizer).to receive(:clean).exactly(3).times
@@ -18,7 +20,19 @@ describe Gitlab::Sanitizers::Exif do
it 'processes only uploads in the selected range' do
expect(sanitizer).to receive(:clean).once
- sanitizer.batch_clean(start_id: uploads[1].id, stop_id: uploads[1].id)
+ sanitizer.batch_clean(start_id: upload1.id, stop_id: upload1.id)
+ end
+
+ it 'processes only uploads for the selected uploader' do
+ expect(sanitizer).to receive(:clean).once
+
+ sanitizer.batch_clean(uploader: 'PersonalFileUploader')
+ end
+
+ it 'processes only uploads created since specified date' do
+ expect(sanitizer).to receive(:clean).exactly(2).times
+
+ sanitizer.batch_clean(since: 2.days.ago)
end
it 'pauses if sleep_time is set' do
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 0ba16b93ee7..fe4853fd819 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -52,38 +52,14 @@ describe Gitlab::Shell do
describe '#add_key' do
context 'when authorized_keys_enabled is true' do
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- allow(gitlab_shell)
- .to receive(:gitlab_shell_keys_path)
- .and_return(:gitlab_shell_keys_path)
- end
-
- it 'calls #gitlab_shell_fast_execute with add-key command' do
- expect(gitlab_shell)
- .to receive(:gitlab_shell_fast_execute)
- .with([
- :gitlab_shell_keys_path,
- 'add-key',
- 'key-123',
- 'ssh-rsa foobar'
- ])
-
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
- end
- end
-
- context 'authorized_keys_file set' do
- it 'calls Gitlab::AuthorizedKeys#add_key with id and key' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ it 'calls Gitlab::AuthorizedKeys#add_key with id and key' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- expect(gitlab_authorized_keys)
- .to receive(:add_key)
- .with('key-123', 'ssh-rsa foobar')
+ expect(gitlab_authorized_keys)
+ .to receive(:add_key)
+ .with('key-123', 'ssh-rsa foobar')
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar')
- end
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar')
end
end
@@ -92,24 +68,10 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: false)
end
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- end
+ it 'does nothing' do
+ expect(Gitlab::AuthorizedKeys).not_to receive(:new)
- it 'does nothing' do
- expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
-
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
- end
- end
-
- context 'authorized_keys_file set' do
- it 'does nothing' do
- expect(Gitlab::AuthorizedKeys).not_to receive(:new)
-
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
- end
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
end
end
@@ -118,38 +80,14 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: nil)
end
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- allow(gitlab_shell)
- .to receive(:gitlab_shell_keys_path)
- .and_return(:gitlab_shell_keys_path)
- end
-
- it 'calls #gitlab_shell_fast_execute with add-key command' do
- expect(gitlab_shell)
- .to receive(:gitlab_shell_fast_execute)
- .with([
- :gitlab_shell_keys_path,
- 'add-key',
- 'key-123',
- 'ssh-rsa foobar'
- ])
-
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
- end
- end
-
- context 'authorized_keys_file set' do
- it 'calls Gitlab::AuthorizedKeys#add_key with id and key' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ it 'calls Gitlab::AuthorizedKeys#add_key with id and key' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- expect(gitlab_authorized_keys)
- .to receive(:add_key)
- .with('key-123', 'ssh-rsa foobar')
+ expect(gitlab_authorized_keys)
+ .to receive(:add_key)
+ .with('key-123', 'ssh-rsa foobar')
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar')
- end
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar')
end
end
end
@@ -158,50 +96,14 @@ describe Gitlab::Shell do
let(:keys) { [double(shell_id: 'key-123', key: 'ssh-rsa foobar')] }
context 'when authorized_keys_enabled is true' do
- context 'authorized_keys_file not set' do
- let(:io) { double }
-
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- end
-
- context 'valid keys' do
- before do
- allow(gitlab_shell)
- .to receive(:gitlab_shell_keys_path)
- .and_return(:gitlab_shell_keys_path)
- end
-
- it 'calls gitlab-keys with batch-add-keys command' do
- expect(IO)
- .to receive(:popen)
- .with("gitlab_shell_keys_path batch-add-keys", 'w')
- .and_yield(io)
-
- expect(io).to receive(:puts).with("key-123\tssh-rsa foobar")
- expect(gitlab_shell.batch_add_keys(keys)).to be_truthy
- end
- end
-
- context 'invalid keys' do
- let(:keys) { [double(shell_id: 'key-123', key: "ssh-rsa A\tSDFA\nSGADG")] }
-
- it 'catches failure and returns false' do
- expect(gitlab_shell.batch_add_keys(keys)).to be_falsey
- end
- end
- end
+ it 'calls Gitlab::AuthorizedKeys#batch_add_keys with keys to be added' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- context 'authorized_keys_file set' do
- it 'calls Gitlab::AuthorizedKeys#batch_add_keys with keys to be added' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ expect(gitlab_authorized_keys)
+ .to receive(:batch_add_keys)
+ .with(keys)
- expect(gitlab_authorized_keys)
- .to receive(:batch_add_keys)
- .with(keys)
-
- gitlab_shell.batch_add_keys(keys)
- end
+ gitlab_shell.batch_add_keys(keys)
end
end
@@ -210,24 +112,10 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: false)
end
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- end
-
- it 'does nothing' do
- expect(IO).not_to receive(:popen)
-
- gitlab_shell.batch_add_keys(keys)
- end
- end
-
- context 'authorized_keys_file set' do
- it 'does nothing' do
- expect(Gitlab::AuthorizedKeys).not_to receive(:new)
+ it 'does nothing' do
+ expect(Gitlab::AuthorizedKeys).not_to receive(:new)
- gitlab_shell.batch_add_keys(keys)
- end
+ gitlab_shell.batch_add_keys(keys)
end
end
@@ -236,72 +124,25 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: nil)
end
- context 'authorized_keys_file not set' do
- let(:io) { double }
+ it 'calls Gitlab::AuthorizedKeys#batch_add_keys with keys to be added' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- allow(gitlab_shell)
- .to receive(:gitlab_shell_keys_path)
- .and_return(:gitlab_shell_keys_path)
- end
-
- it 'calls gitlab-keys with batch-add-keys command' do
- expect(IO)
- .to receive(:popen)
- .with("gitlab_shell_keys_path batch-add-keys", 'w')
- .and_yield(io)
+ expect(gitlab_authorized_keys)
+ .to receive(:batch_add_keys)
+ .with(keys)
- expect(io).to receive(:puts).with("key-123\tssh-rsa foobar")
-
- gitlab_shell.batch_add_keys(keys)
- end
- end
-
- context 'authorized_keys_file set' do
- it 'calls Gitlab::AuthorizedKeys#batch_add_keys with keys to be added' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
-
- expect(gitlab_authorized_keys)
- .to receive(:batch_add_keys)
- .with(keys)
-
- gitlab_shell.batch_add_keys(keys)
- end
+ gitlab_shell.batch_add_keys(keys)
end
end
end
describe '#remove_key' do
context 'when authorized_keys_enabled is true' do
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- allow(gitlab_shell)
- .to receive(:gitlab_shell_keys_path)
- .and_return(:gitlab_shell_keys_path)
- end
-
- it 'calls #gitlab_shell_fast_execute with rm-key command' do
- expect(gitlab_shell)
- .to receive(:gitlab_shell_fast_execute)
- .with([
- :gitlab_shell_keys_path,
- 'rm-key',
- 'key-123'
- ])
-
- gitlab_shell.remove_key('key-123')
- end
- end
+ it 'calls Gitlab::AuthorizedKeys#rm_key with the key to be removed' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ expect(gitlab_authorized_keys).to receive(:rm_key).with('key-123')
- context 'authorized_keys_file not set' do
- it 'calls Gitlab::AuthorizedKeys#rm_key with the key to be removed' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- expect(gitlab_authorized_keys).to receive(:rm_key).with('key-123')
-
- gitlab_shell.remove_key('key-123')
- end
+ gitlab_shell.remove_key('key-123')
end
end
@@ -310,24 +151,10 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: false)
end
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- end
-
- it 'does nothing' do
- expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+ it 'does nothing' do
+ expect(Gitlab::AuthorizedKeys).not_to receive(:new)
- gitlab_shell.remove_key('key-123')
- end
- end
-
- context 'authorized_keys_file set' do
- it 'does nothing' do
- expect(Gitlab::AuthorizedKeys).not_to receive(:new)
-
- gitlab_shell.remove_key('key-123')
- end
+ gitlab_shell.remove_key('key-123')
end
end
@@ -336,64 +163,22 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: nil)
end
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- allow(gitlab_shell)
- .to receive(:gitlab_shell_keys_path)
- .and_return(:gitlab_shell_keys_path)
- end
-
- it 'calls #gitlab_shell_fast_execute with rm-key command' do
- expect(gitlab_shell)
- .to receive(:gitlab_shell_fast_execute)
- .with([
- :gitlab_shell_keys_path,
- 'rm-key',
- 'key-123'
- ])
-
- gitlab_shell.remove_key('key-123')
- end
- end
-
- context 'authorized_keys_file not set' do
- it 'calls Gitlab::AuthorizedKeys#rm_key with the key to be removed' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- expect(gitlab_authorized_keys).to receive(:rm_key).with('key-123')
+ it 'calls Gitlab::AuthorizedKeys#rm_key with the key to be removed' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ expect(gitlab_authorized_keys).to receive(:rm_key).with('key-123')
- gitlab_shell.remove_key('key-123')
- end
+ gitlab_shell.remove_key('key-123')
end
end
end
describe '#remove_all_keys' do
context 'when authorized_keys_enabled is true' do
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- allow(gitlab_shell)
- .to receive(:gitlab_shell_keys_path)
- .and_return(:gitlab_shell_keys_path)
- end
+ it 'calls Gitlab::AuthorizedKeys#clear' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ expect(gitlab_authorized_keys).to receive(:clear)
- it 'calls #gitlab_shell_fast_execute with clear command' do
- expect(gitlab_shell)
- .to receive(:gitlab_shell_fast_execute)
- .with([:gitlab_shell_keys_path, 'clear'])
-
- gitlab_shell.remove_all_keys
- end
- end
-
- context 'authorized_keys_file set' do
- it 'calls Gitlab::AuthorizedKeys#clear' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- expect(gitlab_authorized_keys).to receive(:clear)
-
- gitlab_shell.remove_all_keys
- end
+ gitlab_shell.remove_all_keys
end
end
@@ -402,24 +187,10 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: false)
end
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- end
-
- it 'does nothing' do
- expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+ it 'does nothing' do
+ expect(Gitlab::AuthorizedKeys).not_to receive(:new)
- gitlab_shell.remove_all_keys
- end
- end
-
- context 'authorized_keys_file set' do
- it 'does nothing' do
- expect(Gitlab::AuthorizedKeys).not_to receive(:new)
-
- gitlab_shell.remove_all_keys
- end
+ gitlab_shell.remove_all_keys
end
end
@@ -428,163 +199,73 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: nil)
end
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- allow(gitlab_shell)
- .to receive(:gitlab_shell_keys_path)
- .and_return(:gitlab_shell_keys_path)
- end
-
- it 'calls #gitlab_shell_fast_execute with clear command' do
- expect(gitlab_shell)
- .to receive(:gitlab_shell_fast_execute)
- .with([:gitlab_shell_keys_path, 'clear'])
+ it 'calls Gitlab::AuthorizedKeys#clear' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ expect(gitlab_authorized_keys).to receive(:clear)
- gitlab_shell.remove_all_keys
- end
- end
-
- context 'authorized_keys_file set' do
- it 'calls Gitlab::AuthorizedKeys#clear' do
- expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- expect(gitlab_authorized_keys).to receive(:clear)
-
- gitlab_shell.remove_all_keys
- end
+ gitlab_shell.remove_all_keys
end
end
end
describe '#remove_keys_not_found_in_db' do
context 'when keys are in the file that are not in the DB' do
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- gitlab_shell.remove_all_keys
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF')
- @another_key = create(:key) # this one IS in the DB
- end
-
- it 'removes the keys' do
- expect(gitlab_shell).to receive(:remove_key).with('key-1234')
- expect(gitlab_shell).to receive(:remove_key).with('key-9876')
- expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@another_key.id}")
-
- gitlab_shell.remove_keys_not_found_in_db
- end
+ before do
+ gitlab_shell.remove_all_keys
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF')
+ @another_key = create(:key) # this one IS in the DB
end
- context 'authorized_keys_file set' do
- before do
- gitlab_shell.remove_all_keys
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF')
- @another_key = create(:key) # this one IS in the DB
- end
-
- it 'removes the keys' do
- expect(gitlab_shell).to receive(:remove_key).with('key-1234')
- expect(gitlab_shell).to receive(:remove_key).with('key-9876')
- expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@another_key.id}")
+ it 'removes the keys' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234')
+ expect(gitlab_shell).to receive(:remove_key).with('key-9876')
+ expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@another_key.id}")
- gitlab_shell.remove_keys_not_found_in_db
- end
+ gitlab_shell.remove_keys_not_found_in_db
end
end
context 'when keys there are duplicate keys in the file that are not in the DB' do
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- gitlab_shell.remove_all_keys
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- end
-
- it 'removes the keys' do
- expect(gitlab_shell).to receive(:remove_key).with('key-1234')
-
- gitlab_shell.remove_keys_not_found_in_db
- end
+ before do
+ gitlab_shell.remove_all_keys
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
end
- context 'authorized_keys_file set' do
- before do
- gitlab_shell.remove_all_keys
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- end
-
- it 'removes the keys' do
- expect(gitlab_shell).to receive(:remove_key).with('key-1234')
+ it 'removes the keys' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234')
- gitlab_shell.remove_keys_not_found_in_db
- end
+ gitlab_shell.remove_keys_not_found_in_db
end
end
context 'when keys there are duplicate keys in the file that ARE in the DB' do
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- gitlab_shell.remove_all_keys
- @key = create(:key)
- gitlab_shell.add_key(@key.shell_id, @key.key)
- end
-
- it 'does not remove the key' do
- expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@key.id}")
-
- gitlab_shell.remove_keys_not_found_in_db
- end
+ before do
+ gitlab_shell.remove_all_keys
+ @key = create(:key)
+ gitlab_shell.add_key(@key.shell_id, @key.key)
end
- context 'authorized_keys_file set' do
- before do
- gitlab_shell.remove_all_keys
- @key = create(:key)
- gitlab_shell.add_key(@key.shell_id, @key.key)
- end
-
- it 'does not remove the key' do
- expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@key.id}")
+ it 'does not remove the key' do
+ expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@key.id}")
- gitlab_shell.remove_keys_not_found_in_db
- end
+ gitlab_shell.remove_keys_not_found_in_db
end
end
unless ENV['CI'] # Skip in CI, it takes 1 minute
context 'when the first batch can be skipped, but the next batch has keys that are not in the DB' do
- context 'authorized_keys_file not set' do
- before do
- stub_gitlab_shell_setting(authorized_keys_file: nil)
- gitlab_shell.remove_all_keys
- 100.times { |i| create(:key) } # first batch is all in the DB
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- end
-
- it 'removes the keys not in the DB' do
- expect(gitlab_shell).to receive(:remove_key).with('key-1234')
-
- gitlab_shell.remove_keys_not_found_in_db
- end
+ before do
+ gitlab_shell.remove_all_keys
+ 100.times { |i| create(:key) } # first batch is all in the DB
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
end
- context 'authorized_keys_file set' do
- before do
- gitlab_shell.remove_all_keys
- 100.times { |i| create(:key) } # first batch is all in the DB
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- end
-
- it 'removes the keys not in the DB' do
- expect(gitlab_shell).to receive(:remove_key).with('key-1234')
+ it 'removes the keys not in the DB' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234')
- gitlab_shell.remove_keys_not_found_in_db
- end
+ gitlab_shell.remove_keys_not_found_in_db
end
end
end
diff --git a/spec/lib/gitlab/sidekiq_config_spec.rb b/spec/lib/gitlab/sidekiq_config_spec.rb
index 1e8ccb447b1..49efbac160a 100644
--- a/spec/lib/gitlab/sidekiq_config_spec.rb
+++ b/spec/lib/gitlab/sidekiq_config_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Gitlab::SidekiqConfig do
describe '.workers' do
diff --git a/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb
index e430599bd94..ac97a5ebd15 100644
--- a/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb
@@ -13,7 +13,7 @@ describe Gitlab::SidekiqMiddleware::Metrics do
let(:running_jobs_metric) { double('running jobs metric') }
before do
- allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_completion_seconds, anything, anything).and_return(completion_seconds_metric)
+ allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_completion_seconds, anything, anything, anything).and_return(completion_seconds_metric)
allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_failed_total, anything).and_return(failed_total_metric)
allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_retried_total, anything).and_return(retried_total_metric)
allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_running_jobs, anything, {}, :livesum).and_return(running_jobs_metric)
diff --git a/spec/lib/gitlab/slash_commands/application_help_spec.rb b/spec/lib/gitlab/slash_commands/application_help_spec.rb
index b203a1ee79c..afa63c21584 100644
--- a/spec/lib/gitlab/slash_commands/application_help_spec.rb
+++ b/spec/lib/gitlab/slash_commands/application_help_spec.rb
@@ -4,10 +4,11 @@ require 'spec_helper'
describe Gitlab::SlashCommands::ApplicationHelp do
let(:params) { { command: '/gitlab', text: 'help' } }
+ let(:project) { build(:project) }
describe '#execute' do
subject do
- described_class.new(params).execute
+ described_class.new(project, params).execute
end
it 'displays the help section' do
diff --git a/spec/lib/gitlab/slash_commands/command_spec.rb b/spec/lib/gitlab/slash_commands/command_spec.rb
index c4ea8cbf2b1..dc412c80e68 100644
--- a/spec/lib/gitlab/slash_commands/command_spec.rb
+++ b/spec/lib/gitlab/slash_commands/command_spec.rb
@@ -27,7 +27,7 @@ describe Gitlab::SlashCommands::Command do
it 'displays the help message' do
expect(subject[:response_type]).to be(:ephemeral)
- expect(subject[:text]).to start_with('Unknown command')
+ expect(subject[:text]).to start_with('The specified command is not valid')
expect(subject[:text]).to match('/gitlab issue show')
end
end
@@ -37,7 +37,7 @@ describe Gitlab::SlashCommands::Command do
it 'rejects the actions' do
expect(subject[:response_type]).to be(:ephemeral)
- expect(subject[:text]).to start_with('Whoops! This action is not allowed')
+ expect(subject[:text]).to start_with('You are not allowed')
end
end
@@ -57,7 +57,7 @@ describe Gitlab::SlashCommands::Command do
context 'and user can not create deployment' do
it 'returns action' do
expect(subject[:response_type]).to be(:ephemeral)
- expect(subject[:text]).to start_with('Whoops! This action is not allowed')
+ expect(subject[:text]).to start_with('You are not allowed')
end
end
diff --git a/spec/lib/gitlab/slash_commands/issue_close_spec.rb b/spec/lib/gitlab/slash_commands/issue_close_spec.rb
new file mode 100644
index 00000000000..c0760ce0ba6
--- /dev/null
+++ b/spec/lib/gitlab/slash_commands/issue_close_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SlashCommands::IssueClose do
+ describe '#execute' do
+ let(:issue) { create(:issue, project: project) }
+ let(:project) { create(:project) }
+ let(:user) { issue.author }
+ let(:chat_name) { double(:chat_name, user: user) }
+ let(:regex_match) { described_class.match("issue close #{issue.iid}") }
+
+ subject do
+ described_class.new(project, chat_name).execute(regex_match)
+ end
+
+ context 'when the user does not have permission' do
+ let(:chat_name) { double(:chat_name, user: create(:user)) }
+
+ it 'does not allow the user to close the issue' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to match("not found")
+ expect(issue.reload).to be_open
+ end
+ end
+
+ context 'the issue exists' do
+ let(:title) { subject[:attachments].first[:title] }
+
+ it 'closes and returns the issue' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(issue.reload).to be_closed
+ expect(title).to start_with(issue.title)
+ end
+
+ context 'when its reference is given' do
+ let(:regex_match) { described_class.match("issue close #{issue.to_reference}") }
+
+ it 'closes and returns the issue' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(issue.reload).to be_closed
+ expect(title).to start_with(issue.title)
+ end
+ end
+ end
+
+ context 'the issue does not exist' do
+ let(:regex_match) { described_class.match("issue close 2343242") }
+
+ it "returns not found" do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to match("not found")
+ end
+ end
+
+ context 'when the issue is already closed' do
+ let(:issue) { create(:issue, :closed, project: project) }
+
+ it 'shows the issue' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(issue.reload).to be_closed
+ expect(subject[:text]).to match("already closed")
+ end
+ end
+ end
+
+ describe '.match' do
+ it 'matches the iid' do
+ match = described_class.match("issue close 123")
+
+ expect(match[:iid]).to eq("123")
+ end
+
+ it 'accepts a reference' do
+ match = described_class.match("issue close #{Issue.reference_prefix}123")
+
+ expect(match[:iid]).to eq("123")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/slash_commands/presenters/access_spec.rb b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
index 286fec892e6..f00039c634f 100644
--- a/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
@@ -4,12 +4,14 @@ require 'spec_helper'
describe Gitlab::SlashCommands::Presenters::Access do
describe '#access_denied' do
- subject { described_class.new.access_denied }
+ let(:project) { build(:project) }
+
+ subject { described_class.new.access_denied(project) }
it { is_expected.to be_a(Hash) }
it 'displays an error message' do
- expect(subject[:text]).to match("is not allowed")
+ expect(subject[:text]).to match('are not allowed')
expect(subject[:response_type]).to be(:ephemeral)
end
end
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_close_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_close_spec.rb
new file mode 100644
index 00000000000..adc13b4ee56
--- /dev/null
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_close_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SlashCommands::Presenters::IssueClose do
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project) }
+ let(:attachment) { subject[:attachments].first }
+
+ subject { described_class.new(issue).present }
+
+ it { is_expected.to be_a(Hash) }
+
+ it 'shows the issue' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(subject).to have_key(:attachments)
+ expect(attachment[:title]).to start_with(issue.title)
+ end
+
+ context 'confidential issue' do
+ let(:issue) { create(:issue, :confidential, project: project) }
+
+ it 'shows an ephemeral response' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/tracing_spec.rb b/spec/lib/gitlab/tracing_spec.rb
index db75ce2a998..e913bb600ec 100644
--- a/spec/lib/gitlab/tracing_spec.rb
+++ b/spec/lib/gitlab/tracing_spec.rb
@@ -59,7 +59,7 @@ describe Gitlab::Tracing do
it 'returns the correct state for .tracing_url' do
expect(described_class).to receive(:tracing_url_enabled?).and_return(tracing_url_enabled?)
allow(described_class).to receive(:tracing_url_template).and_return(tracing_url_template)
- allow(Gitlab::CorrelationId).to receive(:current_id).and_return(correlation_id)
+ allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return(correlation_id)
allow(Gitlab).to receive(:process_name).and_return(process_name)
expect(described_class.tracing_url).to eq(tracing_url)
diff --git a/spec/lib/gitlab/visibility_level_checker_spec.rb b/spec/lib/gitlab/visibility_level_checker_spec.rb
new file mode 100644
index 00000000000..325ac3c6f31
--- /dev/null
+++ b/spec/lib/gitlab/visibility_level_checker_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe Gitlab::VisibilityLevelChecker do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:visibility_level_checker) { }
+ let(:override_params) { {} }
+
+ subject { described_class.new(user, project, project_params: override_params) }
+
+ describe '#level_restricted?' do
+ context 'when visibility level is allowed' do
+ it 'returns false with nil for visibility level' do
+ result = subject.level_restricted?
+
+ expect(result.restricted?).to eq(false)
+ expect(result.visibility_level).to be_nil
+ end
+ end
+
+ context 'when visibility level is restricted' do
+ before do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+ end
+
+ it 'returns true and visibility name' do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ result = subject.level_restricted?
+
+ expect(result.restricted?).to eq(true)
+ expect(result.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ context 'overridden visibility' do
+ let(:override_params) do
+ {
+ import_data: {
+ data: {
+ override_params: {
+ visibility: override_visibility
+ }
+ }
+ }
+ }
+ end
+
+ context 'when restricted' do
+ let(:override_visibility) { 'public' }
+
+ it 'returns true and visibility name' do
+ result = subject.level_restricted?
+
+ expect(result.restricted?).to eq(true)
+ expect(result.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+ end
+ end
+
+ context 'when misspelled' do
+ let(:override_visibility) { 'publik' }
+
+ it 'returns false with nil for visibility level' do
+ result = subject.level_restricted?
+
+ expect(result.restricted?).to eq(false)
+ expect(result.visibility_level).to be_nil
+ end
+ end
+
+ context 'when import_data is missing' do
+ let(:override_params) { {} }
+
+ it 'returns false with nil for visibility level' do
+ result = subject.level_restricted?
+
+ expect(result.restricted?).to eq(false)
+ expect(result.visibility_level).to be_nil
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb
index 1fc363460ae..74d4b12a070 100644
--- a/spec/lib/gitlab_spec.rb
+++ b/spec/lib/gitlab_spec.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
-
-require_dependency 'gitlab'
+require 'spec_helper'
describe Gitlab do
+ include RailsHelpers
+
describe '.root' do
it 'returns the root path of the app' do
expect(described_class.root).to eq(Pathname.new(File.expand_path('../..', __dir__)))
@@ -113,7 +113,7 @@ describe Gitlab do
it 'is true when dev env' do
allow(described_class).to receive_messages(com?: false, org?: false)
- allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('development'))
+ stub_rails_env('development')
expect(described_class.dev_env_org_or_com?).to eq true
end
diff --git a/spec/lib/peek/views/detailed_view_spec.rb b/spec/lib/peek/views/detailed_view_spec.rb
new file mode 100644
index 00000000000..d8660a55ea9
--- /dev/null
+++ b/spec/lib/peek/views/detailed_view_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Peek::Views::DetailedView, :request_store do
+ context 'when a class defines thresholds' do
+ let(:threshold_view) do
+ Class.new(described_class) do
+ def self.thresholds
+ {
+ calls: 1,
+ duration: 10,
+ individual_call: 5
+ }
+ end
+
+ def key
+ 'threshold-view'
+ end
+ end.new
+ end
+
+ context 'when the results exceed the calls threshold' do
+ before do
+ allow(threshold_view)
+ .to receive(:detail_store).and_return([{ duration: 0.001 }, { duration: 0.001 }])
+ end
+
+ it 'adds a warning to the results key' do
+ expect(threshold_view.results).to include(warnings: [a_string_matching('threshold-view calls')])
+ end
+ end
+
+ context 'when the results exceed the duration threshold' do
+ before do
+ allow(threshold_view)
+ .to receive(:detail_store).and_return([{ duration: 0.011 }])
+ end
+
+ it 'adds a warning to the results key' do
+ expect(threshold_view.results).to include(warnings: [a_string_matching('threshold-view duration')])
+ end
+ end
+
+ context 'when a single call exceeds the duration threshold' do
+ before do
+ allow(threshold_view)
+ .to receive(:detail_store).and_return([{ duration: 0.001 }, { duration: 0.006 }])
+ end
+
+ it 'adds a warning to that call detail entry' do
+ expect(threshold_view.results)
+ .to include(details: a_collection_containing_exactly(
+ { duration: 1.0, warnings: [] },
+ { duration: 6.0, warnings: ['6.0 over 5'] }
+ ))
+ end
+ end
+ end
+
+ context 'when a view does not define thresholds' do
+ let(:no_threshold_view) { Class.new(described_class).new }
+
+ before do
+ allow(no_threshold_view)
+ .to receive(:detail_store).and_return([{ duration: 100 }, { duration: 100 }])
+ end
+
+ it 'does not add warnings to the top level' do
+ expect(no_threshold_view.results).to include(warnings: [])
+ end
+
+ it 'does not add warnings to call details entries' do
+ expect(no_threshold_view.results)
+ .to include(details: a_collection_containing_exactly(
+ { duration: 100000, warnings: [] },
+ { duration: 100000, warnings: [] }
+ ))
+ end
+ end
+end
diff --git a/spec/lib/peek/views/redis_detailed_spec.rb b/spec/lib/peek/views/redis_detailed_spec.rb
index 61096e6c69e..fa9532226f2 100644
--- a/spec/lib/peek/views/redis_detailed_spec.rb
+++ b/spec/lib/peek/views/redis_detailed_spec.rb
@@ -21,10 +21,10 @@ describe Peek::Views::RedisDetailed, :request_store do
expect(subject.results[:details].count).to eq(1)
expect(subject.results[:details].first)
- .to eq({
- cmd: expected,
- duration: 1000
- })
+ .to include({
+ cmd: expected,
+ duration: 1000
+ })
end
end
diff --git a/spec/lib/peek/views/rugged_spec.rb b/spec/lib/peek/views/rugged_spec.rb
index d07d6b51a1f..b9507f772d2 100644
--- a/spec/lib/peek/views/rugged_spec.rb
+++ b/spec/lib/peek/views/rugged_spec.rb
@@ -8,7 +8,7 @@ describe Peek::Views::Rugged, :request_store do
let(:project) { create(:project) }
before do
- allow(Gitlab::RuggedInstrumentation).to receive(:peek_enabled?).and_return(true)
+ allow(Gitlab::PerformanceBar).to receive(:enabled_for_request?).and_return(true)
end
it 'returns no results' do
diff --git a/spec/lib/system_check/app/authorized_keys_permission_check_spec.rb b/spec/lib/system_check/app/authorized_keys_permission_check_spec.rb
new file mode 100644
index 00000000000..1a8123c3f0a
--- /dev/null
+++ b/spec/lib/system_check/app/authorized_keys_permission_check_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe SystemCheck::App::AuthorizedKeysPermissionCheck do
+ subject(:system_check) { described_class.new }
+
+ describe '#skip?' do
+ subject { system_check.skip? }
+
+ context 'authorized keys enabled' do
+ it { is_expected.to eq(false) }
+ end
+
+ context 'authorized keys not enabled' do
+ before do
+ stub_application_setting(authorized_keys_enabled: false)
+ end
+
+ it { is_expected.to eq(true) }
+ end
+ end
+
+ describe '#check?' do
+ subject { system_check.check? }
+
+ before do
+ expect_next_instance_of(Gitlab::AuthorizedKeys) do |instance|
+ allow(instance).to receive(:accessible?) { accessible? }
+ end
+ end
+
+ context 'authorized keys is accessible' do
+ let(:accessible?) { true }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'authorized keys is not accessible' do
+ let(:accessible?) { false }
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ describe '#repair!' do
+ subject { system_check.repair! }
+
+ before do
+ expect_next_instance_of(Gitlab::AuthorizedKeys) do |instance|
+ allow(instance).to receive(:create) { created }
+ end
+ end
+
+ context 'authorized_keys file created' do
+ let(:created) { true }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'authorized_keys file is not created' do
+ let(:created) { false }
+
+ it { is_expected.to eq(false) }
+ end
+ end
+end
diff --git a/spec/mailers/abuse_report_mailer_spec.rb b/spec/mailers/abuse_report_mailer_spec.rb
index f96870cc112..86153071cd3 100644
--- a/spec/mailers/abuse_report_mailer_spec.rb
+++ b/spec/mailers/abuse_report_mailer_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe AbuseReportMailer do
include EmailSpec::Matchers
diff --git a/spec/mailers/email_rejection_mailer_spec.rb b/spec/mailers/email_rejection_mailer_spec.rb
index bbe0a50ae8e..2ce2e3e1410 100644
--- a/spec/mailers/email_rejection_mailer_spec.rb
+++ b/spec/mailers/email_rejection_mailer_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe EmailRejectionMailer do
include EmailSpec::Matchers
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 6cba7df114c..56fa26d5f23 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -187,6 +187,22 @@ describe Notify do
end
end
+ describe 'that are due soon' do
+ subject { described_class.issue_due_email(recipient.id, issue.id) }
+
+ before do
+ issue.update(due_date: Date.tomorrow)
+ end
+
+ it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
+ let(:model) { issue }
+ end
+ it_behaves_like 'it should show Gmail Actions View Issue link'
+ it_behaves_like 'an unsubscribeable thread'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+ end
+
describe 'status changed' do
let(:status) { 'closed' }
subject { described_class.issue_status_changed_email(recipient.id, issue.id, status, current_user.id) }
diff --git a/spec/mailers/repository_check_mailer_spec.rb b/spec/mailers/repository_check_mailer_spec.rb
index 3dce89f5be2..757d3dfa797 100644
--- a/spec/mailers/repository_check_mailer_spec.rb
+++ b/spec/mailers/repository_check_mailer_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe RepositoryCheckMailer do
include EmailSpec::Matchers
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index a5f8e999d5d..814df472389 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe AbuseReport do
set(:report) { create(:abuse_report) }
diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb
index 2a689754ee0..b1f93dc7189 100644
--- a/spec/models/active_session_spec.rb
+++ b/spec/models/active_session_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
let(:user) do
diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb
index 209d138f956..2c32028c3e5 100644
--- a/spec/models/appearance_spec.rb
+++ b/spec/models/appearance_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Appearance do
subject { build(:appearance) }
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index db80b85360f..4f7a6d102b8 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -355,6 +355,71 @@ describe ApplicationSetting do
end
end
end
+
+ context 'asset proxy settings' do
+ before do
+ subject.asset_proxy_enabled = true
+ end
+
+ describe '#asset_proxy_url' do
+ it { is_expected.not_to allow_value('').for(:asset_proxy_url) }
+ it { is_expected.to allow_value(http).for(:asset_proxy_url) }
+ it { is_expected.to allow_value(https).for(:asset_proxy_url) }
+ it { is_expected.not_to allow_value(ftp).for(:asset_proxy_url) }
+
+ it 'is not required when asset proxy is disabled' do
+ subject.asset_proxy_enabled = false
+ subject.asset_proxy_url = ''
+
+ expect(subject).to be_valid
+ end
+ end
+
+ describe '#asset_proxy_secret_key' do
+ it { is_expected.not_to allow_value('').for(:asset_proxy_secret_key) }
+ it { is_expected.to allow_value('anything').for(:asset_proxy_secret_key) }
+
+ it 'is not required when asset proxy is disabled' do
+ subject.asset_proxy_enabled = false
+ subject.asset_proxy_secret_key = ''
+
+ expect(subject).to be_valid
+ end
+
+ it 'is encrypted' do
+ subject.asset_proxy_secret_key = 'shared secret'
+
+ expect(subject.encrypted_asset_proxy_secret_key).to be_present
+ expect(subject.encrypted_asset_proxy_secret_key).not_to eq(subject.asset_proxy_secret_key)
+ end
+ end
+
+ describe '#asset_proxy_whitelist' do
+ context 'when given an Array' do
+ it 'sets the domains and adds current running host' do
+ setting.asset_proxy_whitelist = ['example.com', 'assets.example.com']
+ expect(setting.asset_proxy_whitelist).to eq(['example.com', 'assets.example.com', 'localhost'])
+ end
+ end
+
+ context 'when given a String' do
+ it 'sets multiple domains with spaces' do
+ setting.asset_proxy_whitelist = 'example.com *.example.com'
+ expect(setting.asset_proxy_whitelist).to eq(['example.com', '*.example.com', 'localhost'])
+ end
+
+ it 'sets multiple domains with newlines and a space' do
+ setting.asset_proxy_whitelist = "example.com\n *.example.com"
+ expect(setting.asset_proxy_whitelist).to eq(['example.com', '*.example.com', 'localhost'])
+ end
+
+ it 'sets multiple domains with commas' do
+ setting.asset_proxy_whitelist = "example.com, *.example.com"
+ expect(setting.asset_proxy_whitelist).to eq(['example.com', '*.example.com', 'localhost'])
+ end
+ end
+ end
+ end
end
context 'restrict creating duplicates' do
diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb
index 8364293b908..62a75bde63b 100644
--- a/spec/models/blob_spec.rb
+++ b/spec/models/blob_spec.rb
@@ -1,7 +1,7 @@
# encoding: utf-8
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Blob do
include FakeBlobHelpers
diff --git a/spec/models/board_spec.rb b/spec/models/board_spec.rb
index 54452faa0e1..f6eee67e539 100644
--- a/spec/models/board_spec.rb
+++ b/spec/models/board_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Board do
describe 'relationships' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 7d84d094bdf..63ca383ac4b 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -323,6 +323,25 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '#merge_train_pipeline?' do
+ subject { pipeline.merge_train_pipeline? }
+
+ let!(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, ref: ref, target_sha: 'xxx')
+ end
+
+ let(:merge_request) { create(:merge_request) }
+ let(:ref) { 'refs/merge-requests/1/train' }
+
+ it { is_expected.to be_truthy }
+
+ context 'when ref is merge ref' do
+ let(:ref) { 'refs/merge-requests/1/merge' }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
describe '#merge_request_ref?' do
subject { pipeline.merge_request_ref? }
@@ -333,6 +352,48 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '#merge_train_ref?' do
+ subject { pipeline.merge_train_ref? }
+
+ it 'calls Mergetrain#merge_train_ref?' do
+ expect(MergeRequest).to receive(:merge_train_ref?).with(pipeline.ref)
+
+ subject
+ end
+ end
+
+ describe '#merge_request_event_type' do
+ subject { pipeline.merge_request_event_type }
+
+ before do
+ allow(pipeline).to receive(:merge_request_event?) { true }
+ end
+
+ context 'when pipeline is merge train pipeline' do
+ before do
+ allow(pipeline).to receive(:merge_train_pipeline?) { true }
+ end
+
+ it { is_expected.to eq(:merge_train) }
+ end
+
+ context 'when pipeline is merge request pipeline' do
+ before do
+ allow(pipeline).to receive(:merge_request_pipeline?) { true }
+ end
+
+ it { is_expected.to eq(:merged_result) }
+ end
+
+ context 'when pipeline is detached merge request pipeline' do
+ before do
+ allow(pipeline).to receive(:detached_merge_request_pipeline?) { true }
+ end
+
+ it { is_expected.to eq(:detached) }
+ end
+ end
+
describe '#legacy_detached_merge_request_pipeline?' do
subject { pipeline.legacy_detached_merge_request_pipeline? }
@@ -782,7 +843,8 @@ describe Ci::Pipeline, :mailer do
'CI_MERGE_REQUEST_TITLE' => merge_request.title,
'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list,
'CI_MERGE_REQUEST_MILESTONE' => milestone.title,
- 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).join(','))
+ 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).join(','),
+ 'CI_MERGE_REQUEST_EVENT_TYPE' => pipeline.merge_request_event_type.to_s)
end
context 'when source project does not exist' do
diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb
index 93050e80b07..bddc09decc3 100644
--- a/spec/models/clusters/applications/cert_manager_spec.rb
+++ b/spec/models/clusters/applications/cert_manager_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Clusters::Applications::CertManager do
let(:cert_manager) { create(:clusters_applications_cert_manager) }
@@ -44,11 +44,18 @@ describe Clusters::Applications::CertManager do
it 'is initialized with cert_manager arguments' do
expect(subject.name).to eq('certmanager')
- expect(subject.chart).to eq('stable/cert-manager')
- expect(subject.version).to eq('v0.5.2')
+ expect(subject.chart).to eq('certmanager/cert-manager')
+ expect(subject.repository).to eq('https://charts.jetstack.io')
+ expect(subject.version).to eq('v0.9.1')
expect(subject).to be_rbac
expect(subject.files).to eq(cert_manager.files.merge(cluster_issuer_file))
- expect(subject.postinstall).to eq(['kubectl create -f /data/helm/certmanager/config/cluster_issuer.yaml'])
+ expect(subject.preinstall).to eq([
+ 'kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.9/deploy/manifests/00-crds.yaml',
+ 'kubectl label --overwrite namespace gitlab-managed-apps certmanager.k8s.io/disable-validation=true'
+ ])
+ expect(subject.postinstall).to eq([
+ 'for i in $(seq 1 30); do kubectl apply -f /data/helm/certmanager/config/cluster_issuer.yaml && break; sleep 1s; echo "Retrying ($i)..."; done'
+ ])
end
context 'for a specific user' do
@@ -75,7 +82,7 @@ describe Clusters::Applications::CertManager do
let(:cert_manager) { create(:clusters_applications_cert_manager, :errored, version: '0.0.1') }
it 'is initialized with the locked version' do
- expect(subject.version).to eq('v0.5.2')
+ expect(subject.version).to eq('v0.9.1')
end
end
end
@@ -93,10 +100,13 @@ describe Clusters::Applications::CertManager do
it 'specifies a post delete command to remove custom resource definitions' do
expect(subject.postdelete).to eq([
- "kubectl delete secret -n gitlab-managed-apps letsencrypt-prod --ignore-not-found",
+ 'kubectl delete secret -n gitlab-managed-apps letsencrypt-prod --ignore-not-found',
'kubectl delete crd certificates.certmanager.k8s.io --ignore-not-found',
+ 'kubectl delete crd certificaterequests.certmanager.k8s.io --ignore-not-found',
+ 'kubectl delete crd challenges.certmanager.k8s.io --ignore-not-found',
'kubectl delete crd clusterissuers.certmanager.k8s.io --ignore-not-found',
- 'kubectl delete crd issuers.certmanager.k8s.io --ignore-not-found'
+ 'kubectl delete crd issuers.certmanager.k8s.io --ignore-not-found',
+ 'kubectl delete crd orders.certmanager.k8s.io --ignore-not-found'
])
end
@@ -111,8 +121,11 @@ describe Clusters::Applications::CertManager do
it 'does not try and delete the secret' do
expect(subject.postdelete).to eq([
'kubectl delete crd certificates.certmanager.k8s.io --ignore-not-found',
+ 'kubectl delete crd certificaterequests.certmanager.k8s.io --ignore-not-found',
+ 'kubectl delete crd challenges.certmanager.k8s.io --ignore-not-found',
'kubectl delete crd clusterissuers.certmanager.k8s.io --ignore-not-found',
- 'kubectl delete crd issuers.certmanager.k8s.io --ignore-not-found'
+ 'kubectl delete crd issuers.certmanager.k8s.io --ignore-not-found',
+ 'kubectl delete crd orders.certmanager.k8s.io --ignore-not-found'
])
end
end
diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb
index 00b5c72a3d3..9672129bb1e 100644
--- a/spec/models/clusters/applications/helm_spec.rb
+++ b/spec/models/clusters/applications/helm_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Clusters::Applications::Helm do
include_examples 'cluster application core specs', :clusters_applications_helm
diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb
index 057517d3820..f984f6ba0ce 100644
--- a/spec/models/clusters/applications/ingress_spec.rb
+++ b/spec/models/clusters/applications/ingress_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Clusters::Applications::Ingress do
let(:ingress) { create(:clusters_applications_ingress) }
diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb
index 3ff66a074e4..1b39328752d 100644
--- a/spec/models/clusters/applications/jupyter_spec.rb
+++ b/spec/models/clusters/applications/jupyter_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Clusters::Applications::Jupyter do
include_examples 'cluster application core specs', :clusters_applications_jupyter
diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb
index 334f10526cb..3825994b733 100644
--- a/spec/models/clusters/applications/knative_spec.rb
+++ b/spec/models/clusters/applications/knative_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Clusters::Applications::Knative do
let(:knative) { create(:clusters_applications_knative) }
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index eb6ccba5584..8fc3b7e4c40 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Clusters::Applications::Prometheus do
include KubernetesHelpers
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index 4abe45a2152..b420a180524 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Clusters::Applications::Runner do
let(:ci_runner) { create(:ci_runner) }
diff --git a/spec/models/concerns/blocks_json_serialization_spec.rb b/spec/models/concerns/blocks_json_serialization_spec.rb
index e8657c45a93..0ef5be3cb61 100644
--- a/spec/models/concerns/blocks_json_serialization_spec.rb
+++ b/spec/models/concerns/blocks_json_serialization_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe BlocksJsonSerialization do
DummyModel = Class.new do
diff --git a/spec/models/concerns/deployable_spec.rb b/spec/models/concerns/deployable_spec.rb
index bb73dd8ade0..ad2c0770a2c 100644
--- a/spec/models/concerns/deployable_spec.rb
+++ b/spec/models/concerns/deployable_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Deployable do
describe '#create_deployment' do
diff --git a/spec/models/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb
index 27f535487c8..220f244ad71 100644
--- a/spec/models/concerns/deployment_platform_spec.rb
+++ b/spec/models/concerns/deployment_platform_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe DeploymentPlatform do
let(:project) { create(:project) }
diff --git a/spec/models/concerns/ignorable_column_spec.rb b/spec/models/concerns/ignorable_column_spec.rb
deleted file mode 100644
index 6b82825d2cc..00000000000
--- a/spec/models/concerns/ignorable_column_spec.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe IgnorableColumn do
- let :base_class do
- Class.new do
- def self.columns
- # This method does not have access to "double"
- [
- Struct.new(:name).new('id'),
- Struct.new(:name).new('title'),
- Struct.new(:name).new('date')
- ]
- end
- end
- end
-
- let :model do
- Class.new(base_class) do
- include IgnorableColumn
- end
- end
-
- describe '.columns' do
- it 'returns the columns, excluding the ignored ones' do
- model.ignore_column(:title, :date)
-
- expect(model.columns.map(&:name)).to eq(%w(id))
- end
- end
-
- describe '.ignored_columns' do
- it 'returns a Set' do
- expect(model.ignored_columns).to be_an_instance_of(Set)
- end
-
- it 'returns the names of the ignored columns' do
- model.ignore_column(:title, :date)
-
- expect(model.ignored_columns).to eq(Set.new(%w(title date)))
- end
- end
-end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 39680c0e51a..65d41edc035 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -46,6 +46,7 @@ describe Issuable do
it { is_expected.to validate_presence_of(:author) }
it { is_expected.to validate_presence_of(:title) }
it { is_expected.to validate_length_of(:title).is_at_most(255) }
+ it { is_expected.to validate_length_of(:description).is_at_most(1_000_000) }
end
describe 'milestone' do
@@ -795,4 +796,29 @@ describe Issuable do
end
end
end
+
+ describe '#matches_cross_reference_regex?' do
+ context "issue description with long path string" do
+ let(:mentionable) { build(:issue, description: "/a" * 50000) }
+
+ it_behaves_like 'matches_cross_reference_regex? fails fast'
+ end
+
+ context "note with long path string" do
+ let(:mentionable) { build(:note, note: "/a" * 50000) }
+
+ it_behaves_like 'matches_cross_reference_regex? fails fast'
+ end
+
+ context "note with long path string" do
+ let(:project) { create(:project, :public, :repository) }
+ let(:mentionable) { project.commit }
+
+ before do
+ expect(mentionable.raw).to receive(:message).and_return("/a" * 50000)
+ end
+
+ it_behaves_like 'matches_cross_reference_regex? fails fast'
+ end
+ end
end
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index 31163a5bb5c..cad705ee594 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -58,7 +58,7 @@ describe Group, 'Routable' do
end
end
- describe '.find_by_full_path' do
+ shared_examples_for '.find_by_full_path' do
let!(:nested_group) { create(:group, parent: group) }
context 'without any redirect routes' do
@@ -66,6 +66,13 @@ describe Group, 'Routable' do
it { expect(described_class.find_by_full_path(group.to_param.upcase)).to eq(group) }
it { expect(described_class.find_by_full_path(nested_group.to_param)).to eq(nested_group) }
it { expect(described_class.find_by_full_path('unknown')).to eq(nil) }
+
+ it 'includes route information when loading a record' do
+ path = group.to_param
+ control_count = ActiveRecord::QueryRecorder.new { described_class.find_by_full_path(path) }.count
+
+ expect { described_class.find_by_full_path(path).route }.not_to exceed_all_query_limit(control_count)
+ end
end
context 'with redirect routes' do
@@ -110,6 +117,24 @@ describe Group, 'Routable' do
end
end
+ describe '.find_by_full_path' do
+ context 'with routable_two_step_lookup feature' do
+ before do
+ stub_feature_flags(routable_two_step_lookup: true)
+ end
+
+ it_behaves_like '.find_by_full_path'
+ end
+
+ context 'without routable_two_step_lookup feature' do
+ before do
+ stub_feature_flags(routable_two_step_lookup: false)
+ end
+
+ it_behaves_like '.find_by_full_path'
+ end
+ end
+
describe '.where_full_path_in' do
context 'without any paths' do
it 'returns an empty relation' do
diff --git a/spec/models/concerns/triggerable_hooks_spec.rb b/spec/models/concerns/triggerable_hooks_spec.rb
index f28e5f56411..ac1bc51d950 100644
--- a/spec/models/concerns/triggerable_hooks_spec.rb
+++ b/spec/models/concerns/triggerable_hooks_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
RSpec.describe TriggerableHooks do
before do
diff --git a/spec/models/conversational_development_index/metric_spec.rb b/spec/models/conversational_development_index/metric_spec.rb
index 60b1a860dfd..55ba466e614 100644
--- a/spec/models/conversational_development_index/metric_spec.rb
+++ b/spec/models/conversational_development_index/metric_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe ConversationalDevelopmentIndex::Metric do
let(:conv_dev_index) { create(:conversational_development_index_metric) }
diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb
index 479b39cd139..b9c914e2506 100644
--- a/spec/models/gpg_key_spec.rb
+++ b/spec/models/gpg_key_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe GpgKey do
describe "associations" do
diff --git a/spec/models/gpg_key_subkey_spec.rb b/spec/models/gpg_key_subkey_spec.rb
index 51d2f9cb9ac..5f80cc02924 100644
--- a/spec/models/gpg_key_subkey_spec.rb
+++ b/spec/models/gpg_key_subkey_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe GpgKeySubkey do
subject { build(:gpg_key_subkey) }
diff --git a/spec/models/gpg_signature_spec.rb b/spec/models/gpg_signature_spec.rb
index 47c343edf0e..4911375c962 100644
--- a/spec/models/gpg_signature_spec.rb
+++ b/spec/models/gpg_signature_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
RSpec.describe GpgSignature do
let(:commit_sha) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' }
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 1c41ceb7deb..796b6917fb2 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -1039,4 +1039,23 @@ describe Group do
.to eq(Gitlab::Access::MAINTAINER_SUBGROUP_ACCESS)
end
end
+
+ describe '#access_request_approvers_to_be_notified' do
+ it 'returns a maximum of ten, active, non_requested owners of the group in recent_sign_in descending order' do
+ group = create(:group, :public)
+
+ users = create_list(:user, 12, :with_sign_ins)
+ active_owners = users.map do |user|
+ create(:group_member, :owner, group: group, user: user)
+ end
+
+ create(:group_member, :owner, :blocked, group: group)
+ create(:group_member, :maintainer, group: group)
+ create(:group_member, :access_request, :owner, group: group)
+
+ active_owners_in_recent_sign_in_desc_order = group.members_and_requesters.where(id: active_owners).order_recent_sign_in.limit(10)
+
+ expect(group.access_request_approvers_to_be_notified).to eq(active_owners_in_recent_sign_in_desc_order)
+ end
+ end
end
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index e0d4d2e4858..a4d202dc4f8 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -64,7 +64,7 @@ describe SystemHook do
).once
end
- it "project_create hook" do
+ it "project member create hook" do
project.add_maintainer(user)
expect(WebMock).to have_requested(:post, system_hook.url).with(
@@ -73,7 +73,7 @@ describe SystemHook do
).once
end
- it "project_destroy hook" do
+ it "project member destroy hook" do
project.add_maintainer(user)
project.project_members.destroy_all # rubocop: disable DestroyAll
@@ -83,6 +83,15 @@ describe SystemHook do
).once
end
+ it "project member update hook" do
+ project.add_guest(user)
+
+ expect(WebMock).to have_requested(:post, system_hook.url).with(
+ body: /user_update_for_team/,
+ headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' }
+ ).once
+ end
+
it 'group create hook' do
create(:group)
@@ -119,6 +128,16 @@ describe SystemHook do
headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' }
).once
end
+
+ it 'group member update hook' do
+ group.add_guest(user)
+ group.add_maintainer(user)
+
+ expect(WebMock).to have_requested(:post, system_hook.url).with(
+ body: /user_update_for_group/,
+ headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' }
+ ).once
+ end
end
describe '.repository_update_hooks' do
diff --git a/spec/models/hooks/web_hook_log_spec.rb b/spec/models/hooks/web_hook_log_spec.rb
index f812149c9be..85934b81086 100644
--- a/spec/models/hooks/web_hook_log_spec.rb
+++ b/spec/models/hooks/web_hook_log_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe WebHookLog do
it { is_expected.to belong_to(:web_hook) }
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index c2e2298823e..baf2cfeab0c 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -84,6 +84,13 @@ describe Label do
end
end
+ describe '#description' do
+ it 'sanitizes description' do
+ label = described_class.new(description: '<b>foo & bar?</b>')
+ expect(label.description).to eq('foo & bar?')
+ end
+ end
+
describe 'priorization' do
subject(:label) { create(:label) }
diff --git a/spec/models/lfs_download_object_spec.rb b/spec/models/lfs_download_object_spec.rb
index 8b53effe98f..d7522fbb969 100644
--- a/spec/models/lfs_download_object_spec.rb
+++ b/spec/models/lfs_download_object_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe LfsDownloadObject do
let(:oid) { 'cd293be6cea034bd45a0352775a219ef5dc7825ce55d1f7dae9762d80ce64411' }
diff --git a/spec/models/lfs_file_lock_spec.rb b/spec/models/lfs_file_lock_spec.rb
index aa64d66944b..a42346c341d 100644
--- a/spec/models/lfs_file_lock_spec.rb
+++ b/spec/models/lfs_file_lock_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe LfsFileLock do
set(:lfs_file_lock) { create(:lfs_file_lock) }
diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb
index 18d4549977c..dc28204d7aa 100644
--- a/spec/models/list_spec.rb
+++ b/spec/models/list_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe List do
it_behaves_like 'having unique enum values'
@@ -81,4 +81,83 @@ describe List do
expect(subject.title).to eq 'Closed'
end
end
+
+ describe '#update_preferences_for' do
+ let(:user) { create(:user) }
+ let(:list) { create(:list) }
+
+ context 'when user is present' do
+ context 'when there are no preferences for user' do
+ it 'creates new user preferences' do
+ expect { list.update_preferences_for(user, collapsed: true) }.to change { ListUserPreference.count }.by(1)
+ expect(list.preferences_for(user).collapsed).to eq(true)
+ end
+ end
+
+ context 'when there are preferences for user' do
+ it 'updates user preferences' do
+ list.update_preferences_for(user, collapsed: false)
+
+ expect { list.update_preferences_for(user, collapsed: true) }.not_to change { ListUserPreference.count }
+ expect(list.preferences_for(user).collapsed).to eq(true)
+ end
+ end
+
+ context 'when user is nil' do
+ it 'does not create user preferences' do
+ expect { list.update_preferences_for(nil, collapsed: true) }.not_to change { ListUserPreference.count }
+ end
+ end
+ end
+ end
+
+ describe '#preferences_for' do
+ let(:user) { create(:user) }
+ let(:list) { create(:list) }
+
+ context 'when user is nil' do
+ it 'returns not persisted preferences' do
+ preferences = list.preferences_for(nil)
+
+ expect(preferences.persisted?).to eq(false)
+ expect(preferences.list_id).to eq(list.id)
+ expect(preferences.user_id).to be_nil
+ end
+ end
+
+ context 'when a user preference already exists' do
+ before do
+ list.update_preferences_for(user, collapsed: true)
+ end
+
+ it 'loads preference for user' do
+ preferences = list.preferences_for(user)
+
+ expect(preferences).to be_persisted
+ expect(preferences.collapsed).to eq(true)
+ end
+
+ context 'when preferences are already loaded for user' do
+ it 'gets preloaded user preferences' do
+ fetched_list = described_class.where(id: list.id).with_preferences_for(user).first
+
+ expect(fetched_list).to receive(:preloaded_preferences_for).with(user).and_call_original
+
+ preferences = fetched_list.preferences_for(user)
+
+ expect(preferences.collapsed).to eq(true)
+ end
+ end
+ end
+
+ context 'when preferences for user does not exist' do
+ it 'returns not persisted preferences' do
+ preferences = list.preferences_for(user)
+
+ expect(preferences.persisted?).to eq(false)
+ expect(preferences.user_id).to eq(user.id)
+ expect(preferences.list_id).to eq(list.id)
+ end
+ end
+ end
end
diff --git a/spec/models/list_user_preference_spec.rb b/spec/models/list_user_preference_spec.rb
new file mode 100644
index 00000000000..1335a3700dc
--- /dev/null
+++ b/spec/models/list_user_preference_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ListUserPreference do
+ set(:user) { create(:user) }
+ set(:list) { create(:list) }
+
+ before do
+ list.update_preferences_for(user, { collapsed: true })
+ end
+
+ describe 'relationships' do
+ it { is_expected.to belong_to(:list) }
+ it { is_expected.to belong_to(:user) }
+
+ it do
+ is_expected.to validate_uniqueness_of(:user_id).scoped_to(:list_id)
+ .with_message("should have only one list preference per user")
+ end
+ end
+end
diff --git a/spec/models/merge_request_diff_commit_spec.rb b/spec/models/merge_request_diff_commit_spec.rb
index ab2aadf7d88..c0a09dab0b0 100644
--- a/spec/models/merge_request_diff_commit_spec.rb
+++ b/spec/models/merge_request_diff_commit_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe MergeRequestDiffCommit do
let(:merge_request) { create(:merge_request) }
diff --git a/spec/models/merge_request_diff_file_spec.rb b/spec/models/merge_request_diff_file_spec.rb
index 66957c24fdc..97b30bb8607 100644
--- a/spec/models/merge_request_diff_file_spec.rb
+++ b/spec/models/merge_request_diff_file_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe MergeRequestDiffFile do
describe '#diff' do
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index d344a6d0f0d..11234982dd4 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -3195,6 +3195,40 @@ describe MergeRequest do
end
end
+ describe '.merge_train_ref?' do
+ subject { described_class.merge_train_ref?(ref) }
+
+ context 'when ref is ref name of a branch' do
+ let(:ref) { 'feature' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when ref is HEAD ref path of a branch' do
+ let(:ref) { 'refs/heads/feature' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when ref is HEAD ref path of a merge request' do
+ let(:ref) { 'refs/merge-requests/1/head' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when ref is merge ref path of a merge request' do
+ let(:ref) { 'refs/merge-requests/1/merge' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when ref is train ref path of a merge request' do
+ let(:ref) { 'refs/merge-requests/1/train' }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
describe '#cleanup_refs' do
subject { merge_request.cleanup_refs(only: only) }
diff --git a/spec/models/milestone_release_spec.rb b/spec/models/milestone_release_spec.rb
new file mode 100644
index 00000000000..d6f73275977
--- /dev/null
+++ b/spec/models/milestone_release_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe MilestoneRelease do
+ let(:project) { create(:project) }
+ let(:release) { create(:release, project: project) }
+ let(:milestone) { create(:milestone, project: project) }
+
+ subject { build(:milestone_release, release: release, milestone: milestone) }
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:milestone) }
+ it { is_expected.to belong_to(:release) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_uniqueness_of(:milestone_id).scoped_to(:release_id) }
+
+ context 'when milestone and release do not have the same project' do
+ it 'is not valid' do
+ other_project = create(:project)
+ release = build(:release, project: other_project)
+ milestone_release = described_class.new(milestone: milestone, release: release)
+ expect(milestone_release).not_to be_valid
+ end
+ end
+
+ context 'when milestone and release have the same project' do
+ it 'is valid' do
+ milestone_release = described_class.new(milestone: milestone, release: release)
+ expect(milestone_release).to be_valid
+ end
+ end
+ end
+end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 3704a2d468d..64030f5b92a 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -54,11 +54,31 @@ describe Milestone do
expect(milestone.errors[:due_date]).to include("date must not be after 9999-12-31")
end
end
+
+ describe 'milestone_release' do
+ let(:milestone) { build(:milestone, project: project) }
+
+ context 'when it is tied to a release for another project' do
+ it 'creates a validation error' do
+ other_project = create(:project)
+ milestone.release = build(:release, project: other_project)
+ expect(milestone).not_to be_valid
+ end
+ end
+
+ context 'when it is tied to a release for the same project' do
+ it 'is valid' do
+ milestone.release = build(:release, project: project)
+ expect(milestone).to be_valid
+ end
+ end
+ end
end
describe "Associations" do
it { is_expected.to belong_to(:project) }
it { is_expected.to have_many(:issues) }
+ it { is_expected.to have_one(:release) }
end
let(:project) { create(:project, :public) }
diff --git a/spec/models/note_diff_file_spec.rb b/spec/models/note_diff_file_spec.rb
index b15bedd257e..11108016b8e 100644
--- a/spec/models/note_diff_file_spec.rb
+++ b/spec/models/note_diff_file_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe NoteDiffFile do
describe 'associations' do
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index bfd0e5f0558..927fbdb93d8 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -22,6 +22,7 @@ describe Note do
end
describe 'validation' do
+ it { is_expected.to validate_length_of(:note).is_at_most(1_000_000) }
it { is_expected.to validate_presence_of(:note) }
it { is_expected.to validate_presence_of(:project) }
diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb
index 85128456918..820d233dbdc 100644
--- a/spec/models/notification_setting_spec.rb
+++ b/spec/models/notification_setting_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
RSpec.describe NotificationSetting do
it_behaves_like 'having unique enum values'
diff --git a/spec/models/project_deploy_token_spec.rb b/spec/models/project_deploy_token_spec.rb
index 2a5fefc1ab0..8c8924762bd 100644
--- a/spec/models/project_deploy_token_spec.rb
+++ b/spec/models/project_deploy_token_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
RSpec.describe ProjectDeployToken, type: :model do
let(:project) { create(:project) }
diff --git a/spec/models/project_import_state_spec.rb b/spec/models/project_import_state_spec.rb
index 472bf8f9713..8a847bbe24e 100644
--- a/spec/models/project_import_state_spec.rb
+++ b/spec/models/project_import_state_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe ProjectImportState, type: :model do
subject { create(:import_state) }
diff --git a/spec/models/project_services/chat_message/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb
index a89645a3ea8..2bde0b93fda 100644
--- a/spec/models/project_services/chat_message/push_message_spec.rb
+++ b/spec/models/project_services/chat_message/push_message_spec.rb
@@ -23,7 +23,7 @@ describe ChatMessage::PushMessage do
before do
args[:commits] = [
{ message: 'message1', url: 'http://url1.com', id: 'abcdefghijkl', author: { name: 'author1' } },
- { message: 'message2', url: 'http://url2.com', id: '123456789012', author: { name: 'author2' } }
+ { message: "message2\nsecondline", url: 'http://url2.com', id: '123456789012', author: { name: 'author2' } }
]
end
@@ -34,7 +34,7 @@ describe ChatMessage::PushMessage do
'<http://url.com|project_name> (<http://url.com/compare/before...after|Compare changes>)')
expect(subject.attachments).to eq([{
text: "<http://url1.com|abcdefgh>: message1 - author1\n\n"\
- "<http://url2.com|12345678>: message2 - author2",
+ "<http://url2.com|12345678>: message2\nsecondline - author2",
color: color
}])
end
@@ -49,7 +49,27 @@ describe ChatMessage::PushMessage do
expect(subject.pretext).to eq(
'test.user pushed to branch [master](http://url.com/commits/master) of [project_name](http://url.com) ([Compare changes](http://url.com/compare/before...after))')
expect(subject.attachments).to eq(
- "[abcdefgh](http://url1.com): message1 - author1\n\n[12345678](http://url2.com): message2 - author2")
+ "[abcdefgh](http://url1.com): message1 - author1\n\n[12345678](http://url2.com): message2\nsecondline - author2")
+ expect(subject.activity).to eq(
+ title: 'test.user pushed to branch [master](http://url.com/commits/master)',
+ subtitle: 'in [project_name](http://url.com)',
+ text: '[Compare changes](http://url.com/compare/before...after)',
+ image: 'http://someavatar.com'
+ )
+ end
+ end
+
+ context 'with markdown and commit message html' do
+ before do
+ args[:commit_message_html] = true
+ args[:markdown] = true
+ end
+
+ it 'returns a message regarding pushes' do
+ expect(subject.pretext).to eq(
+ 'test.user pushed to branch [master](http://url.com/commits/master) of [project_name](http://url.com) ([Compare changes](http://url.com/compare/before...after))')
+ expect(subject.attachments).to eq(
+ "[abcdefgh](http://url1.com): message1 - author1<br/>\n<br/>\n[12345678](http://url2.com): message2<br/>\nsecondline - author2")
expect(subject.activity).to eq(
title: 'test.user pushed to branch [master](http://url.com/commits/master)',
subtitle: 'in [project_name](http://url.com)',
diff --git a/spec/models/project_services/discord_service_spec.rb b/spec/models/project_services/discord_service_spec.rb
index be82f223478..96ac532dcd1 100644
--- a/spec/models/project_services/discord_service_spec.rb
+++ b/spec/models/project_services/discord_service_spec.rb
@@ -8,4 +8,37 @@ describe DiscordService do
let(:client_arguments) { { url: webhook_url } }
let(:content_key) { :content }
end
+
+ describe '#execute' do
+ include StubRequests
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:webhook_url) { "https://example.gitlab.com/" }
+
+ let(:sample_data) do
+ Gitlab::DataBuilder::Push.build_sample(project, user)
+ end
+
+ before do
+ allow(subject).to receive_messages(
+ project: project,
+ project_id: project.id,
+ service_hook: true,
+ webhook: webhook_url
+ )
+
+ WebMock.stub_request(:post, webhook_url)
+ end
+
+ context 'DNS rebind to local address' do
+ before do
+ stub_dns(webhook_url, ip_address: '192.168.2.120')
+ end
+
+ it 'does not allow DNS rebinding' do
+ expect { subject.execute(sample_data) }.to raise_error(ArgumentError, /is blocked/)
+ end
+ end
+ end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index bd352db2236..bfbcac60fea 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -4991,6 +4991,26 @@ describe Project do
end
end
+ describe '#access_request_approvers_to_be_notified' do
+ it 'returns a maximum of ten, active, non_requested maintainers of the project in recent_sign_in descending order' do
+ group = create(:group, :public)
+ project = create(:project, group: group)
+
+ users = create_list(:user, 12, :with_sign_ins)
+ active_maintainers = users.map do |user|
+ create(:project_member, :maintainer, user: user)
+ end
+
+ create(:project_member, :maintainer, :blocked, project: project)
+ create(:project_member, :developer, project: project)
+ create(:project_member, :access_request, :maintainer, project: project)
+
+ active_maintainers_in_recent_sign_in_desc_order = project.members_and_requesters.where(id: active_maintainers).order_recent_sign_in.limit(10)
+
+ expect(project.access_request_approvers_to_be_notified).to eq(active_maintainers_in_recent_sign_in_desc_order)
+ end
+ end
+
def rugged_config
rugged_repo(project.repository).config
end
diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb
index a164ed9bbea..4bc6130387a 100644
--- a/spec/models/project_statistics_spec.rb
+++ b/spec/models/project_statistics_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe ProjectStatistics do
let(:project) { create :project }
diff --git a/spec/models/redirect_route_spec.rb b/spec/models/redirect_route_spec.rb
index 6ecb5c31c7e..b9b2873f8f2 100644
--- a/spec/models/redirect_route_spec.rb
+++ b/spec/models/redirect_route_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe RedirectRoute do
let(:group) { create(:group) }
diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb
index e9d846e7291..c690390e24d 100644
--- a/spec/models/release_spec.rb
+++ b/spec/models/release_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
RSpec.describe Release do
let(:user) { create(:user) }
@@ -13,6 +13,7 @@ RSpec.describe Release do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:author).class_name('User') }
it { is_expected.to have_many(:links).class_name('Releases::Link') }
+ it { is_expected.to have_one(:milestone) }
end
describe 'validation' do
@@ -34,6 +35,20 @@ RSpec.describe Release do
expect(existing_release_without_name.name).to be_nil
end
end
+
+ context 'when a release is tied to a milestone for another project' do
+ it 'creates a validation error' do
+ release.milestone = build(:milestone, project: create(:project))
+ expect(release).not_to be_valid
+ end
+ end
+
+ context 'when a release is tied to a milestone linked to the same project' do
+ it 'is valid' do
+ release.milestone = build(:milestone, project: project)
+ expect(release).to be_valid
+ end
+ end
end
describe '#assets_count' do
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index 7edeb56efe2..63d0bf3f314 100644
--- a/spec/models/remote_mirror_spec.rb
+++ b/spec/models/remote_mirror_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe RemoteMirror, :mailer do
include GitHelpers
@@ -40,6 +40,13 @@ describe RemoteMirror, :mailer do
expect(remote_mirror).to be_invalid
expect(remote_mirror.errors[:url].first).to include('Requests to the local network are not allowed')
end
+
+ it 'returns a nil safe_url' do
+ remote_mirror = build(:remote_mirror, url: 'http://[0:0:0:0:ffff:123.123.123.123]/foo.git')
+
+ expect(remote_mirror.url).to eq('http://[0:0:0:0:ffff:123.123.123.123]/foo.git')
+ expect(remote_mirror.safe_url).to be_nil
+ end
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 79395fcc994..419e1dc2459 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1223,66 +1223,36 @@ describe Repository do
end
describe '#branch_exists?' do
- let(:branch) { repository.root_ref }
+ it 'uses branch_names' do
+ allow(repository).to receive(:branch_names).and_return(['foobar'])
- subject { repository.branch_exists?(branch) }
-
- it 'delegates to branch_names when the cache is empty' do
- repository.expire_branches_cache
-
- expect(repository).to receive(:branch_names).and_call_original
- is_expected.to eq(true)
- end
-
- it 'uses redis set caching when the cache is filled' do
- repository.branch_names # ensure the branch name cache is filled
-
- expect(repository)
- .to receive(:branch_names_include?)
- .with(branch)
- .and_call_original
-
- is_expected.to eq(true)
+ expect(repository.branch_exists?('foobar')).to eq(true)
+ expect(repository.branch_exists?('master')).to eq(false)
end
end
describe '#tag_exists?' do
- let(:tag) { repository.tags.first.name }
-
- subject { repository.tag_exists?(tag) }
-
- it 'delegates to tag_names when the cache is empty' do
- repository.expire_tags_cache
-
- expect(repository).to receive(:tag_names).and_call_original
- is_expected.to eq(true)
- end
-
- it 'uses redis set caching when the cache is filled' do
- repository.tag_names # ensure the tag name cache is filled
-
- expect(repository)
- .to receive(:tag_names_include?)
- .with(tag)
- .and_call_original
+ it 'uses tag_names' do
+ allow(repository).to receive(:tag_names).and_return(['foobar'])
- is_expected.to eq(true)
+ expect(repository.tag_exists?('foobar')).to eq(true)
+ expect(repository.tag_exists?('master')).to eq(false)
end
end
- describe '#branch_names', :clean_gitlab_redis_cache do
+ describe '#branch_names', :use_clean_rails_memory_store_caching do
let(:fake_branch_names) { ['foobar'] }
it 'gets cached across Repository instances' do
allow(repository.raw_repository).to receive(:branch_names).once.and_return(fake_branch_names)
- expect(repository.branch_names).to match_array(fake_branch_names)
+ expect(repository.branch_names).to eq(fake_branch_names)
fresh_repository = Project.find(project.id).repository
expect(fresh_repository.object_id).not_to eq(repository.object_id)
expect(fresh_repository.raw_repository).not_to receive(:branch_names)
- expect(fresh_repository.branch_names).to match_array(fake_branch_names)
+ expect(fresh_repository.branch_names).to eq(fake_branch_names)
end
end
diff --git a/spec/models/resource_label_event_spec.rb b/spec/models/resource_label_event_spec.rb
index cb52f154299..f4023dcb95a 100644
--- a/spec/models/resource_label_event_spec.rb
+++ b/spec/models/resource_label_event_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
RSpec.describe ResourceLabelEvent, type: :model do
subject { build(:resource_label_event, issue: issue) }
diff --git a/spec/models/timelog_spec.rb b/spec/models/timelog_spec.rb
index 9d69a0ab148..28fc82f2a32 100644
--- a/spec/models/timelog_spec.rb
+++ b/spec/models/timelog_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
RSpec.describe Timelog do
subject { build(:timelog) }
diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb
index 02702cb2497..d97bb8cfb90 100644
--- a/spec/models/upload_spec.rb
+++ b/spec/models/upload_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Upload do
describe 'assocations' do
diff --git a/spec/models/user_agent_detail_spec.rb b/spec/models/user_agent_detail_spec.rb
index f191d245045..5c28511b446 100644
--- a/spec/models/user_agent_detail_spec.rb
+++ b/spec/models/user_agent_detail_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe UserAgentDetail do
describe '.submittable?' do
diff --git a/spec/models/user_callout_spec.rb b/spec/models/user_callout_spec.rb
index b87f6f03d6f..de6534b480a 100644
--- a/spec/models/user_callout_spec.rb
+++ b/spec/models/user_callout_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe UserCallout do
let!(:callout) { create(:user_callout) }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index f1408194250..b8c323904b8 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -3065,6 +3065,47 @@ describe User do
end
end
+ describe '#will_save_change_to_login?' do
+ let(:user) { create(:user, username: 'old-username', email: 'old-email@example.org') }
+ let(:new_username) { 'new-name' }
+ let(:new_email) { 'new-email@example.org' }
+
+ subject { user.will_save_change_to_login? }
+
+ context 'when the username is changed' do
+ before do
+ user.username = new_username
+ end
+
+ it { is_expected.to be true }
+ end
+
+ context 'when the email is changed' do
+ before do
+ user.email = new_email
+ end
+
+ it { is_expected.to be true }
+ end
+
+ context 'when both email and username are changed' do
+ before do
+ user.username = new_username
+ user.email = new_email
+ end
+
+ it { is_expected.to be true }
+ end
+
+ context 'when email and username aren\'t changed' do
+ before do
+ user.name = 'new_name'
+ end
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
describe '#sync_attribute?' do
let(:user) { described_class.new }
diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb
index b149dbcf871..25267d36ab8 100644
--- a/spec/policies/issue_policy_spec.rb
+++ b/spec/policies/issue_policy_spec.rb
@@ -172,6 +172,34 @@ describe IssuePolicy do
expect(permissions(assignee, issue_locked)).to be_disallowed(:admin_issue, :reopen_issue)
end
+ context 'when issues are private' do
+ before do
+ project.project_feature.update(issues_access_level: ProjectFeature::PRIVATE)
+ end
+ let(:issue) { create(:issue, project: project, author: author) }
+ let(:visitor) { create(:user) }
+ let(:admin) { create(:user, :admin) }
+
+ it 'forbids visitors from viewing issues' do
+ expect(permissions(visitor, issue)).to be_disallowed(:read_issue)
+ end
+ it 'forbids visitors from commenting' do
+ expect(permissions(visitor, issue)).to be_disallowed(:create_note)
+ end
+ it 'allows guests to view' do
+ expect(permissions(guest, issue)).to be_allowed(:read_issue)
+ end
+ it 'allows guests to comment' do
+ expect(permissions(guest, issue)).to be_allowed(:create_note)
+ end
+ it 'allows admins to view' do
+ expect(permissions(admin, issue)).to be_allowed(:read_issue)
+ end
+ it 'allows admins to comment' do
+ expect(permissions(admin, issue)).to be_allowed(:create_note)
+ end
+ end
+
context 'with confidential issues' do
let(:confidential_issue) { create(:issue, :confidential, project: project, assignees: [assignee], author: author) }
let(:confidential_issue_no_assignee) { create(:issue, :confidential, project: project) }
diff --git a/spec/policies/merge_request_policy_spec.rb b/spec/policies/merge_request_policy_spec.rb
index 81279225d61..87205f56589 100644
--- a/spec/policies/merge_request_policy_spec.rb
+++ b/spec/policies/merge_request_policy_spec.rb
@@ -6,6 +6,7 @@ describe MergeRequestPolicy do
let(:guest) { create(:user) }
let(:author) { create(:user) }
let(:developer) { create(:user) }
+ let(:non_team_member) { create(:user) }
let(:project) { create(:project, :public) }
def permissions(user, merge_request)
@@ -18,6 +19,78 @@ describe MergeRequestPolicy do
project.add_developer(developer)
end
+ MR_PERMS = %i[create_merge_request_in
+ create_merge_request_from
+ read_merge_request
+ create_note].freeze
+
+ shared_examples_for 'a denied user' do
+ let(:perms) { permissions(subject, merge_request) }
+
+ MR_PERMS.each do |thing|
+ it "cannot #{thing}" do
+ expect(perms).to be_disallowed(thing)
+ end
+ end
+ end
+
+ shared_examples_for 'a user with access' do
+ let(:perms) { permissions(subject, merge_request) }
+
+ MR_PERMS.each do |thing|
+ it "can #{thing}" do
+ expect(perms).to be_allowed(thing)
+ end
+ end
+ end
+
+ context 'when merge requests have been disabled' do
+ let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: author) }
+
+ before do
+ project.project_feature.update(merge_requests_access_level: ProjectFeature::DISABLED)
+ end
+
+ describe 'the author' do
+ subject { author }
+ it_behaves_like 'a denied user'
+ end
+
+ describe 'a guest' do
+ subject { guest }
+ it_behaves_like 'a denied user'
+ end
+
+ describe 'a developer' do
+ subject { developer }
+ it_behaves_like 'a denied user'
+ end
+
+ describe 'any other user' do
+ subject { non_team_member }
+ it_behaves_like 'a denied user'
+ end
+ end
+
+ context 'when merge requests are private' do
+ let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: author) }
+
+ before do
+ project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ project.project_feature.update(merge_requests_access_level: ProjectFeature::PRIVATE)
+ end
+
+ describe 'a non-team-member' do
+ subject { non_team_member }
+ it_behaves_like 'a denied user'
+ end
+
+ describe 'a developer' do
+ subject { developer }
+ it_behaves_like 'a user with access'
+ end
+ end
+
context 'when merge request is unlocked' do
let(:merge_request) { create(:merge_request, :closed, source_project: project, target_project: project, author: author) }
@@ -48,6 +121,22 @@ describe MergeRequestPolicy do
it 'prevents guests from reopening merge request' do
expect(permissions(guest, merge_request_locked)).to be_disallowed(:reopen_merge_request)
end
+
+ context 'when the user is not a project member' do
+ let(:user) { create(:user) }
+
+ it 'cannot create a note' do
+ expect(permissions(user, merge_request_locked)).to be_disallowed(:create_note)
+ end
+ end
+
+ context 'when the user is project member, with at least guest access' do
+ let(:user) { guest }
+
+ it 'can create a note' do
+ expect(permissions(user, merge_request_locked)).to be_allowed(:create_note)
+ end
+ end
end
context 'with external authorization enabled' do
diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb
index eacf383be7d..8680e8b9b45 100644
--- a/spec/presenters/blob_presenter_spec.rb
+++ b/spec/presenters/blob_presenter_spec.rb
@@ -39,6 +39,20 @@ describe BlobPresenter, :seed_helper do
subject.highlight(plain: true)
end
+ context '"to" param is present' do
+ before do
+ allow(git_blob)
+ .to receive(:data)
+ .and_return("line one\nline two\nline 3")
+ end
+
+ it 'returns limited highlighted content' do
+ expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', "line one\n", plain: nil, language: nil)
+
+ subject.highlight(to: 1)
+ end
+ end
+
context 'gitlab-language contains a match' do
before do
allow(blob).to receive(:language_from_gitattributes).and_return('ruby')
diff --git a/spec/presenters/ci/pipeline_presenter_spec.rb b/spec/presenters/ci/pipeline_presenter_spec.rb
index cda07a0ae09..7e8bbedcf6d 100644
--- a/spec/presenters/ci/pipeline_presenter_spec.rb
+++ b/spec/presenters/ci/pipeline_presenter_spec.rb
@@ -77,6 +77,40 @@ describe Ci::PipelinePresenter do
end
end
+ describe '#name' do
+ subject { presenter.name }
+
+ context 'when pipeline is detached merge request pipeline' do
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
+ let(:pipeline) { merge_request.all_pipelines.last }
+
+ it { is_expected.to eq('Detached merge request pipeline') }
+ end
+
+ context 'when pipeline is merge request pipeline' do
+ let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) }
+ let(:pipeline) { merge_request.all_pipelines.last }
+
+ it { is_expected.to eq('Merged result pipeline') }
+ end
+
+ context 'when pipeline is merge train pipeline' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ before do
+ allow(pipeline).to receive(:merge_request_event_type) { :merge_train }
+ end
+
+ it { is_expected.to eq('Merge train pipeline') }
+ end
+
+ context 'when pipeline is branch pipeline' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ it { is_expected.to eq('Pipeline') }
+ end
+ end
+
describe '#ref_text' do
subject { presenter.ref_text }
diff --git a/spec/presenters/clusterable_presenter_spec.rb b/spec/presenters/clusterable_presenter_spec.rb
index 05afe5347d1..47ccc59ae45 100644
--- a/spec/presenters/clusterable_presenter_spec.rb
+++ b/spec/presenters/clusterable_presenter_spec.rb
@@ -78,4 +78,13 @@ describe ClusterablePresenter do
it_behaves_like 'appropriate member permissions'
end
end
+
+ describe '#environments_cluster_path' do
+ subject { described_class.new(clusterable).environments_cluster_path(cluster) }
+
+ let(:clusterable) { create(:group) }
+ let(:cluster) { create(:cluster_for_group, groups: [clusterable]) }
+
+ it { is_expected.to be_nil }
+ end
end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
deleted file mode 100644
index 671fd6c8666..00000000000
--- a/spec/rails_helper.rb
+++ /dev/null
@@ -1 +0,0 @@
-require "spec_helper"
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 3ab1818bebb..c94f6d22e74 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -925,19 +925,20 @@ describe API::Internal do
it 'returns link to create new merge request' do
post api('/internal/post_receive'), params: valid_params
- expect(json_response['merge_request_urls']).to match [{
- "branch_name" => branch_name,
- "url" => "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{branch_name}",
- "new_merge_request" => true
- }]
+ message = <<~MESSAGE.strip
+ To create a merge request for #{branch_name}, visit:
+ http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{branch_name}
+ MESSAGE
+
+ expect(json_response['messages']).to include(build_basic_message(message))
end
- it 'returns empty array if printing_merge_request_link_enabled is false' do
+ it 'returns no merge request messages if printing_merge_request_link_enabled is false' do
project.update!(printing_merge_request_link_enabled: false)
post api('/internal/post_receive'), params: valid_params
- expect(json_response['merge_request_urls']).to eq([])
+ expect(json_response['messages']).to be_blank
end
it 'does not invoke MergeRequests::PushOptionsHandlerService' do
@@ -968,11 +969,12 @@ describe API::Internal do
it 'links to the newly created merge request' do
post api('/internal/post_receive'), params: valid_params
- expect(json_response['merge_request_urls']).to match [{
- 'branch_name' => branch_name,
- 'url' => "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/1",
- 'new_merge_request' => false
- }]
+ message = <<~MESSAGE.strip
+ View merge request for #{branch_name}:
+ http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/1
+ MESSAGE
+
+ expect(json_response['messages']).to include(build_basic_message(message))
end
it 'adds errors on the service instance to warnings' do
@@ -982,7 +984,8 @@ describe API::Internal do
post api('/internal/post_receive'), params: valid_params
- expect(json_response['warnings']).to eq('Error encountered with push options \'merge_request.create\': my error')
+ message = "WARNINGS:\nError encountered with push options 'merge_request.create': my error"
+ expect(json_response['messages']).to include(build_alert_message(message))
end
it 'adds ActiveRecord errors on invalid MergeRequest records to warnings' do
@@ -995,38 +998,39 @@ describe API::Internal do
post api('/internal/post_receive'), params: valid_params
- expect(json_response['warnings']).to eq('Error encountered with push options \'merge_request.create\': my error')
+ message = "WARNINGS:\nError encountered with push options 'merge_request.create': my error"
+ expect(json_response['messages']).to include(build_alert_message(message))
end
end
context 'broadcast message exists' do
let!(:broadcast_message) { create(:broadcast_message, starts_at: 1.day.ago, ends_at: 1.day.from_now ) }
- it 'returns one broadcast message' do
+ it 'outputs a broadcast message' do
post api('/internal/post_receive'), params: valid_params
expect(response).to have_gitlab_http_status(200)
- expect(json_response['broadcast_message']).to eq(broadcast_message.message)
+ expect(json_response['messages']).to include(build_alert_message(broadcast_message.message))
end
end
context 'broadcast message does not exist' do
- it 'returns empty string' do
+ it 'does not output a broadcast message' do
post api('/internal/post_receive'), params: valid_params
expect(response).to have_gitlab_http_status(200)
- expect(json_response['broadcast_message']).to eq(nil)
+ expect(has_alert_messages?(json_response['messages'])).to be_falsey
end
end
context 'nil broadcast message' do
- it 'returns empty string' do
+ it 'does not output a broadcast message' do
allow(BroadcastMessage).to receive(:current).and_return(nil)
post api('/internal/post_receive'), params: valid_params
expect(response).to have_gitlab_http_status(200)
- expect(json_response['broadcast_message']).to eq(nil)
+ expect(has_alert_messages?(json_response['messages'])).to be_falsey
end
end
@@ -1038,8 +1042,7 @@ describe API::Internal do
post api('/internal/post_receive'), params: valid_params
expect(response).to have_gitlab_http_status(200)
- expect(json_response["redirected_message"]).to be_present
- expect(json_response["redirected_message"]).to eq(project_moved.message)
+ expect(json_response['messages']).to include(build_basic_message(project_moved.message))
end
end
@@ -1051,8 +1054,7 @@ describe API::Internal do
post api('/internal/post_receive'), params: valid_params
expect(response).to have_gitlab_http_status(200)
- expect(json_response["project_created_message"]).to be_present
- expect(json_response["project_created_message"]).to eq(project_created.message)
+ expect(json_response['messages']).to include(build_basic_message(project_created.message))
end
end
@@ -1172,4 +1174,18 @@ describe API::Internal do
}
)
end
+
+ def build_alert_message(message)
+ { 'type' => 'alert', 'message' => message }
+ end
+
+ def build_basic_message(message)
+ { 'type' => 'basic', 'message' => message }
+ end
+
+ def has_alert_messages?(messages)
+ messages.any? do |message|
+ message['type'] == 'alert'
+ end
+ end
end
diff --git a/spec/requests/api/pages_domains_spec.rb b/spec/requests/api/pages_domains_spec.rb
index 449032b95b7..326b724666d 100644
--- a/spec/requests/api/pages_domains_spec.rb
+++ b/spec/requests/api/pages_domains_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe API::PagesDomains do
set(:project) { create(:project, path: 'my.project', pages_https_only: false) }
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 29f69b6ce20..2e6e13aa927 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe API::ProjectSnippets do
set(:project) { create(:project, :public) }
@@ -96,6 +96,28 @@ describe API::ProjectSnippets do
}
end
+ context 'with a regular user' do
+ let(:user) { create(:user) }
+
+ before do
+ project.add_developer(user)
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::PRIVATE])
+ params['visibility'] = 'internal'
+ end
+
+ it 'creates a new snippet' do
+ post api("/projects/#{project.id}/snippets/", user), params: params
+
+ expect(response).to have_gitlab_http_status(201)
+ snippet = ProjectSnippet.find(json_response['id'])
+ expect(snippet.content).to eq(params[:code])
+ expect(snippet.description).to eq(params[:description])
+ expect(snippet.title).to eq(params[:title])
+ expect(snippet.file_name).to eq(params[:file_name])
+ expect(snippet.visibility_level).to eq(Snippet::INTERNAL)
+ end
+ end
+
it 'creates a new snippet' do
post api("/projects/#{project.id}/snippets/", admin), params: params
@@ -108,6 +130,29 @@ describe API::ProjectSnippets do
expect(snippet.visibility_level).to eq(Snippet::PUBLIC)
end
+ it 'creates a new snippet with content parameter' do
+ params[:content] = params.delete(:code)
+
+ post api("/projects/#{project.id}/snippets/", admin), params: params
+
+ expect(response).to have_gitlab_http_status(201)
+ snippet = ProjectSnippet.find(json_response['id'])
+ expect(snippet.content).to eq(params[:content])
+ expect(snippet.description).to eq(params[:description])
+ expect(snippet.title).to eq(params[:title])
+ expect(snippet.file_name).to eq(params[:file_name])
+ expect(snippet.visibility_level).to eq(Snippet::PUBLIC)
+ end
+
+ it 'returns 400 when both code and content parameters specified' do
+ params[:content] = params[:code]
+
+ post api("/projects/#{project.id}/snippets/", admin), params: params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('code, content are mutually exclusive')
+ end
+
it 'returns 400 for missing parameters' do
params.delete(:title)
@@ -167,7 +212,20 @@ describe API::ProjectSnippets do
new_content = 'New content'
new_description = 'New description'
- put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), params: { code: new_content, description: new_description }
+ put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), params: { code: new_content, description: new_description, visibility: 'private' }
+
+ expect(response).to have_gitlab_http_status(200)
+ snippet.reload
+ expect(snippet.content).to eq(new_content)
+ expect(snippet.description).to eq(new_description)
+ expect(snippet.visibility).to eq('private')
+ end
+
+ it 'updates snippet with content parameter' do
+ new_content = 'New content'
+ new_description = 'New description'
+
+ put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), params: { content: new_content, description: new_description }
expect(response).to have_gitlab_http_status(200)
snippet.reload
@@ -175,6 +233,13 @@ describe API::ProjectSnippets do
expect(snippet.description).to eq(new_description)
end
+ it 'returns 400 when both code and content parameters specified' do
+ put api("/projects/#{snippet.project.id}/snippets/1234", admin), params: { code: 'some content', content: 'other content' }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('code, content are mutually exclusive')
+ end
+
it 'returns 404 for invalid snippet id' do
put api("/projects/#{snippet.project.id}/snippets/1234", admin), params: { title: 'foo' }
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 5465fe0c366..550c7d135a6 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -5,6 +5,8 @@ shared_examples 'languages and percentages JSON response' do
let(:expected_languages) { project.repository.languages.map { |language| language.values_at(:label, :value)}.to_h }
before do
+ allow(DetectRepositoryLanguagesWorker).to receive(:perform_async).and_call_original
+
allow(project.repository).to receive(:languages).and_return(
[{ value: 66.69, label: "Ruby", color: "#701516", highlight: "#701516" },
{ value: 22.98, label: "JavaScript", color: "#f1e05a", highlight: "#f1e05a" },
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 590107d5161..048d04cdefd 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -224,5 +224,33 @@ describe API::Settings, 'Settings' do
expect(json_response['error']).to eq('plantuml_url is missing')
end
end
+
+ context 'asset_proxy settings' do
+ it 'updates application settings' do
+ put api('/application/settings', admin),
+ params: {
+ asset_proxy_enabled: true,
+ asset_proxy_url: 'http://assets.example.com',
+ asset_proxy_secret_key: 'shared secret',
+ asset_proxy_whitelist: ['example.com', '*.example.com']
+ }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['asset_proxy_enabled']).to be(true)
+ expect(json_response['asset_proxy_url']).to eq('http://assets.example.com')
+ expect(json_response['asset_proxy_secret_key']).to be_nil
+ expect(json_response['asset_proxy_whitelist']).to eq(['example.com', '*.example.com', 'localhost'])
+ end
+
+ it 'allows a string for asset_proxy_whitelist' do
+ put api('/application/settings', admin),
+ params: {
+ asset_proxy_whitelist: 'example.com, *.example.com'
+ }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['asset_proxy_whitelist']).to eq(['example.com', '*.example.com', 'localhost'])
+ end
+ end
end
end
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index d600076e9fb..515912cb305 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe API::Snippets do
let!(:user) { create(:user) }
@@ -193,18 +193,32 @@ describe API::Snippets do
}
end
- it 'creates a new snippet' do
- expect do
- post api("/snippets/", user), params: params
- end.to change { PersonalSnippet.count }.by(1)
+ shared_examples 'snippet creation' do
+ it 'creates a new snippet' do
+ expect do
+ post api("/snippets/", user), params: params
+ end.to change { PersonalSnippet.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['title']).to eq(params[:title])
+ expect(json_response['description']).to eq(params[:description])
+ expect(json_response['file_name']).to eq(params[:file_name])
+ expect(json_response['visibility']).to eq(params[:visibility])
+ end
+ end
- expect(response).to have_gitlab_http_status(201)
- expect(json_response['title']).to eq(params[:title])
- expect(json_response['description']).to eq(params[:description])
- expect(json_response['file_name']).to eq(params[:file_name])
- expect(json_response['visibility']).to eq(params[:visibility])
+ context 'with restricted visibility settings' do
+ before do
+ stub_application_setting(restricted_visibility_levels:
+ [Gitlab::VisibilityLevel::INTERNAL,
+ Gitlab::VisibilityLevel::PRIVATE])
+ end
+
+ it_behaves_like 'snippet creation'
end
+ it_behaves_like 'snippet creation'
+
it 'returns 400 for missing parameters' do
params.delete(:title)
@@ -253,18 +267,33 @@ describe API::Snippets do
create(:personal_snippet, author: user, visibility_level: visibility_level)
end
- it 'updates snippet' do
- new_content = 'New content'
- new_description = 'New description'
+ shared_examples 'snippet updates' do
+ it 'updates a snippet' do
+ new_content = 'New content'
+ new_description = 'New description'
- put api("/snippets/#{snippet.id}", user), params: { content: new_content, description: new_description }
+ put api("/snippets/#{snippet.id}", user), params: { content: new_content, description: new_description, visibility: 'internal' }
- expect(response).to have_gitlab_http_status(200)
- snippet.reload
- expect(snippet.content).to eq(new_content)
- expect(snippet.description).to eq(new_description)
+ expect(response).to have_gitlab_http_status(200)
+ snippet.reload
+ expect(snippet.content).to eq(new_content)
+ expect(snippet.description).to eq(new_description)
+ expect(snippet.visibility).to eq('internal')
+ end
+ end
+
+ context 'with restricted visibility settings' do
+ before do
+ stub_application_setting(restricted_visibility_levels:
+ [Gitlab::VisibilityLevel::PUBLIC,
+ Gitlab::VisibilityLevel::PRIVATE])
+ end
+
+ it_behaves_like 'snippet updates'
end
+ it_behaves_like 'snippet updates'
+
it 'returns 404 for invalid snippet id' do
put api("/snippets/1234", user), params: { title: 'foo' }
diff --git a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
index 27df42c0aee..ce20d494542 100644
--- a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
+++ b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
@@ -19,6 +19,15 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do
SOURCE
end
+ it 'flags the use of `prepend_if_ee QA::EE` in the middle of a file' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ prepend_if_ee 'QA::EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ end
+ SOURCE
+ end
+
it 'does not flag the use of `prepend_if_ee EEFoo` in the middle of a file' do
expect_no_offenses(<<~SOURCE)
class Foo
@@ -176,6 +185,16 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do
SOURCE
end
+ it 'disallows the use of prepend to inject a QA::EE module' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.prepend(QA::EE::Foo)
+ ^^^^^^^^^^^^^^^^^^^^^^^^ EE modules must be injected using `include_if_ee`, `extend_if_ee`, or `prepend_if_ee`
+ SOURCE
+ end
+
it 'disallows the use of extend to inject an EE module' do
expect_offense(<<~SOURCE)
class Foo
diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb
index 1b19eac9a97..79f89dc1a9c 100644
--- a/spec/serializers/deployment_entity_spec.rb
+++ b/spec/serializers/deployment_entity_spec.rb
@@ -36,6 +36,15 @@ describe DeploymentEntity do
expect(subject).to include(:deployed_at)
end
+ context 'when deployable is nil' do
+ let(:entity) { described_class.new(deployment, request: request, deployment_details: false) }
+ let(:deployment) { create(:deployment, deployable: nil, project: project) }
+
+ it 'does not expose deployable entry' do
+ expect(subject).not_to include(:deployable)
+ end
+ end
+
context 'when the pipeline has another manual action' do
let(:other_build) { create(:ci_build, :manual, name: 'another deploy', pipeline: pipeline) }
let!(:other_deployment) { create(:deployment, deployable: other_build) }
diff --git a/spec/serializers/entity_date_helper_spec.rb b/spec/serializers/entity_date_helper_spec.rb
index ae0f917415c..73506954965 100644
--- a/spec/serializers/entity_date_helper_spec.rb
+++ b/spec/serializers/entity_date_helper_spec.rb
@@ -57,6 +57,28 @@ describe EntityDateHelper do
end
end
+ context 'when milestone due date is today' do
+ let(:milestone_remaining) { date_helper_class.remaining_days_in_words(Date.today) }
+
+ it 'returns today' do
+ expect(milestone_remaining).to eq("<strong>Today</strong>")
+ end
+ end
+
+ context 'when milestone due date is tomorrow' do
+ let(:milestone_remaining) { date_helper_class.remaining_days_in_words(Date.tomorrow) }
+
+ it 'returns 1 day remaining' do
+ expect(milestone_remaining).to eq("<strong>1</strong> day remaining")
+ end
+
+ it 'returns 1 day remaining when queried mid-day' do
+ Timecop.freeze(Time.utc(2017, 3, 17, 13, 10)) do
+ expect(milestone_remaining).to eq("<strong>1</strong> day remaining")
+ end
+ end
+ end
+
context 'when less than 1 year and more than 30 days remaining' do
let(:milestone_remaining) { date_helper_class.remaining_days_in_words(2.months.from_now.utc.to_date) }
diff --git a/spec/serializers/merge_request_serializer_spec.rb b/spec/serializers/merge_request_serializer_spec.rb
index 276e0f6ff3d..d1483c3c41e 100644
--- a/spec/serializers/merge_request_serializer_spec.rb
+++ b/spec/serializers/merge_request_serializer_spec.rb
@@ -41,6 +41,14 @@ describe MergeRequestSerializer do
end
end
+ context 'noteable merge request serialization' do
+ let(:serializer) { 'noteable' }
+
+ it 'matches noteable merge request json schema' do
+ expect(json_entity).to match_schema('entities/merge_request_noteable', strict: true)
+ end
+ end
+
context 'no serializer' do
let(:serializer) { nil }
diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb
index 6be612ec226..eb9972d3e4d 100644
--- a/spec/serializers/pipeline_entity_spec.rb
+++ b/spec/serializers/pipeline_entity_spec.rb
@@ -41,7 +41,7 @@ describe PipelineEntity do
it 'contains details' do
expect(subject).to include :details
expect(subject[:details])
- .to include :duration, :finished_at
+ .to include :duration, :finished_at, :name
expect(subject[:details][:status]).to include :icon, :favicon, :text, :label, :tooltip
end
@@ -211,6 +211,10 @@ describe PipelineEntity do
expect(subject[:source_sha]).to be_present
expect(subject[:target_sha]).to be_present
end
+
+ it 'exposes merge request event type' do
+ expect(subject[:merge_request_event_type]).to be_present
+ end
end
end
end
diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb
index ab06c1a1209..51fb43907a6 100644
--- a/spec/services/application_settings/update_service_spec.rb
+++ b/spec/services/application_settings/update_service_spec.rb
@@ -110,6 +110,39 @@ describe ApplicationSettings::UpdateService do
end
end
+ describe 'markdown cache invalidators' do
+ shared_examples 'invalidates markdown cache' do |attribute|
+ let(:params) { attribute }
+
+ it 'increments cache' do
+ expect { subject.execute }.to change(application_settings, :local_markdown_version).by(1)
+ end
+ end
+
+ it_behaves_like 'invalidates markdown cache', { asset_proxy_enabled: true }
+ it_behaves_like 'invalidates markdown cache', { asset_proxy_url: 'http://test.com' }
+ it_behaves_like 'invalidates markdown cache', { asset_proxy_secret_key: 'another secret' }
+ it_behaves_like 'invalidates markdown cache', { asset_proxy_whitelist: ['domain.com'] }
+
+ context 'when also setting the local_markdown_version' do
+ let(:params) { { asset_proxy_enabled: true, local_markdown_version: 12 } }
+
+ it 'does not increment' do
+ expect { subject.execute }.to change(application_settings, :local_markdown_version).to(12)
+ end
+ end
+
+ context 'do not invalidate if value does not change' do
+ let(:params) { { asset_proxy_enabled: true, asset_proxy_secret_key: 'secret', asset_proxy_url: 'http://test.com' } }
+
+ it 'does not increment' do
+ described_class.new(application_settings, admin, params).execute
+
+ expect { described_class.new(application_settings, admin, params).execute }.not_to change(application_settings, :local_markdown_version)
+ end
+ end
+ end
+
describe 'performance bar settings' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
index 3ca389ba25b..2807b8c8c85 100644
--- a/spec/services/auth/container_registry_authentication_service_spec.rb
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -476,7 +476,7 @@ describe Auth::ContainerRegistryAuthenticationService do
let(:current_user) { create(:user) }
let(:authentication_abilities) do
- [:build_read_container_image, :build_create_container_image]
+ [:build_read_container_image, :build_create_container_image, :build_destroy_container_image]
end
before do
@@ -507,19 +507,19 @@ describe Auth::ContainerRegistryAuthenticationService do
end
end
- context 'disallow to delete images' do
+ context 'allow to delete images since registry 2.7' do
let(:current_params) do
- { scopes: ["repository:#{current_project.full_path}:*"] }
+ { scopes: ["repository:#{current_project.full_path}:delete"] }
end
- it_behaves_like 'an inaccessible' do
+ it_behaves_like 'a deletable since registry 2.7' do
let(:project) { current_project }
end
end
- context 'disallow to delete images since registry 2.7' do
+ context 'disallow to delete images' do
let(:current_params) do
- { scopes: ["repository:#{current_project.full_path}:delete"] }
+ { scopes: ["repository:#{current_project.full_path}:*"] }
end
it_behaves_like 'an inaccessible' do
diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb
index 2ebfd295fa2..2535f339495 100644
--- a/spec/services/boards/lists/list_service_spec.rb
+++ b/spec/services/boards/lists/list_service_spec.rb
@@ -3,13 +3,15 @@
require 'spec_helper'
describe Boards::Lists::ListService do
+ let(:user) { create(:user) }
+
describe '#execute' do
context 'when board parent is a project' do
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
let(:label) { create(:label, project: project) }
let!(:list) { create(:list, board: board, label: label) }
- let(:service) { described_class.new(project, double) }
+ let(:service) { described_class.new(project, user) }
it_behaves_like 'lists list service'
end
@@ -19,7 +21,7 @@ describe Boards::Lists::ListService do
let(:board) { create(:board, group: group) }
let(:label) { create(:group_label, group: group) }
let!(:list) { create(:list, board: board, label: label) }
- let(:service) { described_class.new(group, double) }
+ let(:service) { described_class.new(group, user) }
it_behaves_like 'lists list service'
end
diff --git a/spec/services/boards/lists/update_service_spec.rb b/spec/services/boards/lists/update_service_spec.rb
new file mode 100644
index 00000000000..f28bbab941a
--- /dev/null
+++ b/spec/services/boards/lists/update_service_spec.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Boards::Lists::UpdateService do
+ let(:user) { create(:user) }
+ let!(:list) { create(:list, board: board, position: 0) }
+
+ shared_examples 'moving list' do
+ context 'when user can admin list' do
+ it 'calls Lists::MoveService to update list position' do
+ board.parent.add_developer(user)
+ service = described_class.new(board.parent, user, position: 1)
+
+ expect(Boards::Lists::MoveService).to receive(:new).with(board.parent, user, { position: 1 }).and_call_original
+ expect_any_instance_of(Boards::Lists::MoveService).to receive(:execute).with(list)
+
+ service.execute(list)
+ end
+ end
+
+ context 'when user cannot admin list' do
+ it 'does not call Lists::MoveService to update list position' do
+ service = described_class.new(board.parent, user, position: 1)
+
+ expect(Boards::Lists::MoveService).not_to receive(:new)
+
+ service.execute(list)
+ end
+ end
+ end
+
+ shared_examples 'updating list preferences' do
+ context 'when user can read list' do
+ it 'updates list preference for user' do
+ board.parent.add_guest(user)
+ service = described_class.new(board.parent, user, collapsed: true)
+
+ service.execute(list)
+
+ expect(list.preferences_for(user).collapsed).to eq(true)
+ end
+ end
+
+ context 'when user cannot read list' do
+ it 'does not update list preference for user' do
+ service = described_class.new(board.parent, user, collapsed: true)
+
+ service.execute(list)
+
+ expect(list.preferences_for(user).collapsed).to be_nil
+ end
+ end
+ end
+
+ describe '#execute' do
+ context 'when position parameter is present' do
+ context 'for projects' do
+ it_behaves_like 'moving list' do
+ let(:project) { create(:project, :private) }
+ let(:board) { create(:board, project: project) }
+ end
+ end
+
+ context 'for groups' do
+ it_behaves_like 'moving list' do
+ let(:group) { create(:group, :private) }
+ let(:board) { create(:board, group: group) }
+ end
+ end
+ end
+
+ context 'when collapsed parameter is present' do
+ context 'for projects' do
+ it_behaves_like 'updating list preferences' do
+ let(:project) { create(:project, :private) }
+ let(:board) { create(:board, project: project) }
+ end
+ end
+
+ context 'for groups' do
+ it_behaves_like 'updating list preferences' do
+ let(:group) { create(:group, :private) }
+ let(:board) { create(:board, group: group) }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/chat_names/authorize_user_service_spec.rb b/spec/services/chat_names/authorize_user_service_spec.rb
index 41cbac4e8e9..7f32948daad 100644
--- a/spec/services/chat_names/authorize_user_service_spec.rb
+++ b/spec/services/chat_names/authorize_user_service_spec.rb
@@ -4,23 +4,36 @@ require 'spec_helper'
describe ChatNames::AuthorizeUserService do
describe '#execute' do
- let(:service) { create(:service) }
+ subject { described_class.new(service, params) }
- subject { described_class.new(service, params).execute }
+ let(:result) { subject.execute }
+ let(:service) { create(:service) }
context 'when all parameters are valid' do
let(:params) { { team_id: 'T0001', team_domain: 'myteam', user_id: 'U0001', user_name: 'user' } }
+ it 'produces a valid HTTP URL' do
+ expect(result).to be_http_url
+ end
+
it 'requests a new token' do
- is_expected.to be_url
+ expect(subject).to receive(:request_token).once.and_call_original
+
+ subject.execute
end
end
context 'when there are missing parameters' do
let(:params) { {} }
+ it 'does not produce a URL' do
+ expect(result).to be_nil
+ end
+
it 'does not request a new token' do
- is_expected.to be_nil
+ expect(subject).not_to receive(:request_token)
+
+ subject.execute
end
end
end
diff --git a/spec/services/clusters/applications/check_installation_progress_service_spec.rb b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
index a54bd85a11a..464a67649ff 100644
--- a/spec/services/clusters/applications/check_installation_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
@@ -14,7 +14,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
let(:phase) { a_phase }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
context "when phase is #{a_phase}" do
@@ -44,7 +44,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
before do
application.update!(cluster: cluster)
- expect(service).to receive(:installation_phase).and_raise(error)
+ expect(service).to receive(:pod_phase).and_raise(error)
end
include_examples 'logs kubernetes errors' do
@@ -77,7 +77,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
context 'when installation POD succeeded' do
let(:phase) { Gitlab::Kubernetes::Pod::SUCCEEDED }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
it 'removes the installation POD' do
@@ -101,7 +101,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
let(:errors) { 'test installation failed' }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
it 'make the application errored' do
@@ -116,7 +116,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
let(:application) { create(:clusters_applications_helm, :timed_out, :updating) }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
it 'make the application errored' do
@@ -138,7 +138,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
context 'when installation POD succeeded' do
let(:phase) { Gitlab::Kubernetes::Pod::SUCCEEDED }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
it 'removes the installation POD' do
@@ -162,7 +162,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
let(:errors) { 'test installation failed' }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
it 'make the application errored' do
@@ -177,7 +177,7 @@ describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
let(:application) { create(:clusters_applications_helm, :timed_out) }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
it 'make the application errored' do
diff --git a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
index a948b442441..1a9f7089c3d 100644
--- a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
@@ -20,7 +20,7 @@ describe Clusters::Applications::CheckUninstallProgressService do
let(:phase) { a_phase }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
context "when phase is #{a_phase}" do
@@ -47,7 +47,7 @@ describe Clusters::Applications::CheckUninstallProgressService do
context 'when installation POD succeeded' do
let(:phase) { Gitlab::Kubernetes::Pod::SUCCEEDED }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
it 'removes the installation POD' do
@@ -95,7 +95,7 @@ describe Clusters::Applications::CheckUninstallProgressService do
let(:errors) { 'test installation failed' }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
it 'make the application errored' do
@@ -110,7 +110,7 @@ describe Clusters::Applications::CheckUninstallProgressService do
let(:application) { create(:clusters_applications_prometheus, :timed_out, :uninstalling) }
before do
- expect(service).to receive(:installation_phase).once.and_return(phase)
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
it 'make the application errored' do
@@ -131,7 +131,7 @@ describe Clusters::Applications::CheckUninstallProgressService do
before do
application.update!(cluster: cluster)
- expect(service).to receive(:installation_phase).and_raise(error)
+ expect(service).to receive(:pod_phase).and_raise(error)
end
include_examples 'logs kubernetes errors' do
diff --git a/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb b/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb
index e44cc3f5a78..5a3b1cd6cfb 100644
--- a/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb
+++ b/spec/services/clusters/kubernetes/create_or_update_namespace_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do
+describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do
include KubernetesHelpers
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
@@ -35,8 +35,8 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d
stub_kubeclient_create_service_account(api_url, namespace: namespace)
stub_kubeclient_create_secret(api_url, namespace: namespace)
stub_kubeclient_put_secret(api_url, "#{namespace}-token", namespace: namespace)
- stub_kubeclient_put_role(api_url, Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace)
- stub_kubeclient_put_role_binding(api_url, Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace)
+ stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace)
+ stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace)
stub_kubeclient_get_secret(
api_url,
@@ -56,7 +56,7 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d
end
it 'creates project service account' do
- expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:execute).once
+ expect_any_instance_of(Clusters::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:execute).once
subject
end
@@ -123,7 +123,7 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d
end
it 'creates project service account' do
- expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:execute).once
+ expect_any_instance_of(Clusters::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:execute).once
subject
end
diff --git a/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb b/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb
index 8b874989758..10dbfc800ff 100644
--- a/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb
+++ b/spec/services/clusters/kubernetes/create_or_update_service_account_service_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-describe Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService do
+describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do
include KubernetesHelpers
let(:api_url) { 'http://111.111.111.111' }
@@ -143,8 +143,8 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService do
stub_kubeclient_get_role_binding_error(api_url, role_binding_name, namespace: namespace)
stub_kubeclient_create_role_binding(api_url, namespace: namespace)
- stub_kubeclient_put_role(api_url, Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace)
- stub_kubeclient_put_role_binding(api_url, Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace)
+ stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace)
+ stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace)
end
it_behaves_like 'creates service account and token'
@@ -175,10 +175,10 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService do
it 'creates a role and role binding granting knative serving permissions to the service account' do
subject
- expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME}").with(
+ expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME}").with(
body: hash_including(
metadata: {
- name: Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME,
+ name: Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME,
namespace: namespace
},
rules: [{
diff --git a/spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb b/spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb
index 93c0dc37ade..145528616ee 100644
--- a/spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb
+++ b/spec/services/clusters/kubernetes/fetch_kubernetes_token_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Clusters::Gcp::Kubernetes::FetchKubernetesTokenService do
+describe Clusters::Kubernetes::FetchKubernetesTokenService do
include KubernetesHelpers
describe '#execute' do
diff --git a/spec/services/create_snippet_service_spec.rb b/spec/services/create_snippet_service_spec.rb
index 9b83f65a17e..7d2491b3a49 100644
--- a/spec/services/create_snippet_service_spec.rb
+++ b/spec/services/create_snippet_service_spec.rb
@@ -34,6 +34,19 @@ describe CreateSnippetService do
expect(snippet.errors.any?).to be_falsey
expect(snippet.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
+
+ describe "when visibility level is passed as a string" do
+ before do
+ @opts[:visibility] = 'internal'
+ @opts.delete(:visibility_level)
+ end
+
+ it "assigns the correct visibility level" do
+ snippet = create_snippet(nil, @user, @opts)
+ expect(snippet.errors.any?).to be_falsey
+ expect(snippet.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+ end
+ end
end
describe 'usage counter' do
diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb
index f3af8cf5f3b..0cbb3122bb0 100644
--- a/spec/services/groups/transfer_service_spec.rb
+++ b/spec/services/groups/transfer_service_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Groups::TransferService do
let(:user) { create(:user) }
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 6874a8a0929..642a49d57d5 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -60,35 +60,63 @@ describe Issues::CloseService do
describe '#close_issue' do
context "closed by a merge request" do
- before do
+ it 'mentions closure via a merge request' do
perform_enqueued_jobs do
described_class.new(project, user).close_issue(issue, closed_via: closing_merge_request)
end
- end
- it 'mentions closure via a merge request' do
email = ActionMailer::Base.deliveries.last
expect(email.to.first).to eq(user2.email)
expect(email.subject).to include(issue.title)
expect(email.body.parts.map(&:body)).to all(include(closing_merge_request.to_reference))
end
+
+ context 'when user cannot read merge request' do
+ it 'does not mention merge request' do
+ project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED)
+ perform_enqueued_jobs do
+ described_class.new(project, user).close_issue(issue, closed_via: closing_merge_request)
+ end
+
+ email = ActionMailer::Base.deliveries.last
+ body_text = email.body.parts.map(&:body).join(" ")
+
+ expect(email.to.first).to eq(user2.email)
+ expect(email.subject).to include(issue.title)
+ expect(body_text).not_to include(closing_merge_request.to_reference)
+ end
+ end
end
context "closed by a commit" do
- before do
+ it 'mentions closure via a commit' do
perform_enqueued_jobs do
described_class.new(project, user).close_issue(issue, closed_via: closing_commit)
end
- end
- it 'mentions closure via a commit' do
email = ActionMailer::Base.deliveries.last
expect(email.to.first).to eq(user2.email)
expect(email.subject).to include(issue.title)
expect(email.body.parts.map(&:body)).to all(include(closing_commit.id))
end
+
+ context 'when user cannot read the commit' do
+ it 'does not mention the commit id' do
+ project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED)
+ perform_enqueued_jobs do
+ described_class.new(project, user).close_issue(issue, closed_via: closing_commit)
+ end
+
+ email = ActionMailer::Base.deliveries.last
+ body_text = email.body.parts.map(&:body).join(" ")
+
+ expect(email.to.first).to eq(user2.email)
+ expect(email.subject).to include(issue.title)
+ expect(body_text).not_to include(closing_commit.id)
+ end
+ end
end
context "valid params" do
diff --git a/spec/services/metrics/dashboard/project_dashboard_service_spec.rb b/spec/services/metrics/dashboard/project_dashboard_service_spec.rb
index 1357914be2a..e76db868425 100644
--- a/spec/services/metrics/dashboard/project_dashboard_service_spec.rb
+++ b/spec/services/metrics/dashboard/project_dashboard_service_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Metrics::Dashboard::ProjectDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
diff --git a/spec/services/milestones/destroy_service_spec.rb b/spec/services/milestones/destroy_service_spec.rb
index 3a22e4d4f92..ff1e1256166 100644
--- a/spec/services/milestones/destroy_service_spec.rb
+++ b/spec/services/milestones/destroy_service_spec.rb
@@ -65,5 +65,19 @@ describe Milestones::DestroyService do
expect { service.execute(group_milestone) }.not_to change { Event.count }
end
end
+
+ context 'when a release is tied to a milestone' do
+ it 'destroys the milestone but not the associated release' do
+ release = create(
+ :release,
+ tag: 'v1.0',
+ project: project,
+ milestone: milestone
+ )
+
+ expect { service.execute(milestone) }.not_to change { Release.count }
+ expect(release.reload).to be_persisted
+ end
+ end
end
end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index d925aa2b6c3..bd6734634cb 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -278,6 +278,7 @@ describe NotificationService, :mailer do
notification.new_note(note)
should_email(note.author)
+ expect(find_email_for(note.author)).to have_header('X-GitLab-NotificationReason', 'own_activity')
end
it_behaves_like 'project emails are disabled' do
@@ -335,6 +336,9 @@ describe NotificationService, :mailer do
should_not_email(@u_participating)
should_not_email(@u_disabled)
should_not_email(@u_lazy_participant)
+
+ expect(find_email_for(@u_mentioned)).to have_header('X-GitLab-NotificationReason', 'mentioned')
+ expect(find_email_for(@u_custom_global)).to have_header('X-GitLab-NotificationReason', '')
end
end
@@ -1932,31 +1936,39 @@ describe NotificationService, :mailer do
let(:added_user) { create(:user) }
describe '#new_access_request' do
- let(:maintainer) { create(:user) }
- let(:owner) { create(:user) }
- let(:developer) { create(:user) }
- let!(:group) do
- create(:group, :public, :access_requestable) do |group|
- group.add_owner(owner)
- group.add_maintainer(maintainer)
- group.add_developer(developer)
+ context 'recipients' do
+ let(:maintainer) { create(:user) }
+ let(:owner) { create(:user) }
+ let(:developer) { create(:user) }
+
+ let!(:group) do
+ create(:group, :public, :access_requestable) do |group|
+ group.add_owner(owner)
+ group.add_maintainer(maintainer)
+ group.add_developer(developer)
+ end
end
- end
- before do
- reset_delivered_emails!
- end
+ before do
+ reset_delivered_emails!
+ end
- it 'sends notification to group owners_and_maintainers' do
- group.request_access(added_user)
+ it 'sends notification only to group owners' do
+ group.request_access(added_user)
+
+ should_email(owner)
+ should_not_email(maintainer)
+ should_not_email(developer)
+ end
- should_email(owner)
- should_email(maintainer)
- should_not_email(developer)
+ it_behaves_like 'group emails are disabled' do
+ let(:notification_target) { group }
+ let(:notification_trigger) { group.request_access(added_user) }
+ end
end
- it_behaves_like 'group emails are disabled' do
- let(:notification_target) { group }
+ it_behaves_like 'sends notification only to a maximum of ten, most recently active group owners' do
+ let(:group) { create(:group, :public, :access_requestable) }
let(:notification_trigger) { group.request_access(added_user) }
end
end
@@ -2012,20 +2024,36 @@ describe NotificationService, :mailer do
describe '#new_access_request' do
context 'for a project in a user namespace' do
- let(:project) do
- create(:project, :public, :access_requestable) do |project|
- project.add_maintainer(project.owner)
+ context 'recipients' do
+ let(:developer) { create(:user) }
+ let(:maintainer) { create(:user) }
+
+ let!(:project) do
+ create(:project, :public, :access_requestable) do |project|
+ project.add_developer(developer)
+ project.add_maintainer(maintainer)
+ end
end
- end
- it 'sends notification to project owners_and_maintainers' do
- project.request_access(added_user)
+ before do
+ reset_delivered_emails!
+ end
+
+ it 'sends notification only to project maintainers' do
+ project.request_access(added_user)
+
+ should_email(maintainer)
+ should_not_email(developer)
+ end
- should_only_email(project.owner)
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { project }
+ let(:notification_trigger) { project.request_access(added_user) }
+ end
end
- it_behaves_like 'project emails are disabled' do
- let(:notification_target) { project }
+ it_behaves_like 'sends notification only to a maximum of ten, most recently active project maintainers' do
+ let(:project) { create(:project, :public, :access_requestable) }
let(:notification_trigger) { project.request_access(added_user) }
end
end
@@ -2033,16 +2061,76 @@ describe NotificationService, :mailer do
context 'for a project in a group' do
let(:group_owner) { create(:user) }
let(:group) { create(:group).tap { |g| g.add_owner(group_owner) } }
- let!(:project) { create(:project, :public, :access_requestable, namespace: group) }
- before do
- reset_delivered_emails!
+ context 'when the project has no maintainers' do
+ context 'when the group has at least one owner' do
+ let!(:project) { create(:project, :public, :access_requestable, namespace: group) }
+
+ before do
+ reset_delivered_emails!
+ end
+
+ context 'recipients' do
+ it 'sends notifications to the group owners' do
+ project.request_access(added_user)
+
+ should_only_email(group_owner)
+ end
+ end
+
+ it_behaves_like 'sends notification only to a maximum of ten, most recently active group owners' do
+ let(:group) { create(:group, :public, :access_requestable) }
+ let(:notification_trigger) { project.request_access(added_user) }
+ end
+ end
+
+ context 'when the group does not have any owners' do
+ let(:group) { create(:group) }
+ let!(:project) { create(:project, :public, :access_requestable, namespace: group) }
+
+ context 'recipients' do
+ before do
+ reset_delivered_emails!
+ end
+
+ it 'does not send any notifications' do
+ project.request_access(added_user)
+
+ should_not_email_anyone
+ end
+ end
+ end
end
- it 'sends notification to group owners_and_maintainers' do
- project.request_access(added_user)
+ context 'when the project has maintainers' do
+ let(:maintainer) { create(:user) }
+ let(:developer) { create(:user) }
+
+ let!(:project) do
+ create(:project, :public, :access_requestable, namespace: group) do |project|
+ project.add_maintainer(maintainer)
+ project.add_developer(developer)
+ end
+ end
+
+ before do
+ reset_delivered_emails!
+ end
+
+ context 'recipients' do
+ it 'sends notifications only to project maintainers' do
+ project.request_access(added_user)
- should_only_email(group_owner)
+ should_email(maintainer)
+ should_not_email(developer)
+ should_not_email(group_owner)
+ end
+ end
+
+ it_behaves_like 'sends notification only to a maximum of ten, most recently active project maintainers' do
+ let(:project) { create(:project, :public, :access_requestable, namespace: group) }
+ let(:notification_trigger) { project.request_access(added_user) }
+ end
end
end
end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index d4fa62fa85d..8178b7d2ba2 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -183,27 +183,65 @@ describe Projects::CreateService, '#execute' do
context 'restricted visibility level' do
before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
+ end
- opts.merge!(
- visibility_level: Gitlab::VisibilityLevel::PUBLIC
- )
+ shared_examples 'restricted visibility' do
+ it 'does not allow a restricted visibility level for non-admins' do
+ project = create_project(user, opts)
+
+ expect(project).to respond_to(:errors)
+ expect(project.errors.messages).to have_key(:visibility_level)
+ expect(project.errors.messages[:visibility_level].first).to(
+ match('restricted by your GitLab administrator')
+ )
+ end
+
+ it 'allows a restricted visibility level for admins' do
+ admin = create(:admin)
+ project = create_project(admin, opts)
+
+ expect(project.errors.any?).to be(false)
+ expect(project.saved?).to be(true)
+ end
end
- it 'does not allow a restricted visibility level for non-admins' do
- project = create_project(user, opts)
- expect(project).to respond_to(:errors)
- expect(project.errors.messages).to have_key(:visibility_level)
- expect(project.errors.messages[:visibility_level].first).to(
- match('restricted by your GitLab administrator')
- )
+ context 'when visibility is project based' do
+ before do
+ opts.merge!(
+ visibility_level: Gitlab::VisibilityLevel::PUBLIC
+ )
+ end
+
+ include_examples 'restricted visibility'
end
- it 'allows a restricted visibility level for admins' do
- admin = create(:admin)
- project = create_project(admin, opts)
+ context 'when visibility is overridden' do
+ let(:visibility) { 'public' }
- expect(project.errors.any?).to be(false)
- expect(project.saved?).to be(true)
+ before do
+ opts.merge!(
+ import_data: {
+ data: {
+ override_params: {
+ visibility: visibility
+ }
+ }
+ }
+ )
+ end
+
+ include_examples 'restricted visibility'
+
+ context 'when visibility is misspelled' do
+ let(:visibility) { 'publik' }
+
+ it 'does not restrict project creation' do
+ project = create_project(user, opts)
+
+ expect(project.errors.any?).to be(false)
+ expect(project.saved?).to be(true)
+ end
+ end
end
end
diff --git a/spec/services/releases/create_service_spec.rb b/spec/services/releases/create_service_spec.rb
index e26676cdd55..5c9d6537df1 100644
--- a/spec/services/releases/create_service_spec.rb
+++ b/spec/services/releases/create_service_spec.rb
@@ -72,6 +72,15 @@ describe Releases::CreateService do
expect(project.releases.find_by(tag: tag_name).description).to eq(description)
end
end
+
+ context 'when a passed-in milestone does not exist for this project' do
+ it 'raises an error saying the milestone is inexistent' do
+ service = described_class.new(project, user, params.merge!({ milestone: 'v111.0' }))
+ result = service.execute
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Milestone does not exist')
+ end
+ end
end
describe '#find_or_build_release' do
@@ -80,5 +89,58 @@ describe Releases::CreateService do
expect(project.releases.count).to eq(0)
end
+
+ context 'when existing milestone is passed in' do
+ let(:title) { 'v1.0' }
+ let(:milestone) { create(:milestone, :active, project: project, title: title) }
+ let(:params_with_milestone) { params.merge!({ milestone: title }) }
+
+ it 'creates a release and ties this milestone to it' do
+ service = described_class.new(milestone.project, user, params_with_milestone)
+ result = service.execute
+
+ expect(project.releases.count).to eq(1)
+ expect(result[:status]).to eq(:success)
+
+ release = project.releases.last
+
+ expect(release.milestone).to eq(milestone)
+ end
+
+ context 'when another release was previously created with that same milestone linked' do
+ it 'also creates another release tied to that same milestone' do
+ other_release = create(:release, milestone: milestone, project: project, tag: 'v1.0')
+ service = described_class.new(milestone.project, user, params_with_milestone)
+ service.execute
+ release = project.releases.last
+
+ expect(release.milestone).to eq(milestone)
+ expect(other_release.milestone).to eq(milestone)
+ expect(release.id).not_to eq(other_release.id)
+ end
+ end
+ end
+
+ context 'when no milestone is passed in' do
+ it 'creates a release without a milestone tied to it' do
+ expect(params.key? :milestone).to be_falsey
+ service.execute
+ release = project.releases.last
+ expect(release.milestone).to be_nil
+ end
+
+ it 'does not create any new MilestoneRelease object' do
+ expect { service.execute }.not_to change { MilestoneRelease.count }
+ end
+ end
+
+ context 'when an empty value is passed as a milestone' do
+ it 'creates a release without a milestone tied to it' do
+ service = described_class.new(project, user, params.merge!({ milestone: '' }))
+ service.execute
+ release = project.releases.last
+ expect(release.milestone).to be_nil
+ end
+ end
end
end
diff --git a/spec/services/releases/destroy_service_spec.rb b/spec/services/releases/destroy_service_spec.rb
index f4c901e6585..c3172e5edbc 100644
--- a/spec/services/releases/destroy_service_spec.rb
+++ b/spec/services/releases/destroy_service_spec.rb
@@ -57,5 +57,15 @@ describe Releases::DestroyService do
http_status: 403)
end
end
+
+ context 'when a milestone is tied to the release' do
+ let!(:milestone) { create(:milestone, :active, project: project, title: 'v1.0') }
+ let!(:release) { create(:release, milestone: milestone, project: project, tag: tag) }
+
+ it 'destroys the release but leave the milestone intact' do
+ expect { subject }.not_to change { Milestone.count }
+ expect(milestone.reload).to be_persisted
+ end
+ end
end
end
diff --git a/spec/services/releases/update_service_spec.rb b/spec/services/releases/update_service_spec.rb
index 14e6a5f13c8..944f3d8c9ad 100644
--- a/spec/services/releases/update_service_spec.rb
+++ b/spec/services/releases/update_service_spec.rb
@@ -48,5 +48,42 @@ describe Releases::UpdateService do
it_behaves_like 'a failed update'
end
+
+ context 'when a milestone is passed in' do
+ let(:old_title) { 'v1.0' }
+ let(:new_title) { 'v2.0' }
+ let(:milestone) { create(:milestone, project: project, title: old_title) }
+ let(:new_milestone) { create(:milestone, project: project, title: new_title) }
+ let(:params_with_milestone) { params.merge!({ milestone: new_title }) }
+
+ before do
+ release.milestone = milestone
+ release.save!
+
+ described_class.new(new_milestone.project, user, params_with_milestone).execute
+ release.reload
+ end
+
+ it 'updates the related milestone accordingly' do
+ expect(release.milestone.title).to eq(new_title)
+ end
+ end
+
+ context "when an 'empty' milestone is passed in" do
+ let(:milestone) { create(:milestone, project: project, title: 'v1.0') }
+ let(:params_with_empty_milestone) { params.merge!({ milestone: '' }) }
+
+ before do
+ release.milestone = milestone
+ release.save!
+
+ described_class.new(milestone.project, user, params_with_empty_milestone).execute
+ release.reload
+ end
+
+ it 'removes the old milestone and does not associate any new milestone' do
+ expect(release.milestone).to be_nil
+ end
+ end
end
end
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
index f5c6e972953..d72e5cc2b16 100644
--- a/spec/services/system_hooks_service_spec.rb
+++ b/spec/services/system_hooks_service_spec.rb
@@ -19,6 +19,7 @@ describe SystemHooksService do
it { expect(event_data(project, :destroy)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
it { expect(event_data(project_member, :create)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) }
it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) }
+ it { expect(event_data(project_member, :update)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) }
it { expect(event_data(key, :create)).to include(:username, :key, :id) }
it { expect(event_data(key, :destroy)).to include(:username, :key, :id) }
it { expect(event_data(deploy_key, :create)).to include(:key, :id) }
@@ -70,6 +71,13 @@ describe SystemHooksService do
)
end
+ it do
+ expect(event_data(group_member, :update)).to include(
+ :event_name, :created_at, :updated_at, :group_name, :group_path,
+ :group_id, :user_id, :user_username, :user_name, :user_email, :group_access
+ )
+ end
+
it 'includes the correct project visibility level' do
data = event_data(project, :create)
@@ -145,6 +153,7 @@ describe SystemHooksService do
it { expect(event_name(project, :update)).to eq "project_update" }
it { expect(event_name(project_member, :create)).to eq "user_add_to_team" }
it { expect(event_name(project_member, :destroy)).to eq "user_remove_from_team" }
+ it { expect(event_name(project_member, :update)).to eq "user_update_for_team" }
it { expect(event_name(key, :create)).to eq 'key_create' }
it { expect(event_name(key, :destroy)).to eq 'key_destroy' }
it { expect(event_name(group, :create)).to eq 'group_create' }
@@ -152,6 +161,7 @@ describe SystemHooksService do
it { expect(event_name(group, :rename)).to eq 'group_rename' }
it { expect(event_name(group_member, :create)).to eq 'user_add_to_group' }
it { expect(event_name(group_member, :destroy)).to eq 'user_remove_from_group' }
+ it { expect(event_name(group_member, :update)).to eq 'user_update_for_group' }
end
def event_data(*args)
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index f46f9633c1c..910fe3b50b7 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -212,6 +212,13 @@ describe SystemNoteService do
expect(build_note([assignee, assignee1, assignee2], [assignee, assignee1])).to eq \
"unassigned @#{assignee2.username}"
end
+
+ it 'builds a correct phrase when the locale is different' do
+ Gitlab::I18n.with_locale('pt-BR') do
+ expect(build_note([assignee, assignee1, assignee2], [assignee3])).to eq \
+ "assigned to @#{assignee3.username} and unassigned @#{assignee.username}, @#{assignee1.username}, and @#{assignee2.username}"
+ end
+ end
end
describe '.change_milestone' do
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index 9ee23f3eb48..bdf2f59704c 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -436,25 +436,114 @@ describe TodoService do
should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
end
- context 'on commit' do
- let(:project) { create(:project, :repository) }
-
- it 'creates a todo for each valid mentioned user when leaving a note on commit' do
- service.new_note(note_on_commit, john_doe)
-
- should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
- should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
- should_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
- should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
+ context 'commits' do
+ let(:base_commit_todo_attrs) { { target_id: nil, target_type: 'Commit', author: john_doe } }
+
+ context 'leaving a note on a commit in a public project' do
+ let(:project) { create(:project, :repository, :public) }
+ it 'creates a todo for each valid mentioned user' do
+ expected_todo = base_commit_todo_attrs.merge(
+ action: Todo::MENTIONED,
+ note: note_on_commit,
+ commit_id: note_on_commit.commit_id
+ )
+
+ service.new_note(note_on_commit, john_doe)
+
+ should_create_todo(expected_todo.merge(user: member))
+ should_create_todo(expected_todo.merge(user: author))
+ should_create_todo(expected_todo.merge(user: john_doe))
+ should_create_todo(expected_todo.merge(user: guest))
+ should_create_todo(expected_todo.merge(user: non_member))
+ end
+
+ it 'creates a directly addressed todo for each valid mentioned user' do
+ expected_todo = base_commit_todo_attrs.merge(
+ action: Todo::DIRECTLY_ADDRESSED,
+ note: addressed_note_on_commit,
+ commit_id: addressed_note_on_commit.commit_id
+ )
+
+ service.new_note(addressed_note_on_commit, john_doe)
+
+ should_create_todo(expected_todo.merge(user: member))
+ should_create_todo(expected_todo.merge(user: author))
+ should_create_todo(expected_todo.merge(user: john_doe))
+ should_create_todo(expected_todo.merge(user: guest))
+ should_create_todo(expected_todo.merge(user: non_member))
+ end
end
- it 'creates a directly addressed todo for each valid mentioned user when leaving a note on commit' do
- service.new_note(addressed_note_on_commit, john_doe)
+ context 'leaving a note on a commit in a public project with private code' do
+ let(:project) { create(:project, :repository, :public, :repository_private) }
+
+ it 'creates a todo for each valid mentioned user' do
+ expected_todo = base_commit_todo_attrs.merge(
+ action: Todo::MENTIONED,
+ note: note_on_commit,
+ commit_id: note_on_commit.commit_id
+ )
+
+ service.new_note(note_on_commit, john_doe)
+
+ should_create_todo(expected_todo.merge(user: member))
+ should_create_todo(expected_todo.merge(user: author))
+ should_create_todo(expected_todo.merge(user: john_doe))
+ should_create_todo(expected_todo.merge(user: guest))
+ should_not_create_todo(expected_todo.merge(user: non_member))
+ end
+
+ it 'creates a directly addressed todo for each valid mentioned user' do
+ expected_todo = base_commit_todo_attrs.merge(
+ action: Todo::DIRECTLY_ADDRESSED,
+ note: addressed_note_on_commit,
+ commit_id: addressed_note_on_commit.commit_id
+ )
+
+ service.new_note(addressed_note_on_commit, john_doe)
+
+ should_create_todo(expected_todo.merge(user: member))
+ should_create_todo(expected_todo.merge(user: author))
+ should_create_todo(expected_todo.merge(user: john_doe))
+ should_create_todo(expected_todo.merge(user: guest))
+ should_not_create_todo(expected_todo.merge(user: non_member))
+ end
+ end
- should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
- should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
- should_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
- should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: addressed_note_on_commit.commit_id, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_commit)
+ context 'leaving a note on a commit in a private project' do
+ let(:project) { create(:project, :repository, :private) }
+
+ it 'creates a todo for each valid mentioned user' do
+ expected_todo = base_commit_todo_attrs.merge(
+ action: Todo::MENTIONED,
+ note: note_on_commit,
+ commit_id: note_on_commit.commit_id
+ )
+
+ service.new_note(note_on_commit, john_doe)
+
+ should_create_todo(expected_todo.merge(user: member))
+ should_create_todo(expected_todo.merge(user: author))
+ should_create_todo(expected_todo.merge(user: john_doe))
+ should_not_create_todo(expected_todo.merge(user: guest))
+ should_not_create_todo(expected_todo.merge(user: non_member))
+ end
+
+ it 'creates a directly addressed todo for each valid mentioned user' do
+ expected_todo = base_commit_todo_attrs.merge(
+ action: Todo::DIRECTLY_ADDRESSED,
+ note: addressed_note_on_commit,
+ commit_id: addressed_note_on_commit.commit_id
+ )
+
+ service.new_note(addressed_note_on_commit, john_doe)
+
+ should_create_todo(expected_todo.merge(user: member))
+ should_create_todo(expected_todo.merge(user: author))
+ should_create_todo(expected_todo.merge(user: john_doe))
+ should_not_create_todo(expected_todo.merge(user: guest))
+ should_not_create_todo(expected_todo.merge(user: non_member))
+ end
end
end
diff --git a/spec/services/update_merge_request_metrics_service_spec.rb b/spec/services/update_merge_request_metrics_service_spec.rb
index 12a2b287c72..bb07dfa1a0e 100644
--- a/spec/services/update_merge_request_metrics_service_spec.rb
+++ b/spec/services/update_merge_request_metrics_service_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe MergeRequestMetricsService do
let(:metrics) { create(:merge_request).metrics }
diff --git a/spec/services/update_snippet_service_spec.rb b/spec/services/update_snippet_service_spec.rb
index 0678f54c195..19b35dca6a7 100644
--- a/spec/services/update_snippet_service_spec.rb
+++ b/spec/services/update_snippet_service_spec.rb
@@ -32,12 +32,25 @@ describe UpdateSnippetService do
expect(@snippet.visibility_level).to eq(old_visibility)
end
- it 'admins should be able to update to pubic visibility' do
+ it 'admins should be able to update to public visibility' do
old_visibility = @snippet.visibility_level
update_snippet(@project, @admin, @snippet, @opts)
expect(@snippet.visibility_level).not_to eq(old_visibility)
expect(@snippet.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
+
+ describe "when visibility level is passed as a string" do
+ before do
+ @opts[:visibility] = 'internal'
+ @opts.delete(:visibility_level)
+ end
+
+ it "assigns the correct visibility level" do
+ update_snippet(@project, @user, @snippet, @opts)
+ expect(@snippet.errors.any?).to be_falsey
+ expect(@snippet.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+ end
+ end
end
describe 'usage counter' do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index bd504f1553b..6ce76af556f 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -148,6 +148,12 @@ RSpec.configure do |config|
.with(:force_autodevops_on_by_default, anything)
.and_return(false)
+ # Stub this call due to being an expensive operation
+ # It can be reenabled for specific tests via:
+ #
+ # allow(DetectRepositoryLanguagesWorker).to receive(:perform_async).and_call_original
+ allow(DetectRepositoryLanguagesWorker).to receive(:perform_async).and_return(true)
+
Gitlab::ThreadMemoryCache.cache_backend.clear
end
diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb
index 5590bf0fb7e..f070243f111 100644
--- a/spec/support/features/discussion_comments_shared_example.rb
+++ b/spec/support/features/discussion_comments_shared_example.rb
@@ -73,7 +73,7 @@ shared_examples 'thread comments' do |resource_name|
expect(page).not_to have_selector menu_selector
find(toggle_selector).click
- execute_script("document.querySelector('body').click()")
+ find("#{form_selector} .note-textarea").click
expect(page).not_to have_selector menu_selector
end
diff --git a/spec/support/helpers/capybara_helpers.rb b/spec/support/helpers/capybara_helpers.rb
index 5abbc1e2951..a7baa7042c9 100644
--- a/spec/support/helpers/capybara_helpers.rb
+++ b/spec/support/helpers/capybara_helpers.rb
@@ -42,4 +42,8 @@ module CapybaraHelpers
def clear_browser_session
page.driver.browser.manage.delete_cookie('_gitlab_session')
end
+
+ def javascript_test?
+ Capybara.current_driver == Capybara.javascript_driver
+ end
end
diff --git a/spec/support/helpers/project_forks_helper.rb b/spec/support/helpers/project_forks_helper.rb
index b2d22853e4c..90d0d1845fc 100644
--- a/spec/support/helpers/project_forks_helper.rb
+++ b/spec/support/helpers/project_forks_helper.rb
@@ -45,7 +45,7 @@ module ProjectForksHelper
# not reset the @exists variable of this forked_project.repository
# so we have to explicitly call this method to clear the @exists variable.
# of the instance we're returning here.
- forked_project.repository.after_import
+ forked_project.repository.expire_content_cache
end
forked_project
diff --git a/spec/support/helpers/rails_helpers.rb b/spec/support/helpers/rails_helpers.rb
new file mode 100644
index 00000000000..e1875b2fb15
--- /dev/null
+++ b/spec/support/helpers/rails_helpers.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module RailsHelpers
+ def stub_rails_env(env_name)
+ allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new(env_name))
+ end
+end
diff --git a/spec/support/helpers/search_helpers.rb b/spec/support/helpers/search_helpers.rb
index 815337f8615..2cf3f4b83c4 100644
--- a/spec/support/helpers/search_helpers.rb
+++ b/spec/support/helpers/search_helpers.rb
@@ -1,7 +1,22 @@
# frozen_string_literal: true
module SearchHelpers
- def select_filter(name)
- find(:xpath, "//ul[contains(@class, 'search-filter')]//a[contains(.,'#{name}')]").click
+ def submit_search(query, scope: nil)
+ page.within('.search-form, .search-page-form') do
+ field = find_field('search')
+ field.fill_in(with: query)
+
+ if javascript_test?
+ field.send_keys(:enter)
+ else
+ click_button('Search')
+ end
+ end
+ end
+
+ def select_search_scope(scope)
+ page.within '.search-filter' do
+ click_link scope
+ end
end
end
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index dec7898d8d2..f364e4fd158 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -105,6 +105,10 @@ module StubConfiguration
allow(Gitlab.config.gitlab_shell).to receive_messages(to_settings(messages))
end
+ def stub_asset_proxy_setting(messages)
+ allow(Gitlab.config.asset_proxy).to receive_messages(to_settings(messages))
+ end
+
def stub_rack_attack_setting(messages)
allow(Gitlab.config.rack_attack).to receive(:git_basic_auth).and_return(messages)
allow(Gitlab.config.rack_attack.git_basic_auth).to receive_messages(to_settings(messages))
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index a4acf76e1a3..8ca362ce2df 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -244,7 +244,6 @@ module TestEnv
FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{File.expand_path(bare_repo)}/.", target_repo_path)
FileUtils.chmod_R 0755, target_repo_path
- set_repo_refs(target_repo_path, refs)
end
def create_bare_repository(path)
diff --git a/spec/support/helpers/wait_for_requests.rb b/spec/support/helpers/wait_for_requests.rb
index 3bb2f7c5b51..30dff1063b5 100644
--- a/spec/support/helpers/wait_for_requests.rb
+++ b/spec/support/helpers/wait_for_requests.rb
@@ -61,8 +61,4 @@ module WaitForRequests
Capybara.page.evaluate_script('jQuery.active').zero?
end
-
- def javascript_test?
- Capybara.current_driver == Capybara.javascript_driver
- end
end
diff --git a/spec/support/matchers/be_url.rb b/spec/support/matchers/be_url.rb
index 69171f53891..388c1b384c7 100644
--- a/spec/support/matchers/be_url.rb
+++ b/spec/support/matchers/be_url.rb
@@ -1,11 +1,29 @@
# frozen_string_literal: true
-RSpec::Matchers.define :be_url do |_|
+# Assert that this value is a valid URL of at least one type.
+#
+# By default, this checks that the URL is either a HTTP or HTTPS URI,
+# but you can check other URI schemes by passing the type, eg:
+#
+# ```
+# expect(value).to be_url(URI::FTP)
+# ```
+#
+# Pass an empty array of types if you want to match any URI scheme (be
+# aware that this might not do what you think it does! `foo` is a valid
+# URI, for instance).
+RSpec::Matchers.define :be_url do |types = [URI::HTTP, URI::HTTPS]|
match do |actual|
- URI.parse(actual) rescue false
+ next false unless actual.present?
+
+ uri = URI.parse(actual)
+ Array.wrap(types).any? { |t| uri.is_a?(t) }
+ rescue URI::InvalidURIError
+ false
end
end
# looks better when used like:
# expect(thing).to receive(:method).with(a_valid_url)
RSpec::Matchers.alias_matcher :a_valid_url, :be_url
+RSpec::Matchers.alias_matcher :be_http_url, :be_url
diff --git a/spec/support/services/clusters/create_service_shared.rb b/spec/support/services/clusters/create_service_shared.rb
index 27f6d0570b6..468f25bfffe 100644
--- a/spec/support/services/clusters/create_service_shared.rb
+++ b/spec/support/services/clusters/create_service_shared.rb
@@ -32,56 +32,24 @@ shared_context 'invalid cluster create params' do
end
shared_examples 'create cluster service success' do
- context 'namespace per environment feature is enabled' do
- before do
- stub_feature_flags(kubernetes_namespace_per_environment: true)
- end
-
- it 'creates a cluster object and performs a worker' do
- expect(ClusterProvisionWorker).to receive(:perform_async)
-
- expect { subject }
- .to change { Clusters::Cluster.count }.by(1)
- .and change { Clusters::Providers::Gcp.count }.by(1)
-
- expect(subject.name).to eq('test-cluster')
- expect(subject.user).to eq(user)
- expect(subject.project).to eq(project)
- expect(subject.provider.gcp_project_id).to eq('gcp-project')
- expect(subject.provider.zone).to eq('us-central1-a')
- expect(subject.provider.num_nodes).to eq(1)
- expect(subject.provider.machine_type).to eq('machine_type-a')
- expect(subject.provider.access_token).to eq(access_token)
- expect(subject.provider).to be_legacy_abac
- expect(subject.platform).to be_nil
- expect(subject.namespace_per_environment).to eq true
- end
- end
-
- context 'namespace per environment feature is disabled' do
- before do
- stub_feature_flags(kubernetes_namespace_per_environment: false)
- end
-
- it 'creates a cluster object and performs a worker' do
- expect(ClusterProvisionWorker).to receive(:perform_async)
-
- expect { subject }
- .to change { Clusters::Cluster.count }.by(1)
- .and change { Clusters::Providers::Gcp.count }.by(1)
-
- expect(subject.name).to eq('test-cluster')
- expect(subject.user).to eq(user)
- expect(subject.project).to eq(project)
- expect(subject.provider.gcp_project_id).to eq('gcp-project')
- expect(subject.provider.zone).to eq('us-central1-a')
- expect(subject.provider.num_nodes).to eq(1)
- expect(subject.provider.machine_type).to eq('machine_type-a')
- expect(subject.provider.access_token).to eq(access_token)
- expect(subject.provider).to be_legacy_abac
- expect(subject.platform).to be_nil
- expect(subject.namespace_per_environment).to eq false
- end
+ it 'creates a cluster object and performs a worker' do
+ expect(ClusterProvisionWorker).to receive(:perform_async)
+
+ expect { subject }
+ .to change { Clusters::Cluster.count }.by(1)
+ .and change { Clusters::Providers::Gcp.count }.by(1)
+
+ expect(subject.name).to eq('test-cluster')
+ expect(subject.user).to eq(user)
+ expect(subject.project).to eq(project)
+ expect(subject.provider.gcp_project_id).to eq('gcp-project')
+ expect(subject.provider.zone).to eq('us-central1-a')
+ expect(subject.provider.num_nodes).to eq(1)
+ expect(subject.provider.machine_type).to eq('machine_type-a')
+ expect(subject.provider.access_token).to eq(access_token)
+ expect(subject.provider).to be_legacy_abac
+ expect(subject.platform).to be_nil
+ expect(subject.namespace_per_environment).to eq true
end
end
diff --git a/spec/support/shared_examples/chat_slash_commands_shared_examples.rb b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb
index dcc92dda950..a99068ab678 100644
--- a/spec/support/shared_examples/chat_slash_commands_shared_examples.rb
+++ b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb
@@ -103,7 +103,7 @@ RSpec.shared_examples 'chat slash commands service' do
expect_any_instance_of(Gitlab::SlashCommands::Command).not_to receive(:execute)
result = subject.trigger(params)
- expect(result).to include(text: /^Whoops! This action is not allowed/)
+ expect(result).to include(text: /^You are not allowed/)
end
end
end
diff --git a/spec/support/issuables_requiring_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuables_requiring_filter_shared_examples.rb
index ee25df00dfb..ee25df00dfb 100644
--- a/spec/support/issuables_requiring_filter_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/issuables_requiring_filter_shared_examples.rb
diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
index 39d13cccb13..4bc22861d58 100644
--- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
@@ -7,6 +7,8 @@ shared_examples 'handle uploads' do
let(:secret) { FileUploader.generate_secret }
let(:uploader_class) { FileUploader }
+ it_behaves_like 'handle uploads authorize'
+
describe "POST #create" do
context 'when a user is not authorized to upload a file' do
it 'returns 404 status' do
@@ -271,7 +273,9 @@ shared_examples 'handle uploads' do
end
end
end
+end
+shared_examples 'handle uploads authorize' do
describe "POST #authorize" do
context 'when a user is not authorized to upload a file' do
it 'returns 404 status' do
@@ -284,7 +288,12 @@ shared_examples 'handle uploads' do
context 'when a user can upload a file' do
before do
sign_in(user)
- model.add_developer(user)
+
+ if model.is_a?(PersonalSnippet)
+ model.update!(author: user)
+ else
+ model.add_developer(user)
+ end
end
context 'and the request bypassed workhorse' do
diff --git a/spec/support/shared_examples/lib/banzai/filters/reference_filter_shared_examples.rb b/spec/support/shared_examples/lib/banzai/filters/reference_filter_shared_examples.rb
new file mode 100644
index 00000000000..b1ecd4fd007
--- /dev/null
+++ b/spec/support/shared_examples/lib/banzai/filters/reference_filter_shared_examples.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'HTML text with references' do
+ let(:markdown_prepend) { "&lt;img src=\"\" onerror=alert(`bug`)&gt;" }
+
+ it 'preserves escaped HTML text and adds valid references' do
+ reference = resource.to_reference(format: :name)
+
+ doc = reference_filter("#{markdown_prepend}#{reference}")
+
+ expect(doc.to_html).to start_with(markdown_prepend)
+ expect(doc.text).to eq %(<img src="" onerror=alert(`bug`)>#{resource_text})
+ end
+
+ it 'preserves escaped HTML text if there are no valid references' do
+ reference = "#{resource.class.reference_prefix}invalid"
+ text = "#{markdown_prepend}#{reference}"
+
+ doc = reference_filter(text)
+
+ expect(doc.to_html).to eq text
+ end
+end
diff --git a/spec/support/active_record_enum.rb b/spec/support/shared_examples/models/active_record_enum_shared_examples.rb
index fb1189c7f17..fb1189c7f17 100644
--- a/spec/support/active_record_enum.rb
+++ b/spec/support/shared_examples/models/active_record_enum_shared_examples.rb
diff --git a/spec/support/shared_examples/models/concern/issuable_shared_examples.rb b/spec/support/shared_examples/models/concern/issuable_shared_examples.rb
new file mode 100644
index 00000000000..9604555c57d
--- /dev/null
+++ b/spec/support/shared_examples/models/concern/issuable_shared_examples.rb
@@ -0,0 +1,8 @@
+shared_examples_for 'matches_cross_reference_regex? fails fast' do
+ it 'fails fast for long strings' do
+ # took well under 1 second in CI https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/3267#note_172823
+ expect do
+ Timeout.timeout(3.seconds) { mentionable.matches_cross_reference_regex? }
+ end.not_to raise_error
+ end
+end
diff --git a/spec/support/shared_examples/services/notification_service_shared_examples.rb b/spec/support/shared_examples/services/notification_service_shared_examples.rb
index dd338ea47c7..ad580b581d6 100644
--- a/spec/support/shared_examples/services/notification_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/notification_service_shared_examples.rb
@@ -52,3 +52,47 @@ shared_examples 'group emails are disabled' do
should_email_anyone
end
end
+
+shared_examples 'sends notification only to a maximum of ten, most recently active group owners' do
+ let(:owners) { create_list(:user, 12, :with_sign_ins) }
+
+ before do
+ owners.each do |owner|
+ group.add_owner(owner)
+ end
+
+ reset_delivered_emails!
+ end
+
+ context 'limit notification emails' do
+ it 'sends notification only to a maximum of ten, most recently active group owners' do
+ ten_most_recently_active_group_owners = owners.sort_by(&:last_sign_in_at).last(10)
+
+ notification_trigger
+
+ should_only_email(*ten_most_recently_active_group_owners)
+ end
+ end
+end
+
+shared_examples 'sends notification only to a maximum of ten, most recently active project maintainers' do
+ let(:maintainers) { create_list(:user, 12, :with_sign_ins) }
+
+ before do
+ maintainers.each do |maintainer|
+ project.add_maintainer(maintainer)
+ end
+
+ reset_delivered_emails!
+ end
+
+ context 'limit notification emails' do
+ it 'sends notification only to a maximum of ten, most recently active project maintainers' do
+ ten_most_recently_active_project_maintainers = maintainers.sort_by(&:last_sign_in_at).last(10)
+
+ notification_trigger
+
+ should_only_email(*ten_most_recently_active_project_maintainers)
+ end
+ end
+end
diff --git a/spec/uploaders/gitlab_uploader_spec.rb b/spec/uploaders/gitlab_uploader_spec.rb
index 44718ed1212..3bee4875348 100644
--- a/spec/uploaders/gitlab_uploader_spec.rb
+++ b/spec/uploaders/gitlab_uploader_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
require 'carrierwave/storage/fog'
describe GitlabUploader do
diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb
index 6bad5d49b1c..0cf486c7087 100644
--- a/spec/uploaders/object_storage_spec.rb
+++ b/spec/uploaders/object_storage_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
require 'carrierwave/storage/fog'
class Implementation < GitlabUploader
diff --git a/spec/uploaders/records_uploads_spec.rb b/spec/uploaders/records_uploads_spec.rb
index 6134137d2b7..046ad3406d9 100644
--- a/spec/uploaders/records_uploads_spec.rb
+++ b/spec/uploaders/records_uploads_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe RecordsUploads do
let!(:uploader) do
diff --git a/spec/uploaders/uploader_helper_spec.rb b/spec/uploaders/uploader_helper_spec.rb
index fd6712d4645..7c9ec8ff59c 100644
--- a/spec/uploaders/uploader_helper_spec.rb
+++ b/spec/uploaders/uploader_helper_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe UploaderHelper do
let(:uploader) do
diff --git a/spec/views/devise/shared/_signin_box.html.haml_spec.rb b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
index 66c064e3fba..0563984a03c 100644
--- a/spec/views/devise/shared/_signin_box.html.haml_spec.rb
+++ b/spec/views/devise/shared/_signin_box.html.haml_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe 'devise/shared/_signin_box' do
describe 'Crowd form' do
@@ -7,6 +7,7 @@ describe 'devise/shared/_signin_box' do
assign(:ldap_servers, [])
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
allow(view).to receive(:captcha_enabled?).and_return(false)
+ allow(view).to receive(:captcha_on_login_required?).and_return(false)
end
it 'is shown when Crowd is enabled' do
diff --git a/spec/views/groups/edit.html.haml_spec.rb b/spec/views/groups/edit.html.haml_spec.rb
index 47804411b9d..0da3470433c 100644
--- a/spec/views/groups/edit.html.haml_spec.rb
+++ b/spec/views/groups/edit.html.haml_spec.rb
@@ -23,7 +23,7 @@ describe 'groups/edit.html.haml' do
render
expect(rendered).to have_content("Prevent sharing a project within #{test_group.name} with other groups")
- expect(rendered).to have_css('.descr', text: 'help text here')
+ expect(rendered).to have_css('.js-descr', text: 'help text here')
expect(rendered).to have_field('group_share_with_group_lock', checkbox_options)
end
end
diff --git a/spec/views/help/index.html.haml_spec.rb b/spec/views/help/index.html.haml_spec.rb
index 257991549a9..f25e05dca7f 100644
--- a/spec/views/help/index.html.haml_spec.rb
+++ b/spec/views/help/index.html.haml_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'help/index' do
include StubVersion
diff --git a/spec/views/help/instance_configuration.html.haml_spec.rb b/spec/views/help/instance_configuration.html.haml_spec.rb
index ceb7e34a540..18628ddebf7 100644
--- a/spec/views/help/instance_configuration.html.haml_spec.rb
+++ b/spec/views/help/instance_configuration.html.haml_spec.rb
@@ -1,4 +1,4 @@
-require 'rails_helper'
+require 'spec_helper'
describe 'help/instance_configuration' do
describe 'General Sections:' do
diff --git a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
index ff2d491539b..697e44be065 100644
--- a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
+++ b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
@@ -17,10 +17,16 @@ describe 'projects/settings/ci_cd/_autodevops_form' do
context 'when the project has an available kubernetes cluster' do
let!(:cluster) { create(:cluster, cluster_type: :project_type, projects: [project]) }
- it 'does not show a warning message' do
+ it 'does not show a warning message about Kubernetes cluster' do
render
expect(rendered).not_to have_text('You must add a Kubernetes cluster')
end
+
+ it 'shows a warning message about base domain' do
+ render
+
+ expect(rendered).to have_text('You must add a base domain to your Kubernetes cluster in order for your deployment strategy to work.')
+ end
end
end
diff --git a/spec/views/projects/settings/operations/show.html.haml_spec.rb b/spec/views/projects/settings/operations/show.html.haml_spec.rb
index 6762fe3759b..ece9c16650f 100644
--- a/spec/views/projects/settings/operations/show.html.haml_spec.rb
+++ b/spec/views/projects/settings/operations/show.html.haml_spec.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
-require 'rails_helper'
describe 'projects/settings/operations/show' do
let(:project) { create(:project) }
diff --git a/spec/views/search/_results.html.haml_spec.rb b/spec/views/search/_results.html.haml_spec.rb
new file mode 100644
index 00000000000..177ade3b700
--- /dev/null
+++ b/spec/views/search/_results.html.haml_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'search/_results' do
+ before do
+ controller.params[:action] = 'show'
+
+ 3.times { create(:issue) }
+
+ @search_objects = Issue.page(1).per(2)
+ @scope = 'issues'
+ @search_term = 'foo'
+ end
+
+ it 'displays the page size' do
+ render
+
+ expect(rendered).to have_content('Showing 1 - 2 of 3 issues for "foo"')
+ end
+
+ context 'when search results do not have a count' do
+ before do
+ @search_objects = @search_objects.without_count
+ end
+
+ it 'does not display the page size' do
+ render
+
+ expect(rendered).not_to have_content(/Showing .* of .*/)
+ end
+ end
+end
diff --git a/spec/workers/repository_remove_remote_worker_spec.rb b/spec/workers/repository_remove_remote_worker_spec.rb
index 6eba5c50960..afac9212fab 100644
--- a/spec/workers/repository_remove_remote_worker_spec.rb
+++ b/spec/workers/repository_remove_remote_worker_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe RepositoryRemoveRemoteWorker do
include ExclusiveLeaseHelpers
diff --git a/spec/workers/repository_update_remote_mirror_worker_spec.rb b/spec/workers/repository_update_remote_mirror_worker_spec.rb
index 66d517332ba..7bc499d480d 100644
--- a/spec/workers/repository_update_remote_mirror_worker_spec.rb
+++ b/spec/workers/repository_update_remote_mirror_worker_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe RepositoryUpdateRemoteMirrorWorker, :clean_gitlab_redis_shared_state do
subject { described_class.new }
diff --git a/spec/workers/upload_checksum_worker_spec.rb b/spec/workers/upload_checksum_worker_spec.rb
index 7202c8001b4..ad054f0ff72 100644
--- a/spec/workers/upload_checksum_worker_spec.rb
+++ b/spec/workers/upload_checksum_worker_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe UploadChecksumWorker do
describe '#perform' do
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index 20aacff75fd..41dd9eb5256 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -152,6 +152,7 @@ assert,1.4.1,MIT
assign-symbols,1.0.0,MIT
async-each,1.0.1,MIT
async-limiter,1.0.0,MIT
+atlassian-jwt,0.2.0,Apache 2.0
atob,2.1.2,(MIT OR Apache-2.0)
atomic,1.1.99,Apache 2.0
attr_encrypted,3.1.0,MIT
diff --git a/yarn.lock b/yarn.lock
index c4fe112d833..bd8f9014131 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -991,15 +991,15 @@
dependencies:
vue-eslint-parser "^6.0.4"
-"@gitlab/svgs@^1.70.0":
- version "1.70.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.70.0.tgz#bdae478148c15d955fc06e69fd5d5ecae8298943"
- integrity sha512-0uV9fgTwe17Fyy0hTcrsGX2jJuCrz3uRIe8yffuqc6pbQrSfYJyN66mfCCB45wq8lKTgOB5q0qcUyJx3RQEcKg==
+"@gitlab/svgs@^1.71.0":
+ version "1.71.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.71.0.tgz#c8e6e8f500ea91e5cbba4ac08df533fb2e622a00"
+ integrity sha512-kkeNic/FFwaqKnzwio4NE7whBOZ/toRJ8cS0587DBotajAzSYhph5ij4TCY2GTjPa33zIJ5OUr/k90C0Kr71hQ==
-"@gitlab/ui@5.19.0":
- version "5.19.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.19.0.tgz#ef5431e0e91eb4009a30edcf49a40716cc2570d9"
- integrity sha512-gCeTymtVzzO2uOWZGIweLM5JcHq3TN1QwP61CXw7b9Lp5A2MysRb8upehtR5d4JLOf/GQg8rHQB26uRvUc+AXQ==
+"@gitlab/ui@5.20.2":
+ version "5.20.2"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.20.2.tgz#a51270d5a521e71059c5fd05f86cfc835f5e28ae"
+ integrity sha512-TSaD5Cz0YXBTsRtQwsa7LbS2O5h0CL3YkdYmBKrMZkphL76xQaN08ZImkQ5Xl8cD1ZiWN2CsTvoUbF19UP2V1w==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.2.1"
@@ -1014,10 +1014,10 @@
vue "^2.6.10"
vue-loader "^15.4.2"
-"@gitlab/visual-review-tools@^1.0.0":
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/@gitlab/visual-review-tools/-/visual-review-tools-1.0.0.tgz#6012e0a19797c1f5dad34ccf4dacdaf38e400a73"
- integrity sha512-xMvz9IwrXisQ1MH+Tj6lfbQcQSiQy88nTPuQV6OTLBGuV+vIQeVwXeIkQeTKuSpd0GqZvigPdRqxyQCa3blpIg==
+"@gitlab/visual-review-tools@1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@gitlab/visual-review-tools/-/visual-review-tools-1.0.1.tgz#7e588328ed018d91560633d56865d65b72c3a11b"
+ integrity sha512-vNqpui0khtPi3crrrFtfuT+nw0SdD/nMyb+aurbJzc3RXuVJGCdgYwosvTLPcJkdMOVfTijizV5+ys75s8INBw==
"@gitlab/vue-toasted@^1.2.1":
version "1.2.1"
@@ -4909,7 +4909,7 @@ exports-loader@^0.7.0:
express@^4.16.2, express@^4.16.3:
version "4.16.3"
- resolved "http://registry.npmjs.org/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53"
+ resolved "https://registry.npmjs.org/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53"
integrity sha1-avilAjUNsyRuzEvs9rWjTSL37VM=
dependencies:
accepts "~1.3.5"
@@ -8116,10 +8116,10 @@ merge2@^1.2.3:
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.3.tgz#7ee99dbd69bb6481689253f018488a1b902b0ed5"
integrity sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==
-mermaid@^8.2.4:
- version "8.2.4"
- resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.2.4.tgz#52bcd45611fd8552ab9ac4e385d2766a0e38dcf7"
- integrity sha512-2la1eJhu4n+Uug4zbxFnkETFDJ9U32OY/fRP8g8A1DrRdfT3Er+7CuUSvxfhIDxl+AxSEU4dXdqCiToZAVMCmQ==
+mermaid@^8.2.6:
+ version "8.2.6"
+ resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.2.6.tgz#e73f396461a435c39a998819171c2114f59e46e1"
+ integrity sha512-A8y4zW2aXPj8Yw+BkrCkV6fvzhsFWVESV1IkzRjqQ6T/+tzhkz946+bdebCmHqicEJGTncu/U6h8dgjo5pWo6Q==
dependencies:
"@braintree/sanitize-url" "^3.1.0"
d3 "^5.7.0"
@@ -8304,7 +8304,7 @@ mixin-deep@^1.2.0:
mkdirp@0.5.x, mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
version "0.5.1"
- resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
+ resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
dependencies:
minimist "0.0.8"