summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.dockerignore10
-rw-r--r--.gitignore6
-rw-r--r--.gitlab-ci.yml31
-rw-r--r--.gitlab/CODEOWNERS13
-rw-r--r--.gitlab/ci/cng.gitlab-ci.yml7
-rw-r--r--.gitlab/ci/docs.gitlab-ci.yml52
-rw-r--r--.gitlab/ci/ee-specific-checks.gitlab-ci.yml22
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml182
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml102
-rw-r--r--.gitlab/ci/memory.gitlab-ci.yml17
-rw-r--r--.gitlab/ci/pages.gitlab-ci.yml23
-rw-r--r--.gitlab/ci/qa.gitlab-ci.yml35
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml300
-rw-r--r--.gitlab/ci/reports.gitlab-ci.yml37
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml148
-rw-r--r--.gitlab/ci/setup.gitlab-ci.yml47
-rw-r--r--.gitlab/ci/test-metadata.gitlab-ci.yml30
-rw-r--r--.gitlab/ci/yaml.gitlab-ci.yml8
-rw-r--r--.gitlab/issue_templates/Feature proposal.md5
-rw-r--r--.markdownlint.json31
-rw-r--r--.mdlrc7
-rw-r--r--.mdlrc.style32
-rw-r--r--.overcommit.yml.example28
-rw-r--r--.rubocop.yml66
-rw-r--r--.rubocop_todo.yml7
-rw-r--r--CHANGELOG.md90
-rw-r--r--Dangerfile23
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_ELASTICSEARCH_INDEXER_VERSION2
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile17
-rw-r--r--Gemfile.lock81
-rw-r--r--PHILOSOPHY.md5
-rw-r--r--PROCESS.md4
-rw-r--r--README.md13
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_math.js146
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue8
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue1
-rw-r--r--app/assets/javascripts/boards/components/project_select.vue12
-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/clusters_bundle.js49
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue2
-rw-r--r--app/assets/javascripts/clusters/stores/clusters_store.js31
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/components/gke_dropdown_mixin.js (renamed from app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_dropdown_mixin.js)0
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/components/gke_machine_type_dropdown.vue (renamed from app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue)0
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown.vue (renamed from app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue)0
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/components/gke_zone_dropdown.vue (renamed from app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue)0
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/constants.js (renamed from app/assets/javascripts/projects/gke_cluster_dropdowns/constants.js)0
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/index.js (renamed from app/assets/javascripts/projects/gke_cluster_dropdowns/index.js)0
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/store/actions.js (renamed from app/assets/javascripts/projects/gke_cluster_dropdowns/store/actions.js)0
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/store/getters.js (renamed from app/assets/javascripts/projects/gke_cluster_dropdowns/store/getters.js)0
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/store/index.js (renamed from app/assets/javascripts/projects/gke_cluster_dropdowns/store/index.js)0
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/store/mutation_types.js (renamed from app/assets/javascripts/projects/gke_cluster_dropdowns/store/mutation_types.js)0
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/store/mutations.js (renamed from app/assets/javascripts/projects/gke_cluster_dropdowns/store/mutations.js)0
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/store/state.js (renamed from app/assets/javascripts/projects/gke_cluster_dropdowns/store/state.js)0
-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/error_tracking/components/error_tracking_list.vue20
-rw-r--r--app/assets/javascripts/error_tracking/index.js4
-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.vue12
-rw-r--r--app/assets/javascripts/jobs/components/job_log_json.vue10
-rw-r--r--app/assets/javascripts/jobs/components/log/line.vue28
-rw-r--r--app/assets/javascripts/jobs/components/log/line_header.vue45
-rw-r--r--app/assets/javascripts/jobs/components/log/line_number.vue52
-rw-r--r--app/assets/javascripts/jobs/index.js2
-rw-r--r--app/assets/javascripts/jobs/store/utils.js40
-rw-r--r--app/assets/javascripts/lib/utils/axios_utils.js11
-rw-r--r--app/assets/javascripts/lib/utils/http_status.js1
-rw-r--r--app/assets/javascripts/main.js2
-rw-r--r--app/assets/javascripts/monitoring/components/charts/area.vue304
-rw-r--r--app/assets/javascripts/monitoring/components/charts/time_series.vue10
-rw-r--r--app/assets/javascripts/monitoring/components/embed.vue6
-rw-r--r--app/assets/javascripts/monitoring/components/panel_type.vue8
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue31
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue4
-rw-r--r--app/assets/javascripts/notes/stores/actions.js22
-rw-r--r--app/assets/javascripts/pages/admin/clusters/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/index.js2
-rw-r--r--app/assets/javascripts/pages/profiles/two_factor_auths/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/project.js7
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue7
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/constants.js2
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue3
-rw-r--r--app/assets/javascripts/pipelines/mixins/graph_component_mixin.js10
-rw-r--r--app/assets/javascripts/project_find_file.js2
-rw-r--r--app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue4
-rw-r--r--app/assets/javascripts/releases/components/release_block.vue2
-rw-r--r--app/assets/javascripts/test_utils/simulate_drag.js6
-rw-r--r--app/assets/javascripts/tracking.js28
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/comment.js39
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/comment_mr_note.js31
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/comment_post.js145
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/comment_storage.js20
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/form_elements.js17
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/index.js23
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/login.js47
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/mr_id.js63
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/note.js35
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/utils.js51
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/wrapper.js79
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/wrapper_icons.js15
-rw-r--r--app/assets/javascripts/visual_review_toolbar/index.js51
-rw-r--r--app/assets/javascripts/visual_review_toolbar/shared/constants.js56
-rw-r--r--app/assets/javascripts/visual_review_toolbar/shared/index.js55
-rw-r--r--app/assets/javascripts/visual_review_toolbar/shared/storage_utils.js42
-rw-r--r--app/assets/javascripts/visual_review_toolbar/store/events.js73
-rw-r--r--app/assets/javascripts/visual_review_toolbar/store/index.js11
-rw-r--r--app/assets/javascripts/visual_review_toolbar/store/state.js95
-rw-r--r--app/assets/javascripts/visual_review_toolbar/store/utils.js15
-rw-r--r--app/assets/javascripts/visual_review_toolbar/styles/toolbar.css188
-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/csslab.scss1
-rw-r--r--app/assets/stylesheets/framework/common.scss3
-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.scss14
-rw-r--r--app/assets/stylesheets/framework/variables.scss3
-rw-r--r--app/assets/stylesheets/pages/boards.scss3
-rw-r--r--app/assets/stylesheets/pages/clusters.scss9
-rw-r--r--app/assets/stylesheets/pages/diff.scss28
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss4
-rw-r--r--app/assets/stylesheets/pages/search.scss13
-rw-r--r--app/assets/stylesheets/pages/todos.scss16
-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.rb7
-rw-r--r--app/controllers/concerns/uploads_actions.rb4
-rw-r--r--app/controllers/concerns/with_performance_bar.rb16
-rw-r--r--app/controllers/dashboard/projects_controller.rb1
-rw-r--r--app/controllers/groups/runners_controller.rb6
-rw-r--r--app/controllers/jwt_controller.rb1
-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/projects/notes_controller.rb2
-rw-r--r--app/controllers/projects/pipelines_controller.rb2
-rw-r--r--app/controllers/projects/settings/operations_controller.rb2
-rw-r--r--app/controllers/registrations_controller.rb1
-rw-r--r--app/controllers/sessions_controller.rb50
-rw-r--r--app/controllers/uploads_controller.rb6
-rw-r--r--app/finders/group_projects_finder.rb12
-rw-r--r--app/finders/issuable_finder.rb29
-rw-r--r--app/finders/members_finder.rb35
-rw-r--r--app/finders/todos_finder.rb2
-rw-r--r--app/graphql/functions/base_function.rb6
-rw-r--r--app/graphql/functions/echo.rb15
-rw-r--r--app/graphql/gitlab_schema.rb2
-rw-r--r--app/graphql/mutations/notes/create/base.rb4
-rw-r--r--app/graphql/resolvers/echo_resolver.rb14
-rw-r--r--app/graphql/resolvers/full_path_resolver.rb2
-rw-r--r--app/graphql/resolvers/issues_resolver.rb4
-rw-r--r--app/graphql/resolvers/merge_requests_resolver.rb6
-rw-r--r--app/graphql/resolvers/namespace_projects_resolver.rb4
-rw-r--r--app/graphql/types/permission_types/project.rb2
-rw-r--r--app/graphql/types/query_type.rb2
-rw-r--r--app/helpers/application_settings_helper.rb5
-rw-r--r--app/helpers/avatars_helper.rb9
-rw-r--r--app/helpers/boards_helper.rb2
-rw-r--r--app/helpers/ci_status_helper.rb3
-rw-r--r--app/helpers/emails_helper.rb4
-rw-r--r--app/helpers/external_link_helper.rb9
-rw-r--r--app/helpers/jobs_helper.rb19
-rw-r--r--app/helpers/labels_helper.rb2
-rw-r--r--app/helpers/notes_helper.rb4
-rw-r--r--app/helpers/notifications_helper.rb4
-rw-r--r--app/helpers/performance_bar_helper.rb4
-rw-r--r--app/helpers/projects/error_tracking_helper.rb3
-rw-r--r--app/helpers/projects_helper.rb2
-rw-r--r--app/helpers/search_helper.rb41
-rw-r--r--app/helpers/todos_helper.rb18
-rw-r--r--app/mailers/emails/issues.rb4
-rw-r--r--app/mailers/emails/notes.rb25
-rw-r--r--app/models/analytics/cycle_analytics/project_stage.rb5
-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.rb18
-rw-r--r--app/models/ci/build_runner_session.rb2
-rw-r--r--app/models/ci/job_artifact.rb2
-rw-r--r--app/models/ci/pipeline.rb50
-rw-r--r--app/models/ci/pipeline_enums.rb3
-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.rb9
-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.rb10
-rw-r--r--app/models/commit.rb7
-rw-r--r--app/models/concerns/analytics/cycle_analytics/stage.rb68
-rw-r--r--app/models/concerns/cacheable_attributes.rb2
-rw-r--r--app/models/concerns/ci/metadatable.rb9
-rw-r--r--app/models/concerns/has_status.rb3
-rw-r--r--app/models/concerns/ignorable_column.rb30
-rw-r--r--app/models/concerns/issuable.rb24
-rw-r--r--app/models/concerns/noteable.rb4
-rw-r--r--app/models/concerns/protected_ref_access.rb6
-rw-r--r--app/models/concerns/routable.rb22
-rw-r--r--app/models/concerns/sortable.rb6
-rw-r--r--app/models/concerns/taskable.rb4
-rw-r--r--app/models/deploy_key.rb3
-rw-r--r--app/models/deploy_token.rb4
-rw-r--r--app/models/deployment.rb8
-rw-r--r--app/models/diff_viewer/base.rb2
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/external_pull_request.rb96
-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.rb11
-rw-r--r--app/models/label.rb8
-rw-r--r--app/models/lfs_object.rb1
-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.rb14
-rw-r--r--app/models/merge_request_diff.rb7
-rw-r--r--app/models/milestone.rb9
-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.rb18
-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/protected_branch.rb3
-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.rb5
-rw-r--r--app/models/user.rb32
-rw-r--r--app/models/user_status.rb2
-rw-r--r--app/policies/board_policy.rb18
-rw-r--r--app/policies/concerns/find_group_projects.rb13
-rw-r--r--app/policies/group_policy.rb4
-rw-r--r--app/policies/issue_policy.rb9
-rw-r--r--app/policies/merge_request_policy.rb6
-rw-r--r--app/policies/project_policy.rb3
-rw-r--r--app/presenters/blob_presenter.rb18
-rw-r--r--app/presenters/blobs/unfold_presenter.rb8
-rw-r--r--app/presenters/ci/build_runner_presenter.rb4
-rw-r--r--app/presenters/ci/pipeline_presenter.rb12
-rw-r--r--app/presenters/clusterable_presenter.rb5
-rw-r--r--app/serializers/deployment_entity.rb4
-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.rb21
-rw-r--r--app/services/audit_event_service.rb8
-rw-r--r--app/services/auth/container_registry_authentication_service.rb12
-rw-r--r--app/services/auto_merge_service.rb2
-rw-r--r--app/services/base_count_service.rb2
-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.rb28
-rw-r--r--app/services/clusters/applications/check_installation_progress_service.rb31
-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/external_pull_requests/create_pipeline_service.rb29
-rw-r--r--app/services/git/base_hooks_service.rb28
-rw-r--r--app/services/groups/create_service.rb4
-rw-r--r--app/services/issuable_base_service.rb16
-rw-r--r--app/services/labels/available_labels_service.rb6
-rw-r--r--app/services/labels/find_or_create_service.rb8
-rw-r--r--app/services/merge_requests/build_service.rb12
-rw-r--r--app/services/merge_requests/create_service.rb1
-rw-r--r--app/services/merge_requests/push_options_handler_service.rb10
-rw-r--r--app/services/merge_requests/rebase_service.rb2
-rw-r--r--app/services/milestones/find_or_create_service.rb34
-rw-r--r--app/services/milestones/transfer_service.rb84
-rw-r--r--app/services/notes/update_service.rb66
-rw-r--r--app/services/notification_service.rb17
-rw-r--r--app/services/projects/create_service.rb27
-rw-r--r--app/services/projects/destroy_service.rb2
-rw-r--r--app/services/projects/lfs_pointers/lfs_download_link_list_service.rb2
-rw-r--r--app/services/projects/lfs_pointers/lfs_link_service.rb29
-rw-r--r--app/services/projects/lfs_pointers/lfs_list_service.rb2
-rw-r--r--app/services/projects/lfs_pointers/lfs_object_download_list_service.rb4
-rw-r--r--app/services/projects/open_issues_count_service.rb4
-rw-r--r--app/services/projects/transfer_service.rb3
-rw-r--r--app/services/projects/update_pages_service.rb4
-rw-r--r--app/services/protected_branches/create_service.rb7
-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/self_monitoring/project/create_service.rb219
-rw-r--r--app/services/submit_usage_ping_service.rb2
-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/services/wikis/create_attachment_service.rb2
-rw-r--r--app/uploaders/object_storage.rb2
-rw-r--r--app/uploaders/personal_file_uploader.rb4
-rw-r--r--app/validators/addressable_url_validator.rb8
-rw-r--r--app/views/admin/application_settings/_email.html.haml2
-rw-r--r--app/views/admin/application_settings/_ip_limits.html.haml8
-rw-r--r--app/views/admin/application_settings/_spam.html.haml13
-rw-r--r--app/views/admin/application_settings/network.html.haml2
-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/ci/status/_icon.html.haml3
-rw-r--r--app/views/clusters/clusters/_configure.html.haml26
-rw-r--r--app/views/clusters/clusters/show.html.haml35
-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/dashboard/todos/_todo.html.haml34
-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/edit.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.haml4
-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/_head.html.haml1
-rw-r--r--app/views/layouts/_page.html.haml2
-rw-r--r--app/views/layouts/_piwik.html.haml4
-rw-r--r--app/views/layouts/_search.html.haml5
-rw-r--r--app/views/layouts/_snowplow.html.haml21
-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.haml4
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml8
-rw-r--r--app/views/layouts/nav/sidebar/_project.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/profiles/preferences/show.html.haml2
-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/_blob.html.haml2
-rw-r--r--app/views/projects/blob/edit.html.haml50
-rw-r--r--app/views/projects/blob/preview.html.haml2
-rw-r--r--app/views/projects/blob/show.html.haml19
-rw-r--r--app/views/projects/blob/viewers/_markup.html.haml2
-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.haml7
-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/error_tracking/index.html.haml2
-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.haml10
-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/_how_to_merge.html.haml17
-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/_instructions.html.haml1
-rw-r--r--app/views/projects/mirrors/_mirror_repos.html.haml4
-rw-r--r--app/views/projects/network/_head.html.haml13
-rw-r--r--app/views/projects/pages/_access.html.haml10
-rw-r--r--app/views/projects/pages/_list.html.haml4
-rw-r--r--app/views/projects/pages_domains/show.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.haml5
-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/snippets/show.html.haml2
-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/projects/wikis/show.html.haml2
-rw-r--r--app/views/shared/_import_form.html.haml1
-rw-r--r--app/views/shared/boards/_show.html.haml2
-rw-r--r--app/views/shared/boards/components/_board.html.haml2
-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/shared/issuable/_sidebar.html.haml4
-rw-r--r--app/views/shared/members/_sort_dropdown.html.haml2
-rw-r--r--app/views/shared/projects/_project.html.haml2
-rw-r--r--app/views/shared/runners/_form.html.haml5
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--app/workers/all_queues.yml1
-rw-r--r--app/workers/git_garbage_collect_worker.rb2
-rw-r--r--app/workers/hashed_storage/base_worker.rb2
-rw-r--r--app/workers/stuck_ci_jobs_worker.rb2
-rw-r--r--app/workers/update_external_pull_requests_worker.rb25
-rwxr-xr-xbin/rspec-stackprof2
-rw-r--r--changelogs/unreleased/21505-quickactions-update-pd.yml5
-rw-r--r--changelogs/unreleased/28643-access-request-emails-limit-to-ten-owners.yml5
-rw-r--r--changelogs/unreleased/31735-only-show-copy_metadata-when-usable.yml5
-rw-r--r--changelogs/unreleased/35060-remove-token-field.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/55360-redundant-index-in-the-releases-table_v2.yml5
-rw-r--r--changelogs/unreleased/55999-misleading-pipeline-tooltip-messages-and-misleading-ci-status-icons.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/56883-migration.yml5
-rw-r--r--changelogs/unreleased/57402-upate-issues-list-sort-options.yml5
-rw-r--r--changelogs/unreleased/57538-not-null-constraint-on-users-private-profile.yml5
-rw-r--r--changelogs/unreleased/57657-promote-label-to-group-label-via-api-endpoint.yml5
-rw-r--r--changelogs/unreleased/59729-estimate-quick-action-does-not-produce-correct-time-for-1mo.yml5
-rw-r--r--changelogs/unreleased/60141-mr-resolve-conflicts-file-headers-bad-positioning-on-scroll.yml5
-rw-r--r--changelogs/unreleased/60372-milestone-link-prevent-delete-issue-after-move-it-to-another-projec.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/62322-add-optional-id-to-label-api-put-delete-pd.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/63502-encrypt-deploy-token.yml5
-rw-r--r--changelogs/unreleased/64009-show-a-meaningful-error-message-when-due-quick_actions-command-fail.yml5
-rw-r--r--changelogs/unreleased/64251-branch-name-set-cache.yml5
-rw-r--r--changelogs/unreleased/64269-pipeline-api-fails-with-401.yml5
-rw-r--r--changelogs/unreleased/64764-fix-serverless-layout.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/66037-deployment-user.yml5
-rw-r--r--changelogs/unreleased/66066-dark-theme-style-for-expansion-on-mr-diffs.yml5
-rw-r--r--changelogs/unreleased/66067-pages-domain-doesnt-set-target-blank.yml5
-rw-r--r--changelogs/unreleased/66073-use-time-series-chart-instead-of-area-chart-in-panel_types.yml5
-rw-r--r--changelogs/unreleased/66150-remove-dynamically-constructed-feature-flags-starting-with-promethe.yml5
-rw-r--r--changelogs/unreleased/66264-moved-issue-reference.yml5
-rw-r--r--changelogs/unreleased/66402-use-visual-review-tools-npm-package.yml5
-rw-r--r--changelogs/unreleased/66443-unrecoverable-configuration-loop-in-external-auth-control.yml5
-rw-r--r--changelogs/unreleased/66454-base-components.yml5
-rw-r--r--changelogs/unreleased/66454-utils-parser.yml5
-rw-r--r--changelogs/unreleased/66467-enable-error-tracking-only-user-can-read-sentry-logs.yml5
-rw-r--r--changelogs/unreleased/66524-issue-due-notification-emails-are-threaded-incorrectly.yml5
-rw-r--r--changelogs/unreleased/66616-follow-up-documentation-for-merge-trains-cancel-when-running.yml5
-rw-r--r--changelogs/unreleased/66715-delete-search-animation.yml5
-rw-r--r--changelogs/unreleased/67037-user-content-gitlab-static-net-brings-back-404-only.yml5
-rw-r--r--changelogs/unreleased/ab-add-index-for-ci-builds-metrics.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/ab-unconfirmed-email-index.yml5
-rw-r--r--changelogs/unreleased/ac-accelerate-wiki-attachments.yml5
-rw-r--r--changelogs/unreleased/add-label-push-opts.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/bump-pages-1-8.yml5
-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/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/fix-regression-remove-installation-pod.yml5
-rw-r--r--changelogs/unreleased/fix-search-input-dropdown.yml5
-rw-r--r--changelogs/unreleased/fj-66723-add-dns-rebinding-protection-check.yml5
-rw-r--r--changelogs/unreleased/fj-remove-dns-protection-when-validating.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-13698-override-params.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-54023-fogbugz-visibility-level.yml5
-rw-r--r--changelogs/unreleased/group_level_jupyterhub.yml5
-rw-r--r--changelogs/unreleased/handle-invalid-mirror-url.yml5
-rw-r--r--changelogs/unreleased/id-autosave-for-new-mr.yml5
-rw-r--r--changelogs/unreleased/id-code-review-smau.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/instance-group-level-knative.yml5
-rw-r--r--changelogs/unreleased/issue-32741.yml5
-rw-r--r--changelogs/unreleased/issue_10770.yml5
-rw-r--r--changelogs/unreleased/issue_40630.yml5
-rw-r--r--changelogs/unreleased/issue_54042.yml5
-rw-r--r--changelogs/unreleased/je-add-cluster-domain-warning.yml5
-rw-r--r--changelogs/unreleased/je-add-group-deployments-page-fe.yml5
-rw-r--r--changelogs/unreleased/new-project-milestone-primary-button.yml5
-rw-r--r--changelogs/unreleased/oauth_bypass_two_factor.yml5
-rw-r--r--changelogs/unreleased/persist-needs-error.yml5
-rw-r--r--changelogs/unreleased/quote-branch-names-in-instructions.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-add-margin-member-list.yml5
-rw-r--r--changelogs/unreleased/sh-add-sidekiq-logging-for-bad-ci.yml5
-rw-r--r--changelogs/unreleased/sh-eliminate-gitaly-nplus-one-notes.yml5
-rw-r--r--changelogs/unreleased/sh-fix-ci-lint-500-error.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-move-api.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issues-api-gitaly-nplusone.yml5
-rw-r--r--changelogs/unreleased/sh-guard-against-orphaned-project-feature.yml5
-rw-r--r--changelogs/unreleased/sh-lfs-object-batches.yml5
-rw-r--r--changelogs/unreleased/sh-project-feature-nplus-one.yml5
-rw-r--r--changelogs/unreleased/sh-revert-redis-cache-store.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/ss-add-board-name-to-page-title.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/todos-include-issue-mr-titles.yml5
-rw-r--r--changelogs/unreleased/tr-param-undefined-fix.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-8-0.yml5
-rw-r--r--changelogs/unreleased/update-workhorse.yml5
-rw-r--r--changelogs/unreleased/use_default_external_auth_label_empty.yml6
-rw-r--r--changelogs/unreleased/user_name_migration.yml5
-rw-r--r--changelogs/unreleased/winh-deduplicate-board-headers.yml5
-rw-r--r--config/application.rb1
-rw-r--r--config/dependency_decisions.yml7
-rw-r--r--config/gitlab.yml.example10
-rw-r--r--config/initializers/0_inject_enterprise_edition_module.rb11
-rw-r--r--config/initializers/1_settings.rb3
-rw-r--r--config/initializers/asset_proxy_settings.rb6
-rw-r--r--config/initializers/chronic_duration.rb4
-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.rb4
-rw-r--r--config/routes/uploads.rb4
-rw-r--r--config/sidekiq_queues.yml2
-rw-r--r--config/webpack.config.js7
-rw-r--r--config/webpack.config.review_toolbar.js58
-rw-r--r--danger/database/Dangerfile18
-rw-r--r--danger/documentation/Dangerfile22
-rw-r--r--danger/duplicate_yarn_dependencies/Dangerfile24
-rw-r--r--danger/eslint/Dangerfile22
-rw-r--r--danger/frozen_string/Dangerfile12
-rw-r--r--danger/gemfile/Dangerfile19
-rw-r--r--danger/only_documentation/Dangerfile2
-rw-r--r--danger/prettier/Dangerfile26
-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/20171230123729_init_schema.rb2
-rw-r--r--db/migrate/20180101160629_create_prometheus_metrics.rb2
-rw-r--r--db/migrate/20180122162010_add_auto_devops_domain_to_application_settings.rb2
-rw-r--r--db/migrate/20180129193323_add_uploads_builder_context.rb2
-rw-r--r--db/migrate/20180212030105_add_external_ip_to_clusters_applications_ingress.rb2
-rw-r--r--db/migrate/20180214093516_create_badges.rb2
-rw-r--r--db/migrate/20180214155405_create_clusters_applications_runners.rb2
-rw-r--r--db/migrate/20180216120000_add_pages_domain_verification.rb2
-rw-r--r--db/migrate/20180222043024_add_ip_address_to_runner.rb2
-rw-r--r--db/migrate/20180308125206_add_user_internal_regex_to_application_setting.rb2
-rw-r--r--db/migrate/20180315160435_add_external_auth_mutual_tls_fields_to_project_settings.rb2
-rw-r--r--db/migrate/20180319190020_create_deploy_tokens.rb2
-rw-r--r--db/migrate/20180502122856_create_project_mirror_data.rb2
-rw-r--r--db/migrate/20180503131624_create_remote_mirrors.rb2
-rw-r--r--db/migrate/20180503175053_ensure_missing_columns_to_project_mirror_data.rb4
-rw-r--r--db/migrate/20180511131058_create_clusters_applications_jupyter.rb6
-rw-r--r--db/migrate/20180515121227_create_notes_diff_files.rb2
-rw-r--r--db/migrate/20180529093006_ensure_remote_mirror_columns.rb2
-rw-r--r--db/migrate/20180531185349_add_repository_languages.rb2
-rw-r--r--db/migrate/20180613081317_create_ci_builds_runner_session.rb2
-rw-r--r--db/migrate/20180713092803_create_user_statuses.rb2
-rw-r--r--db/migrate/20180814153625_add_commit_email_to_users.rb2
-rw-r--r--db/migrate/20180831164908_add_identifier_to_prometheus_metric.rb2
-rw-r--r--db/migrate/20180910115836_add_attr_encrypted_columns_to_web_hook.rb2
-rw-r--r--db/migrate/20180910153412_add_token_digest_to_personal_access_tokens.rb2
-rw-r--r--db/migrate/20180912111628_add_knative_application.rb2
-rw-r--r--db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb2
-rw-r--r--db/migrate/20181019032400_add_shards_table.rb2
-rw-r--r--db/migrate/20181019032408_add_repositories_table.rb2
-rw-r--r--db/migrate/20181025115728_add_private_commit_email_hostname_to_application_settings.rb2
-rw-r--r--db/migrate/20181031190559_drop_gcp_clusters_table.rb2
-rw-r--r--db/migrate/20181101191341_create_clusters_applications_cert_manager.rb2
-rw-r--r--db/migrate/20181115140140_add_encrypted_runners_token_to_settings.rb2
-rw-r--r--db/migrate/20181116050532_knative_external_ip.rb2
-rw-r--r--db/migrate/20181116141415_add_encrypted_runners_token_to_namespaces.rb2
-rw-r--r--db/migrate/20181116141504_add_encrypted_runners_token_to_projects.rb2
-rw-r--r--db/migrate/20181120151656_add_token_encrypted_to_ci_runners.rb2
-rw-r--r--db/migrate/20181122160027_create_project_repositories.rb2
-rw-r--r--db/migrate/20181123144235_create_suggestions.rb2
-rw-r--r--db/migrate/20181128123704_add_state_to_pool_repository.rb2
-rw-r--r--db/migrate/20181129104854_add_token_encrypted_to_ci_builds.rb2
-rw-r--r--db/migrate/20181203002526_add_project_bfg_object_map_column.rb2
-rw-r--r--db/migrate/20181211092510_add_name_author_id_and_sha_to_releases.rb4
-rw-r--r--db/migrate/20181212171634_create_error_tracking_settings.rb2
-rw-r--r--db/migrate/20181228175414_create_releases_link_table.rb2
-rw-r--r--db/migrate/20190109153125_add_merge_request_external_diffs.rb2
-rw-r--r--db/migrate/20190114172110_add_domain_to_cluster.rb2
-rw-r--r--db/migrate/20190115092821_add_columns_project_error_tracking_settings.rb4
-rw-r--r--db/migrate/20190116234221_add_sorting_fields_to_user_preference.rb4
-rw-r--r--db/migrate/20190219201635_add_asset_proxy_settings.rb16
-rw-r--r--db/migrate/20190301182457_add_external_hostname_to_ingress_and_knative.rb4
-rw-r--r--db/migrate/20190320174702_add_lets_encrypt_notification_email_to_application_settings.rb2
-rw-r--r--db/migrate/20190325105715_add_fields_to_user_preferences.rb2
-rw-r--r--db/migrate/20190327163904_add_notification_email_to_notification_settings.rb2
-rw-r--r--db/migrate/20190402150158_backport_enterprise_schema.rb2
-rw-r--r--db/migrate/20190409224933_add_name_to_geo_nodes.rb2
-rw-r--r--db/migrate/20190422082247_create_project_metrics_settings.rb2
-rw-r--r--db/migrate/20190429082448_create_pages_domain_acme_orders.rb2
-rw-r--r--db/migrate/20190430131225_create_issue_tracker_data.rb2
-rw-r--r--db/migrate/20190430142025_create_jira_tracker_data.rb2
-rw-r--r--db/migrate/20190514105711_create_ip_restriction.rb2
-rw-r--r--db/migrate/20190527011309_add_required_template_name_to_application_settings.rb2
-rw-r--r--db/migrate/20190606054742_add_token_encrypted_to_operations_feature_flags_clients.rb2
-rw-r--r--db/migrate/20190613044655_add_username_to_deploy_tokens.rb2
-rw-r--r--db/migrate/20190613073003_create_project_aliases.rb2
-rw-r--r--db/migrate/20190621151636_add_merge_request_rebase_jid.rb2
-rw-r--r--db/migrate/20190624123615_add_grafana_url_to_settings.rb2
-rw-r--r--db/migrate/20190711124721_create_job_variables.rb2
-rw-r--r--db/migrate/20190711200053_change_deploy_tokens_token_not_null.rb11
-rw-r--r--db/migrate/20190711200508_add_token_encrypted_to_deploy_tokens.rb11
-rw-r--r--db/migrate/20190719122333_add_login_recaptcha_protection_enabled_to_application_settings.rb12
-rw-r--r--db/migrate/20190719174505_add_index_to_deploy_tokens_token_encrypted.rb17
-rw-r--r--db/migrate/20190722144316_create_milestone_releases_table.rb20
-rw-r--r--db/migrate/20190726101050_rename_allow_local_requests_from_hooks_and_services_application_setting.rb2
-rw-r--r--db/migrate/20190729180447_add_merge_requests_require_code_owner_approval_to_protected_branches.rb31
-rw-r--r--db/migrate/20190805140353_remove_rendundant_index_from_releases.rb21
-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/20190820163320_add_first_last_name_to_user.rb14
-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/20190828083843_add_index_to_ci_job_artifacts_on_project_id_for_security_reports.rb22
-rw-r--r--db/migrate/20190828110802_add_not_null_constraints_to_prometheus_metrics_y_label_and_unit.rb8
-rw-r--r--db/migrate/20190829131130_create_external_pull_requests.rb25
-rw-r--r--db/migrate/20190830075508_add_external_pull_request_id_to_ci_pipelines.rb15
-rw-r--r--db/migrate/20190830080123_add_index_to_ci_pipelines_external_pull_request.rb17
-rw-r--r--db/migrate/20190830080626_add_foreign_key_to_ci_pipelines_external_pull_request.rb17
-rw-r--r--db/migrate/20190902131045_replace_indexes_for_counting_active_users.rb23
-rw-r--r--db/migrate/20190902152329_add_index_for_ci_builds_metrics.rb19
-rw-r--r--db/migrate/20190904173203_add_index_on_users_unconfirmed_email.rb17
-rw-r--r--db/migrate/20190905223800_add_interruptible_to_builds_metadata.rb9
-rw-r--r--db/migrate/20190905223900_add_concurrent_index_to_builds_metadata.rb19
-rw-r--r--db/post_migrate/20181013005024_remove_koding_from_application_settings.rb2
-rw-r--r--db/post_migrate/20190404231137_remove_alternate_url_from_geo_nodes.rb2
-rw-r--r--db/post_migrate/20190625184066_remove_sentry_from_application_settings.rb2
-rw-r--r--db/post_migrate/20190711201818_encrypt_deploy_tokens_tokens.rb27
-rw-r--r--db/post_migrate/20190725080128_set_not_null_on_users_private_profile.rb26
-rw-r--r--db/post_migrate/20190801072937_add_gitlab_instance_administration_project.rb16
-rw-r--r--db/post_migrate/20190801114109_cleanup_allow_local_requests_from_hooks_and_services_application_setting_rename.rb2
-rw-r--r--db/post_migrate/20190809072552_set_self_monitoring_project_alerting_token.rb73
-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.rb82
-rw-r--r--doc/README.md7
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md4
-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/auth/okta.md16
-rw-r--r--doc/administration/database_load_balancing.md40
-rw-r--r--doc/administration/geo/disaster_recovery/index.md4
-rw-r--r--doc/administration/geo/replication/troubleshooting.md4
-rw-r--r--doc/administration/geo/replication/updating_the_geo_nodes.md456
-rw-r--r--doc/administration/geo/replication/version_specific_updates.md426
-rw-r--r--doc/administration/gitaly/index.md82
-rw-r--r--doc/administration/high_availability/gitlab.md27
-rw-r--r--doc/administration/index.md37
-rw-r--r--doc/administration/integration/plantuml.md3
-rw-r--r--doc/administration/issue_closing_pattern.md17
-rw-r--r--doc/administration/logs.md10
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md259
-rw-r--r--doc/administration/pages/source.md16
-rw-r--r--doc/administration/raketasks/github_import.md2
-rw-r--r--doc/administration/raketasks/ldap.md4
-rw-r--r--doc/administration/raketasks/maintenance.md4
-rw-r--r--doc/administration/raketasks/uploads/migrate.md2
-rw-r--r--doc/administration/raketasks/uploads/sanitize.md2
-rw-r--r--doc/administration/repository_storage_types.md12
-rw-r--r--doc/administration/smime_signing_email.md2
-rw-r--r--doc/administration/troubleshooting/elasticsearch.md6
-rw-r--r--doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md1040
-rw-r--r--doc/administration/troubleshooting/kubernetes_cheat_sheet.md10
-rw-r--r--doc/administration/troubleshooting/linux_cheat_sheet.md339
-rw-r--r--doc/administration/troubleshooting/sidekiq.md15
-rw-r--r--doc/administration/troubleshooting/test_environments.md126
-rw-r--r--doc/api/README.md4
-rw-r--r--doc/api/applications.md4
-rw-r--r--doc/api/dependencies.md16
-rw-r--r--doc/api/deploy_keys.md13
-rw-r--r--doc/api/epics.md4
-rw-r--r--doc/api/events.md12
-rw-r--r--doc/api/graphql/reference/index.md1
-rw-r--r--doc/api/groups.md4
-rw-r--r--doc/api/issues.md23
-rw-r--r--doc/api/labels.md42
-rw-r--r--doc/api/merge_request_approvals.md343
-rw-r--r--doc/api/merge_requests.md4
-rw-r--r--doc/api/notes.md16
-rw-r--r--doc/api/project_snippets.md58
-rw-r--r--doc/api/projects.md8
-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/api/templates/dockerfiles.md20
-rw-r--r--doc/api/templates/gitignores.md22
-rw-r--r--doc/api/templates/gitlab_ci_ymls.md20
-rw-r--r--doc/api/templates/licenses.md21
-rw-r--r--doc/ci/README.md2
-rw-r--r--doc/ci/caching/index.md23
-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/directed_acyclic_graph/index.md2
-rw-r--r--doc/ci/docker/using_docker_build.md37
-rw-r--r--doc/ci/environments.md5
-rw-r--r--doc/ci/examples/README.md2
-rw-r--r--doc/ci/examples/browser_performance.md157
-rw-r--r--doc/ci/examples/license_management.md4
-rw-r--r--doc/ci/jenkins/index.md8
-rw-r--r--doc/ci/large_repositories/index.md2
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md14
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md2
-rw-r--r--doc/ci/multi_project_pipelines.md14
-rw-r--r--doc/ci/runners/README.md3
-rw-r--r--doc/ci/services/mysql.md4
-rw-r--r--doc/ci/triggers/README.md5
-rw-r--r--doc/ci/variables/README.md2
-rw-r--r--doc/ci/yaml/README.md262
-rw-r--r--doc/development/README.md2
-rw-r--r--doc/development/api_graphql_styleguide.md45
-rw-r--r--doc/development/architecture.md76
-rw-r--r--doc/development/automatic_ce_ee_merge.md4
-rw-r--r--doc/development/background_migrations.md17
-rw-r--r--doc/development/build_test_package.md8
-rw-r--r--doc/development/changelog.md1
-rw-r--r--doc/development/code_review.md11
-rw-r--r--doc/development/contributing/index.md20
-rw-r--r--doc/development/contributing/issue_workflow.md305
-rw-r--r--doc/development/contributing/merge_request_workflow.md2
-rw-r--r--doc/development/contributing/style_guides.md5
-rw-r--r--doc/development/dangerbot.md116
-rw-r--r--doc/development/database_debugging.md2
-rw-r--r--doc/development/database_review.md5
-rw-r--r--doc/development/documentation/feature-change-workflow.md2
-rw-r--r--doc/development/documentation/index.md141
-rw-r--r--doc/development/documentation/site_architecture/global_nav.md87
-rw-r--r--doc/development/documentation/site_architecture/index.md8
-rw-r--r--doc/development/documentation/structure.md2
-rw-r--r--doc/development/documentation/styleguide.md29
-rw-r--r--doc/development/ee_features.md6
-rw-r--r--doc/development/elasticsearch.md10
-rw-r--r--doc/development/emails.md2
-rw-r--r--doc/development/fe_guide/development_process.md18
-rw-r--r--doc/development/fe_guide/performance.md2
-rw-r--r--doc/development/feature_flags/index.md4
-rw-r--r--doc/development/file_storage.md2
-rw-r--r--doc/development/filtering_by_label.md18
-rw-r--r--doc/development/gemfile.md4
-rw-r--r--doc/development/geo.md2
-rw-r--r--doc/development/git_object_deduplication.md28
-rw-r--r--doc/development/gitaly.md14
-rw-r--r--doc/development/go_guide/index.md2
-rw-r--r--doc/development/gotchas.md4
-rw-r--r--doc/development/i18n/translation.md1
-rw-r--r--doc/development/integrations/jira_connect.md6
-rw-r--r--doc/development/kubernetes.md2
-rw-r--r--doc/development/logging.md2
-rw-r--r--doc/development/migration_style_guide.md141
-rw-r--r--doc/development/namespaces_storage_statistics.md24
-rw-r--r--doc/development/new_fe_guide/dependencies.md2
-rw-r--r--doc/development/new_fe_guide/development/testing.md1
-rw-r--r--doc/development/omnibus.md22
-rw-r--r--doc/development/prometheus_metrics.md23
-rw-r--r--doc/development/python_guide/index.md8
-rw-r--r--doc/development/session.md2
-rw-r--r--doc/development/shell_commands.md4
-rw-r--r--doc/development/testing_guide/best_practices.md2
-rw-r--r--doc/development/testing_guide/end_to_end/best_practices.md17
-rw-r--r--doc/development/testing_guide/end_to_end/index.md2
-rw-r--r--doc/development/testing_guide/end_to_end/page_objects.md12
-rw-r--r--doc/development/testing_guide/end_to_end/quick_start_guide.md2
-rw-r--r--doc/development/testing_guide/end_to_end/style_guide.md2
-rw-r--r--doc/development/testing_guide/index.md4
-rw-r--r--doc/development/testing_guide/review_apps.md6
-rw-r--r--doc/development/testing_guide/testing_levels.md2
-rw-r--r--doc/development/uploads.md270
-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.md16
-rw-r--r--doc/gitlab-basics/add-file.md2
-rw-r--r--doc/gitlab-basics/add-merge-request.md2
-rw-r--r--doc/gitlab-basics/command-line-commands.md2
-rw-r--r--doc/gitlab-basics/create-project.md2
-rw-r--r--doc/gitlab-basics/create-your-ssh-keys.md2
-rw-r--r--doc/gitlab-basics/start-using-git.md6
-rw-r--r--doc/install/README.md2
-rw-r--r--doc/install/azure/index.md3
-rw-r--r--doc/install/installation.md18
-rw-r--r--doc/install/relative_url.md2
-rw-r--r--doc/integration/auth0.md4
-rw-r--r--doc/integration/azure.md4
-rw-r--r--doc/integration/cas.md4
-rw-r--r--doc/integration/elasticsearch.md330
-rw-r--r--doc/integration/facebook.md4
-rw-r--r--doc/integration/github.md6
-rw-r--r--doc/integration/gitlab.md4
-rw-r--r--doc/integration/jenkins_deprecated.md4
-rw-r--r--doc/integration/jira.md2
-rw-r--r--doc/integration/jira_development_panel.md2
-rw-r--r--doc/integration/kerberos.md7
-rw-r--r--doc/integration/omniauth.md23
-rw-r--r--doc/integration/salesforce.md4
-rw-r--r--doc/integration/saml.md10
-rw-r--r--doc/integration/shibboleth.md16
-rw-r--r--doc/integration/slash_commands.md1
-rw-r--r--doc/integration/twitter.md4
-rw-r--r--doc/integration/ultra_auth.md4
-rw-r--r--doc/legal/corporate_contributor_license_agreement.md2
-rw-r--r--doc/migrate_ci_to_ce/README.md4
-rw-r--r--doc/project_services/bamboo.md2
-rw-r--r--doc/project_services/bugzilla.md2
-rw-r--r--doc/project_services/emails_on_push.md2
-rw-r--r--doc/project_services/hipchat.md6
-rw-r--r--doc/project_services/irker.md2
-rw-r--r--doc/project_services/jira.md2
-rw-r--r--doc/project_services/kubernetes.md2
-rw-r--r--doc/project_services/mattermost.md2
-rw-r--r--doc/project_services/mattermost_slash_commands.md2
-rw-r--r--doc/project_services/project_services.md2
-rw-r--r--doc/project_services/redmine.md2
-rw-r--r--doc/project_services/services_templates.md2
-rw-r--r--doc/project_services/slack.md2
-rw-r--r--doc/project_services/slack_slash_commands.md2
-rw-r--r--doc/push_rules/push_rules.md6
-rw-r--r--doc/raketasks/README.md2
-rw-r--r--doc/raketasks/backup_restore.md41
-rw-r--r--doc/raketasks/features.md2
-rw-r--r--doc/raketasks/import.md8
-rw-r--r--doc/security/README.md2
-rw-r--r--doc/security/asset_proxy.md28
-rw-r--r--doc/security/information_exclusivity.md2
-rw-r--r--doc/security/password_storage.md13
-rw-r--r--doc/security/rack_attack.md9
-rw-r--r--doc/security/reset_root_password.md2
-rw-r--r--doc/system_hooks/system_hooks.md40
-rw-r--r--doc/topics/autodevops/index.md78
-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/diff_limits.md2
-rw-r--r--doc/user/admin_area/license.md2
-rw-r--r--doc/user/admin_area/monitoring/health_check.md74
-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/admin_area/settings/terms.md3
-rw-r--r--doc/user/admin_area/settings/third_party_offers.md3
-rw-r--r--doc/user/application_security/container_scanning/index.md32
-rw-r--r--doc/user/application_security/dast/index.md6
-rw-r--r--doc/user/application_security/dependency_scanning/index.md42
-rw-r--r--doc/user/application_security/index.md4
-rw-r--r--doc/user/application_security/license_compliance/img/license_compliance.png (renamed from doc/user/application_security/license_management/img/license_management.png)bin5184 -> 5184 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_decision.png (renamed from doc/user/application_security/license_management/img/license_management_decision.png)bin5975 -> 5975 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_v12_3.pngbin0 -> 26074 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.md243
-rw-r--r--doc/user/application_security/license_management/img/license_management_add_license.pngbin24247 -> 0 bytes
-rw-r--r--doc/user/application_security/license_management/img/license_management_pipeline_tab.pngbin12115 -> 0 bytes
-rw-r--r--doc/user/application_security/license_management/img/license_management_search.pngbin28237 -> 0 bytes
-rw-r--r--doc/user/application_security/license_management/img/license_management_settings.pngbin44790 -> 0 bytes
-rw-r--r--doc/user/application_security/license_management/index.md244
-rw-r--r--doc/user/application_security/sast/analyzers.md2
-rw-r--r--doc/user/application_security/sast/img/security_report.pngbin38475 -> 0 bytes
-rw-r--r--doc/user/application_security/sast/index.md44
-rw-r--r--doc/user/application_security/security_dashboard/img/group_security_dashboard.pngbin68332 -> 0 bytes
-rwxr-xr-xdoc/user/application_security/security_dashboard/img/group_security_dashboard_v12_3.pngbin0 -> 61667 bytes
-rwxr-xr-xdoc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v12_3.pngbin0 -> 52247 bytes
-rw-r--r--doc/user/application_security/security_dashboard/img/project_security_dashboard.pngbin73425 -> 0 bytes
-rwxr-xr-xdoc/user/application_security/security_dashboard/img/project_security_dashboard_v12_3.pngbin0 -> 48767 bytes
-rw-r--r--doc/user/application_security/security_dashboard/index.md41
-rw-r--r--doc/user/clusters/applications.md14
-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.md19
-rw-r--r--doc/user/group/epics/index.md13
-rw-r--r--doc/user/group/index.md19
-rw-r--r--doc/user/index.md2
-rw-r--r--doc/user/instance/clusters/index.md2
-rw-r--r--doc/user/markdown.md7
-rw-r--r--doc/user/permissions.md42
-rw-r--r--doc/user/project/clusters/index.md2
-rw-r--r--doc/user/project/code_owners.md2
-rw-r--r--doc/user/project/img/protected_branches_devs_can_push.pngbin11221 -> 0 bytes
-rw-r--r--doc/user/project/img/protected_branches_devs_can_push_v12_3.pngbin0 -> 11941 bytes
-rw-r--r--doc/user/project/img/protected_branches_list.pngbin6933 -> 0 bytes
-rw-r--r--doc/user/project/img/protected_branches_list_v12_3.pngbin0 -> 8774 bytes
-rw-r--r--doc/user/project/img/protected_branches_page.pngbin7199 -> 0 bytes
-rw-r--r--doc/user/project/img/protected_branches_page_v12_3.pngbin0 -> 9445 bytes
-rw-r--r--doc/user/project/img/protected_tags_list.pngbin7227 -> 0 bytes
-rw-r--r--doc/user/project/img/protected_tags_list_v12_3.pngbin0 -> 4395 bytes
-rw-r--r--doc/user/project/img/protected_tags_page.pngbin13813 -> 0 bytes
-rw-r--r--doc/user/project/img/protected_tags_page_v12_3.pngbin0 -> 10431 bytes
-rw-r--r--doc/user/project/img/protected_tags_permissions_dropdown.pngbin7770 -> 0 bytes
-rw-r--r--doc/user/project/img/protected_tags_permissions_dropdown_v12_3.pngbin0 -> 4517 bytes
-rw-r--r--doc/user/project/import/gemnasium.md2
-rw-r--r--doc/user/project/import/img/gemnasium/report.pngbin144883 -> 0 bytes
-rw-r--r--doc/user/project/import/img/import_projects_from_repo_url.pngbin142329 -> 142284 bytes
-rw-r--r--doc/user/project/import/tfvc.md2
-rw-r--r--doc/user/project/index.md6
-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/mattermost.md9
-rw-r--r--doc/user/project/integrations/mock_ci.md2
-rw-r--r--doc/user/project/integrations/prometheus.md53
-rw-r--r--doc/user/project/integrations/webhooks.md33
-rw-r--r--doc/user/project/issue_board.md30
-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/browser_performance_testing.md173
-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/img/merge_request_diff_v12_2.pngbin0 -> 136144 bytes
-rw-r--r--doc/user/project/merge_requests/index.md56
-rw-r--r--doc/user/project/merge_requests/license_management.md4
-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.md41
-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/protected_branches.md8
-rw-r--r--doc/user/project/protected_tags.md6
-rw-r--r--doc/user/project/quick_actions.md137
-rw-r--r--doc/user/project/repository/index.md2
-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/forking/branch_select.pngbin15410 -> 18042 bytes
-rw-r--r--doc/workflow/forking/merge_request.pngbin16329 -> 24625 bytes
-rw-r--r--doc/workflow/forking_workflow.md7
-rw-r--r--doc/workflow/git_annex.md12
-rw-r--r--doc/workflow/img/forking_workflow_choose_namespace.pngbin26275 -> 35084 bytes
-rw-r--r--doc/workflow/img/forking_workflow_fork_button.pngbin12962 -> 25754 bytes
-rw-r--r--doc/workflow/img/forking_workflow_path_taken_error.pngbin10092 -> 21497 bytes
-rw-r--r--doc/workflow/img/repository_mirroring_push_settings.pngbin50526 -> 72515 bytes
-rw-r--r--doc/workflow/issue_weight.md5
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md3
-rw-r--r--doc/workflow/releases.md8
-rw-r--r--doc/workflow/repository_mirroring.md12
-rw-r--r--doc/workflow/time_tracking.md2
-rw-r--r--doc/workflow/timezone.md2
-rw-r--r--lib/api/api.rb2
-rw-r--r--lib/api/discussions.rb2
-rw-r--r--lib/api/entities.rb39
-rw-r--r--lib/api/groups.rb3
-rw-r--r--lib/api/helpers.rb33
-rw-r--r--lib/api/helpers/groups_helpers.rb7
-rw-r--r--lib/api/helpers/internal_helpers.rb20
-rw-r--r--lib/api/helpers/issues_helpers.rb9
-rw-r--r--lib/api/helpers/label_helpers.rb32
-rw-r--r--lib/api/helpers/notes_helpers.rb8
-rw-r--r--lib/api/internal.rb293
-rw-r--r--lib/api/internal/base.rb273
-rw-r--r--lib/api/issues.rb5
-rw-r--r--lib/api/labels.rb33
-rw-r--r--lib/api/notes.rb13
-rw-r--r--lib/api/pipelines.rb3
-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/support/git_access_actor.rb42
-rw-r--r--lib/api/validations/types/comma_separated_to_array.rb22
-rw-r--r--lib/api/validations/types/workhorse_file.rb20
-rw-r--r--lib/api/wikis.rb22
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb16
-rw-r--r--lib/banzai/filter/asset_proxy_filter.rb66
-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/feature/gitaly.rb1
-rw-r--r--lib/gitlab.rb2
-rw-r--r--lib/gitlab/analytics/cycle_analytics/default_stages.rb98
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events.rb71
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/issue_created.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/simple_stage_event.rb13
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb28
-rw-r--r--lib/gitlab/anonymous_session.rb39
-rw-r--r--lib/gitlab/auth.rb8
-rw-r--r--lib/gitlab/auth/o_auth/user.rb7
-rw-r--r--lib/gitlab/auth/unique_ips_limiter.rb2
-rw-r--r--lib/gitlab/auth/user_auth_finders.rb2
-rw-r--r--lib/gitlab/authorized_keys.rb30
-rw-r--r--lib/gitlab/background_migration/merge_request_assignees_migration_progress_check.rb2
-rw-r--r--lib/gitlab/background_migration/prepare_untracked_uploads.rb8
-rw-r--r--lib/gitlab/bitbucket_server_import/importer.rb2
-rw-r--r--lib/gitlab/checks/lfs_check.rb4
-rw-r--r--lib/gitlab/checks/project_created.rb2
-rw-r--r--lib/gitlab/checks/project_moved.rb2
-rw-r--r--lib/gitlab/ci/build/port.rb4
-rw-r--r--lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb2
-rw-r--r--lib/gitlab/ci/build/step.rb6
-rw-r--r--lib/gitlab/ci/config/entry/cache.rb2
-rw-r--r--lib/gitlab/ci/config/entry/job.rb9
-rw-r--r--lib/gitlab/ci/config/external/file/base.rb8
-rw-r--r--lib/gitlab/ci/config/external/file/template.rb2
-rw-r--r--lib/gitlab/ci/cron_parser.rb4
-rw-r--r--lib/gitlab/ci/pipeline/chain/build.rb1
-rw-r--r--lib/gitlab/ci/pipeline/chain/command.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/helpers.rb7
-rw-r--r--lib/gitlab/ci/pipeline/chain/limit/job_activity.rb21
-rw-r--r--lib/gitlab/ci/pipeline/chain/populate.rb2
-rw-r--r--lib/gitlab/ci/reports/test_case.rb8
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml22
-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/ci/yaml_processor.rb1
-rw-r--r--lib/gitlab/cleanup/project_upload_file_finder.rb6
-rw-r--r--lib/gitlab/correlation_id.rb40
-rw-r--r--lib/gitlab/danger/helper.rb21
-rw-r--r--lib/gitlab/danger/teammate.rb4
-rw-r--r--lib/gitlab/database.rb7
-rw-r--r--lib/gitlab/database/migration_helpers.rb83
-rw-r--r--lib/gitlab/database/sha_attribute.rb2
-rw-r--r--lib/gitlab/database_importers/self_monitoring/project/create_service.rb261
-rw-r--r--lib/gitlab/diff/suggestion.rb2
-rw-r--r--lib/gitlab/downtime_check/message.rb4
-rw-r--r--lib/gitlab/ee_compat_check.rb5
-rw-r--r--lib/gitlab/etag_caching/store.rb2
-rw-r--r--lib/gitlab/fogbugz_import/project_creator.rb2
-rw-r--r--lib/gitlab/git.rb6
-rw-r--r--lib/gitlab/git/diff_collection.rb6
-rw-r--r--lib/gitlab/git/lfs_pointer_file.rb4
-rw-r--r--lib/gitlab/git/repository.rb4
-rw-r--r--lib/gitlab/git/util.rb2
-rw-r--r--lib/gitlab/gitaly_client.rb13
-rw-r--r--lib/gitlab/github_import/issuable_finder.rb2
-rw-r--r--lib/gitlab/github_import/label_finder.rb2
-rw-r--r--lib/gitlab/github_import/milestone_finder.rb2
-rw-r--r--lib/gitlab/github_import/page_counter.rb2
-rw-r--r--lib/gitlab/github_import/parallel_scheduling.rb2
-rw-r--r--lib/gitlab/github_import/user_finder.rb6
-rw-r--r--lib/gitlab/graphql/authorize/authorize_field_service.rb12
-rw-r--r--lib/gitlab/graphql/authorize/authorize_resource.rb10
-rw-r--r--lib/gitlab/graphql/loaders/batch_lfs_oid_loader.rb2
-rw-r--r--lib/gitlab/graphql/loaders/batch_model_loader.rb2
-rw-r--r--lib/gitlab/graphql/loaders/batch_project_statistics_loader.rb2
-rw-r--r--lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader.rb2
-rw-r--r--lib/gitlab/graphql/loaders/pipeline_for_sha_loader.rb2
-rw-r--r--lib/gitlab/graphql/present/instrumentation.rb4
-rw-r--r--lib/gitlab/graphql/representation/submodule_tree_entry.rb2
-rw-r--r--lib/gitlab/graphql/representation/tree_entry.rb2
-rw-r--r--lib/gitlab/health_checks/gitaly_check.rb2
-rw-r--r--lib/gitlab/import_export.rb2
-rw-r--r--lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb2
-rw-r--r--lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb6
-rw-r--r--lib/gitlab/import_export/import_export.yml2
-rw-r--r--lib/gitlab/import_export/project_tree_restorer.rb9
-rw-r--r--lib/gitlab/incoming_email.rb6
-rw-r--r--lib/gitlab/internal_post_receive/response.rb51
-rw-r--r--lib/gitlab/jira/http_client.rb66
-rw-r--r--lib/gitlab/job_waiter.rb2
-rw-r--r--lib/gitlab/jwt_authenticatable.rb42
-rw-r--r--lib/gitlab/kubernetes/helm.rb12
-rw-r--r--lib/gitlab/kubernetes/pod.rb10
-rw-r--r--lib/gitlab/logger.rb2
-rw-r--r--lib/gitlab/markdown_cache.rb2
-rw-r--r--lib/gitlab/metrics/samplers/ruby_sampler.rb2
-rw-r--r--lib/gitlab/metrics/subscribers/action_view.rb2
-rw-r--r--lib/gitlab/metrics/transaction.rb7
-rw-r--r--lib/gitlab/metrics/web_transaction.rb4
-rw-r--r--lib/gitlab/middleware/multipart.rb20
-rw-r--r--lib/gitlab/middleware/read_only/controller.rb4
-rw-r--r--lib/gitlab/optimistic_locking.rb3
-rw-r--r--lib/gitlab/patch/chronic_duration.rb35
-rw-r--r--lib/gitlab/path_regex.rb8
-rw-r--r--lib/gitlab/performance_bar.rb8
-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/polling_interval.rb2
-rw-r--r--lib/gitlab/private_commit_email.rb2
-rw-r--r--lib/gitlab/profiler.rb2
-rw-r--r--lib/gitlab/prometheus/additional_metrics_parser.rb2
-rw-r--r--lib/gitlab/push_options.rb21
-rw-r--r--lib/gitlab/query_limiting/middleware.rb4
-rw-r--r--lib/gitlab/quick_actions/issuable_actions.rb5
-rw-r--r--lib/gitlab/quick_actions/issue_actions.rb6
-rw-r--r--lib/gitlab/quick_actions/issue_and_merge_request_actions.rb2
-rw-r--r--lib/gitlab/recaptcha.rb6
-rw-r--r--lib/gitlab/redis/cache.rb6
-rw-r--r--lib/gitlab/redis/queues.rb8
-rw-r--r--lib/gitlab/redis/shared_state.rb11
-rw-r--r--lib/gitlab/redis/wrapper.rb4
-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/request_profiler.rb2
-rw-r--r--lib/gitlab/rugged_instrumentation.rb8
-rw-r--r--lib/gitlab/sanitizers/exif.rb7
-rw-r--r--lib/gitlab/shard_health_cache.rb2
-rw-r--r--lib/gitlab/shell.rb86
-rw-r--r--lib/gitlab/sidekiq_middleware/metrics.rb2
-rw-r--r--lib/gitlab/sidekiq_monitor.rb2
-rw-r--r--lib/gitlab/sidekiq_status.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/snowplow_tracker.rb35
-rw-r--r--lib/gitlab/tracing.rb2
-rw-r--r--lib/gitlab/tracking.rb44
-rw-r--r--lib/gitlab/url_blocker.rb8
-rw-r--r--lib/gitlab/url_helpers.rb2
-rw-r--r--lib/gitlab/usage_data.rb3
-rw-r--r--lib/gitlab/usage_data_counters/merge_request_counter.rb10
-rw-r--r--lib/gitlab/visibility_level_checker.rb88
-rw-r--r--lib/gitlab/workhorse.rb46
-rw-r--r--lib/gitlab_danger.rb54
-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/assets.rake6
-rw-r--r--lib/tasks/gitlab/uploads/sanitize.rake6
-rw-r--r--lib/tasks/gitlab_danger.rake17
-rw-r--r--locale/gitlab.pot306
-rw-r--r--package.json15
-rw-r--r--plugins/.gitignore5
-rw-r--r--qa/Dockerfile2
-rw-r--r--qa/README.md11
-rw-r--r--qa/qa.rb19
-rw-r--r--qa/qa/page/admin/menu.rb19
-rw-r--r--qa/qa/page/admin/settings/component/ip_limits.rb30
-rw-r--r--qa/qa/page/admin/settings/network.rb23
-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/group/settings/general.rb18
-rw-r--r--qa/qa/page/main/menu.rb4
-rw-r--r--qa/qa/page/merge_request/new.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/auto_devops.rb21
-rw-r--r--qa/qa/page/project/settings/ci_cd.rb10
-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/sub_menus/issues.rb9
-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/group.rb6
-rw-r--r--qa/qa/resource/merge_request.rb19
-rw-r--r--qa/qa/resource/project.rb10
-rw-r--r--qa/qa/resource/repository/commit.rb66
-rw-r--r--qa/qa/resource/runner.rb36
-rw-r--r--qa/qa/resource/sandbox.rb2
-rw-r--r--qa/qa/resource/user.rb1
-rw-r--r--qa/qa/runtime/api/client.rb21
-rw-r--r--qa/qa/runtime/api/request.rb4
-rw-r--r--qa/qa/runtime/browser.rb2
-rw-r--r--qa/qa/runtime/env.rb2
-rw-r--r--qa/qa/runtime/fixtures.rb13
-rw-r--r--qa/qa/scenario/test/sanity/selectors.rb2
-rw-r--r--qa/qa/service/cluster_provider/base.rb41
-rw-r--r--qa/qa/service/cluster_provider/gcloud.rb87
-rw-r--r--qa/qa/service/cluster_provider/k3d.rb131
-rw-r--r--qa/qa/service/cluster_provider/minikube.rb26
-rw-r--r--qa/qa/service/kubernetes_cluster.rb134
-rw-r--r--qa/qa/service/runner.rb27
-rw-r--r--qa/qa/specs/features/api/1_manage/rate_limits_spec.rb20
-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/2_plan/issue/filter_issue_comments_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb3
-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--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb16
-rw-r--r--rubocop/cop/gitlab/httparty.rb6
-rw-r--r--rubocop/cop/gitlab/union.rb4
-rw-r--r--rubocop/cop/graphql/authorize_types.rb6
-rw-r--r--rubocop/cop/include_action_view_context.rb5
-rw-r--r--rubocop/cop/include_sidekiq_worker.rb5
-rw-r--r--rubocop/cop/inject_enterprise_edition_module.rb2
-rw-r--r--rubocop/cop/migration/add_limit_to_string_columns.rb59
-rw-r--r--rubocop/cop/rspec/be_success_matcher.rb53
-rw-r--r--rubocop/cop/rspec/env_assignment.rb5
-rw-r--r--rubocop/cop/rspec/factories_in_migration_specs.rb5
-rw-r--r--rubocop/cop/sidekiq_options_queue.rb5
-rw-r--r--rubocop/rubocop.rb2
-rw-r--r--rubocop/spec_helpers.rb30
-rwxr-xr-xscripts/frontend/check_dependencies.sh11
-rwxr-xr-xscripts/review_apps/review-apps.sh7
-rw-r--r--shared/.gitignore4
-rw-r--r--spec/config/smime_signature_settings_spec.rb2
-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/registrations_controller_spec.rb12
-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/ci/job_artifacts.rb4
-rw-r--r--spec/factories/deploy_tokens.rb3
-rw-r--r--spec/factories/external_pull_requests.rb17
-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/sequences.rb2
-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_runners_spec.rb4
-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/admin/clusters/applications_spec.rb21
-rw-r--r--spec/features/boards/add_issues_modal_spec.rb2
-rw-r--r--spec/features/boards/boards_spec.rb16
-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.rb42
-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/clusters/installing_applications_shared_examples.rb228
-rw-r--r--spec/features/commits/user_uses_quick_actions_spec.rb2
-rw-r--r--spec/features/dashboard/projects_spec.rb26
-rw-r--r--spec/features/dashboard/todos/target_state_spec.rb2
-rw-r--r--spec/features/dashboard/todos/todos_filtering_spec.rb12
-rw-r--r--spec/features/dashboard/todos/todos_sorting_spec.rb36
-rw-r--r--spec/features/dashboard/todos/todos_spec.rb18
-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/applications_spec.rb23
-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/help_pages_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.rb13
-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.rb4
-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/oauth_login_spec.rb12
-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/applications_spec.rb229
-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_browses_files_spec.rb1
-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/show/user_sees_last_commit_ci_status_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.rb4
-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/issues_finder_spec.rb50
-rw-r--r--spec/finders/members_finder_spec.rb44
-rw-r--r--spec/finders/merge_requests_finder_spec.rb20
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_noteable.json32
-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/components/applications_spec.js41
-rw-r--r--spec/frontend/clusters/stores/clusters_store_spec.js6
-rw-r--r--spec/frontend/error_tracking/components/error_tracking_list_spec.js32
-rw-r--r--spec/frontend/ide/components/preview/clientside_spec.js318
-rw-r--r--spec/frontend/jobs/store/utils_spec.js60
-rw-r--r--spec/frontend/lib/utils/axios_utils_spec.js45
-rw-r--r--spec/frontend/mocks/ce/lib/utils/axios_utils.js65
-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/monitoring/embed/embed_spec.js8
-rw-r--r--spec/frontend/notes/components/note_app_spec.js31
-rw-r--r--spec/frontend/notes/old_notes_spec.js9
-rw-r--r--spec/frontend/project_find_file_spec.js77
-rw-r--r--spec/frontend/tracking_spec.js46
-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/graphql/features/authorization_spec.rb28
-rw-r--r--spec/graphql/gitlab_schema_spec.rb8
-rw-r--r--spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb2
-rw-r--r--spec/graphql/resolvers/echo_resolver_spec.rb24
-rw-r--r--spec/graphql/resolvers/group_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/merge_requests_resolver_spec.rb12
-rw-r--r--spec/graphql/resolvers/namespace_projects_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/project_resolver_spec.rb4
-rw-r--r--spec/helpers/avatars_helper_spec.rb44
-rw-r--r--spec/helpers/boards_helper_spec.rb2
-rw-r--r--spec/helpers/ci_status_helper_spec.rb76
-rw-r--r--spec/helpers/commits_helper_spec.rb2
-rw-r--r--spec/helpers/emails_helper_spec.rb56
-rw-r--r--spec/helpers/external_link_helper_spec.rb17
-rw-r--r--spec/helpers/form_helper_spec.rb2
-rw-r--r--spec/helpers/icons_helper_spec.rb7
-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/error_tracking_helper_spec.rb28
-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/helpers/version_check_helper_spec.rb4
-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/board_new_issue_spec.js26
-rw-r--r--spec/javascripts/boards/issue_spec.js6
-rw-r--r--spec/javascripts/boards/mock_data.js2
-rw-r--r--spec/javascripts/create_cluster/gke_cluster/components/gke_machine_type_dropdown_spec.js (renamed from spec/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown_spec.js)6
-rw-r--r--spec/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js (renamed from spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js)6
-rw-r--r--spec/javascripts/create_cluster/gke_cluster/components/gke_zone_dropdown_spec.js (renamed from spec/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown_spec.js)6
-rw-r--r--spec/javascripts/create_cluster/gke_cluster/helpers.js (renamed from spec/javascripts/projects/gke_cluster_dropdowns/helpers.js)0
-rw-r--r--spec/javascripts/create_cluster/gke_cluster/mock_data.js (renamed from spec/javascripts/projects/gke_cluster_dropdowns/mock_data.js)0
-rw-r--r--spec/javascripts/create_cluster/gke_cluster/stores/actions_spec.js (renamed from spec/javascripts/projects/gke_cluster_dropdowns/stores/actions_spec.js)4
-rw-r--r--spec/javascripts/create_cluster/gke_cluster/stores/getters_spec.js (renamed from spec/javascripts/projects/gke_cluster_dropdowns/stores/getters_spec.js)2
-rw-r--r--spec/javascripts/create_cluster/gke_cluster/stores/mutations_spec.js (renamed from spec/javascripts/projects/gke_cluster_dropdowns/stores/mutations_spec.js)4
-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/ide/components/preview/clientside_spec.js363
-rw-r--r--spec/javascripts/issue_show/components/edit_actions_spec.js2
-rw-r--r--spec/javascripts/jobs/components/job_app_spec.js1
-rw-r--r--spec/javascripts/jobs/components/log/line_header_spec.js84
-rw-r--r--spec/javascripts/jobs/components/log/line_number_spec.js40
-rw-r--r--spec/javascripts/jobs/components/log/line_spec.js49
-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/monitoring/panel_type_spec.js7
-rw-r--r--spec/javascripts/notes/mock_data.js2
-rw-r--r--spec/javascripts/notes/stores/actions_spec.js45
-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/api/helpers/label_helpers_spec.rb33
-rw-r--r--spec/lib/api/support/git_access_actor_spec.rb128
-rw-r--r--spec/lib/banzai/filter/asset_proxy_filter_spec.rb106
-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/analytics/cycle_analytics/stage_events/stage_event_spec.rb10
-rw-r--r--spec/lib/gitlab/anonymous_session_spec.rb78
-rw-r--r--spec/lib/gitlab/auth/o_auth/user_spec.rb22
-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/diff_check_spec.rb5
-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/policy/refs_spec.rb14
-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/ci/pipeline/chain/build_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb26
-rw-r--r--spec/lib/gitlab/danger/helper_spec.rb58
-rw-r--r--spec/lib/gitlab/danger/teammate_spec.rb22
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb134
-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.rb (renamed from spec/services/self_monitoring/project/create_service_spec.rb)150
-rw-r--r--spec/lib/gitlab/database_spec.rb11
-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.rb8
-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/fogbugz_import/project_creator_spec.rb29
-rw-r--r--spec/lib/gitlab/gfm/reference_rewriter_spec.rb17
-rw-r--r--spec/lib/gitlab/git/diff_collection_spec.rb69
-rw-r--r--spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb12
-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/graphql/authorize/authorize_field_service_spec.rb6
-rw-r--r--spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb12
-rw-r--r--spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb6
-rw-r--r--spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/markdown_field_spec.rb7
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml14
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb39
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml17
-rw-r--r--spec/lib/gitlab/internal_post_receive/response_spec.rb121
-rw-r--r--spec/lib/gitlab/jwt_authenticatable_spec.rb93
-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/metrics/transaction_spec.rb8
-rw-r--r--spec/lib/gitlab/metrics/web_transaction_spec.rb4
-rw-r--r--spec/lib/gitlab/middleware/go_spec.rb2
-rw-r--r--spec/lib/gitlab/patch/chronic_duration_spec.rb27
-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/query_limiting/transaction_spec.rb4
-rw-r--r--spec/lib/gitlab/query_limiting_spec.rb9
-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/snowplow_tracker_spec.rb45
-rw-r--r--spec/lib/gitlab/time_tracking_formatter_spec.rb8
-rw-r--r--spec/lib/gitlab/tracing_spec.rb2
-rw-r--r--spec/lib/gitlab/tracking_spec.rb88
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb115
-rw-r--r--spec/lib/gitlab/usage_data_counters/merge_request_counter_spec.rb9
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb1
-rw-r--r--spec/lib/gitlab/visibility_level_checker_spec.rb82
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb64
-rw-r--r--spec/lib/gitlab_danger_spec.rb76
-rw-r--r--spec/lib/gitlab_spec.rb31
-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/migrations/add_gitlab_instance_administration_project_spec.rb252
-rw-r--r--spec/migrations/encrypt_deploy_tokens_tokens_spec.rb47
-rw-r--r--spec/models/abuse_report_spec.rb2
-rw-r--r--spec/models/active_session_spec.rb2
-rw-r--r--spec/models/analytics/cycle_analytics/project_stage_spec.rb14
-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.rb89
-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.rb50
-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/commit_spec.rb18
-rw-r--r--spec/models/concerns/blocks_json_serialization_spec.rb2
-rw-r--r--spec/models/concerns/cacheable_attributes_spec.rb4
-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/has_status_spec.rb12
-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/noteable_spec.rb18
-rw-r--r--spec/models/concerns/routable_spec.rb27
-rw-r--r--spec/models/concerns/sha_attribute_spec.rb4
-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/deployment_spec.rb26
-rw-r--r--spec/models/external_pull_request_spec.rb220
-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/internal_id_spec.rb3
-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/metrics_spec.rb2
-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_diff_spec.rb14
-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.rb52
-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/todo_spec.rb4
-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.rb63
-rw-r--r--spec/policies/board_policy_spec.rb53
-rw-r--r--spec/policies/issue_policy_spec.rb28
-rw-r--r--spec/policies/merge_request_policy_spec.rb89
-rw-r--r--spec/policies/project_policy_spec.rb13
-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/discussions_spec.rb54
-rw-r--r--spec/requests/api/graphql/gitlab_schema_spec.rb2
-rw-r--r--spec/requests/api/graphql/multiplexed_queries_spec.rb8
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb11
-rw-r--r--spec/requests/api/groups_spec.rb16
-rw-r--r--spec/requests/api/internal/base_spec.rb (renamed from spec/requests/api/internal_spec.rb)66
-rw-r--r--spec/requests/api/issues/get_project_issues_spec.rb8
-rw-r--r--spec/requests/api/issues/issues_spec.rb16
-rw-r--r--spec/requests/api/labels_spec.rb363
-rw-r--r--spec/requests/api/notes_spec.rb7
-rw-r--r--spec/requests/api/pages_domains_spec.rb2
-rw-r--r--spec/requests/api/pipelines_spec.rb11
-rw-r--r--spec/requests/api/project_snapshots_spec.rb7
-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/requests/api/wikis_spec.rb13
-rw-r--r--spec/requests/jwt_controller_spec.rb8
-rw-r--r--spec/requests/projects/uploads_spec.rb38
-rw-r--r--spec/requests/rack_attack_global_spec.rb222
-rw-r--r--spec/rubocop/cop/gitlab/union_spec.rb6
-rw-r--r--spec/rubocop/cop/inject_enterprise_edition_module_spec.rb19
-rw-r--r--spec/rubocop/cop/migration/add_limit_to_string_columns_spec.rb268
-rw-r--r--spec/rubocop/cop/rspec/be_success_matcher_spec.rb63
-rw-r--r--spec/rubocop/cop/rspec/env_assignment_spec.rb26
-rw-r--r--spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb22
-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.rb13
-rw-r--r--spec/serializers/pipeline_entity_spec.rb6
-rw-r--r--spec/services/application_settings/update_service_spec.rb51
-rw-r--r--spec/services/audit_event_service_spec.rb12
-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/ci/create_pipeline_service_spec.rb380
-rw-r--r--spec/services/clusters/applications/check_installation_progress_service_spec.rb22
-rw-r--r--spec/services/clusters/applications/check_uninstall_progress_service_spec.rb18
-rw-r--r--spec/services/clusters/applications/create_service_spec.rb13
-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/external_pull_requests/create_pipeline_service_spec.rb72
-rw-r--r--spec/services/git/branch_push_service_spec.rb14
-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/merge_requests/create_service_spec.rb8
-rw-r--r--spec/services/merge_requests/push_options_handler_service_spec.rb145
-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/milestones/find_or_create_service_spec.rb82
-rw-r--r--spec/services/milestones/transfer_service_spec.rb122
-rw-r--r--spec/services/notification_service_spec.rb156
-rw-r--r--spec/services/projects/create_service_spec.rb68
-rw-r--r--spec/services/projects/forks_count_service_spec.rb14
-rw-r--r--spec/services/projects/lfs_pointers/lfs_link_service_spec.rb18
-rw-r--r--spec/services/projects/open_issues_count_service_spec.rb13
-rw-r--r--spec/services/projects/open_merge_requests_count_service_spec.rb11
-rw-r--r--spec/services/projects/transfer_service_spec.rb13
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb74
-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/services/users/keys_count_service_spec.rb44
-rw-r--r--spec/services/web_hook_service_spec.rb33
-rw-r--r--spec/spec_helper.rb7
-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/drag_to_helper.rb19
-rw-r--r--spec/support/helpers/graphql_helpers.rb18
-rw-r--r--spec/support/helpers/project_forks_helper.rb2
-rw-r--r--spec/support/helpers/query_recorder.rb5
-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.rb8
-rw-r--r--spec/support/helpers/test_env.rb1
-rw-r--r--spec/support/helpers/wait_for_requests.rb4
-rw-r--r--spec/support/helpers/workhorse_helpers.rb31
-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_contexts/finders/group_projects_finder_shared_contexts.rb3
-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/cycle_analytics_stage_examples.rb74
-rw-r--r--spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb2
-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/quick_actions/issue/move_quick_action_shared_examples.rb49
-rw-r--r--spec/support/shared_examples/requests/api/discussions.rb54
-rw-r--r--spec/support/shared_examples/requests/api/pipelines/visibility_table_examples.rb235
-rw-r--r--spec/support/shared_examples/requests/rack_attack_shared_examples.rb221
-rw-r--r--spec/support/shared_examples/services/count_service_shared_examples.rb54
-rw-r--r--spec/support/shared_examples/services/notification_service_shared_examples.rb44
-rw-r--r--spec/tasks/gitlab/gitaly_rake_spec.rb4
-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/validators/addressable_url_validator_spec.rb37
-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/layouts/_head.html.haml_spec.rb20
-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/update_external_pull_requests_worker_spec.rb54
-rw-r--r--spec/workers/upload_checksum_worker_spec.rb2
-rw-r--r--tmp/.gitignore5
-rw-r--r--vendor/licenses.csv2
-rw-r--r--yarn.lock179
1968 files changed, 27823 insertions, 12336 deletions
diff --git a/.dockerignore b/.dockerignore
index b8e239e4049..4f5b33de167 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -4,6 +4,7 @@
# the files we need to build the QA image are in these folders.
# Following are the files we need:
# - ./config/initializers/0_inject_enterprise_edition_module.rb
+# - ./ee/app/models/license.rb
# - ./lib/gitlab.rb
# - ./qa/
# - ./INSTALLATION_TYPE
@@ -23,7 +24,14 @@
/db/
/doc/
/docker/
-/ee/
+/ee/bin/
+/ee/changelogs/
+/ee/config/
+/ee/db/
+/ee/fixtures/
+/ee/lib/
+/ee/locale/
+/ee/spec/
/fixtures/
/templates/
/lint/
diff --git a/.gitignore b/.gitignore
index 104c6930050..fcbb4c352a9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -61,11 +61,9 @@ eslint-report.html
/shared/artifacts/
/rails_best_practices_output.html
/tags
-/tmp/*
/vendor/bundle/*
/vendor/gitaly-ruby
/builds*
-/shared/*
/.gitlab_workhorse_secret
/webpack-report/
/knapsack/
@@ -73,7 +71,6 @@ eslint-report.html
/locale/**/LC_MESSAGES
/locale/**/*.time_stamp
/.rspec
-/plugins/*
/.gitlab_pages_secret
/.gitlab_smime_key
/.gitlab_smime_cert
@@ -81,4 +78,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 f926cbc2939..b3b6beb1068 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,5 +1,16 @@
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"
+stages:
+ - build
+ - prepare
+ - quick-test
+ - test
+ - review-prepare
+ - review
+ - qa
+ - post-test
+ - pages
+
variables:
RAILS_ENV: "test"
NODE_ENV: "test"
@@ -8,28 +19,15 @@ variables:
GIT_SUBMODULE_STRATEGY: "none"
GET_SOURCES_ATTEMPTS: "3"
KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/rspec_report-master.json
+ EE_KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/rspec_report-master-ee.json
FLAKY_RSPEC_SUITE_REPORT_PATH: rspec_flaky/report-suite.json
BUILD_ASSETS_IMAGE: "false"
-
-before_script:
- - date
- - source scripts/utils.sh
- - source scripts/prepare_build.sh
- - date
+ ES_JAVA_OPTS: "-Xms256m -Xmx256m"
+ ELASTIC_URL: "http://elastic:changeme@docker.elastic.co-elasticsearch-elasticsearch:9200"
after_script:
- date
-stages:
- - build
- - prepare
- - merge
- - test
- - review
- - qa
- - post-test
- - pages
-
include:
- local: .gitlab/ci/global.gitlab-ci.yml
- local: .gitlab/ci/cng.gitlab-ci.yml
@@ -44,3 +42,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/CODEOWNERS b/.gitlab/CODEOWNERS
index dae3c349ff4..0f2dd081e9e 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -9,12 +9,13 @@
app/assets/ @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter
*.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter
-# Maintainers from the Database team should review changes in `db/`
-db/ @gl-database
-lib/gitlab/background_migration/ @gl-database
-lib/gitlab/database/ @gl-database
-lib/gitlab/sql/ @gl-database
-/ee/db/ @gl-database
+# Database maintainers should review changes in `db/`
+db/ @gitlab-org/maintainers/database
+lib/gitlab/background_migration/ @gitlab-org/maintainers/database
+lib/gitlab/database/ @gitlab-org/maintainers/database
+lib/gitlab/sql/ @gitlab-org/maintainers/database
+lib/gitlab/github_import/ @gitlab-org/maintainers/database
+/ee/db/ @gitlab-org/maintainers/database
# Feature specific owners
/ee/lib/gitlab/code_owners/ @reprazent
diff --git a/.gitlab/ci/cng.gitlab-ci.yml b/.gitlab/ci/cng.gitlab-ci.yml
index d624e8d09f6..a43d3694103 100644
--- a/.gitlab/ci/cng.gitlab-ci.yml
+++ b/.gitlab/ci/cng.gitlab-ci.yml
@@ -1,16 +1,15 @@
cloud-native-image:
image: ruby:2.6-alpine
- before_script: []
dependencies: []
stage: post-test
allow_failure: true
variables:
GIT_DEPTH: "1"
- cache: {}
when: manual
script:
- install_gitlab_gem
- CNG_PROJECT_PATH="gitlab-org/build/CNG" BUILD_TRIGGER_TOKEN=$CI_JOB_TOKEN ./scripts/trigger-build cng
only:
- - tags@gitlab-org/gitlab-ce
- - tags@gitlab-org/gitlab-ee
+ refs:
+ - tags@gitlab-org/gitlab-ce
+ - tags@gitlab-org/gitlab-ee
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml
index 39ae62a43c9..3f29adddf73 100644
--- a/.gitlab/ci/docs.gitlab-ci.yml
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -1,24 +1,34 @@
-.review-docs: &review-docs
- extends: .single-script-job-dedicated-runner
+.review-docs:
+ extends:
+ - .default-tags
+ - .default-retry
+ image: ruby:2.6-alpine
+ stage: review
+ dependencies: []
variables:
- SCRIPT_NAME: trigger-build-docs
+ GIT_STRATEGY: none
environment:
name: review-docs/$CI_COMMIT_REF_SLUG
# DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are CI variables
# Discussion: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14236/diffs#note_40140693
url: http://$CI_ENVIRONMENT_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
on_stop: review-docs-cleanup
+ before_script:
+ # We don't clone the repo by using GIT_STRATEGY: none and only download the
+ # single script we need here so it's much faster than cloning.
+ - apk add --update openssl
+ - wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/trigger-build-docs
+ - chmod 755 trigger-build-docs
# Trigger a manual docs build in gitlab-docs only on non docs-only branches.
# Useful to preview the docs changes live.
review-docs-deploy-manual:
extends:
- .review-docs
- - .no-docs-and-no-qa
- stage: review
+ - .except-docs-qa
script:
- gem install gitlab --no-document
- - ./$SCRIPT_NAME deploy
+ - ./trigger-build-docs deploy
when: manual
only:
- branches@gitlab-org/gitlab-ce
@@ -27,50 +37,50 @@ review-docs-deploy-manual:
# Always trigger a docs build in gitlab-docs only on docs-only branches.
# Useful to preview the docs changes live.
review-docs-deploy:
- <<: *review-docs
- stage: review
+ extends:
+ - .review-docs
+ - .except-qa
script:
- gem install gitlab --no-document
- - ./$SCRIPT_NAME deploy
+ - ./trigger-build-docs deploy
only:
- /(^docs[\/-].+|.+-docs$)/@gitlab-org/gitlab-ce
- /(^docs[\/-].+|.+-docs$)/@gitlab-org/gitlab-ee
- except:
- - /(^qa[\/-].*|.*-qa$)/
# Cleanup remote environment of gitlab-docs
review-docs-cleanup:
- <<: *review-docs
- stage: review
+ extends:
+ - .review-docs
+ - .except-qa
environment:
name: review-docs/$CI_COMMIT_REF_SLUG
action: stop
script:
- gem install gitlab --no-document
- - ./$SCRIPT_NAME cleanup
+ - ./trigger-build-docs cleanup
when: manual
only:
- branches@gitlab-org/gitlab-ce
- branches@gitlab-org/gitlab-ee
docs lint:
- extends: .dedicated-runner
+ extends:
+ - .default-tags
+ - .default-retry
+ - .except-qa
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-docs-lint"
stage: test
- cache: {}
dependencies: []
- before_script: []
script:
- scripts/lint-doc.sh
+ # Lint Markdown
+ - markdownlint --config .markdownlint.json 'doc/**/*.md'
+ # Prepare docs for build
- mv doc/ /tmp/gitlab-docs/content/$DOCS_GITLAB_REPO_SUFFIX
- cd /tmp/gitlab-docs
- # Lint Markdown
- - bundle exec mdl content/$DOCS_GITLAB_REPO_SUFFIX -c $CI_PROJECT_DIR/.mdlrc
# Build HTML from Markdown
- bundle exec nanoc
# Check the internal links
- bundle exec nanoc check internal_links
# Check the internal anchor links
- bundle exec nanoc check internal_anchors
- except:
- - /(^qa[\/-].*|.*-qa$)/
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 5c3278fcf53..a20215694c0 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -1,23 +1,21 @@
-.assets-compile-cache: &assets-compile-cache
+.assets-compile-cache:
cache:
- key: "assets-compile:vendor_ruby:.yarn-cache:tmp_cache_assets_sprockets:v6"
paths:
- vendor/ruby/
- .yarn-cache/
- tmp/cache/assets/sprockets
-.use-pg: &use-pg
- services:
- - name: postgres:9.6.14
- command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- - name: redis:alpine
-
.gitlab:assets:compile-metadata:
- <<: *assets-compile-cache
- extends: .dedicated-no-docs-pull-cache-job
+ extends:
+ - .default-tags
+ - .default-retry
+ - .assets-compile-cache
+ - .default-before_script
+ - .except-docs
image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-git-2.22-chrome-73.0-node-12.x-yarn-1.16-graphicsmagick-1.3.33-docker-18.06.1
- dependencies:
- - setup-test-env
+ stage: test
+ dependencies: ["setup-test-env"]
+ needs: ["setup-test-env"]
services:
- docker:19.03.0-dind
variables:
@@ -30,6 +28,14 @@
NODE_OPTIONS: --max_old_space_size=3584
DOCKER_DRIVER: overlay2
DOCKER_HOST: tcp://docker:2375
+ cache:
+ key: "assets-compile:production:vendor_ruby:.yarn-cache:tmp_cache_assets_sprockets:v6"
+ artifacts:
+ name: webpack-report
+ expire_in: 31d
+ paths:
+ - webpack-report/
+ - public/assets/
script:
- node --version
- retry yarn install --frozen-lockfile --production --cache-folder .yarn-cache --prefer-offline
@@ -38,47 +44,41 @@
- time scripts/build_assets_image
- scripts/clean-old-cached-assets
- rm -f /etc/apt/sources.list.d/google*.list # We don't need to update Chrome here
- # Play dependent manual jobs
- - install_api_client_dependencies_with_apt
- - play_job "review-build-cng" || true # this job might not exist so ignore the failure if it cannot be played
- - play_job "schedule:review-build-cng" || true # this job might not exist so ignore the failure if it cannot be played
- artifacts:
- name: webpack-report
- expire_in: 31d
- paths:
- - webpack-report/
- - public/assets/
only:
- /.+/@gitlab-org/gitlab-ce
- /.+/@gitlab-org/gitlab-ee
- /.+/@gitlab/gitlabhq
- /.+/@gitlab/gitlab-ee
tags:
- - docker
- gitlab-org
+ - docker
gitlab:assets:compile:
extends: .gitlab:assets:compile-metadata
+ only:
+ refs:
+ - master@gitlab-org/gitlab-ce
+ - master@gitlab-org/gitlab-ee
cache:
policy: pull-push
- only:
- - master@gitlab-org/gitlab-ce
- - master@gitlab-org/gitlab-ee
gitlab:assets:compile pull-cache:
extends: .gitlab:assets:compile-metadata
- cache:
- policy: pull
except:
refs:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- /(^docs[\/-].+|.+-docs$)/
+ cache:
+ policy: pull
.compile-assets-metadata:
- extends: .dedicated-runner
- <<: *use-pg
- <<: *assets-compile-cache
+ extends:
+ - .default-tags
+ - .default-retry
+ - .assets-compile-cache
+ - .default-before_script
+ - .use-pg
stage: prepare
script:
- node --version
@@ -89,6 +89,8 @@ gitlab:assets:compile pull-cache:
variables:
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
+ cache:
+ key: "assets-compile:test:vendor_ruby:.yarn-cache:tmp_cache_assets_sprockets:v6"
artifacts:
expire_in: 7d
paths:
@@ -96,30 +98,34 @@ gitlab:assets:compile pull-cache:
- public/assets
compile-assets:
- extends: .compile-assets-metadata
+ extends:
+ - .compile-assets-metadata
+ only:
+ refs:
+ - master@gitlab-org/gitlab-ce
+ - master@gitlab-org/gitlab-ee
cache:
policy: pull-push
- only:
- - master@gitlab-org/gitlab-ce
- - master@gitlab-org/gitlab-ee
compile-assets pull-cache:
extends: .compile-assets-metadata
- cache:
- policy: pull
except:
refs:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- /(^docs[\/-].+|.+-docs$)/
+ cache:
+ policy: pull
karma:
- extends: .dedicated-no-docs-pull-cache-job
- <<: *use-pg
- dependencies:
- - compile-assets
- - compile-assets pull-cache
- - setup-test-env
+ extends:
+ - .default-tags
+ - .default-retry
+ - .default-cache
+ - .default-before_script
+ - .use-pg
+ - .except-docs
+ dependencies: ["compile-assets", "compile-assets pull-cache", "setup-test-env"]
variables:
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
@@ -142,12 +148,14 @@ karma:
junit: junit_karma.xml
jest:
- extends: .dedicated-no-docs-and-no-qa-pull-cache-job
- <<: *use-pg
- dependencies:
- - compile-assets
- - compile-assets pull-cache
- - setup-test-env
+ extends:
+ - .default-tags
+ - .default-retry
+ - .default-cache
+ - .default-before_script
+ - .use-pg
+ - .except-docs-qa
+ dependencies: ["compile-assets", "compile-assets pull-cache", "setup-test-env"]
script:
- scripts/gitaly-test-spawn
- date
@@ -170,36 +178,42 @@ jest:
- tmp/jest/jest/
policy: pull-push
-qa:internal:
- extends: .dedicated-no-docs-no-db-pull-cache-job
- services: []
- script:
+.qa:
+ extends:
+ - .default-tags
+ - .default-retry
+ - .default-cache
+ - .except-docs
+ dependencies: []
+ stage: test
+ variables:
+ SETUP_DB: "false"
+ before_script:
- cd qa/
- bundle install
+
+qa:internal:
+ extends: .qa
+ script:
- bundle exec rspec
- dependencies:
- - setup-test-env
qa:selectors:
- extends: .dedicated-no-docs-no-db-pull-cache-job
- services: []
+ extends: .qa
script:
- - cd qa/
- - bundle install
- bundle exec bin/qa Test::Sanity::Selectors
- dependencies:
- - setup-test-env
-.qa-frontend-node: &qa-frontend-node
- extends: .dedicated-no-docs-no-db-pull-cache-job
- stage: test
+.qa-frontend-node:
+ extends:
+ - .default-tags
+ - .default-retry
+ - .default-cache
+ - .except-docs
+ dependencies: []
cache:
key: "$CI_JOB_NAME"
paths:
- .yarn-cache/
policy: pull-push
- dependencies: []
- before_script: []
script:
- date
- yarn install --frozen-lockfile --cache-folder .yarn-cache --prefer-offline
@@ -207,44 +221,14 @@ qa:selectors:
- yarn run webpack-prod
qa-frontend-node:8:
- <<: *qa-frontend-node
+ extends: .qa-frontend-node
image: node:carbon
qa-frontend-node:10:
- <<: *qa-frontend-node
+ extends: .qa-frontend-node
image: node:dubnium
qa-frontend-node:latest:
- <<: *qa-frontend-node
+ extends: .qa-frontend-node
image: node:latest
allow_failure: true
-
-lint:javascript:report:
- extends: .dedicated-no-docs-no-db-pull-cache-job
- stage: post-test
- dependencies: []
- before_script: []
- script:
- - date
- - yarn run eslint-report || true # ignore exit code
- artifacts:
- name: eslint-report
- expire_in: 31d
- paths:
- - eslint-report.html
-
-jsdoc:
- extends: .dedicated-no-docs-no-db-pull-cache-job
- stage: post-test
- dependencies:
- - compile-assets
- - compile-assets pull-cache
- before_script: []
- script:
- - date
- - yarn run jsdoc || true # ignore exit code
- artifacts:
- name: jsdoc
- expire_in: 31d
- paths:
- - jsdoc/
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index 56fe9739d5f..04135447ca4 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -1,83 +1,56 @@
-.dedicated-runner:
+.default-tags:
+ tags:
+ - gitlab-org
+
+.default-retry:
retry:
max: 2 # This is confusing but this means "3 runs at max".
when:
- unknown_failure
- api_failure
- runner_system_failure
- tags:
- - gitlab-org
-
-.default-cache: &default-cache
- key: "debian-stretch-ruby-2.6.3-node-12.x"
- paths:
- - vendor/ruby
- - .yarn-cache/
- - vendor/gitaly-ruby
-.dedicated-runner-default-cache:
- extends: .dedicated-runner
- cache:
- <<: *default-cache
+.default-before_script:
+ before_script:
+ - date
+ - source scripts/utils.sh
+ - source scripts/prepare_build.sh
+ - date
# Jobs that only need to pull cache
-.dedicated-pull-cache-job:
- extends: .dedicated-runner
+.default-cache:
cache:
- <<: *default-cache
+ key: "debian-stretch-ruby-2.6.3-node-12.x"
+ paths:
+ - vendor/ruby
+ - .yarn-cache/
+ - vendor/gitaly-ruby
policy: pull
- stage: test
-.no-docs:
+.except-docs:
except:
refs:
- /(^docs[\/-].+|.+-docs$)/
-.no-docs-and-no-qa:
+.except-qa:
except:
refs:
- - /(^docs[\/-].+|.+-docs$)/
- /(^qa[\/-].*|.*-qa$)/
-.dedicated-no-docs-pull-cache-job:
- extends:
- - .dedicated-pull-cache-job
- - .no-docs
-
-.dedicated-no-docs-and-no-qa-pull-cache-job:
- extends:
- - .dedicated-pull-cache-job
- - .no-docs-and-no-qa
-
-# Jobs that do not need a DB
-.dedicated-no-docs-no-db-pull-cache-job:
- extends: .dedicated-no-docs-pull-cache-job
- variables:
- SETUP_DB: "false"
-
-# Jobs that need a dedicated runner, with no cache
-.dedicated-no-docs:
- extends:
- - .dedicated-runner
- - .no-docs
+.except-docs-qa:
+ except:
+ refs:
+ - /(^docs[\/-].+|.+-docs$)/
+ - /(^qa[\/-].*|.*-qa$)/
-.single-script-job-dedicated-runner:
- extends: .dedicated-runner
- image: ruby:2.6-alpine
- stage: test
- cache: {}
- dependencies: []
- variables:
- GIT_STRATEGY: none
- before_script:
- # We don't clone the repo by using GIT_STRATEGY: none and only download the
- # single script we need here so it's much faster than cloning.
- - export SCRIPT_NAME="${SCRIPT_NAME:-$CI_JOB_NAME}"
- - apk add --update openssl
- - wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/$SCRIPT_NAME
- - chmod 755 $(basename $SCRIPT_NAME)
+.except-docs-qa-geo:
+ except:
+ refs:
+ - /(^docs[\/-].+|.+-docs$)/
+ - /(^qa[\/-].*|.*-qa$)/
+ - /(^geo[\/-].*|.*-geo$)/
-.review-only: &review-only
+.review-only:
only:
refs:
- branches@gitlab-org/gitlab-ce
@@ -88,3 +61,16 @@
- master
- /^\d+-\d+-auto-deploy-\d+$/
- /(^docs[\/-].+|.+-docs$)/
+
+.use-pg:
+ services:
+ - name: postgres:9.6.14
+ command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
+ - name: redis:alpine
+
+.use-pg-10:
+ 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-10-graphicsmagick-1.3.33"
+ services:
+ - name: postgres:10.9
+ command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
+ - name: redis:alpine
diff --git a/.gitlab/ci/memory.gitlab-ci.yml b/.gitlab/ci/memory.gitlab-ci.yml
index 9923732e587..1936933cca4 100644
--- a/.gitlab/ci/memory.gitlab-ci.yml
+++ b/.gitlab/ci/memory.gitlab-ci.yml
@@ -1,5 +1,12 @@
memory-static:
- extends: .dedicated-no-docs-no-db-pull-cache-job
+ extends:
+ - .default-tags
+ - .default-retry
+ - .default-cache
+ - .default-before_script
+ - .except-docs
+ variables:
+ SETUP_DB: "false"
script:
# Uses two different reports from the 'derailed_benchmars' gem.
@@ -23,7 +30,13 @@ memory-static:
# The application is booted in `production` environment.
# All tests are run without a webserver (directly using Rack::Mock by default).
memory-on-boot:
- extends: .rspec-metadata-pg-10
+ extends:
+ - .default-tags
+ - .default-retry
+ - .default-cache
+ - .default-before_script
+ - .use-pg-10
+ - .except-docs-qa
variables:
NODE_ENV: "production"
RAILS_ENV: "production"
diff --git a/.gitlab/ci/pages.gitlab-ci.yml b/.gitlab/ci/pages.gitlab-ci.yml
index f7b18b809b4..2de09753cca 100644
--- a/.gitlab/ci/pages.gitlab-ci.yml
+++ b/.gitlab/ci/pages.gitlab-ci.yml
@@ -1,26 +1,23 @@
pages:
- extends: .dedicated-no-docs-no-db-pull-cache-job
- before_script: []
+ extends:
+ - .default-tags
+ - .default-retry
+ - .default-cache
+ - .except-docs
+ only:
+ refs:
+ - 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
- only:
- - master@gitlab-org/gitlab-ce
- - master@gitlab-org/gitlab-ee
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
index 144e5392e55..9c021b23db6 100644
--- a/.gitlab/ci/qa.gitlab-ci.yml
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -1,9 +1,7 @@
.package-and-qa-base:
image: ruby:2.6-alpine
- stage: review # So even if review-deploy failed we can still run this
- before_script: []
+ stage: qa
dependencies: []
- cache: {}
variables:
GIT_DEPTH: "1"
retry: 0
@@ -12,18 +10,33 @@
- install_gitlab_gem
- ./scripts/trigger-build omnibus
only:
- - branches@gitlab-org/gitlab-ce
- - branches@gitlab-org/gitlab-ee
+ refs:
+ - branches@gitlab-org/gitlab-ce
+ - branches@gitlab-org/gitlab-ee
package-and-qa-manual:
- extends:
- - .package-and-qa-base
- - .no-docs-and-no-qa
+ extends: .package-and-qa-base
+ except:
+ refs:
+ - master
+ - /(^docs[\/-].+|.+-docs$)/
+ - /(^qa[\/-].*|.*-qa$)/
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/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index 50476b43dd6..878be25c39b 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -1,52 +1,31 @@
-.use-pg: &use-pg
- services:
- - name: postgres:9.6.14
- command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- - name: redis:alpine
-
-.use-pg-10: &use-pg-10
- services:
- - name: postgres:10.9
- command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- - name: redis:alpine
-
-.only-schedules-master: &only-schedules-master
+.only-schedules-master:
only:
- - schedules@gitlab-org/gitlab-ce
- - schedules@gitlab-org/gitlab-ee
- - master@gitlab-org/gitlab-ce
- - master@gitlab-org/gitlab-ee
- - master@gitlab/gitlabhq
- - master@gitlab/gitlab-ee
+ refs:
+ - schedules@gitlab-org/gitlab-ce
+ - schedules@gitlab-org/gitlab-ee
+ - master@gitlab-org/gitlab-ce
+ - master@gitlab-org/gitlab-ee
+ - master@gitlab/gitlabhq
+ - master@gitlab/gitlab-ee
-.gitlab-setup: &gitlab-setup
+.rake-exec:
extends:
- - .dedicated-no-docs-and-no-qa-pull-cache-job
- - .use-pg
+ - .default-tags
+ - .default-retry
+ - .default-cache
+ - .default-before_script
variables:
SETUP_DB: "false"
script:
- # Manually clone gitlab-test and only seed this project in
- # db/fixtures/development/04_project.rb thanks to SIZE=1 below
- - git clone https://gitlab.com/gitlab-org/gitlab-test.git
- /home/git/repositories/gitlab-org/gitlab-test.git
- - scripts/gitaly-test-spawn
- - force=yes SIZE=1 FIXTURE_PATH="db/fixtures/development" bundle exec rake gitlab:setup
- artifacts:
- when: on_failure
- expire_in: 1d
- paths:
- - log/development.log
-
-.rake-exec: &rake-exec
- extends: .dedicated-no-docs-no-db-pull-cache-job
- script:
- bundle exec rake $CI_JOB_NAME
-.rspec-metadata: &rspec-metadata
+.rspec-base:
extends:
- - .dedicated-pull-cache-job
- - .no-docs-and-no-qa
+ - .default-tags
+ - .default-retry
+ - .default-cache
+ - .default-before_script
+ - .except-docs-qa
stage: test
script:
- JOB_NAME=( $CI_JOB_NAME )
@@ -83,52 +62,24 @@
reports:
junit: junit_rspec.xml
-.rspec-metadata-pg: &rspec-metadata-pg
- <<: *rspec-metadata
- <<: *use-pg
-
-.rspec-metadata-pg-10: &rspec-metadata-pg-10
- <<: *rspec-metadata
- <<: *use-pg-10
- 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-10-graphicsmagick-1.3.33"
-
-# DB migration, rollback, and seed jobs
-.db-migrate-reset: &db-migrate-reset
- extends: .dedicated-no-docs-and-no-qa-pull-cache-job
- script:
- - bundle exec rake db:migrate:reset
- dependencies:
- - setup-test-env
+.rspec-base-pg:
+ extends:
+ - .rspec-base
+ - .use-pg
-.migration-paths: &migration-paths
- extends: .dedicated-no-docs-and-no-qa-pull-cache-job
- variables:
- SETUP_DB: "false"
- script:
- - git fetch https://gitlab.com/gitlab-org/gitlab-ce.git v11.11.0
- - git checkout -f FETCH_HEAD
- - sed -i "s/gem 'oj', '~> 2.17.4'//" Gemfile
- - sed -i "s/gem 'bootsnap', '~> 1.0.0'/gem 'bootsnap'/" Gemfile
- - bundle update google-protobuf grpc bootsnap
- - bundle install $BUNDLE_INSTALL_FLAGS
- - date
- - cp config/gitlab.yml.example config/gitlab.yml
- - bundle exec rake db:drop db:create db:schema:load db:seed_fu
- - date
- - git checkout -f $CI_COMMIT_SHA
- - bundle install $BUNDLE_INSTALL_FLAGS
- - date
- - . scripts/prepare_build.sh
- - date
- - bundle exec rake db:migrate
- dependencies:
- - setup-test-env
+.rspec-base-pg-10:
+ extends:
+ - .rspec-base
+ - .use-pg-10
setup-test-env:
extends:
- - .dedicated-runner-default-cache
- - .no-docs
+ - .default-tags
+ - .default-retry
+ - .default-cache
+ - .default-before_script
- .use-pg
+ - .except-docs
stage: prepare
script:
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
@@ -141,67 +92,72 @@ setup-test-env:
- vendor/gitaly-ruby
rspec unit pg:
- <<: *rspec-metadata-pg
+ extends: .rspec-base-pg
parallel: 20
rspec integration pg:
- <<: *rspec-metadata-pg
+ extends: .rspec-base-pg
parallel: 6
rspec system pg:
- <<: *rspec-metadata-pg
+ extends: .rspec-base-pg
parallel: 24
rspec unit pg-10:
- <<: *rspec-metadata-pg-10
- <<: *only-schedules-master
+ extends:
+ - .rspec-base-pg-10
+ - .only-schedules-master
parallel: 20
rspec integration pg-10:
- <<: *rspec-metadata-pg-10
- <<: *only-schedules-master
+ extends:
+ - .rspec-base-pg-10
+ - .only-schedules-master
parallel: 6
rspec system pg-10:
- <<: *rspec-metadata-pg-10
- <<: *only-schedules-master
+ extends:
+ - .rspec-base-pg-10
+ - .only-schedules-master
parallel: 24
rspec-fast-spec-helper:
- <<: *rspec-metadata-pg
+ extends: .rspec-base-pg
script:
- bundle exec rspec spec/fast_spec_helper.rb
-.rspec-quarantine: &rspec-quarantine
- <<: *only-schedules-master
+rspec quarantine pg:
+ extends:
+ - .default-before_script
+ - .rspec-base-pg
+ - .only-schedules-master
script:
- - export CACHE_CLASSES=true
+ - export NO_KNAPSACK=1 CACHE_CLASSES=true
- scripts/gitaly-test-spawn
- bin/rspec --color --format documentation --tag quarantine -- spec/
-
-rspec quarantine pg:
- <<: *rspec-metadata-pg
- <<: *rspec-quarantine
allow_failure: true
static-analysis:
- extends: .dedicated-no-docs-no-db-pull-cache-job
- dependencies:
- - compile-assets
- - compile-assets pull-cache
- - setup-test-env
+ extends:
+ - .default-tags
+ - .default-retry
+ - .default-cache
+ - .default-before_script
+ - .except-docs
+ dependencies: ["setup-test-env", "compile-assets", "compile-assets pull-cache"]
+ variables:
+ SETUP_DB: "false"
script:
- scripts/static-analysis
cache:
- key: "debian-stretch-ruby-2.6.3-node-12.x-and-rubocop"
+ key: "debian-stretch-ruby-2.6.3-and-rubocop"
paths:
- vendor/ruby
- - .yarn-cache/
- tmp/rubocop_cache
policy: pull-push
downtime_check:
- <<: *rake-exec
+ extends: .rake-exec
except:
refs:
- master
@@ -209,22 +165,22 @@ downtime_check:
- /^[\d-]+-stable(-ee)?$/
- /(^docs[\/-].+|.+-docs$)/
- /(^qa[\/-].*|.*-qa$)/
- dependencies:
- - setup-test-env
+ stage: test
+ dependencies: ["setup-test-env"]
+ needs: ["setup-test-env"]
ee_compat_check:
- <<: *rake-exec
+ extends: .rake-exec
dependencies: []
except:
refs:
- master
- tags
- - /[\d-]+-stable(-ee)?/
- - /^security-/
- branches@gitlab-org/gitlab-ee
- branches@gitlab/gitlab-ee
+ - /^[\d-]+-stable(-ee)?$/
- /(^docs[\/-].+|.+-docs$)/
- retry: 0
+ - /^security-/
artifacts:
name: "${CI_JOB_NAME}_${CI_COMIT_REF_NAME}_${CI_COMMIT_SHA}"
when: always
@@ -232,44 +188,116 @@ ee_compat_check:
paths:
- ee_compat_check/patches/*.patch
-db:migrate:reset-pg:
- <<: *db-migrate-reset
- <<: *use-pg
+# DB migration, rollback, and seed jobs
+db:migrate:reset:
+ extends:
+ - .default-tags
+ - .default-retry
+ - .default-cache
+ - .default-before_script
+ - .use-pg
+ - .except-docs-qa
+ stage: test
+ dependencies: ["setup-test-env"]
+ needs: ["setup-test-env"]
+ script:
+ - bundle exec rake db:migrate:reset
-db:check-schema-pg:
- <<: *db-migrate-reset
- <<: *use-pg
+db:check-schema:
+ extends:
+ - .default-tags
+ - .default-retry
+ - .default-cache
+ - .default-before_script
+ - .use-pg
+ - .except-docs-qa
+ stage: test
+ dependencies: ["setup-test-env"]
+ needs: ["setup-test-env"]
script:
- source scripts/schema_changed.sh
-migration:path-pg:
- <<: *migration-paths
- <<: *use-pg
+db:migrate-from-v11.11.0:
+ extends:
+ - .default-tags
+ - .default-retry
+ - .default-cache
+ - .default-before_script
+ - .use-pg
+ - .except-docs-qa
+ stage: test
+ dependencies: ["setup-test-env"]
+ needs: ["setup-test-env"]
+ variables:
+ SETUP_DB: "false"
+ script:
+ - git fetch https://gitlab.com/gitlab-org/gitlab-ce.git v11.11.0
+ - git checkout -f FETCH_HEAD
+ - sed -i "s/gem 'oj', '~> 2.17.4'//" Gemfile
+ - sed -i "s/gem 'bootsnap', '~> 1.0.0'/gem 'bootsnap'/" Gemfile
+ - bundle update google-protobuf grpc bootsnap
+ - bundle install $BUNDLE_INSTALL_FLAGS
+ - date
+ - cp config/gitlab.yml.example config/gitlab.yml
+ - bundle exec rake db:drop db:create db:schema:load db:seed_fu
+ - date
+ - git checkout -f $CI_COMMIT_SHA
+ - bundle install $BUNDLE_INSTALL_FLAGS
+ - date
+ - . scripts/prepare_build.sh
+ - date
+ - bundle exec rake db:migrate
-.db-rollback: &db-rollback
- extends: .dedicated-no-docs-and-no-qa-pull-cache-job
+db:rollback:
+ extends:
+ - .default-tags
+ - .default-retry
+ - .default-cache
+ - .default-before_script
+ - .use-pg
+ - .except-docs-qa
+ stage: test
+ dependencies: ["setup-test-env"]
+ needs: ["setup-test-env"]
script:
- bundle exec rake db:migrate VERSION=20180101160629
- bundle exec rake db:migrate SKIP_SCHEMA_VERSION_CHECK=true
- dependencies:
- - setup-test-env
-db:rollback-pg:
- <<: *db-rollback
- <<: *use-pg
-
-gitlab:setup-pg:
- <<: *gitlab-setup
- <<: *use-pg
- dependencies:
- - setup-test-env
+gitlab:setup:
+ extends:
+ - .default-tags
+ - .default-retry
+ - .default-cache
+ - .default-before_script
+ - .use-pg
+ - .except-docs-qa
+ stage: test
+ dependencies: ["setup-test-env"]
+ needs: ["setup-test-env"]
+ variables:
+ SETUP_DB: "false"
+ script:
+ # Manually clone gitlab-test and only seed this project in
+ # db/fixtures/development/04_project.rb thanks to SIZE=1 below
+ - git clone https://gitlab.com/gitlab-org/gitlab-test.git
+ /home/git/repositories/gitlab-org/gitlab-test.git
+ - scripts/gitaly-test-spawn
+ - force=yes SIZE=1 FIXTURE_PATH="db/fixtures/development" bundle exec rake gitlab:setup
+ artifacts:
+ when: on_failure
+ expire_in: 1d
+ paths:
+ - log/development.log
coverage:
# Don't include dedicated-no-docs-no-db-pull-cache-job here since we need to
# download artifacts from all the rspec jobs instead of from setup-test-env only
extends:
- - .dedicated-runner-default-cache
- - .no-docs-and-no-qa
+ - .default-tags
+ - .default-retry
+ - .default-cache
+ - .default-before_script
+ - .except-docs-qa
cache:
policy: pull
variables:
diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml
index ccd7d6ec84d..5622cd232ca 100644
--- a/.gitlab/ci/reports.gitlab-ci.yml
+++ b/.gitlab/ci/reports.gitlab-ci.yml
@@ -4,35 +4,36 @@ include:
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/DAST.gitlab-ci.yml
+.reports:
+ extends:
+ - .default-retry
+ - .except-docs
+
code_quality:
- extends: .dedicated-no-docs
- # gitlab-org runners set `privileged: false` but we need to have it set to true
- # since we're using Docker in Docker
- tags: []
- before_script: []
- cache: {}
+ extends: .reports
sast:
- extends: .dedicated-no-docs
- tags: []
- before_script: []
- cache: {}
+ extends: .reports
variables:
SAST_BRAKEMAN_LEVEL: 2
+ SAST_EXCLUDED_PATHS: qa,spec,doc
+ artifacts:
+ expire_in: 7 days
+ paths:
+ - gl-sast-report.json
dependency_scanning:
- extends: .dedicated-no-docs
- tags: []
- before_script: []
- cache: {}
+ extends: .reports
dast:
extends:
- - .dedicated-runner
+ - .reports
- .review-only
stage: qa
- dependencies:
- - review-deploy
+ dependencies: ["review-deploy"]
before_script:
- export DAST_WEBSITE="$(cat review_app_url.txt)"
- cache: {}
+ artifacts:
+ expire_in: 7 days
+ paths:
+ - gl-dast-report.json
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index beb049c0b3b..6f1505b5c0d 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -1,4 +1,4 @@
-.review-schedules-only: &review-schedules-only
+.review-schedules-only:
only:
refs:
- schedules@gitlab-org/gitlab-ce
@@ -11,69 +11,80 @@
- tags
- /(^docs[\/-].+|.+-docs$)/
-.review-base: &review-base
+.review-base:
extends:
- - .dedicated-runner
+ - .default-tags
+ - .default-retry
- .review-only
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
- cache: {}
dependencies: []
before_script:
- source scripts/utils.sh
-.review-docker: &review-docker
- <<: *review-base
+.review-docker:
+ extends:
+ - .default-tags
+ - .default-retry
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine
services:
- docker:19.03.0-dind
tags:
- gitlab-org
- docker
- variables: &review-docker-variables
+ variables:
DOCKER_DRIVER: overlay2
DOCKER_HOST: tcp://docker:2375
LATEST_QA_IMAGE: "gitlab/${CI_PROJECT_NAME}-qa:nightly"
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab/${CI_PROJECT_NAME}-qa:${CI_COMMIT_REF_SLUG}"
build-qa-image:
- <<: *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 ./
- echo "${CI_JOB_TOKEN}" | docker login --username gitlab-ci-token --password-stdin ${CI_REGISTRY}
- time docker push ${QA_IMAGE}
-.review-build-cng-base: &review-build-cng-base
+.review-build-cng-base:
image: ruby:2.6-alpine
- stage: test
- when: manual
+ stage: review-prepare
before_script:
- source scripts/utils.sh
- install_api_client_dependencies_with_apk
- install_gitlab_gem
dependencies: []
- cache: {}
script:
- BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng
review-build-cng:
- extends: .review-only
- <<: *review-build-cng-base
+ extends:
+ - .review-build-cng-base
+ - .review-only
+ needs: ["gitlab:assets:compile pull-cache"]
schedule:review-build-cng:
- <<: *review-schedules-only
- <<: *review-build-cng-base
+ extends:
+ - .review-build-cng-base
+ - .review-schedules-only
+ needs: ["gitlab:assets:compile"]
-.review-deploy-base: &review-deploy-base
- <<: *review-base
+review-deploy:
+ extends: .review-base
allow_failure: true
retry: 1
stage: review
+ needs: ["review-build-cng"]
variables:
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
GITLAB_HELM_CHART_REF: "master"
- environment: &review-environment
+ environment:
name: review/${CI_COMMIT_REF_NAME}
url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
on_stop: review-stop
@@ -98,50 +109,48 @@ schedule:review-build-cng:
expire_in: 2 days
when: always
-review-deploy:
- <<: *review-deploy-base
-
schedule:review-deploy:
- <<: *review-deploy-base
- <<: *review-schedules-only
+ extends:
+ - review-deploy
+ - .review-schedules-only
review-stop:
- extends:
- - .single-script-job-dedicated-runner
- - .review-only
- image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
- stage: review
+ extends: review-deploy
when: manual
- allow_failure: true
- variables:
- SCRIPT_NAME: review_apps/review-apps.sh
environment:
- <<: *review-environment
action: stop
- script:
+ variables:
+ GIT_STRATEGY: none
+ before_script:
+ # We don't clone the repo by using GIT_STRATEGY: none and only download the
+ # single script we need here so it's much faster than cloning.
+ - apk add --update openssl
+ - wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/review_apps/review-apps.sh
- wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/utils.sh
- source utils.sh
- - source $(basename $SCRIPT_NAME)
- - delete
+ - source review-apps.sh
+ script:
+ - delete_release
+ artifacts:
+ paths: []
-.review-qa-base: &review-qa-base
- <<: *review-docker
- allow_failure: true
+.review-qa-base:
+ extends:
+ - .review-docker
+ - .review-only
retry: 2
stage: qa
variables:
- <<: *review-docker-variables
QA_ARTIFACTS_DIR: "${CI_PROJECT_DIR}/qa"
QA_CAN_TEST_GIT_PROTOCOL_V2: "false"
+ QA_DEBUG: "true"
GITLAB_USERNAME: "root"
GITLAB_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
GITLAB_ADMIN_USERNAME: "root"
GITLAB_ADMIN_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}"
- QA_DEBUG: "true"
- dependencies:
- - review-deploy
+ dependencies: ["review-deploy"]
artifacts:
paths:
- ./qa/gitlab-qa-run-*
@@ -156,12 +165,13 @@ review-stop:
- gem install gitlab-qa --no-document ${GITLAB_QA_VERSION:+ --version ${GITLAB_QA_VERSION}}
review-qa-smoke:
- <<: *review-qa-base
+ extends: .review-qa-base
+ allow_failure: true
script:
- gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
review-qa-all:
- <<: *review-qa-base
+ extends: .review-qa-base
allow_failure: true
when: manual
parallel: 5
@@ -172,21 +182,16 @@ review-qa-all:
parallel-spec-reports:
extends:
- - .dedicated-runner
- - .no-docs
- dependencies:
- - review-qa-all
+ - .default-tags
+ - .except-docs
image: ruby:2.6-alpine
- services: []
- before_script: []
+ stage: post-test
+ dependencies: ["review-qa-all"]
variables:
- SETUP_DB: "false"
NEW_PARALLEL_SPECS_REPORT: qa/report-new.html
BASE_ARTIFACT_URL: "${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/file/qa/"
- stage: post-test
allow_failure: true
when: manual
- retry: 0
artifacts:
when: always
paths:
@@ -196,15 +201,15 @@ parallel-spec-reports:
junit: qa/gitlab-qa-run-*/**/rspec-*.xml
script:
- apk add --update build-base libxml2-dev libxslt-dev && rm -rf /var/cache/apk/*
- - gem install nokogiri
+ - gem install nokogiri --no-document
- cd qa/gitlab-qa-run-*/gitlab-*
- ARTIFACT_DIRS=$(pwd |rev| awk -F / '{print $1,$2}' | rev | sed s_\ _/_)
- - cd ../../..
+ - cd -
- '[[ -f $NEW_PARALLEL_SPECS_REPORT ]] || echo "{}" > ${NEW_PARALLEL_SPECS_REPORT}'
- scripts/merge-html-reports ${NEW_PARALLEL_SPECS_REPORT} ${BASE_ARTIFACT_URL}${ARTIFACT_DIRS} qa/gitlab-qa-run-*/**/rspec.htm
-.review-performance-base: &review-performance-base
- <<: *review-qa-base
+review-performance:
+ extends: .review-qa-base
allow_failure: true
before_script:
- export CI_ENVIRONMENT_URL="$(cat review_app_url.txt)"
@@ -222,18 +227,16 @@ parallel-spec-reports:
reports:
performance: performance.json
-review-performance:
- <<: *review-performance-base
-
schedule:review-performance:
- <<: *review-performance-base
- <<: *review-schedules-only
- dependencies:
- - schedule:review-deploy
+ extends:
+ - review-performance
+ - .review-schedules-only
+ dependencies: ["schedule:review-deploy"]
schedule:review-cleanup:
- <<: *review-base
- <<: *review-schedules-only
+ extends:
+ - .review-base
+ - .review-schedules-only
stage: build
allow_failure: true
environment:
@@ -246,11 +249,13 @@ schedule:review-cleanup:
- ruby -rrubygems scripts/review_apps/automated_cleanup.rb
danger-review:
- extends: .dedicated-pull-cache-job
+ extends:
+ - .default-tags
+ - .default-retry
+ - .default-cache
image: registry.gitlab.com/gitlab-org/gitlab-build-images:danger
stage: test
dependencies: []
- before_script: []
only:
variables:
- $DANGER_GITLAB_API_TOKEN
@@ -259,9 +264,8 @@ danger-review:
- master
- /^\d+-\d+-auto-deploy-\d+$/
- /^[\d-]+-stable(-ee)?$/
- variables:
- - $CI_COMMIT_REF_NAME =~ /^ce-to-ee-.*/
- - $CI_COMMIT_REF_NAME =~ /.*-stable(-ee)?-prepare-.*/
+ - /^ce-to-ee-.*/
+ - /.*-stable(-ee)?-prepare-.*/
script:
- git version
- node --version
diff --git a/.gitlab/ci/setup.gitlab-ci.yml b/.gitlab/ci/setup.gitlab-ci.yml
index c1fc3a893ca..cd7ca4d30bf 100644
--- a/.gitlab/ci/setup.gitlab-ci.yml
+++ b/.gitlab/ci/setup.gitlab-ci.yml
@@ -1,41 +1,44 @@
# Insurance in case a gem needed by one of our releases gets yanked from
# rubygems.org in the future.
cache gems:
- extends: .dedicated-no-docs-no-db-pull-cache-job
+ extends:
+ - .default-tags
+ - .default-retry
+ - .default-cache
+ - .default-before_script
+ - .except-docs
+ stage: test
+ dependencies: ["setup-test-env"]
+ needs: ["setup-test-env"]
+ variables:
+ SETUP_DB: "false"
script:
- bundle package --all --all-platforms
artifacts:
paths:
- vendor/cache
only:
- - master@gitlab-org/gitlab-ce
- - master@gitlab-org/gitlab-ee
- - tags
- dependencies:
- - setup-test-env
+ refs:
+ - master@gitlab-org/gitlab-ce
+ - master@gitlab-org/gitlab-ee
+ - tags
-gitlab_git_test:
+.minimal-job:
extends:
- - .dedicated-runner
- - .no-docs-and-no-qa
- variables:
- SETUP_DB: "false"
- before_script: []
+ - .default-tags
+ - .default-retry
+ - .except-docs-qa
dependencies: []
- cache: {}
+
+gitlab_git_test:
+ extends: .minimal-job
script:
- spec/support/prepare-gitlab-git-test-for-commit --check-for-changes
no_ee_check:
- extends:
- - .dedicated-runner
- - .no-docs-and-no-qa
- variables:
- SETUP_DB: "false"
- before_script: []
- dependencies: []
- cache: {}
+ extends: .minimal-job
script:
- scripts/no-ee-check
only:
- - /.+/@gitlab-org/gitlab-ce
+ refs:
+ - branches@gitlab-org/gitlab-ce
diff --git a/.gitlab/ci/test-metadata.gitlab-ci.yml b/.gitlab/ci/test-metadata.gitlab-ci.yml
index 4c97a4feb18..b9dac64957e 100644
--- a/.gitlab/ci/test-metadata.gitlab-ci.yml
+++ b/.gitlab/ci/test-metadata.gitlab-ci.yml
@@ -1,5 +1,4 @@
-.tests-metadata-state: &tests-metadata-state
- extends: .dedicated-runner
+.tests-metadata-state:
variables:
TESTS_METADATA_S3_BUCKET: "gitlab-ce-cache"
before_script:
@@ -14,7 +13,7 @@
retrieve-tests-metadata:
extends:
- .tests-metadata-state
- - .no-docs-and-no-qa
+ - .except-docs-qa
stage: prepare
cache:
key: tests_metadata
@@ -29,7 +28,7 @@ retrieve-tests-metadata:
- '[[ -f $FLAKY_RSPEC_SUITE_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_SUITE_REPORT_PATH}'
update-tests-metadata:
- <<: *tests-metadata-state
+ extends: .tests-metadata-state
stage: post-test
cache:
key: tests_metadata
@@ -49,25 +48,24 @@ update-tests-metadata:
- rm -f rspec_flaky/all_*.json rspec_flaky/new_*.json
- scripts/insert-rspec-profiling-data
only:
- - master@gitlab-org/gitlab-ce
- - master@gitlab-org/gitlab-ee
- - master@gitlab/gitlabhq
- - master@gitlab/gitlab-ee
+ refs:
+ - master@gitlab-org/gitlab-ce
+ - master@gitlab-org/gitlab-ee
+ - master@gitlab/gitlabhq
+ - master@gitlab/gitlab-ee
flaky-examples-check:
- extends: .dedicated-runner
+ extends:
+ - .default-tags
+ - .default-retry
image: ruby:2.6-alpine
- services: []
- before_script: []
+ stage: post-test
variables:
- SETUP_DB: "false"
- USE_BUNDLE_INSTALL: "false"
NEW_FLAKY_SPECS_REPORT: rspec_flaky/report-new.json
- stage: post-test
allow_failure: true
- retry: 0
only:
- - branches
+ refs:
+ - branches
except:
refs:
- master
diff --git a/.gitlab/ci/yaml.gitlab-ci.yml b/.gitlab/ci/yaml.gitlab-ci.yml
index b7aa418d8f7..dd61cb3f035 100644
--- a/.gitlab/ci/yaml.gitlab-ci.yml
+++ b/.gitlab/ci/yaml.gitlab-ci.yml
@@ -1,9 +1,11 @@
# Yamllint of *.yml for .gitlab-ci.yml.
# This uses rules from project root `.yamllint`.
lint-ci-gitlab:
- extends: .dedicated-runner
- before_script: []
- dependencies: []
+ extends:
+ - .default-tags
+ - .default-retry
+ - .except-docs
image: sdesbure/yamllint:latest
+ dependencies: []
script:
- yamllint .gitlab-ci.yml .gitlab/ci lib/gitlab/ci/templates changelogs
diff --git a/.gitlab/issue_templates/Feature proposal.md b/.gitlab/issue_templates/Feature proposal.md
index 2ba6b68a53e..5d93758595a 100644
--- a/.gitlab/issue_templates/Feature proposal.md
+++ b/.gitlab/issue_templates/Feature proposal.md
@@ -17,12 +17,13 @@ Personas can be found at https://about.gitlab.com/handbook/marketing/product-mar
### Permissions and Security
-<!-- What permissions are required to perform the described actions? Are they consistent with the existing permissions as documented for users, groups, and projects as appropriate? Is the proposed behavior consistent between the UI, API, and other access methods (e.g. email replies)? -->
+<!-- What permissions are required to perform the described actions? Are they consistent with the existing permissions as documented for users, groups, and projects as appropriate? Is the proposed behavior consistent between the UI, API, and other access methods (e.g. email replies)?-->
### Documentation
<!-- See the Feature Change Documentation Workflow https://docs.gitlab.com/ee/development/documentation/feature-change-workflow.html
-Add all known Documentation Requirements here, per https://docs.gitlab.com/ee/development/documentation/feature-change-workflow.html#documentation-requirements -->
+Add all known Documentation Requirements here, per https://docs.gitlab.com/ee/development/documentation/feature-change-workflow.html#documentation-requirements
+If this feature requires changing permissions, this document https://docs.gitlab.com/ee/user/permissions.html must be updated accordingly. -->
### Testing
diff --git a/.markdownlint.json b/.markdownlint.json
new file mode 100644
index 00000000000..2c40c0859f0
--- /dev/null
+++ b/.markdownlint.json
@@ -0,0 +1,31 @@
+{
+ "default": true,
+ "first-header-h1": true,
+ "header-style": {
+ "style": "atx"
+ },
+ "ul-style": {
+ "style": "dash"
+ },
+ "line-length": false,
+ "commands-show-output": false,
+ "no-duplicate-header": {
+ "allow_different_nesting": true
+ },
+ "no-trailing-punctuation": {
+ "punctuation": ".,;:!。,;:!?"
+ },
+ "ol-prefix": {
+ "style": "one"
+ },
+ "no-inline-html": false,
+ "hr-style": {
+ "style": "---"
+ },
+ "no-emphasis-as-heading": false,
+ "fenced-code-language": false,
+ "first-line-h1": false,
+ "code-block-style": {
+ "style": "fenced"
+ }
+}
diff --git a/.mdlrc b/.mdlrc
deleted file mode 100644
index 151c54f7d44..00000000000
--- a/.mdlrc
+++ /dev/null
@@ -1,7 +0,0 @@
-# This is the options file for mdl, configured in .gitlab/ci/docs.gitlab-ci.yml,
-# and related to the style file ./mdlrc.style
-
-# See https://github.com/markdownlint/markdownlint/blob/master/docs/configuration.md
-
-ignore_front_matter true
-style File.expand_path('.mdlrc.style', __dir__)
diff --git a/.mdlrc.style b/.mdlrc.style
deleted file mode 100644
index 85bc3aaa99b..00000000000
--- a/.mdlrc.style
+++ /dev/null
@@ -1,32 +0,0 @@
-# This is the style file for mdl, configured in .gitlab/ci/docs.gitlab-ci.yml,
-# and related to the options file ./mdlrc
-
-# See https://github.com/markdownlint/markdownlint/blob/master/docs/RULES.md
-# for more detailed information on the rules and styles.
-
-rule "MD001"
-rule "MD002"
-rule "MD003", :style => :atx
-rule "MD006"
-rule "MD010"
-rule "MD011"
-rule "MD012"
-rule "MD019"
-rule "MD022"
-rule "MD023"
-rule "MD025"
-rule "MD028"
-rule "MD029", :style => :one
-rule "MD030"
-# rule "MD032"
-rule "MD034"
-rule "MD037"
-rule "MD038"
-
-# Should not be used currently:
-
-# rule "MD004", :style => :dash # unordered list style - dash
-# False positives, see https://github.com/markdownlint/markdownlint/issues/261
-
-# rule "MD039" # Spaces inside link text
-# Crashes when link text has certain punctuation
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 b75c63e1f58..f24cbb6ce92 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -50,7 +50,9 @@ Style/FrozenStringLiteralComment:
- 'config/**/*'
- 'danger/**/*'
- 'db/**/*'
- - 'ee/**/*'
+ - 'ee/db/**/*'
+ - 'ee/spec/**/*'
+ - 'ee/lib/tasks/**/*'
- 'lib/tasks/**/*'
- 'qa/**/*'
- 'rubocop/**/*'
@@ -91,6 +93,7 @@ Naming/FileName:
- JSON
- LDAP
- SAML
+ - SSO
- IO
- HMAC
- QA
@@ -178,6 +181,9 @@ Gitlab/ModuleWithInstanceVariables:
Gitlab/HTTParty:
Enabled: true
+ Exclude:
+ - 'spec/**/*'
+ - 'ee/spec/**/*'
GitlabSecurity/PublicSend:
Enabled: true
@@ -211,3 +217,61 @@ ActiveRecordAssociationReload:
Exclude:
- 'spec/**/*'
- 'ee/spec/**/*'
+
+RSpec/FactoriesInMigrationSpecs:
+ Enabled: true
+ Include:
+ - 'spec/migrations/**/*.rb'
+ - 'ee/spec/migrations/**/*.rb'
+ - 'spec/lib/gitlab/background_migration/**/*.rb'
+ - 'ee/spec/lib/gitlab/background_migration/**/*.rb'
+
+Cop/IncludeActionViewContext:
+ Enabled: true
+ Exclude:
+ - 'spec/**/*'
+ - 'ee/spec/**/*'
+
+Cop/IncludeSidekiqWorker:
+ Enabled: true
+ Exclude:
+ - 'spec/**/*'
+ - 'ee/spec/**/*'
+
+Gitlab/Union:
+ Enabled: true
+ Exclude:
+ - 'spec/**/*'
+ - 'ee/spec/**/*'
+
+Cop/SidekiqOptionsQueue:
+ Enabled: true
+ Exclude:
+ - 'spec/**/*.rb'
+ - 'ee/spec/**/*.rb'
+
+Graphql/AuthorizeTypes:
+ Enabled: true
+ Exclude:
+ - 'spec/**/*.rb'
+ - 'ee/spec/**/*.rb'
+
+RSpec/EnvAssignment:
+ Enable: true
+ Include:
+ - 'spec/**/*.rb'
+ - 'ee/spec/**/*.rb'
+ Exclude:
+ - 'spec/**/fast_spec_helper.rb'
+ - 'ee/spec/**/fast_spec_helper.rb'
+ - 'spec/**/spec_helper.rb'
+ - 'ee/spec/**/spec_helper.rb'
+RSpec/BeSuccessMatcher:
+ Enabled: true
+ Include:
+ - 'spec/controllers/**/*'
+ - 'ee/spec/controllers/**/*'
+ - 'spec/support/shared_examples/controllers/**/*'
+ - 'ee/spec/support/shared_examples/controllers/**/*'
+ - 'spec/support/controllers/**/*'
+ - 'ee/spec/support/controllers/**/*'
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 3898206e3b5..f1f8ff6e862 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -218,7 +218,7 @@ Lint/UriEscapeUnescape:
- 'app/models/project_services/drone_ci_service.rb'
- 'spec/lib/google_api/auth_spec.rb'
- 'spec/requests/api/files_spec.rb'
- - 'spec/requests/api/internal_spec.rb'
+ - 'spec/requests/api/internal/base_spec.rb'
# Offense count: 1
# Configuration parameters: CheckForMethodsWithNoSideEffects.
@@ -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 a6c8eccf0b2..a432b091c7e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,68 @@
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)
+
+- Fix for embedded metrics undefined params. !31975
+- Fix "ERR value is not an integer or out of range" errors. !32126
+- Prevent duplicated trigger action button.
+
+### Performance (1 change)
+
+- Fix Gitaly N+1 calls with listing issues/MRs via API. !31938
+
+
## 12.2.0
### Security (4 changes, 1 of them is from the community)
@@ -578,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/Dangerfile b/Dangerfile
index 094d55e8652..228190cd530 100644
--- a/Dangerfile
+++ b/Dangerfile
@@ -1,23 +1,12 @@
# frozen_string_literal: true
+
+require_relative 'lib/gitlab_danger'
+
danger.import_plugin('danger/plugins/helper.rb')
danger.import_plugin('danger/plugins/roulette.rb')
unless helper.release_automation?
- danger.import_dangerfile(path: 'danger/metadata')
- danger.import_dangerfile(path: 'danger/changes_size')
- danger.import_dangerfile(path: 'danger/changelog')
- danger.import_dangerfile(path: 'danger/specs')
- danger.import_dangerfile(path: 'danger/gemfile')
- danger.import_dangerfile(path: 'danger/database')
- danger.import_dangerfile(path: 'danger/documentation')
- danger.import_dangerfile(path: 'danger/frozen_string')
- danger.import_dangerfile(path: 'danger/commit_messages')
- danger.import_dangerfile(path: 'danger/duplicate_yarn_dependencies')
- danger.import_dangerfile(path: 'danger/prettier')
- danger.import_dangerfile(path: 'danger/eslint')
- danger.import_dangerfile(path: 'danger/roulette')
- danger.import_dangerfile(path: 'danger/single_codebase')
- danger.import_dangerfile(path: 'danger/gitlab_ui_wg')
- danger.import_dangerfile(path: 'danger/ce_ee_vue_templates')
- danger.import_dangerfile(path: 'danger/only_documentation')
+ GitlabDanger.new(helper.gitlab_helper).rule_names.each do |file|
+ danger.import_dangerfile(path: File.join('danger', file))
+ end
end
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_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 943f9cbc4ec..27f9cd322bb 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-1.7.1
+1.8.0
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index ccfb75e5120..a13e7b9c87e 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-9.4.1
+10.0.0
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 3b6825376ad..7f6758ef97b 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-8.8.0
+8.10.0
diff --git a/Gemfile b/Gemfile
index e5023a6f67d..fdb30aeb187 100644
--- a/Gemfile
+++ b/Gemfile
@@ -83,7 +83,7 @@ gem 'grape-entity', '~> 0.7.1'
gem 'rack-cors', '~> 1.0.0', require: 'rack/cors'
# GraphQL API
-gem 'graphql', '~> 1.8.0'
+gem 'graphql', '~> 1.9.11'
gem 'graphiql-rails', '~> 1.4.10'
gem 'apollo_upload_server', '~> 2.0.0.beta3'
gem 'graphql-docs', '~> 1.6.0', group: [:development, :test]
@@ -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'
@@ -309,13 +311,14 @@ group :metrics do
gem 'influxdb', '~> 0.2', require: false
# Prometheus
- gem 'prometheus-client-mmap', '~> 0.9.8'
+ gem 'prometheus-client-mmap', '~> 0.9.9'
gem 'raindrops', '~> 0.18'
end
group :development do
gem 'foreman', '~> 0.84.0'
gem 'brakeman', '~> 4.2', require: false
+ gem 'danger', '~> 6.0', require: false
gem 'letter_opener_web', '~> 1.3.4'
gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
@@ -367,7 +370,6 @@ group :development, :test do
gem 'haml_lint', '~> 0.31.0', require: false
gem 'simplecov', '~> 0.16.1', require: false
gem 'bundler-audit', '~> 0.5.0', require: false
- gem 'mdl', '~> 0.5.0', require: false
gem 'benchmark-ips', '~> 2.3.0', require: false
@@ -399,7 +401,7 @@ gem 'mail_room', '~> 0.9.1'
gem 'email_reply_trimmer', '~> 0.1'
gem 'html2text'
-gem 'ruby-prof', '~> 0.17.0'
+gem 'ruby-prof', '~> 1.0.0'
gem 'rbtrace', '~> 0.4', require: false
gem 'memory_profiler', '~> 0.9', require: false
gem 'benchmark-memory', '~> 0.1', require: false
@@ -438,6 +440,7 @@ gem 'toml-rb', '~> 1.0.0', require: false
gem 'flipper', '~> 0.13.0'
gem 'flipper-active_record', '~> 0.13.0'
gem 'flipper-active_support_cache_store', '~> 0.13.0'
+gem 'unleash', '~> 0.1.5'
# Structured logging
gem 'lograge', '~> 0.5'
diff --git a/Gemfile.lock b/Gemfile.lock
index d9a32027f3c..d787b5c0569 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)
@@ -138,19 +140,25 @@ GEM
numerizer (~> 0.1.1)
chunky_png (1.3.5)
citrus (3.0.2)
+ claide (1.0.3)
+ claide-plugins (0.9.2)
+ cork
+ nap
+ open4 (~> 1.3)
coderay (1.1.2)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
+ colored2 (3.1.2)
commonmarker (0.17.13)
ruby-enum (~> 0.5)
concord (0.1.5)
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)
+ cork (0.3.0)
+ colored2 (~> 3.1)
crack (0.4.3)
safe_yaml (~> 1.0.0)
crass (1.0.4)
@@ -158,6 +166,19 @@ GEM
css_parser (1.5.0)
addressable
daemons (1.2.6)
+ danger (6.0.9)
+ claide (~> 1.0)
+ claide-plugins (>= 0.9.2)
+ colored2 (~> 3.1)
+ cork (~> 0.1)
+ faraday (~> 0.9)
+ faraday-http-cache (~> 2.0)
+ git (~> 1.5)
+ kramdown (~> 2.0)
+ kramdown-parser-gfm (~> 1.0)
+ no_proxy_fix
+ octokit (~> 4.7)
+ terminal-table (~> 1)
database_cleaner (1.7.0)
debug_inspector (0.0.3)
debugger-ruby_core_source (1.3.8)
@@ -227,6 +248,8 @@ GEM
railties (>= 3.0.0)
faraday (0.12.2)
multipart-post (>= 1.2, < 3)
+ faraday-http-cache (2.0.0)
+ faraday (~> 0.8)
faraday_middleware (0.12.2)
faraday (>= 0.7.4, < 1.0)
faraday_middleware-multi_json (0.0.6)
@@ -308,6 +331,7 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
+ git (1.5.0)
gitaly (1.58.0)
grpc (~> 1.0)
github-markup (1.7.0)
@@ -319,7 +343,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)
@@ -375,7 +401,7 @@ GEM
graphiql-rails (1.4.10)
railties
sprockets-rails
- graphql (1.8.1)
+ graphql (1.9.11)
graphql-docs (1.6.0)
commonmarker (~> 0.16)
escape_utils (~> 1.2)
@@ -444,8 +470,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)
@@ -475,7 +502,9 @@ GEM
kgio (2.11.2)
knapsack (1.17.0)
rake
- kramdown (1.17.0)
+ kramdown (2.1.0)
+ kramdown-parser-gfm (1.1.0)
+ kramdown (~> 2.0)
kubeclient (4.2.2)
http (~> 3.0)
recursive-open-struct (~> 1.0, >= 1.0.4)
@@ -511,10 +540,6 @@ GEM
mail_room (0.9.1)
marcel (0.3.3)
mimemagic (~> 0.3.2)
- mdl (0.5.0)
- kramdown (~> 1.12, >= 1.12.0)
- mixlib-cli (~> 1.7, >= 1.7.0)
- mixlib-config (~> 2.2, >= 2.2.1)
memoist (0.16.0)
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
@@ -528,21 +553,21 @@ GEM
mini_mime (1.0.1)
mini_portile2 (2.4.0)
minitest (5.11.3)
- mixlib-cli (1.7.0)
- mixlib-config (2.2.18)
- tomlrb
msgpack (1.3.0)
multi_json (1.13.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
+ murmurhash3 (0.1.6)
mustermann (1.0.3)
mustermann-grape (1.0.0)
mustermann (~> 1.0.0)
nakayoshi_fork (0.0.4)
+ nap (1.1.0)
net-ldap (0.16.0)
net-ssh (5.2.0)
netrc (0.11.0)
nio4r (2.3.1)
+ no_proxy_fix (0.1.2)
nokogiri (1.10.4)
mini_portile2 (~> 2.4.0)
nokogumbo (1.5.0)
@@ -619,6 +644,7 @@ GEM
addressable (~> 2.5)
omniauth (~> 1.3)
openid_connect (~> 1.1)
+ open4 (1.3.4)
openid_connect (1.1.6)
activemodel
attr_required (>= 1.0.0)
@@ -639,10 +665,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)
@@ -658,7 +680,7 @@ GEM
parser
unparser
procto (0.0.3)
- prometheus-client-mmap (0.9.8)
+ prometheus-client-mmap (0.9.9)
pry (0.11.3)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
@@ -836,7 +858,7 @@ GEM
i18n
ruby-fogbugz (0.2.1)
crack (~> 0.4)
- ruby-prof (0.17.0)
+ ruby-prof (1.0.0)
ruby-progressbar (1.10.1)
ruby-saml (1.7.2)
nokogiri (>= 1.5.10)
@@ -934,6 +956,8 @@ GEM
ffi
sysexits (1.2.0)
temple (0.8.0)
+ terminal-table (1.8.0)
+ unicode-display_width (~> 1.1, >= 1.1.1)
test-prof (0.2.5)
text (1.3.1)
thin (1.7.2)
@@ -950,7 +974,6 @@ GEM
parslet (~> 1.8.0)
toml-rb (1.0.0)
citrus (~> 3.0, > 3.0)
- tomlrb (1.2.8)
truncato (0.7.11)
htmlentities (~> 4.3.1)
nokogiri (>= 1.7.0, <= 2.0)
@@ -972,6 +995,8 @@ GEM
get_process_mem (~> 0)
unicorn (>= 4, < 6)
uniform_notifier (1.10.0)
+ unleash (0.1.5)
+ murmurhash3 (~> 0.1.6)
unparser (0.4.5)
abstract_type (~> 0.0.7)
adamantium (~> 0.2.0)
@@ -1035,6 +1060,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)
@@ -1061,6 +1087,7 @@ DEPENDENCIES
concurrent-ruby (~> 1.1)
connection_pool (~> 2.0)
creole (~> 0.5.0)
+ danger (~> 6.0)
database_cleaner (~> 1.7.0)
deckar01-task_list (= 2.2.0)
default_value_for (~> 3.2.0)
@@ -1103,7 +1130,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)
@@ -1115,7 +1143,7 @@ DEPENDENCIES
grape-path-helpers (~> 1.1)
grape_logging (~> 1.7)
graphiql-rails (~> 1.4.10)
- graphql (~> 1.8.0)
+ graphql (~> 1.9.11)
graphql-docs (~> 1.6.0)
grpc (~> 1.19.0)
haml_lint (~> 0.31.0)
@@ -1130,7 +1158,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)
@@ -1143,7 +1171,6 @@ DEPENDENCIES
lograge (~> 0.5)
loofah (~> 2.2)
mail_room (~> 0.9.1)
- mdl (~> 0.5.0)
memory_profiler (~> 0.9)
method_source (~> 0.8)
mimemagic (~> 0.3.2)
@@ -1174,10 +1201,9 @@ 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)
+ prometheus-client-mmap (~> 0.9.9)
pry-byebug (~> 3.5.1)
pry-rails (~> 0.3.4)
puma (~> 3.12)
@@ -1215,7 +1241,7 @@ DEPENDENCIES
rubocop-performance (~> 1.1.0)
rubocop-rspec (~> 1.22.1)
ruby-fogbugz (~> 0.2.1)
- ruby-prof (~> 0.17.0)
+ ruby-prof (~> 1.0.0)
ruby-progressbar
ruby_parser (~> 3.8)
rubyzip (~> 1.2.2)
@@ -1251,6 +1277,7 @@ DEPENDENCIES
unf (~> 0.1.4)
unicorn (~> 5.4.1)
unicorn-worker-killer (~> 0.4.4)
+ unleash (~> 0.1.5)
validates_hostname (~> 1.0.6)
version_sorter (~> 2.2.4)
vmstat (~> 2.3.0)
diff --git a/PHILOSOPHY.md b/PHILOSOPHY.md
index e966d88ef78..483063731d1 100644
--- a/PHILOSOPHY.md
+++ b/PHILOSOPHY.md
@@ -1 +1,4 @@
-This document is intended to communicate the product philosophy GitLab uses in creating GitLab Community Edition. The principles can be found in the [Product Section of the GitLab Handbook](https://about.gitlab.com/handbook/product/#product-at-gitlab). \ No newline at end of file
+To learn about the product philosophy GitLab the company uses in creating GitLab
+the product, visit our [Product Handbook page].
+
+[Product Handbook page]: https://about.gitlab.com/handbook/product/#product-at-gitlab
diff --git a/PROCESS.md b/PROCESS.md
index 22b68b0aaca..f0a82838f62 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -267,9 +267,7 @@ The two scenarios below can [bypass the exception request in the release process
When a bug is found:
1. Create an issue describing the problem in the most detailed way possible.
1. If possible, provide links to real examples and how to reproduce the problem.
-1. Label the issue properly, using the [team label](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#team-labels),
- the [subject label](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#subject-labels)
- and any other label that may apply in the specific case
+1. Label the issue properly, by respecting the [Partial triage level](https://about.gitlab.com/handbook/engineering/issue-triage/#partial-triage).
1. Notify the respective Engineering Manager to evaluate and apply the [Severity label](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#severity-labels) and [Priority label](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#priority-labels).
The counterpart Product Manager is included to weigh-in on prioritization as needed.
1. If the ~bug is **NOT** a regression:
diff --git a/README.md b/README.md
index 054e2d02461..bfc55f28279 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,15 @@
# GitLab
-## Test coverage
-
-- [![Ruby coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) Ruby
-- [![JavaScript coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=karma)](https://gitlab-org.gitlab.io/gitlab-ce/coverage-javascript) JavaScript
-
## Canonical source
The canonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/).
+The source of GitLab Enterprise Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ee).
+
+## Free trial
+
+You can request a free trial of GitLab Ultimate [on our website](https://about.gitlab.com/free-trial/).
+
## Open source software to collaborate on code
To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/).
@@ -103,7 +104,7 @@ For upgrading information please see our [update page](https://about.gitlab.com/
## Documentation
-All documentation can be found on [docs.gitlab.com/ce/](https://docs.gitlab.com/ce/).
+All documentation can be found on <https://docs.gitlab.com>.
## Getting help
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/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index 4180023b7db..f9a08f151c5 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -2,9 +2,9 @@
import $ from 'jquery';
import { GlButton } from '@gitlab/ui';
import { getMilestone } from 'ee_else_ce/boards/boards_util';
+import ListIssue from 'ee_else_ce/boards/models/issue';
import eventHub from '../eventhub';
import ProjectSelect from './project_select.vue';
-import ListIssue from '../models/issue';
import boardsStore from '../stores/boards_store';
export default {
@@ -54,6 +54,9 @@ export default {
const assignees = this.list.assignee ? [this.list.assignee] : [];
const milestone = getMilestone(this.list);
+ const { weightFeatureAvailable } = boardsStore;
+ const { weight } = weightFeatureAvailable ? boardsStore.state.currentBoard : {};
+
const issue = new ListIssue({
title: this.title,
labels,
@@ -61,6 +64,7 @@ export default {
assignees,
milestone,
project_id: this.selectedProject.id,
+ weight,
});
eventHub.$emit(`scroll-board-list-${this.list.id}`);
@@ -114,7 +118,7 @@ export default {
name="issue_title"
autocomplete="off"
/>
- <project-select v-if="groupId" :group-id="groupId" />
+ <project-select v-if="groupId" :group-id="groupId" :list="list" />
<div class="clearfix prepend-top-10">
<gl-button
ref="submit-button"
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue
index 7296426549a..ebb2f5b23e4 100644
--- a/app/assets/javascripts/boards/components/boards_selector.vue
+++ b/app/assets/javascripts/boards/components/boards_selector.vue
@@ -245,6 +245,7 @@ export default {
<div
v-if="!loading"
ref="content"
+ data-qa-selector="boards_dropdown_content"
class="dropdown-content flex-fill"
@scroll.passive="throttledSetScrollFade"
>
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
index e8d25e84be1..e5ebb887ce0 100644
--- a/app/assets/javascripts/boards/components/project_select.vue
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -6,6 +6,7 @@ import Icon from '~/vue_shared/components/icon.vue';
import { GlLoadingIcon } from '@gitlab/ui';
import eventHub from '../eventhub';
import Api from '../../api';
+import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
export default {
name: 'BoardProjectSelect',
@@ -19,6 +20,10 @@ export default {
required: true,
default: 0,
},
+ list: {
+ type: Object,
+ required: true,
+ },
},
data() {
return {
@@ -49,6 +54,12 @@ export default {
selectable: true,
data: (term, callback) => {
this.loading = true;
+ const additionalAttrs = {};
+
+ if (this.list.type && this.list.type !== 'backlog') {
+ additionalAttrs.min_access_level = featureAccessLevel.EVERYONE;
+ }
+
return Api.groupProjects(
this.groupId,
term,
@@ -56,6 +67,7 @@ export default {
with_issues_enabled: true,
with_shared: false,
include_subgroups: true,
+ ...additionalAttrs,
},
projects => {
this.loading = false;
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/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index 5f5c8044b49..1f213d5aaf2 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -14,6 +14,8 @@ import ClustersStore from './stores/clusters_store';
import Applications from './components/applications.vue';
import setupToggleButtons from '../toggle_buttons';
+const Environments = () => import('ee_component/clusters/components/environments.vue');
+
Vue.use(GlToast);
/**
@@ -44,6 +46,9 @@ export default class Clusters {
helpPath,
ingressHelpPath,
ingressDnsHelpPath,
+ environmentsHelpPath,
+ clustersHelpPath,
+ deployBoardsHelpPath,
clusterId,
} = document.querySelector('.js-edit-cluster-form').dataset;
@@ -52,7 +57,14 @@ export default class Clusters {
this.clusterBannerDismissedKey = `cluster_${this.clusterId}_banner_dismissed`;
this.store = new ClustersStore();
- this.store.setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath);
+ this.store.setHelpPaths(
+ helpPath,
+ ingressHelpPath,
+ ingressDnsHelpPath,
+ environmentsHelpPath,
+ clustersHelpPath,
+ deployBoardsHelpPath,
+ );
this.store.setManagePrometheusPath(managePrometheusPath);
this.store.updateStatus(clusterStatus);
this.store.updateStatusReason(clusterStatusReason);
@@ -95,11 +107,12 @@ export default class Clusters {
setupToggleButtons(toggleButtonsContainer);
}
this.initApplications(clusterType);
+ this.initEnvironments();
this.updateContainer(null, this.store.state.status, this.store.state.statusReason);
this.addListeners();
- if (statusPath) {
+ if (statusPath && !this.environments) {
this.initPolling();
}
}
@@ -131,6 +144,34 @@ export default class Clusters {
});
}
+ initEnvironments() {
+ const { store } = this;
+ const el = document.querySelector('#js-cluster-environments');
+
+ if (!el) {
+ return;
+ }
+
+ this.environments = new Vue({
+ el,
+ data() {
+ return {
+ state: store.state,
+ };
+ },
+ render(createElement) {
+ return createElement(Environments, {
+ props: {
+ environments: this.state.environments,
+ environmentsHelpPath: this.state.environmentsHelpPath,
+ clustersHelpPath: this.state.clustersHelpPath,
+ deployBoardsHelpPath: this.state.deployBoardsHelpPath,
+ },
+ });
+ },
+ });
+ }
+
static initDismissableCallout() {
const callout = document.querySelector('.js-cluster-security-warning');
PersistentUserCallout.factory(callout);
@@ -390,6 +431,10 @@ export default class Clusters {
this.poll.stop();
}
+ if (this.environments) {
+ this.environments.$destroy();
+ }
+
this.applications.$destroy();
}
}
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index 970f5a7b297..27959898fb7 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -397,7 +397,6 @@ export default {
</div>
</application-row>
<application-row
- v-if="isProjectCluster"
id="jupyter"
:logo-url="jupyterhubLogo"
:title="applications.jupyter.title"
@@ -459,7 +458,6 @@ export default {
</div>
</application-row>
<application-row
- v-if="isProjectCluster"
id="knative"
:logo-url="knativeLogo"
:title="applications.knative.title"
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index ada5a49e246..83533c88f69 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -32,6 +32,9 @@ export default class ClusterStore {
this.state = {
helpPath: null,
ingressHelpPath: null,
+ environmentsHelpPath: null,
+ clustersHelpPath: null,
+ deployBoardsHelpPath: null,
status: null,
rbac: false,
statusReason: null,
@@ -55,7 +58,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,
@@ -80,13 +83,24 @@ export default class ClusterStore {
updateFailed: false,
},
},
+ environments: [],
};
}
- setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath) {
+ setHelpPaths(
+ helpPath,
+ ingressHelpPath,
+ ingressDnsHelpPath,
+ environmentsHelpPath,
+ clustersHelpPath,
+ deployBoardsHelpPath,
+ ) {
this.state.helpPath = helpPath;
this.state.ingressHelpPath = ingressHelpPath;
this.state.ingressDnsHelpPath = ingressDnsHelpPath;
+ this.state.environmentsHelpPath = environmentsHelpPath;
+ this.state.clustersHelpPath = clustersHelpPath;
+ this.state.deployBoardsHelpPath = deployBoardsHelpPath;
}
setManagePrometheusPath(managePrometheusPath) {
@@ -191,4 +205,17 @@ export default class ClusterStore {
}
});
}
+
+ updateEnvironments(environments = []) {
+ this.state.environments = environments.map(environment => ({
+ name: environment.name,
+ project: environment.project,
+ environmentPath: environment.environment_path,
+ lastDeployment: environment.last_deployment,
+ rolloutStatus: {
+ instances: environment.rollout_status ? environment.rollout_status.instances : [],
+ },
+ updatedAt: environment.updatedAt,
+ }));
+ }
}
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_dropdown_mixin.js b/app/assets/javascripts/create_cluster/gke_cluster/components/gke_dropdown_mixin.js
index 5a3407693e5..5a3407693e5 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_dropdown_mixin.js
+++ b/app/assets/javascripts/create_cluster/gke_cluster/components/gke_dropdown_mixin.js
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue b/app/assets/javascripts/create_cluster/gke_cluster/components/gke_machine_type_dropdown.vue
index 83811ab489a..83811ab489a 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue
+++ b/app/assets/javascripts/create_cluster/gke_cluster/components/gke_machine_type_dropdown.vue
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue b/app/assets/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown.vue
index a2eb79af4f9..a2eb79af4f9 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue
+++ b/app/assets/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown.vue
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue b/app/assets/javascripts/create_cluster/gke_cluster/components/gke_zone_dropdown.vue
index fd5d5f86401..fd5d5f86401 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue
+++ b/app/assets/javascripts/create_cluster/gke_cluster/components/gke_zone_dropdown.vue
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/constants.js b/app/assets/javascripts/create_cluster/gke_cluster/constants.js
index 2a1c0819916..2a1c0819916 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/constants.js
+++ b/app/assets/javascripts/create_cluster/gke_cluster/constants.js
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/index.js b/app/assets/javascripts/create_cluster/gke_cluster/index.js
index 729b9404b64..729b9404b64 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/index.js
+++ b/app/assets/javascripts/create_cluster/gke_cluster/index.js
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/actions.js b/app/assets/javascripts/create_cluster/gke_cluster/store/actions.js
index f05ad7773a2..f05ad7773a2 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/actions.js
+++ b/app/assets/javascripts/create_cluster/gke_cluster/store/actions.js
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/getters.js b/app/assets/javascripts/create_cluster/gke_cluster/store/getters.js
index f9e2e2f74fb..f9e2e2f74fb 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/getters.js
+++ b/app/assets/javascripts/create_cluster/gke_cluster/store/getters.js
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/index.js b/app/assets/javascripts/create_cluster/gke_cluster/store/index.js
index 5f72060633e..5f72060633e 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/index.js
+++ b/app/assets/javascripts/create_cluster/gke_cluster/store/index.js
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/mutation_types.js b/app/assets/javascripts/create_cluster/gke_cluster/store/mutation_types.js
index 45a91efc2d9..45a91efc2d9 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/mutation_types.js
+++ b/app/assets/javascripts/create_cluster/gke_cluster/store/mutation_types.js
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/mutations.js b/app/assets/javascripts/create_cluster/gke_cluster/store/mutations.js
index 88a2c1b630d..88a2c1b630d 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/mutations.js
+++ b/app/assets/javascripts/create_cluster/gke_cluster/store/mutations.js
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/state.js b/app/assets/javascripts/create_cluster/gke_cluster/store/state.js
index 9f3c473d4bc..9f3c473d4bc 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/store/state.js
+++ b/app/assets/javascripts/create_cluster/gke_cluster/store/state.js
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/error_tracking/components/error_tracking_list.vue b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
index 43ae54133af..b1d568532a6 100644
--- a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
+++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
@@ -38,6 +38,10 @@ export default {
type: String,
required: true,
},
+ userCanEnableErrorTracking: {
+ type: Boolean,
+ required: true,
+ },
},
computed: {
...mapState(['errors', 'externalUrl', 'loading']),
@@ -111,14 +115,26 @@ export default {
</gl-table>
</div>
</div>
- <div v-else>
+ <div v-else-if="userCanEnableErrorTracking">
<gl-empty-state
:title="__('Get started with error tracking')"
- :description="__('Monitor your errors by integrating with Sentry')"
+ :description="__('Monitor your errors by integrating with Sentry.')"
:primary-button-text="__('Enable error tracking')"
:primary-button-link="enableErrorTrackingLink"
:svg-path="illustrationPath"
/>
</div>
+ <div v-else>
+ <gl-empty-state :title="__('Get started with error tracking')" :svg-path="illustrationPath">
+ <template #description>
+ <div>
+ <span>{{ __('Monitor your errors by integrating with Sentry.') }}</span>
+ <a href="/help/user/project/operations/error_tracking.html">
+ {{ __('More information') }}
+ </a>
+ </div>
+ </template>
+ </gl-empty-state>
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/error_tracking/index.js b/app/assets/javascripts/error_tracking/index.js
index 3d609448efe..073e2c8f1c7 100644
--- a/app/assets/javascripts/error_tracking/index.js
+++ b/app/assets/javascripts/error_tracking/index.js
@@ -14,9 +14,10 @@ export default () => {
render(createElement) {
const domEl = document.querySelector(this.$options.el);
const { indexPath, enableErrorTrackingLink, illustrationPath } = domEl.dataset;
- let { errorTrackingEnabled } = domEl.dataset;
+ let { errorTrackingEnabled, userCanEnableErrorTracking } = domEl.dataset;
errorTrackingEnabled = parseBoolean(errorTrackingEnabled);
+ userCanEnableErrorTracking = parseBoolean(userCanEnableErrorTracking);
return createElement('error-tracking-list', {
props: {
@@ -24,6 +25,7 @@ export default () => {
enableErrorTrackingLink,
errorTrackingEnabled,
illustrationPath,
+ userCanEnableErrorTracking,
},
});
},
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..c7d4d7c4b9b 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,
@@ -81,6 +83,11 @@ export default {
type: String,
required: true,
},
+ subscriptionsMoreMinutesUrl: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
computed: {
...mapState([
@@ -263,6 +270,7 @@ export default {
:quota-limit="job.runners.quota.limit"
:runners-path="runnerHelpUrl"
:project-path="projectPath"
+ :subscriptions-more-minutes-url="subscriptionsMoreMinutesUrl"
/>
<environments-block
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/components/log/line.vue b/app/assets/javascripts/jobs/components/log/line.vue
new file mode 100644
index 00000000000..86d0fcc3b74
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/log/line.vue
@@ -0,0 +1,28 @@
+<script>
+import LineNumber from './line_number.vue';
+
+export default {
+ components: {
+ LineNumber,
+ },
+ props: {
+ line: {
+ type: Object,
+ required: true,
+ },
+ path: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="line">
+ <line-number :line-number="line.lineNumber" :path="path" />
+ <span v-for="(content, i) in line.content" :key="i" class="line-text" :class="content.style">{{
+ content.text
+ }}</span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/log/line_header.vue b/app/assets/javascripts/jobs/components/log/line_header.vue
new file mode 100644
index 00000000000..4ec212d2333
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/log/line_header.vue
@@ -0,0 +1,45 @@
+<script>
+import Icon from '~/vue_shared/components/icon.vue';
+import LineNumber from './line_number.vue';
+
+export default {
+ components: {
+ Icon,
+ LineNumber,
+ },
+ props: {
+ line: {
+ type: Object,
+ required: true,
+ },
+ isClosed: {
+ type: Boolean,
+ required: true,
+ },
+ path: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ iconName() {
+ return this.isClosed ? 'angle-right' : 'angle-down';
+ },
+ },
+ methods: {
+ handleOnClick() {
+ this.$emit('toggleLine');
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="line collapsible-line" role="button" @click="handleOnClick">
+ <icon :name="iconName" class="arrow" />
+ <line-number :line-number="line.lineNumber" :path="path" />
+ <span v-for="(content, i) in line.content" :key="i" class="line-text" :class="content.style">{{
+ content.text
+ }}</span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/log/line_number.vue b/app/assets/javascripts/jobs/components/log/line_number.vue
new file mode 100644
index 00000000000..e06836e2e97
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/log/line_number.vue
@@ -0,0 +1,52 @@
+<script>
+import { GlLink } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlLink,
+ },
+ props: {
+ lineNumber: {
+ type: Number,
+ required: true,
+ },
+ path: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ /**
+ * Builds the url for each line number
+ *
+ * @returns {String}
+ */
+ buildLineNumber() {
+ return `${this.path}#${this.lineNumberId}`;
+ },
+ /**
+ * Array indexes start with 0, so we add 1
+ * to create the line number
+ *
+ * @returns {Number} the line number
+ */
+ parsedLineNumber() {
+ return this.lineNumber + 1;
+ },
+
+ /**
+ * Creates the anchor for each link
+ *
+ * @returns {String}
+ */
+ lineNumberId() {
+ return `L${this.parsedLineNumber}`;
+ },
+ },
+};
+</script>
+<template>
+ <gl-link :id="lineNumberId" class="line-number" :href="buildLineNumber">{{
+ parsedLineNumber
+ }}</gl-link>
+</template>
diff --git a/app/assets/javascripts/jobs/index.js b/app/assets/javascripts/jobs/index.js
index add7f9b710a..9c35534523e 100644
--- a/app/assets/javascripts/jobs/index.js
+++ b/app/assets/javascripts/jobs/index.js
@@ -15,6 +15,7 @@ export default () => {
runnerHelpUrl,
runnerSettingsUrl,
variablesSettingsUrl,
+ subscriptionsMoreMinutesUrl,
endpoint,
pagePath,
logState,
@@ -28,6 +29,7 @@ export default () => {
runnerHelpUrl,
runnerSettingsUrl,
variablesSettingsUrl,
+ subscriptionsMoreMinutesUrl,
endpoint,
pagePath,
logState,
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/lib/utils/axios_utils.js b/app/assets/javascripts/lib/utils/axios_utils.js
index 69159e2d741..37721cd030c 100644
--- a/app/assets/javascripts/lib/utils/axios_utils.js
+++ b/app/assets/javascripts/lib/utils/axios_utils.js
@@ -10,21 +10,18 @@ axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
axios.interceptors.request.use(config => {
window.activeVueResources = window.activeVueResources || 0;
window.activeVueResources += 1;
-
return config;
});
// Remove the global counter
axios.interceptors.response.use(
- config => {
+ response => {
window.activeVueResources -= 1;
-
- return config;
+ return response;
},
- e => {
+ err => {
window.activeVueResources -= 1;
-
- return Promise.reject(e);
+ return Promise.reject(err);
},
);
diff --git a/app/assets/javascripts/lib/utils/http_status.js b/app/assets/javascripts/lib/utils/http_status.js
index 37ad1676f7a..5e5d10883a3 100644
--- a/app/assets/javascripts/lib/utils/http_status.js
+++ b/app/assets/javascripts/lib/utils/http_status.js
@@ -19,6 +19,7 @@ const httpStatusCodes = {
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
+ GONE: 410,
UNPROCESSABLE_ENTITY: 422,
};
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 39f2097c174..0ddf40b0405 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -35,6 +35,7 @@ import initPerformanceBar from './performance_bar';
import initSearchAutocomplete from './search_autocomplete';
import GlFieldErrors from './gl_field_errors';
import initUserPopovers from './user_popovers';
+import { initUserTracking } from './tracking';
import { __ } from './locale';
import 'ee_else_ce/main_ee';
@@ -94,6 +95,7 @@ function deferredInitialisation() {
initLogoAnimation();
initUsagePingConsent();
initUserPopovers();
+ initUserTracking();
if (document.querySelector('.search')) initSearchAutocomplete();
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/monitoring/components/charts/time_series.vue b/app/assets/javascripts/monitoring/components/charts/time_series.vue
index 2fdc75f63ca..02e7a7ba0a6 100644
--- a/app/assets/javascripts/monitoring/components/charts/time_series.vue
+++ b/app/assets/javascripts/monitoring/components/charts/time_series.vue
@@ -48,6 +48,11 @@ export default {
required: false,
default: false,
},
+ singleEmbed: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
thresholds: {
type: Array,
required: false,
@@ -267,7 +272,10 @@ export default {
</script>
<template>
- <div class="prometheus-graph col-12 col-lg-6" :class="[showBorder ? 'p-2' : 'p-0']">
+ <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 class="prometheus-graph-title js-graph-title">{{ graphData.title }}</h5>
diff --git a/app/assets/javascripts/monitoring/components/embed.vue b/app/assets/javascripts/monitoring/components/embed.vue
index e3256147618..b516a82c170 100644
--- a/app/assets/javascripts/monitoring/components/embed.vue
+++ b/app/assets/javascripts/monitoring/components/embed.vue
@@ -2,7 +2,7 @@
import { mapActions, mapState } from 'vuex';
import { getParameterValues, removeParams } from '~/lib/utils/url_utility';
import GraphGroup from './graph_group.vue';
-import MonitorAreaChart from './charts/area.vue';
+import MonitorTimeSeriesChart from './charts/time_series.vue';
import { sidebarAnimationDuration } from '../constants';
import { getTimeDiff } from '../utils';
@@ -11,7 +11,7 @@ let sidebarMutationObserver;
export default {
components: {
GraphGroup,
- MonitorAreaChart,
+ MonitorTimeSeriesChart,
},
props: {
dashboardUrl: {
@@ -92,7 +92,7 @@ export default {
<template>
<div class="metrics-embed" :class="{ 'd-inline-flex col-lg-6 p-0': isSingleChart }">
<div v-if="charts.length" class="row w-100 m-n2 pb-4">
- <monitor-area-chart
+ <monitor-time-series-chart
v-for="graphData in charts"
:key="graphData.title"
:graph-data="graphData"
diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue
index 96f62bc85ee..73ff651d510 100644
--- a/app/assets/javascripts/monitoring/components/panel_type.vue
+++ b/app/assets/javascripts/monitoring/components/panel_type.vue
@@ -10,14 +10,14 @@ import {
GlTooltipDirective,
} from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
-import MonitorAreaChart from './charts/area.vue';
+import MonitorTimeSeriesChart from './charts/time_series.vue';
import MonitorSingleStatChart from './charts/single_stat.vue';
import MonitorEmptyChart from './charts/empty_chart.vue';
export default {
components: {
- MonitorAreaChart,
MonitorSingleStatChart,
+ MonitorTimeSeriesChart,
MonitorEmptyChart,
Icon,
GlDropdown,
@@ -92,7 +92,7 @@ export default {
v-if="isPanelType('single-stat') && graphDataHasMetrics"
:graph-data="graphData"
/>
- <monitor-area-chart
+ <monitor-time-series-chart
v-else-if="graphDataHasMetrics"
:graph-data="graphData"
:deployment-data="deploymentData"
@@ -136,6 +136,6 @@ export default {
</gl-dropdown-item>
</gl-dropdown>
</div>
- </monitor-area-chart>
+ </monitor-time-series-chart>
<monitor-empty-chart v-else :graph-title="graphData.title" />
</template>
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 2f201839d45..9019f0542b6 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -14,6 +14,7 @@ import NoteBody from './note_body.vue';
import eventHub from '../event_hub';
import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable';
+import httpStatusCodes from '~/lib/utils/http_status';
export default {
name: 'NoteableNote',
@@ -122,7 +123,13 @@ export default {
},
methods: {
- ...mapActions(['deleteNote', 'updateNote', 'toggleResolveNote', 'scrollToNoteIfNeeded']),
+ ...mapActions([
+ 'deleteNote',
+ 'removeNote',
+ 'updateNote',
+ 'toggleResolveNote',
+ 'scrollToNoteIfNeeded',
+ ]),
editHandler() {
this.isEditing = true;
this.$emit('handleEdit');
@@ -185,15 +192,21 @@ export default {
this.updateSuccess();
callback();
})
- .catch(() => {
- this.isRequesting = false;
- this.isEditing = true;
- this.$nextTick(() => {
- const msg = __('Something went wrong while editing your comment. Please try again.');
- Flash(msg, 'alert', this.$el);
- this.recoverNoteContent(noteText);
+ .catch(response => {
+ if (response.status === httpStatusCodes.GONE) {
+ this.removeNote(this.note);
+ this.updateSuccess();
callback();
- });
+ } else {
+ this.isRequesting = false;
+ this.isEditing = true;
+ this.$nextTick(() => {
+ const msg = __('Something went wrong while editing your comment. Please try again.');
+ Flash(msg, 'alert', this.$el);
+ this.recoverNoteContent(noteText);
+ callback();
+ });
+ }
});
},
formCancelHandler(shouldConfirm, isDirty) {
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index a0695f9e191..16a0fb3f33a 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -75,9 +75,9 @@ export default {
},
allDiscussions() {
if (this.isLoading) {
- const totalNotes = parseInt(this.notesData.totalNotes, 10) || 0;
+ const prerenderedNotesCount = parseInt(this.notesData.prerenderedNotesCount, 10) || 0;
- return new Array(totalNotes).fill({
+ return new Array(prerenderedNotesCount).fill({
isSkeletonNote: true,
});
}
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index b7857997d42..411bd585672 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -61,18 +61,22 @@ export const updateDiscussion = ({ commit, state }, discussion) => {
return utils.findNoteObjectById(state.discussions, discussion.id);
};
-export const deleteNote = ({ commit, dispatch, state }, note) =>
- axios.delete(note.path).then(() => {
- const discussion = state.discussions.find(({ id }) => id === note.discussion_id);
+export const removeNote = ({ commit, dispatch, state }, note) => {
+ const discussion = state.discussions.find(({ id }) => id === note.discussion_id);
- commit(types.DELETE_NOTE, note);
+ commit(types.DELETE_NOTE, note);
- dispatch('updateMergeRequestWidget');
- dispatch('updateResolvableDiscussionsCounts');
+ dispatch('updateMergeRequestWidget');
+ dispatch('updateResolvableDiscussionsCounts');
- if (isInMRPage()) {
- dispatch('diffs/removeDiscussionsFromDiff', discussion);
- }
+ if (isInMRPage()) {
+ dispatch('diffs/removeDiscussionsFromDiff', discussion);
+ }
+};
+
+export const deleteNote = ({ dispatch }, note) =>
+ axios.delete(note.path).then(() => {
+ dispatch('removeNote', note);
});
export const updateNote = ({ commit, dispatch }, { endpoint, note }) =>
diff --git a/app/assets/javascripts/pages/admin/clusters/index.js b/app/assets/javascripts/pages/admin/clusters/index.js
index d0c9ae66c6a..43992938d07 100644
--- a/app/assets/javascripts/pages/admin/clusters/index.js
+++ b/app/assets/javascripts/pages/admin/clusters/index.js
@@ -1,5 +1,5 @@
import PersistentUserCallout from '~/persistent_user_callout';
-import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
+import initGkeDropdowns from '~/create_cluster/gke_cluster';
function initGcpSignupCallout() {
const callout = document.querySelector('.gcp-signup-offer');
diff --git a/app/assets/javascripts/pages/groups/index.js b/app/assets/javascripts/pages/groups/index.js
index 451be6497de..a33d242908b 100644
--- a/app/assets/javascripts/pages/groups/index.js
+++ b/app/assets/javascripts/pages/groups/index.js
@@ -1,5 +1,5 @@
import PersistentUserCallout from '~/persistent_user_callout';
-import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
+import initGkeDropdowns from '~/create_cluster/gke_cluster';
function initGcpSignupCallout() {
const callout = document.querySelector('.gcp-signup-offer');
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/index.js b/app/assets/javascripts/pages/projects/index.js
index 55c377ebec0..196798a9076 100644
--- a/app/assets/javascripts/pages/projects/index.js
+++ b/app/assets/javascripts/pages/projects/index.js
@@ -1,4 +1,4 @@
-import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
+import initGkeDropdowns from '~/create_cluster/gke_cluster';
import initGkeNamespace from '~/projects/gke_cluster_namespace';
import PersistentUserCallout from '../../persistent_user_callout';
import Project from './project';
diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js
index 332b6811af6..33e9a8e9d56 100644
--- a/app/assets/javascripts/pages/projects/project.js
+++ b/app/assets/javascripts/pages/projects/project.js
@@ -73,13 +73,6 @@ export default class Project {
.remove();
return e.preventDefault();
});
- $('.hide-shared-runner-limit-message').on('click', function(e) {
- var $alert = $(this).parents('.shared-runner-quota-message');
- var scope = $alert.data('scope');
- Cookies.set('hide_shared_runner_quota_message', 'false', { path: scope });
- $alert.remove();
- e.preventDefault();
- });
$('.hide-auto-devops-implicitly-enabled-banner').on('click', function(e) {
const projectId = $(this).data('project-id');
const cookieKey = `hide_auto_devops_implicitly_enabled_banner_${projectId}`;
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/pages/projects/shared/permissions/constants.js b/app/assets/javascripts/pages/projects/shared/permissions/constants.js
index 73269c6f3ba..6771391254e 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/constants.js
+++ b/app/assets/javascripts/pages/projects/shared/permissions/constants.js
@@ -16,7 +16,7 @@ export const visibilityLevelDescriptions = {
),
};
-const featureAccessLevel = {
+export const featureAccessLevel = {
NOT_ENABLED: 0,
PROJECT_MEMBERS: 10,
EVERYONE: 20,
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index ba0dea626dc..27c1b639889 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -20,6 +20,9 @@ export default {
<stage-column-component
v-for="(stage, index) in graph"
:key="stage.name"
+ :class="{
+ 'append-right-48': shouldAddRightMargin(index),
+ }"
:title="capitalizeStageName(stage.name)"
:groups="stage.groups"
:stage-connector-class="stageConnectorClass(index, stage)"
diff --git a/app/assets/javascripts/pipelines/mixins/graph_component_mixin.js b/app/assets/javascripts/pipelines/mixins/graph_component_mixin.js
index 66e9476dadf..f383a4b3368 100644
--- a/app/assets/javascripts/pipelines/mixins/graph_component_mixin.js
+++ b/app/assets/javascripts/pipelines/mixins/graph_component_mixin.js
@@ -40,5 +40,15 @@ export default {
refreshPipelineGraph() {
this.$emit('refreshPipelineGraph');
},
+ /**
+ * CSS class is applied:
+ * - if pipeline graph contains only one stage column component
+ *
+ * @param {number} index
+ * @returns {boolean}
+ */
+ shouldAddRightMargin(index) {
+ return !(index === this.graph.length - 1);
+ },
},
};
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/projects/tree/components/commit_pipeline_status_component.vue b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
index 03281aa1317..12ee1ce2f0c 100644
--- a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
+++ b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
@@ -38,7 +38,9 @@ export default {
},
computed: {
statusTitle() {
- return sprintf(s__('Commits|Commit: %{commitText}'), { commitText: this.ciStatus.text });
+ return sprintf(s__('PipelineStatusTooltip|Pipeline: %{ciStatus}'), {
+ ciStatus: this.ciStatus.text,
+ });
},
},
mounted() {
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/test_utils/simulate_drag.js b/app/assets/javascripts/test_utils/simulate_drag.js
index be9ebc81c6b..c9bf234fcce 100644
--- a/app/assets/javascripts/test_utils/simulate_drag.js
+++ b/app/assets/javascripts/test_utils/simulate_drag.js
@@ -153,7 +153,11 @@ export default function simulateDrag(options) {
if (progress >= 1) {
if (options.ondragend) options.ondragend();
- simulateEvent(toEl, 'mouseup');
+
+ if (options.performDrop) {
+ simulateEvent(toEl, 'mouseup');
+ }
+
clearInterval(dragInterval);
window.SIMULATE_DRAG_ACTIVE = 0;
}
diff --git a/app/assets/javascripts/tracking.js b/app/assets/javascripts/tracking.js
index a852f937eec..03281b5ef49 100644
--- a/app/assets/javascripts/tracking.js
+++ b/app/assets/javascripts/tracking.js
@@ -1,5 +1,23 @@
import $ from 'jquery';
+const DEFAULT_SNOWPLOW_OPTIONS = {
+ namespace: 'gl',
+ hostname: window.location.hostname,
+ cookieDomain: window.location.hostname,
+ appId: '',
+ userFingerprint: false,
+ respectDoNotTrack: true,
+ forceSecureTracker: true,
+ eventMethod: 'post',
+ contexts: { webPage: true },
+ // Page tracking tracks a single event when the page loads.
+ pageTrackingEnabled: false,
+ // Activity tracking tracks when a user is still interacting with the page.
+ // Events like scrolling and mouse movements are used to determine if the
+ // user has the tab active and is still actively engaging.
+ activityTrackingEnabled: false,
+};
+
const extractData = (el, opts = {}) => {
const { trackEvent, trackLabel = '', trackProperty = '' } = el.dataset;
let trackValue = el.dataset.trackValue || el.value || '';
@@ -71,3 +89,13 @@ export default class Tracking {
};
}
}
+
+export function initUserTracking() {
+ if (!Tracking.enabled()) return;
+
+ const opts = Object.assign({}, DEFAULT_SNOWPLOW_OPTIONS, window.snowplowOptions);
+ window.snowplow('newTracker', opts.namespace, opts.hostname, opts);
+
+ if (opts.activityTrackingEnabled) window.snowplow('enableActivityTracking', 30, 30);
+ if (opts.pageTrackingEnabled) window.snowplow('trackPageView'); // must be after enableActivityTracking
+}
diff --git a/app/assets/javascripts/visual_review_toolbar/components/comment.js b/app/assets/javascripts/visual_review_toolbar/components/comment.js
deleted file mode 100644
index a03dc14b319..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/comment.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import { nextView } from '../store';
-import { localStorage, COMMENT_BOX, LOGOUT, STORAGE_MR_ID, STORAGE_TOKEN } from '../shared';
-import { clearNote } from './note';
-import { buttonClearStyles } from './utils';
-import { addForm } from './wrapper';
-import { changeSelectedMr, selectedMrNote } from './comment_mr_note';
-import postComment from './comment_post';
-import { saveComment, getSavedComment } from './comment_storage';
-
-const comment = state => {
- const savedComment = getSavedComment();
-
- return `
- <div>
- <textarea id="${COMMENT_BOX}" name="${COMMENT_BOX}" rows="3" placeholder="Enter your feedback or idea" class="gitlab-input" aria-required="true">${savedComment}</textarea>
- ${selectedMrNote(state)}
- <p class="gitlab-metadata-note">Additional metadata will be included: browser, OS, current page, user agent, and viewport dimensions.</p>
- </div>
- <div class="gitlab-button-wrapper">
- <button class="gitlab-button gitlab-button-success" style="${buttonClearStyles}" type="button" id="gitlab-comment-button"> Send feedback </button>
- <button class="gitlab-button gitlab-button-secondary" style="${buttonClearStyles}" type="button" id="${LOGOUT}"> Log out </button>
- </div>
- `;
-};
-
-// This function is here becaause it is called only from the comment view
-// If we reach a design where we can logout from multiple views, promote this
-// to it's own package
-const logoutUser = state => {
- localStorage.removeItem(STORAGE_TOKEN);
- localStorage.removeItem(STORAGE_MR_ID);
- state.token = '';
- state.mergeRequestId = '';
-
- clearNote();
- addForm(nextView(state, COMMENT_BOX));
-};
-
-export { changeSelectedMr, comment, logoutUser, postComment, saveComment };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/comment_mr_note.js b/app/assets/javascripts/visual_review_toolbar/components/comment_mr_note.js
deleted file mode 100644
index da67763261c..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/comment_mr_note.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import { nextView } from '../store';
-import { localStorage, CHANGE_MR_ID_BUTTON, COMMENT_BOX, STORAGE_MR_ID } from '../shared';
-import { clearNote } from './note';
-import { buttonClearStyles } from './utils';
-import { addForm } from './wrapper';
-
-const selectedMrNote = state => {
- const { mrUrl, projectPath, mergeRequestId } = state;
-
- const mrLink = `${mrUrl}/${projectPath}/merge_requests/${mergeRequestId}`;
-
- return `
- <p class="gitlab-metadata-note">
- This posts to merge request <a class="gitlab-link" href="${mrLink}">!${mergeRequestId}</a>.
- <button style="${buttonClearStyles}" type="button" id="${CHANGE_MR_ID_BUTTON}" class="gitlab-link gitlab-link-button">Change</button>
- </p>
- `;
-};
-
-const clearMrId = state => {
- localStorage.removeItem(STORAGE_MR_ID);
- state.mergeRequestId = '';
-};
-
-const changeSelectedMr = state => {
- clearMrId(state);
- clearNote();
- addForm(nextView(state, COMMENT_BOX));
-};
-
-export { changeSelectedMr, selectedMrNote };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/comment_post.js b/app/assets/javascripts/visual_review_toolbar/components/comment_post.js
deleted file mode 100644
index ee5f2b62425..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/comment_post.js
+++ /dev/null
@@ -1,145 +0,0 @@
-import { BLACK, COMMENT_BOX, MUTED } from '../shared';
-import { clearSavedComment } from './comment_storage';
-import { clearNote, postError } from './note';
-import { selectCommentBox, selectCommentButton, selectNote, selectNoteContainer } from './utils';
-
-const resetCommentButton = () => {
- const commentButton = selectCommentButton();
-
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- commentButton.innerText = 'Send feedback';
- commentButton.classList.replace('gitlab-button-secondary', 'gitlab-button-success');
- commentButton.style.opacity = 1;
-};
-
-const resetCommentBox = () => {
- const commentBox = selectCommentBox();
- commentBox.style.pointerEvents = 'auto';
- commentBox.style.color = BLACK;
-};
-
-const resetCommentText = () => {
- const commentBox = selectCommentBox();
- commentBox.value = '';
- clearSavedComment();
-};
-
-const resetComment = () => {
- resetCommentButton();
- resetCommentBox();
- resetCommentText();
-};
-
-const confirmAndClear = feedbackInfo => {
- const commentButton = selectCommentButton();
- const currentNote = selectNote();
- const noteContainer = selectNoteContainer();
-
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- commentButton.innerText = 'Feedback sent';
- noteContainer.style.visibility = 'visible';
- currentNote.insertAdjacentHTML('beforeend', feedbackInfo);
-
- setTimeout(resetComment, 1000);
- setTimeout(clearNote, 6000);
-};
-
-const setInProgressState = () => {
- const commentButton = selectCommentButton();
- const commentBox = selectCommentBox();
-
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- commentButton.innerText = 'Sending feedback';
- commentButton.classList.replace('gitlab-button-success', 'gitlab-button-secondary');
- commentButton.style.opacity = 0.5;
- commentBox.style.color = MUTED;
- commentBox.style.pointerEvents = 'none';
-};
-
-const commentErrors = error => {
- switch (error.status) {
- case 401:
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- return 'Unauthorized. You may have entered an incorrect authentication token.';
- case 404:
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- return 'Not found. You may have entered an incorrect merge request ID.';
- default:
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- return `Your comment could not be sent. Please try again. Error: ${error.message}`;
- }
-};
-
-const postComment = ({
- platform,
- browser,
- userAgent,
- innerWidth,
- innerHeight,
- projectId,
- projectPath,
- mergeRequestId,
- mrUrl,
- token,
-}) => {
- // Clear any old errors
- clearNote(COMMENT_BOX);
-
- setInProgressState();
-
- const commentText = selectCommentBox().value.trim();
- // Get the href at the last moment to support SPAs
- const { href } = window.location;
-
- if (!commentText) {
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- postError('Your comment appears to be empty.', COMMENT_BOX);
- resetCommentBox();
- resetCommentButton();
- return;
- }
-
- const detailText = `
- \n
-<details>
- <summary>Metadata</summary>
- Posted from ${href} | ${platform} | ${browser} | ${innerWidth} x ${innerHeight}.
- <br /><br />
- <em>User agent: ${userAgent}</em>
-</details>
- `;
-
- const url = `
- ${mrUrl}/api/v4/projects/${projectId}/merge_requests/${mergeRequestId}/discussions`;
-
- const body = `${commentText} ${detailText}`;
-
- fetch(url, {
- method: 'POST',
- headers: {
- 'PRIVATE-TOKEN': token,
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ body }),
- })
- .then(response => {
- if (response.ok) {
- return response.json();
- }
-
- throw response;
- })
- .then(data => {
- const commentId = data.notes[0].id;
- const feedbackLink = `${mrUrl}/${projectPath}/merge_requests/${mergeRequestId}#note_${commentId}`;
- const feedbackInfo = `Feedback sent. View at <a class="gitlab-link" href="${feedbackLink}">${projectPath} !${mergeRequestId} (comment ${commentId})</a>`;
- confirmAndClear(feedbackInfo);
- })
- .catch(err => {
- postError(commentErrors(err), COMMENT_BOX);
- resetCommentBox();
- resetCommentButton();
- });
-};
-
-export default postComment;
diff --git a/app/assets/javascripts/visual_review_toolbar/components/comment_storage.js b/app/assets/javascripts/visual_review_toolbar/components/comment_storage.js
deleted file mode 100644
index 49c9400437e..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/comment_storage.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import { selectCommentBox } from './utils';
-import { sessionStorage, STORAGE_COMMENT } from '../shared';
-
-const getSavedComment = () => sessionStorage.getItem(STORAGE_COMMENT) || '';
-
-const saveComment = () => {
- const currentComment = selectCommentBox();
-
- // This may be added to any view via top-level beforeunload listener
- // so let's skip if it does not apply
- if (currentComment && currentComment.value) {
- sessionStorage.setItem(STORAGE_COMMENT, currentComment.value);
- }
-};
-
-const clearSavedComment = () => {
- sessionStorage.removeItem(STORAGE_COMMENT);
-};
-
-export { getSavedComment, saveComment, clearSavedComment };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/form_elements.js b/app/assets/javascripts/visual_review_toolbar/components/form_elements.js
deleted file mode 100644
index 608488a6fea..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/form_elements.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import { REMEMBER_ITEM } from '../shared';
-import { buttonClearStyles } from './utils';
-
-/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
-const rememberBox = (rememberText = 'Remember me') => `
- <div class="gitlab-checkbox-wrapper">
- <input type="checkbox" id="${REMEMBER_ITEM}" name="${REMEMBER_ITEM}" value="remember">
- <label for="${REMEMBER_ITEM}" class="gitlab-checkbox-label">${rememberText}</label>
- </div>
-`;
-
-const submitButton = buttonId => `
- <div class="gitlab-button-wrapper">
- <button class="gitlab-button-wide gitlab-button gitlab-button-success" style="${buttonClearStyles}" type="button" id="${buttonId}"> Submit </button>
- </div>
-`;
-export { rememberBox, submitButton };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/index.js b/app/assets/javascripts/visual_review_toolbar/components/index.js
deleted file mode 100644
index e88b3637ad8..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/index.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import { changeSelectedMr, comment, logoutUser, postComment, saveComment } from './comment';
-import { authorizeUser, login } from './login';
-import { addMr, mrForm } from './mr_id';
-import { note } from './note';
-import { selectContainer, selectForm } from './utils';
-import { buttonAndForm, toggleForm } from './wrapper';
-
-export {
- addMr,
- authorizeUser,
- buttonAndForm,
- changeSelectedMr,
- comment,
- login,
- logoutUser,
- mrForm,
- note,
- postComment,
- saveComment,
- selectContainer,
- selectForm,
- toggleForm,
-};
diff --git a/app/assets/javascripts/visual_review_toolbar/components/login.js b/app/assets/javascripts/visual_review_toolbar/components/login.js
deleted file mode 100644
index 20ab01bc690..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/login.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import { nextView } from '../store';
-import { localStorage, LOGIN, TOKEN_BOX, STORAGE_TOKEN } from '../shared';
-import { clearNote, postError } from './note';
-import { rememberBox, submitButton } from './form_elements';
-import { selectRemember, selectToken } from './utils';
-import { addForm } from './wrapper';
-
-const labelText = `
- Enter your <a class="gitlab-link" href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html">personal access token</a>
-`;
-
-const login = `
- <div>
- <label for="${TOKEN_BOX}" class="gitlab-label">${labelText}</label>
- <input class="gitlab-input" type="password" id="${TOKEN_BOX}" name="${TOKEN_BOX}" autocomplete="current-password" aria-required="true">
- </div>
- ${rememberBox()}
- ${submitButton(LOGIN)}
-`;
-
-const storeToken = (token, state) => {
- const rememberMe = selectRemember().checked;
-
- if (rememberMe) {
- localStorage.setItem(STORAGE_TOKEN, token);
- }
-
- state.token = token;
-};
-
-const authorizeUser = state => {
- // Clear any old errors
- clearNote(TOKEN_BOX);
-
- const token = selectToken().value;
-
- if (!token) {
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- postError('Please enter your token.', TOKEN_BOX);
- return;
- }
-
- storeToken(token, state);
- addForm(nextView(state, LOGIN));
-};
-
-export { authorizeUser, login, storeToken };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/mr_id.js b/app/assets/javascripts/visual_review_toolbar/components/mr_id.js
deleted file mode 100644
index 695b3af8aa0..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/mr_id.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import { nextView } from '../store';
-import { MR_ID, MR_ID_BUTTON, STORAGE_MR_ID, localStorage } from '../shared';
-import { clearNote, postError } from './note';
-import { rememberBox, submitButton } from './form_elements';
-import { selectForm, selectMrBox, selectRemember } from './utils';
-import { addForm } from './wrapper';
-
-/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
-const mrLabel = `Enter your merge request ID`;
-/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
-const mrRememberText = `Remember this number`;
-
-const mrForm = `
- <div>
- <label for="${MR_ID}" class="gitlab-label">${mrLabel}</label>
- <input class="gitlab-input" type="text" pattern="[1-9][0-9]*" id="${MR_ID}" name="${MR_ID}" placeholder="e.g., 321" aria-required="true">
- </div>
- ${rememberBox(mrRememberText)}
- ${submitButton(MR_ID_BUTTON)}
-`;
-
-const storeMR = (id, state) => {
- const rememberMe = selectRemember().checked;
-
- if (rememberMe) {
- localStorage.setItem(STORAGE_MR_ID, id);
- }
-
- state.mergeRequestId = id;
-};
-
-const getFormError = (mrNumber, form) => {
- if (!mrNumber) {
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- return 'Please enter your merge request ID number.';
- }
-
- if (!form.checkValidity()) {
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- return 'Please remove any non-number values from the field.';
- }
-
- return null;
-};
-
-const addMr = state => {
- // Clear any old errors
- clearNote(MR_ID);
-
- const mrNumber = selectMrBox().value;
- const form = selectForm();
- const formError = getFormError(mrNumber, form);
-
- if (formError) {
- postError(formError, MR_ID);
- return;
- }
-
- storeMR(mrNumber, state);
- addForm(nextView(state, MR_ID));
-};
-
-export { addMr, mrForm, storeMR };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/note.js b/app/assets/javascripts/visual_review_toolbar/components/note.js
deleted file mode 100644
index 9cddcb710f2..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/note.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { NOTE, NOTE_CONTAINER, RED } from '../shared';
-import { selectById, selectNote, selectNoteContainer } from './utils';
-
-const note = `
- <div id="${NOTE_CONTAINER}" style="visibility: hidden;">
- <p id="${NOTE}" class="gitlab-message"></p>
- </div>
-`;
-
-const clearNote = inputId => {
- const currentNote = selectNote();
- const noteContainer = selectNoteContainer();
-
- currentNote.innerText = '';
- currentNote.style.color = '';
- noteContainer.style.visibility = 'hidden';
-
- if (inputId) {
- const field = document.getElementById(inputId);
- field.style.borderColor = '';
- }
-};
-
-const postError = (message, inputId) => {
- const currentNote = selectNote();
- const noteContainer = selectNoteContainer();
- const field = selectById(inputId);
- field.style.borderColor = RED;
- currentNote.style.color = RED;
- currentNote.innerText = message;
- noteContainer.style.visibility = 'visible';
- setTimeout(clearNote.bind(null, inputId), 5000);
-};
-
-export { clearNote, note, postError };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/utils.js b/app/assets/javascripts/visual_review_toolbar/components/utils.js
deleted file mode 100644
index 4ec9bd4a32a..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/utils.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/* global document */
-
-import {
- COLLAPSE_BUTTON,
- COMMENT_BOX,
- COMMENT_BUTTON,
- FORM,
- FORM_CONTAINER,
- MR_ID,
- NOTE,
- NOTE_CONTAINER,
- REMEMBER_ITEM,
- REVIEW_CONTAINER,
- TOKEN_BOX,
-} from '../shared';
-
-// this style must be applied inline in a handful of components
-/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
-const buttonClearStyles = `
- -webkit-appearance: none;
-`;
-
-// selector functions to abstract out a little
-const selectById = id => document.getElementById(id);
-const selectCollapseButton = () => document.getElementById(COLLAPSE_BUTTON);
-const selectCommentBox = () => document.getElementById(COMMENT_BOX);
-const selectCommentButton = () => document.getElementById(COMMENT_BUTTON);
-const selectContainer = () => document.getElementById(REVIEW_CONTAINER);
-const selectForm = () => document.getElementById(FORM);
-const selectFormContainer = () => document.getElementById(FORM_CONTAINER);
-const selectMrBox = () => document.getElementById(MR_ID);
-const selectNote = () => document.getElementById(NOTE);
-const selectNoteContainer = () => document.getElementById(NOTE_CONTAINER);
-const selectRemember = () => document.getElementById(REMEMBER_ITEM);
-const selectToken = () => document.getElementById(TOKEN_BOX);
-
-export {
- buttonClearStyles,
- selectById,
- selectCollapseButton,
- selectContainer,
- selectCommentBox,
- selectCommentButton,
- selectForm,
- selectFormContainer,
- selectMrBox,
- selectNote,
- selectNoteContainer,
- selectRemember,
- selectToken,
-};
diff --git a/app/assets/javascripts/visual_review_toolbar/components/wrapper.js b/app/assets/javascripts/visual_review_toolbar/components/wrapper.js
deleted file mode 100644
index fdf8ad7c41f..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/wrapper.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import { CLEAR, FORM, FORM_CONTAINER, WHITE } from '../shared';
-import {
- selectCollapseButton,
- selectForm,
- selectFormContainer,
- selectNoteContainer,
-} from './utils';
-import { collapseButton, commentIcon, compressIcon } from './wrapper_icons';
-
-const form = content => `
- <form id="${FORM}" novalidate>
- ${content}
- </form>
-`;
-
-const buttonAndForm = content => `
- <div id="${FORM_CONTAINER}" class="gitlab-form-open">
- ${collapseButton}
- ${form(content)}
- </div>
-`;
-
-const addForm = nextForm => {
- const formWrapper = selectForm();
- formWrapper.innerHTML = nextForm;
-};
-
-function toggleForm() {
- const toggleButton = selectCollapseButton();
- const currentForm = selectForm();
- const formContainer = selectFormContainer();
- const noteContainer = selectNoteContainer();
- const OPEN = 'open';
- const CLOSED = 'closed';
-
- /*
- You may wonder why we spread the arrays before we reverse them.
- In the immortal words of MDN,
- Careful: reverse is destructive. It also changes the original array
- */
-
- const openButtonClasses = ['gitlab-collapse-closed', 'gitlab-collapse-open'];
- const closedButtonClasses = [...openButtonClasses].reverse();
- const openContainerClasses = ['gitlab-wrapper-closed', 'gitlab-wrapper-open'];
- const closedContainerClasses = [...openContainerClasses].reverse();
-
- const stateVals = {
- [OPEN]: {
- buttonClasses: openButtonClasses,
- containerClasses: openContainerClasses,
- icon: compressIcon,
- display: 'flex',
- backgroundColor: WHITE,
- },
- [CLOSED]: {
- buttonClasses: closedButtonClasses,
- containerClasses: closedContainerClasses,
- icon: commentIcon,
- display: 'none',
- backgroundColor: CLEAR,
- },
- };
-
- const nextState = toggleButton.classList.contains('gitlab-collapse-open') ? CLOSED : OPEN;
- const currentVals = stateVals[nextState];
-
- formContainer.classList.replace(...currentVals.containerClasses);
- formContainer.style.backgroundColor = currentVals.backgroundColor;
- formContainer.classList.toggle('gitlab-form-open');
- currentForm.style.display = currentVals.display;
- toggleButton.classList.replace(...currentVals.buttonClasses);
- toggleButton.innerHTML = currentVals.icon;
-
- if (noteContainer && noteContainer.innerText.length > 0) {
- noteContainer.style.display = currentVals.display;
- }
-}
-
-export { addForm, buttonAndForm, toggleForm };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/wrapper_icons.js b/app/assets/javascripts/visual_review_toolbar/components/wrapper_icons.js
deleted file mode 100644
index b686fd4f5c2..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/wrapper_icons.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import { buttonClearStyles } from './utils';
-
-const commentIcon = `
- <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>icn/comment</title><path d="M4 11.132l1.446-.964A1 1 0 0 1 6 10h5a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v6.132zM6.303 12l-2.748 1.832A1 1 0 0 1 2 13V5a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v4a3 3 0 0 1-3 3H6.303z" id="gitlab-comment-icon"/></svg>
-`;
-
-const compressIcon = `
- <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>icn/compress</title><path d="M5.27 12.182l-1.562 1.561a1 1 0 0 1-1.414 0h-.001a1 1 0 0 1 0-1.415l1.56-1.56L2.44 9.353a.5.5 0 0 1 .353-.854H7.09a.5.5 0 0 1 .5.5v4.294a.5.5 0 0 1-.853.353l-1.467-1.465zm6.911-6.914l1.464 1.464a.5.5 0 0 1-.353.854H8.999a.5.5 0 0 1-.5-.5V2.793a.5.5 0 0 1 .854-.354l1.414 1.415 1.56-1.561a1 1 0 1 1 1.415 1.414l-1.561 1.56z" id="gitlab-compress-icon"/></svg>
-`;
-
-const collapseButton = `
- <button id='gitlab-collapse' style='${buttonClearStyles}' class='gitlab-button gitlab-button-secondary gitlab-collapse gitlab-collapse-open'>${compressIcon}</button>
-`;
-
-export { commentIcon, compressIcon, collapseButton };
diff --git a/app/assets/javascripts/visual_review_toolbar/index.js b/app/assets/javascripts/visual_review_toolbar/index.js
deleted file mode 100644
index 67b3fadd772..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/index.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import './styles/toolbar.css';
-
-import { buttonAndForm, note, selectForm, selectContainer } from './components';
-import { REVIEW_CONTAINER } from './shared';
-import { eventLookup, getInitialView, initializeGlobalListeners, initializeState } from './store';
-
-/*
-
- Welcome to the visual review toolbar files. A few useful notes:
-
- - These files build a static script that is served from our webpack
- assets folder. (https://gitlab.com/assets/webpack/visual_review_toolbar.js)
-
- - To compile this file, run `yarn webpack-vrt`.
-
- - Vue is not used in these files because we do not want to ask users to
- install another library at this time. It's all pure vanilla javascript.
-
-*/
-
-window.addEventListener('load', () => {
- initializeState(window, document);
-
- const mainContent = buttonAndForm(getInitialView());
- const container = document.createElement('div');
- container.setAttribute('id', REVIEW_CONTAINER);
- container.insertAdjacentHTML('beforeend', note);
- container.insertAdjacentHTML('beforeend', mainContent);
-
- document.body.insertBefore(container, document.body.firstChild);
-
- selectContainer().addEventListener('click', event => {
- eventLookup(event.target.id)();
- });
-
- selectForm().addEventListener('submit', event => {
- // this is important to prevent the form from adding data
- // as URL params and inadvertently revealing secrets
- event.preventDefault();
-
- const id =
- event.target.querySelector('.gitlab-button-wrapper') &&
- event.target.querySelector('.gitlab-button-wrapper').getElementsByTagName('button')[0] &&
- event.target.querySelector('.gitlab-button-wrapper').getElementsByTagName('button')[0].id;
-
- // even if this is called with false, it's ok; it will get the default no-op
- eventLookup(id)();
- });
-
- initializeGlobalListeners();
-});
diff --git a/app/assets/javascripts/visual_review_toolbar/shared/constants.js b/app/assets/javascripts/visual_review_toolbar/shared/constants.js
deleted file mode 100644
index 0d5b666ab0a..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/shared/constants.js
+++ /dev/null
@@ -1,56 +0,0 @@
-// component selectors
-const CHANGE_MR_ID_BUTTON = 'gitlab-change-mr';
-const COLLAPSE_BUTTON = 'gitlab-collapse';
-const COMMENT_BOX = 'gitlab-comment';
-const COMMENT_BUTTON = 'gitlab-comment-button';
-const FORM = 'gitlab-form';
-const FORM_CONTAINER = 'gitlab-form-wrapper';
-const LOGIN = 'gitlab-login-button';
-const LOGOUT = 'gitlab-logout-button';
-const MR_ID = 'gitlab-submit-mr';
-const MR_ID_BUTTON = 'gitlab-submit-mr-button';
-const NOTE = 'gitlab-validation-note';
-const NOTE_CONTAINER = 'gitlab-note-wrapper';
-const REMEMBER_ITEM = 'gitlab-remember-item';
-const REVIEW_CONTAINER = 'gitlab-review-container';
-const TOKEN_BOX = 'gitlab-token';
-
-// Storage keys
-const STORAGE_PREFIX = '--gitlab'; // Using `--` to make the prefix more unique
-const STORAGE_MR_ID = `${STORAGE_PREFIX}-merge-request-id`;
-const STORAGE_TOKEN = `${STORAGE_PREFIX}-token`;
-const STORAGE_COMMENT = `${STORAGE_PREFIX}-comment`;
-
-// colors — these are applied programmatically
-// rest of styles belong in ./styles
-const BLACK = 'rgba(46, 46, 46, 1)';
-const CLEAR = 'rgba(255, 255, 255, 0)';
-const MUTED = 'rgba(223, 223, 223, 0.5)';
-const RED = 'rgba(219, 59, 33, 1)';
-const WHITE = 'rgba(250, 250, 250, 1)';
-
-export {
- CHANGE_MR_ID_BUTTON,
- COLLAPSE_BUTTON,
- COMMENT_BOX,
- COMMENT_BUTTON,
- FORM,
- FORM_CONTAINER,
- LOGIN,
- LOGOUT,
- MR_ID,
- MR_ID_BUTTON,
- NOTE,
- NOTE_CONTAINER,
- REMEMBER_ITEM,
- REVIEW_CONTAINER,
- TOKEN_BOX,
- STORAGE_MR_ID,
- STORAGE_TOKEN,
- STORAGE_COMMENT,
- BLACK,
- CLEAR,
- MUTED,
- RED,
- WHITE,
-};
diff --git a/app/assets/javascripts/visual_review_toolbar/shared/index.js b/app/assets/javascripts/visual_review_toolbar/shared/index.js
deleted file mode 100644
index d8ccb170592..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/shared/index.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import {
- CHANGE_MR_ID_BUTTON,
- COLLAPSE_BUTTON,
- COMMENT_BOX,
- COMMENT_BUTTON,
- FORM,
- FORM_CONTAINER,
- LOGIN,
- LOGOUT,
- MR_ID,
- MR_ID_BUTTON,
- NOTE,
- NOTE_CONTAINER,
- REMEMBER_ITEM,
- REVIEW_CONTAINER,
- TOKEN_BOX,
- STORAGE_MR_ID,
- STORAGE_TOKEN,
- STORAGE_COMMENT,
- BLACK,
- CLEAR,
- MUTED,
- RED,
- WHITE,
-} from './constants';
-
-import { localStorage, sessionStorage } from './storage_utils';
-
-export {
- localStorage,
- sessionStorage,
- CHANGE_MR_ID_BUTTON,
- COLLAPSE_BUTTON,
- COMMENT_BOX,
- COMMENT_BUTTON,
- FORM,
- FORM_CONTAINER,
- LOGIN,
- LOGOUT,
- MR_ID,
- MR_ID_BUTTON,
- NOTE,
- NOTE_CONTAINER,
- REMEMBER_ITEM,
- REVIEW_CONTAINER,
- TOKEN_BOX,
- STORAGE_MR_ID,
- STORAGE_TOKEN,
- STORAGE_COMMENT,
- BLACK,
- CLEAR,
- MUTED,
- RED,
- WHITE,
-};
diff --git a/app/assets/javascripts/visual_review_toolbar/shared/storage_utils.js b/app/assets/javascripts/visual_review_toolbar/shared/storage_utils.js
deleted file mode 100644
index 00456d3536e..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/shared/storage_utils.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import { setUsingGracefulStorageFlag } from '../store/state';
-
-const TEST_KEY = 'gitlab-storage-test';
-
-const createStorageStub = () => {
- const items = {};
-
- return {
- getItem(key) {
- return items[key];
- },
- setItem(key, value) {
- items[key] = value;
- },
- removeItem(key) {
- delete items[key];
- },
- };
-};
-
-const hasStorageSupport = storage => {
- // Support test taken from https://stackoverflow.com/a/11214467/1708147
- try {
- storage.setItem(TEST_KEY, TEST_KEY);
- storage.removeItem(TEST_KEY);
- setUsingGracefulStorageFlag(true);
-
- return true;
- } catch (err) {
- setUsingGracefulStorageFlag(false);
- return false;
- }
-};
-
-const useGracefulStorage = storage =>
- // If a browser does not support local storage, let's return a graceful implementation.
- hasStorageSupport(storage) ? storage : createStorageStub();
-
-const localStorage = useGracefulStorage(window.localStorage);
-const sessionStorage = useGracefulStorage(window.sessionStorage);
-
-export { localStorage, sessionStorage };
diff --git a/app/assets/javascripts/visual_review_toolbar/store/events.js b/app/assets/javascripts/visual_review_toolbar/store/events.js
deleted file mode 100644
index c9095c77ef1..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/store/events.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import {
- addMr,
- authorizeUser,
- changeSelectedMr,
- logoutUser,
- postComment,
- saveComment,
- toggleForm,
-} from '../components';
-
-import {
- CHANGE_MR_ID_BUTTON,
- COLLAPSE_BUTTON,
- COMMENT_BUTTON,
- LOGIN,
- LOGOUT,
- MR_ID_BUTTON,
-} from '../shared';
-
-import { state } from './state';
-import debounce from './utils';
-
-const noop = () => {};
-
-// State needs to be bound here to be acted on
-// because these are called by click events and
-// as such are called with only the `event` object
-const eventLookup = id => {
- switch (id) {
- case CHANGE_MR_ID_BUTTON:
- return () => {
- saveComment();
- changeSelectedMr(state);
- };
- case COLLAPSE_BUTTON:
- return toggleForm;
- case COMMENT_BUTTON:
- return postComment.bind(null, state);
- case LOGIN:
- return authorizeUser.bind(null, state);
- case LOGOUT:
- return () => {
- saveComment();
- logoutUser(state);
- };
- case MR_ID_BUTTON:
- return addMr.bind(null, state);
- default:
- return noop;
- }
-};
-
-const updateWindowSize = wind => {
- state.innerWidth = wind.innerWidth;
- state.innerHeight = wind.innerHeight;
-};
-
-const initializeGlobalListeners = () => {
- window.addEventListener('resize', debounce(updateWindowSize.bind(null, window), 200));
- window.addEventListener('beforeunload', event => {
- if (state.usingGracefulStorage) {
- // if there is no browser storage support, reloading will lose the comment; this way, the user will be warned
- // we assign the return value because it is required by Chrome see: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload#Example,
- event.preventDefault();
- /* eslint-disable-next-line no-param-reassign */
- event.returnValue = '';
- }
-
- saveComment();
- });
-};
-
-export { eventLookup, initializeGlobalListeners };
diff --git a/app/assets/javascripts/visual_review_toolbar/store/index.js b/app/assets/javascripts/visual_review_toolbar/store/index.js
deleted file mode 100644
index 07c8dd6f1d2..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/store/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import { eventLookup, initializeGlobalListeners } from './events';
-import { nextView, getInitialView, initializeState, setUsingGracefulStorageFlag } from './state';
-
-export {
- eventLookup,
- getInitialView,
- initializeGlobalListeners,
- initializeState,
- nextView,
- setUsingGracefulStorageFlag,
-};
diff --git a/app/assets/javascripts/visual_review_toolbar/store/state.js b/app/assets/javascripts/visual_review_toolbar/store/state.js
deleted file mode 100644
index b7853bb0723..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/store/state.js
+++ /dev/null
@@ -1,95 +0,0 @@
-import { comment, login, mrForm } from '../components';
-import { localStorage, COMMENT_BOX, LOGIN, MR_ID, STORAGE_MR_ID, STORAGE_TOKEN } from '../shared';
-
-const state = {
- browser: '',
- usingGracefulStorage: '',
- innerWidth: '',
- innerHeight: '',
- mergeRequestId: '',
- mrUrl: '',
- platform: '',
- projectId: '',
- userAgent: '',
- token: '',
-};
-
-// adapted from https://developer.mozilla.org/en-US/docs/Web/API/Window/navigator#Example_2_Browser_detect_and_return_an_index
-const getBrowserId = sUsrAg => {
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- const aKeys = ['MSIE', 'Edge', 'Firefox', 'Safari', 'Chrome', 'Opera'];
- let nIdx = aKeys.length - 1;
-
- for (nIdx; nIdx > -1 && sUsrAg.indexOf(aKeys[nIdx]) === -1; nIdx -= 1);
- return aKeys[nIdx];
-};
-
-const nextView = (appState, form = 'none') => {
- const formsList = {
- [COMMENT_BOX]: currentState => (currentState.token ? mrForm : login),
- [LOGIN]: currentState => (currentState.mergeRequestId ? comment(currentState) : mrForm),
- [MR_ID]: currentState => (currentState.token ? comment(currentState) : login),
- none: currentState => {
- if (!currentState.token) {
- return login;
- }
-
- if (currentState.token && !currentState.mergeRequestId) {
- return mrForm;
- }
-
- return comment(currentState);
- },
- };
-
- return formsList[form](appState);
-};
-
-const initializeState = (wind, doc) => {
- const {
- innerWidth,
- innerHeight,
- navigator: { platform, userAgent },
- } = wind;
-
- const browser = getBrowserId(userAgent);
-
- const scriptEl = doc.getElementById('review-app-toolbar-script');
- const { projectId, mergeRequestId, mrUrl, projectPath } = scriptEl.dataset;
-
- // This mutates our default state object above. It's weird but it makes the linter happy.
- Object.assign(state, {
- browser,
- innerWidth,
- innerHeight,
- mergeRequestId,
- mrUrl,
- platform,
- projectId,
- projectPath,
- userAgent,
- });
-
- return state;
-};
-
-const getInitialView = () => {
- const token = localStorage.getItem(STORAGE_TOKEN);
- const mrId = localStorage.getItem(STORAGE_MR_ID);
-
- if (token) {
- state.token = token;
- }
-
- if (mrId) {
- state.mergeRequestId = mrId;
- }
-
- return nextView(state);
-};
-
-const setUsingGracefulStorageFlag = flag => {
- state.usingGracefulStorage = !flag;
-};
-
-export { initializeState, getInitialView, nextView, setUsingGracefulStorageFlag, state };
diff --git a/app/assets/javascripts/visual_review_toolbar/store/utils.js b/app/assets/javascripts/visual_review_toolbar/store/utils.js
deleted file mode 100644
index 5cf145351b3..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/store/utils.js
+++ /dev/null
@@ -1,15 +0,0 @@
-const debounce = (fn, time) => {
- let current;
-
- const debounced = () => {
- if (current) {
- clearTimeout(current);
- }
-
- current = setTimeout(fn, time);
- };
-
- return debounced;
-};
-
-export default debounce;
diff --git a/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css b/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css
deleted file mode 100644
index d1a8d66ef40..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- As a standalone script, the toolbar has its own css
- */
-
-#gitlab-collapse > * {
- pointer-events: none;
-}
-
-#gitlab-comment {
- background-color: #fafafa;
-}
-
-#gitlab-form {
- display: flex;
- flex-direction: column;
- width: 100%;
- margin-bottom: 0;
-}
-
-#gitlab-note-wrapper {
- display: flex;
- flex-direction: column;
- background-color: #fafafa;
- border-radius: 4px;
- margin-bottom: .5rem;
- padding: 1rem;
-}
-
-#gitlab-form-wrapper {
- overflow: auto;
- display: flex;
- flex-direction: row-reverse;
- border-radius: 4px;
-}
-
-#gitlab-review-container {
- max-width: 22rem;
- max-height: 22rem;
- overflow: auto;
- display: flex;
- flex-direction: column;
- position: fixed;
- bottom: 1rem;
- right: 1rem;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Noto Sans', Ubuntu, Cantarell,
- 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
- 'Noto Color Emoji';
- font-size: .8rem;
- font-weight: 400;
- color: #2e2e2e;
- z-index: 9999; /* toolbar should always be on top */
-}
-
-.gitlab-wrapper-open {
- max-width: 22rem;
- max-height: 22rem;
-}
-
-.gitlab-wrapper-closed {
- max-width: 3.4rem;
- max-height: 3.4rem;
-}
-
-.gitlab-button {
- cursor: pointer;
- transition: background-color 100ms linear, border-color 100ms linear, color 100ms linear, box-shadow 100ms linear;
-}
-
-.gitlab-button-secondary {
- background: none #fafafa;
- margin: 0 .5rem;
- border: 1px solid #e3e3e3;
-}
-
-.gitlab-button-secondary:hover {
- background-color: #f0f0f0;
- border-color: #e3e3e3;
- color: #2e2e2e;
-}
-
-.gitlab-button-secondary:active {
- color: #2e2e2e;
- background-color: #e1e1e1;
- border-color: #dadada;
-}
-
-.gitlab-button-success:hover {
- color: #fff;
- background-color: #137e3f;
- border-color: #127339;
-}
-
-.gitlab-button-success:active {
- background-color: #168f48;
- border-color: #12753a;
- color: #fff;
-}
-
-.gitlab-button-success {
- background-color: #1aaa55;
- border: 1px solid #168f48;
- color: #fff;
-}
-
-.gitlab-button-wide {
- width: 100%;
-}
-
-.gitlab-button-wrapper {
- margin-top: 0.5rem;
- display: flex;
- align-items: baseline;
- /*
- this makes sure the hit enter to submit picks the correct button
- on the comment view
- */
- flex-direction: row-reverse;
-}
-
-.gitlab-collapse {
- width: 2.4rem;
- height: 2.2rem;
- margin-left: 1rem;
- padding: .5rem;
-}
-
-.gitlab-collapse-closed {
- align-self: center;
-}
-
-.gitlab-checkbox-label {
- padding: 0 .2rem;
-}
-
-.gitlab-checkbox-wrapper {
- display: flex;
- align-items: baseline;
-}
-
-.gitlab-form-open {
- padding: 1rem;
- background-color: #fafafa;
-}
-
-.gitlab-label {
- font-weight: 600;
- display: inline-block;
- width: 100%;
-}
-
-.gitlab-link {
- color: #1b69b6;
- text-decoration: none;
- background-color: transparent;
- background-image: none;
-}
-
-.gitlab-link:hover {
- text-decoration: underline;
-}
-
-.gitlab-link-button {
- border: none;
- cursor: pointer;
- padding: 0 .15rem;
-}
-
-.gitlab-message {
- padding: .25rem 0;
- margin: 0;
- line-height: 1.2rem;
-}
-
-.gitlab-metadata-note {
- font-size: .7rem;
- line-height: 1rem;
- color: #666;
- margin-bottom: .5rem;
-}
-
-.gitlab-input {
- width: 100%;
- border: 1px solid #dfdfdf;
- border-radius: 4px;
- padding: .1rem .2rem;
- min-height: 2rem;
- max-width: 17rem;
-}
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/csslab.scss b/app/assets/stylesheets/csslab.scss
deleted file mode 100644
index 87c59cd42c0..00000000000
--- a/app/assets/stylesheets/csslab.scss
+++ /dev/null
@@ -1 +0,0 @@
-@import "@gitlab/csslab/dist/css/csslab-slim";
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index e9218dcec67..b95978b6966 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -395,6 +395,7 @@ img.emoji {
.prepend-left-default { margin-left: $gl-padding; }
.prepend-left-20 { margin-left: 20px; }
.prepend-left-32 { margin-left: 32px; }
+.prepend-left-64 { margin-left: 64px; }
.append-right-4 { margin-right: 4px; }
.append-right-5 { margin-right: 5px; }
.append-right-8 { margin-right: 8px; }
@@ -402,6 +403,8 @@ img.emoji {
.append-right-15 { margin-right: 15px; }
.append-right-default { margin-right: $gl-padding; }
.append-right-20 { margin-right: 20px; }
+.append-right-32 { margin-right: 32px; }
+.append-right-48 { margin-right: 48px; }
.prepend-right-32 { margin-right: 32px; }
.append-bottom-0 { margin-bottom: 0; }
.append-bottom-4 { margin-bottom: $gl-padding-4; }
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 c201605e83d..33caac4d725 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -2,7 +2,7 @@
* Apply Markup (Markdown/AsciiDoc) typography
*
*/
-.md:not(.use-csslab) {
+.md {
color: $gl-text-color;
word-wrap: break-word;
@@ -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 9871771542d..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;
@@ -529,7 +530,7 @@ $award-emoji-width-xs: 90%;
*/
$search-input-border-color: rgba($blue-400, 0.8);
$search-input-width: 200px;
-$search-input-active-width: 320px;
+$search-input-xl-width: 320px;
$location-icon-color: #e7e9ed;
/*
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 343cca96851..e77a2d1e333 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -86,6 +86,9 @@
}
.board {
+ // the next line cannot be replaced with .d-inline-block because it breaks display: none of SortableJS
+ // see https://gitlab.com/gitlab-org/gitlab-ce/issues/64828
+ display: inline-block;
width: calc(85vw - 15px);
@include media-breakpoint-up(sm) {
diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss
index 255383d89c8..88d6b0d3746 100644
--- a/app/assets/stylesheets/pages/clusters.scss
+++ b/app/assets/stylesheets/pages/clusters.scss
@@ -154,3 +154,12 @@
}
}
}
+
+.cluster-deployments-warning {
+ color: $orange-600;
+}
+
+.badge.pods-badge {
+ color: $black;
+ font-weight: $gl-font-weight-bold;
+}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 77a2fd6b876..defa1a6c0d5 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -2,8 +2,14 @@
.diff-file {
margin-bottom: $gl-padding;
+ &.conflict {
+ border-top: 1px solid $border-color;
+ }
+
.file-title,
.file-title-flex-parent {
+ border-top-left-radius: $border-radius-default;
+ border-top-right-radius: $border-radius-default;
cursor: pointer;
@media (min-width: map-get($grid-breakpoints, md)) {
@@ -67,6 +73,28 @@
}
}
+ @media (min-width: map-get($grid-breakpoints, md)) {
+ &.conflict .file-title,
+ &.conflict .file-title-flex-parent {
+ top: $header-height;
+ }
+
+ .with-performance-bar &.conflict .file-title,
+ .with-performance-bar &.conflict .file-title-flex-parent {
+ top: $header-height + $performance-bar-height;
+ }
+
+ .with-system-header &.conflict .file-title,
+ .with-system-header &.conflict .file-title-flex-parent {
+ top: $header-height + $system-header-height;
+ }
+
+ .with-system-header.with-performance-bar &.conflict .file-title,
+ .with-system-header.with-performance-bar &.conflict .file-title-flex-parent {
+ top: $header-height + $performance-bar-height + $system-header-height;
+ }
+ }
+
.diff-content {
background: $white-light;
color: $gl-text-color;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 5f4db37c317..d4bd5b1b7dc 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -476,10 +476,6 @@
display: inline-block;
vertical-align: top;
- &:not(:last-child) {
- margin-right: 44px;
- }
-
&.left-margin {
&:not(:first-child) {
margin-left: 44px;
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 58e46cfb70f..2d2f0c531c7 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -45,8 +45,11 @@ 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;
+ }
&:hover {
box-shadow: none;
@@ -116,7 +119,7 @@ input[type='checkbox']:hover {
overflow: auto;
@include media-breakpoint-up(xl) {
- width: $search-input-active-width;
+ width: $search-input-xl-width;
}
}
@@ -131,10 +134,6 @@ input[type='checkbox']:hover {
border-color: $blue-300;
box-shadow: none;
- @include media-breakpoint-up(xl) {
- width: $search-input-active-width;
- }
-
.search-input-wrap {
.search-icon,
.clear-icon {
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 7b64c67ae34..ece0ac04baf 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -72,12 +72,7 @@
@include transition(opacity);
.todo-title {
- display: flex;
-
> .title-item {
- flex: 0 0 auto;
- margin: 0 2px;
-
&:first-child {
margin-left: 0;
}
@@ -105,8 +100,12 @@
font-size: 14px;
}
- .action-name {
- font-weight: $gl-font-weight-normal;
+ .todo-label,
+ .todo-project {
+ a {
+ color: $blue-600;
+ font-weight: $gl-font-weight-normal;
+ }
}
.todo-body {
@@ -170,7 +169,7 @@
}
}
-@include media-breakpoint-down(xs) {
+@include media-breakpoint-down(sm) {
.todo {
.avatar {
display: none;
@@ -179,7 +178,6 @@
.todo-item {
.todo-title {
- flex-flow: row wrap;
margin-bottom: 10px;
.todo-label {
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 d2a961efff7..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?
@@ -73,6 +73,11 @@ module NotesActions
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def update
@note = Notes::UpdateService.new(project, current_user, update_note_params).execute(note)
+ unless @note
+ head :gone
+ return
+ end
+
prepare_notes_for_rendering([@note])
respond_to do |format|
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/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 71f18694613..1dc89943f7f 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -70,6 +70,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
.new(params: finder_params, current_user: current_user)
.execute
.includes(:route, :creator, :group, namespace: [:route, :owner])
+ .preload(:project_feature)
.page(finder_params[:page])
prepare_projects_for_rendering(projects)
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/jwt_controller.rb b/app/controllers/jwt_controller.rb
index 5ecf4f114cf..da39d64c93d 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
class JwtController < ApplicationController
+ skip_around_action :set_session_storage
skip_before_action :authenticate_user!
skip_before_action :verify_authenticity_token
before_action :authenticate_project_or_user
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/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 65d9b074eee..13e8453ed00 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -6,7 +6,7 @@ class Projects::NotesController < Projects::ApplicationController
include NotesHelper
include ToggleAwardEmoji
- before_action :whitelist_query_limiting, only: [:create]
+ before_action :whitelist_query_limiting, only: [:create, :update]
before_action :authorize_read_note!
before_action :authorize_create_note!, only: [:create]
before_action :authorize_resolve_note!, only: [:resolve, :unresolve]
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index e6e3a440925..499d4918899 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -180,7 +180,7 @@ class Projects::PipelinesController < Projects::ApplicationController
else
project
.all_pipelines
- .includes(user: :status)
+ .includes(builds: :tags, user: :status)
.find_by!(id: params[:id])
.present(current_user: current_user)
end
diff --git a/app/controllers/projects/settings/operations_controller.rb b/app/controllers/projects/settings/operations_controller.rb
index 5cfb0ac307d..ec89bb89edc 100644
--- a/app/controllers/projects/settings/operations_controller.rb
+++ b/app/controllers/projects/settings/operations_controller.rb
@@ -3,7 +3,7 @@
module Projects
module Settings
class OperationsController < Projects::ApplicationController
- before_action :authorize_update_environment!
+ before_action :authorize_admin_operations!
helper_method :error_tracking_setting
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index e773ec09924..fb631f09f10 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -20,6 +20,7 @@ class RegistrationsController < Devise::RegistrationsController
super do |new_user|
persist_accepted_terms_if_required(new_user)
+ yield new_user if block_given?
end
rescue Gitlab::Access::AccessDeniedError
redirect_to(new_user_session_path)
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/group_projects_finder.rb b/app/finders/group_projects_finder.rb
index 4155b6af8da..5e0dbbfca2e 100644
--- a/app/finders/group_projects_finder.rb
+++ b/app/finders/group_projects_finder.rb
@@ -23,8 +23,12 @@ class GroupProjectsFinder < ProjectsFinder
attr_reader :group, :options
def initialize(group:, params: {}, options: {}, current_user: nil, project_ids_relation: nil)
- super(params: params, current_user: current_user, project_ids_relation: project_ids_relation)
- @group = group
+ super(
+ params: params,
+ current_user: current_user,
+ project_ids_relation: project_ids_relation
+ )
+ @group = group
@options = options
end
@@ -84,15 +88,13 @@ class GroupProjectsFinder < ProjectsFinder
options.fetch(:include_subgroups, false)
end
- # rubocop: disable CodeReuse/ActiveRecord
def owned_projects
if include_subgroups?
- Project.where(namespace_id: group.self_and_descendants.select(:id))
+ Project.for_group_and_its_subgroups(group)
else
group.projects
end
end
- # rubocop: enable CodeReuse/ActiveRecord
def shared_projects
group.shared_projects
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 1773ac2d508..8ed6ff56e2b 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -40,11 +40,11 @@ class IssuableFinder
requires_cross_project_access unless: -> { project? }
# This is used as a common filter for None / Any
- FILTER_NONE = 'none'.freeze
- FILTER_ANY = 'any'.freeze
+ FILTER_NONE = 'none'
+ FILTER_ANY = 'any'
# This is used in unassigning users
- NONE = '0'.freeze
+ NONE = '0'
attr_accessor :current_user, :params
@@ -193,15 +193,30 @@ class IssuableFinder
projects =
if current_user && params[:authorized_only].presence && !current_user_related?
current_user.authorized_projects(min_access_level)
- elsif group
- find_group_projects
else
- Project.public_or_visible_to_user(current_user, min_access_level)
+ projects_public_or_visible_to_user
end
@projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil) # rubocop: disable CodeReuse/ActiveRecord
end
+ def projects_public_or_visible_to_user
+ projects =
+ if group
+ if params[:projects]
+ find_group_projects.id_in(params[:projects])
+ else
+ find_group_projects
+ end
+ elsif params[:projects]
+ Project.id_in(params[:projects])
+ else
+ Project
+ end
+
+ projects.public_or_visible_to_user(current_user, min_access_level)
+ end
+
def find_group_projects
return Project.none unless group
@@ -209,7 +224,7 @@ class IssuableFinder
Project.where(namespace_id: group.self_and_descendants) # rubocop: disable CodeReuse/ActiveRecord
else
group.projects
- end.public_or_visible_to_user(current_user, min_access_level)
+ end
end
def search
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/finders/todos_finder.rb b/app/finders/todos_finder.rb
index d001e18fea9..ed6d20b9585 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -21,7 +21,7 @@ class TodosFinder
requires_cross_project_access unless: -> { project? }
- NONE = '0'.freeze
+ NONE = '0'
TODO_TYPES = Set.new(%w(Issue MergeRequest Epic)).freeze
diff --git a/app/graphql/functions/base_function.rb b/app/graphql/functions/base_function.rb
deleted file mode 100644
index 2512ecbd255..00000000000
--- a/app/graphql/functions/base_function.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-module Functions
- class BaseFunction < GraphQL::Function
- end
-end
diff --git a/app/graphql/functions/echo.rb b/app/graphql/functions/echo.rb
deleted file mode 100644
index 3104486faac..00000000000
--- a/app/graphql/functions/echo.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-module Functions
- class Echo < BaseFunction
- argument :text, GraphQL::STRING_TYPE
-
- description "Testing endpoint to validate the API with"
-
- def call(obj, args, ctx)
- username = ctx[:current_user]&.username
-
- "#{username.inspect} says: #{args[:text]}"
- end
- end
-end
diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb
index 7edd14e48f7..4c8612c8f2e 100644
--- a/app/graphql/gitlab_schema.rb
+++ b/app/graphql/gitlab_schema.rb
@@ -49,7 +49,7 @@ class GitlabSchema < GraphQL::Schema
def id_from_object(object)
unless object.respond_to?(:to_global_id)
# This is an error in our schema and needs to be solved. So raise a
- # more meaningfull error message
+ # more meaningful error message
raise "#{object} does not implement `to_global_id`. "\
"Include `GlobalID::Identification` into `#{object.class}"
end
diff --git a/app/graphql/mutations/notes/create/base.rb b/app/graphql/mutations/notes/create/base.rb
index d3a5dae2188..cf9f74a63d8 100644
--- a/app/graphql/mutations/notes/create/base.rb
+++ b/app/graphql/mutations/notes/create/base.rb
@@ -18,8 +18,6 @@ module Mutations
required: true,
description: copy_field_description(Types::Notes::NoteType, :body)
- private
-
def resolve(args)
noteable = authorized_find!(id: args[:noteable_id])
@@ -37,6 +35,8 @@ module Mutations
}
end
+ private
+
def create_note_params(noteable, args)
{
noteable: noteable,
diff --git a/app/graphql/resolvers/echo_resolver.rb b/app/graphql/resolvers/echo_resolver.rb
new file mode 100644
index 00000000000..8076e1784ce
--- /dev/null
+++ b/app/graphql/resolvers/echo_resolver.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class EchoResolver < BaseResolver
+ argument :text, GraphQL::STRING_TYPE, required: true
+ description 'Testing endpoint to validate the API with'
+
+ def resolve(**args)
+ username = context[:current_user]&.username
+
+ "#{username.inspect} says: #{args[:text]}"
+ end
+ end
+end
diff --git a/app/graphql/resolvers/full_path_resolver.rb b/app/graphql/resolvers/full_path_resolver.rb
index 972f318c806..2afd0411ea6 100644
--- a/app/graphql/resolvers/full_path_resolver.rb
+++ b/app/graphql/resolvers/full_path_resolver.rb
@@ -11,7 +11,7 @@ module Resolvers
end
def model_by_full_path(model, full_path)
- BatchLoader.for(full_path).batch(key: model) do |full_paths, loader, args|
+ BatchLoader::GraphQL.for(full_path).batch(key: model) do |full_paths, loader, args|
# `with_route` avoids an N+1 calculating full_path
args[:key].where_full_path_in(full_paths).with_route.each do |model_instance|
loader.call(model_instance.full_path, model_instance)
diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb
index 6988b451ec3..dd104e83f43 100644
--- a/app/graphql/resolvers/issues_resolver.rb
+++ b/app/graphql/resolvers/issues_resolver.rb
@@ -41,13 +41,11 @@ module Resolvers
type Types::IssueType, null: true
- alias_method :project, :object
-
def resolve(**args)
# The project could have been loaded in batch by `BatchLoader`.
# At this point we need the `id` of the project to query for issues, so
# make sure it's loaded and not `nil` before continuing.
- project.sync if project.respond_to?(:sync)
+ project = object.respond_to?(:sync) ? object.sync : object
return Issue.none if project.nil?
# Will need to be be made group & namespace aware with
diff --git a/app/graphql/resolvers/merge_requests_resolver.rb b/app/graphql/resolvers/merge_requests_resolver.rb
index b84e60066e1..1740d614b69 100644
--- a/app/graphql/resolvers/merge_requests_resolver.rb
+++ b/app/graphql/resolvers/merge_requests_resolver.rb
@@ -25,8 +25,10 @@ module Resolvers
# rubocop: disable CodeReuse/ActiveRecord
def batch_load(iid)
- BatchLoader.for(iid.to_s).batch(key: project) do |iids, loader, args|
- args[:key].merge_requests.where(iid: iids).each do |mr|
+ BatchLoader::GraphQL.for(iid.to_s).batch(key: project) do |iids, loader, args|
+ arg_key = args[:key].respond_to?(:sync) ? args[:key].sync : args[:key]
+
+ arg_key.merge_requests.where(iid: iids).each do |mr|
loader.call(mr.iid.to_s, mr)
end
end
diff --git a/app/graphql/resolvers/namespace_projects_resolver.rb b/app/graphql/resolvers/namespace_projects_resolver.rb
index 677ea808aeb..f5b60f91be6 100644
--- a/app/graphql/resolvers/namespace_projects_resolver.rb
+++ b/app/graphql/resolvers/namespace_projects_resolver.rb
@@ -9,13 +9,11 @@ module Resolvers
type Types::ProjectType, null: true
- alias_method :namespace, :object
-
def resolve(include_subgroups:)
# The namespace could have been loaded in batch by `BatchLoader`.
# At this point we need the `id` or the `full_path` of the namespace
# to query for projects, so make sure it's loaded and not `nil` before continuing.
- namespace.sync if namespace.respond_to?(:sync)
+ namespace = object.respond_to?(:sync) ? object.sync : object
return Project.none if namespace.nil?
if include_subgroups
diff --git a/app/graphql/types/permission_types/project.rb b/app/graphql/types/permission_types/project.rb
index e9a4ea9157b..993d33c4fc2 100644
--- a/app/graphql/types/permission_types/project.rb
+++ b/app/graphql/types/permission_types/project.rb
@@ -16,7 +16,7 @@ module Types
:create_deployment, :push_to_delete_protected_branch,
:admin_wiki, :admin_project, :update_pages,
:admin_remote_mirror, :create_label, :update_wiki, :destroy_wiki,
- :create_pages, :destroy_pages, :read_pages_content
+ :create_pages, :destroy_pages, :read_pages_content, :admin_operations
end
end
end
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index 53d36b43576..c686300b25d 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -24,6 +24,6 @@ module Types
resolver: Resolvers::MetadataResolver,
description: 'Metadata about GitLab'
- field :echo, GraphQL::STRING_TYPE, null: false, function: Functions::Echo.new
+ field :echo, GraphQL::STRING_TYPE, null: false, resolver: Resolvers::EchoResolver
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/boards_helper.rb b/app/helpers/boards_helper.rb
index bbe05f40999..8ef3ed9e8a5 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -10,7 +10,7 @@ module BoardsHelper
boards_endpoint: @boards_endpoint,
lists_endpoint: board_lists_path(board),
board_id: board.id,
- disabled: "#{!can?(current_user, :admin_list, current_board_parent)}",
+ disabled: (!can?(current_user, :create_non_backlog_issues, board)).to_s,
issue_link_base: build_issue_link_base,
root_path: root_path,
bulk_update_path: @bulk_issues_path,
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index f2b5b82b013..144df676304 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -105,14 +105,13 @@ module CiStatusHelper
path = pipelines_project_commit_path(project, commit, ref: ref)
render_status_with_link(
- 'commit',
commit.status(ref),
path,
tooltip_placement: tooltip_placement,
icon_size: 24)
end
- def render_status_with_link(type, status, path = nil, tooltip_placement: 'left', cssclass: '', container: 'body', icon_size: 16)
+ def render_status_with_link(status, path = nil, type: _('pipeline'), tooltip_placement: 'left', cssclass: '', container: 'body', icon_size: 16)
klass = "ci-status-link ci-status-icon-#{status.dasherize} d-inline-flex #{cssclass}"
title = "#{type.titleize}: #{ci_label_for_status(status)}"
data = { toggle: 'tooltip', placement: tooltip_placement, container: container }
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/external_link_helper.rb b/app/helpers/external_link_helper.rb
new file mode 100644
index 00000000000..9dbad1f5032
--- /dev/null
+++ b/app/helpers/external_link_helper.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module ExternalLinkHelper
+ def external_link(body, url, options = {})
+ link_to url, { target: '_blank', rel: 'noopener noreferrer' }.merge(options) do
+ "#{body} #{icon('external-link')}".html_safe
+ end
+ end
+end
diff --git a/app/helpers/jobs_helper.rb b/app/helpers/jobs_helper.rb
new file mode 100644
index 00000000000..46edba261dd
--- /dev/null
+++ b/app/helpers/jobs_helper.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module JobsHelper
+ def jobs_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
+ }
+ end
+end
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/notes_helper.rb b/app/helpers/notes_helper.rb
index 2e31a5e2ed4..4e88b379e16 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
module NotesHelper
+ MAX_PRERENDERED_NOTES = 10
+
def note_target_fields(note)
if note.noteable
hidden_field_tag(:target_type, note.noteable.class.name.underscore) +
@@ -169,7 +171,7 @@ module NotesHelper
closePath: close_issuable_path(issuable),
reopenPath: reopen_issuable_path(issuable),
notesPath: notes_url,
- totalNotes: issuable.discussions.length,
+ prerenderedNotesCount: issuable.capped_notes_count(MAX_PRERENDERED_NOTES),
lastFetchedAt: Time.now.to_i
}
end
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index 5678304ffcf..8855e0cdd70 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -106,9 +106,9 @@ module NotificationsHelper
end
end
- def notification_setting_icon(notification_setting)
+ def notification_setting_icon(notification_setting = nil)
sprite_icon(
- notification_setting.disabled? ? "notifications-off" : "notifications",
+ !notification_setting.present? || notification_setting.disabled? ? "notifications-off" : "notifications",
css_class: "icon notifications-icon js-notifications-icon"
)
end
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/error_tracking_helper.rb b/app/helpers/projects/error_tracking_helper.rb
index 6daf2e21ca2..fd1222a1dfb 100644
--- a/app/helpers/projects/error_tracking_helper.rb
+++ b/app/helpers/projects/error_tracking_helper.rb
@@ -1,12 +1,13 @@
# frozen_string_literal: true
module Projects::ErrorTrackingHelper
- def error_tracking_data(project)
+ def error_tracking_data(current_user, project)
error_tracking_enabled = !!project.error_tracking_setting&.enabled?
{
'index-path' => project_error_tracking_index_path(project,
format: :json),
+ 'user-can-enable-error-tracking' => can?(current_user, :admin_operations, project).to_s,
'enable-error-tracking-link' => project_settings_operations_path(project),
'error-tracking-enabled' => error_tracking_enabled.to_s,
'illustration-path' => image_path('illustrations/cluster_popover.svg')
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/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 38142bc68cb..f5333bb332e 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -33,7 +33,23 @@ module TodosHelper
todo.target_reference
end
- link_to text, todo_target_path(todo), class: 'has-tooltip', title: todo.target.title
+ link_to text, todo_target_path(todo)
+ end
+
+ def todo_target_title(todo)
+ if todo.target
+ "\"#{todo.target.title}\""
+ else
+ ""
+ end
+ end
+
+ def todo_parent_path(todo)
+ if todo.parent.is_a?(Group)
+ link_to todo.parent.name, group_path(todo.parent)
+ else
+ link_to_project(todo.project)
+ end
end
def todo_target_type_name(todo)
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/analytics/cycle_analytics/project_stage.rb b/app/models/analytics/cycle_analytics/project_stage.rb
index 88c8cb40ccb..a312bd24e78 100644
--- a/app/models/analytics/cycle_analytics/project_stage.rb
+++ b/app/models/analytics/cycle_analytics/project_stage.rb
@@ -3,7 +3,12 @@
module Analytics
module CycleAnalytics
class ProjectStage < ApplicationRecord
+ include Analytics::CycleAnalytics::Stage
+
+ validates :project, presence: true
belongs_to :project
+
+ alias_attribute :parent, :project
end
end
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..72782827906 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
@@ -87,6 +88,7 @@ module Ci
validates :coverage, numericality: true, allow_blank: true
validates :ref, presence: true
+ scope :not_interruptible, -> { joins(:metadata).where(ci_builds_metadata: { interruptible: false }) }
scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) }
scope :with_artifacts_archive, ->() do
@@ -444,7 +446,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/job_artifact.rb b/app/models/ci/job_artifact.rb
index e132cb045e2..b4497d8af09 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -87,6 +87,8 @@ module Ci
scope :expired, -> (limit) { where('expire_at < ?', Time.now).limit(limit) }
+ scope :scoped_project, -> { where('ci_job_artifacts.project_id = projects.id') }
+
delegate :filename, :exists?, :open, to: :file
enum file_type: {
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 0a943a33bbb..d2271c1335c 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -23,6 +23,7 @@ module Ci
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule'
belongs_to :merge_request, class_name: 'MergeRequest'
+ belongs_to :external_pull_request
has_internal_id :iid, scope: :project, presence: false, init: ->(s) do
s&.project&.all_pipelines&.maximum(:iid) || s&.project&.all_pipelines&.count
@@ -64,6 +65,11 @@ module Ci
validates :merge_request, presence: { if: :merge_request_event? }
validates :merge_request, absence: { unless: :merge_request_event? }
validates :tag, inclusion: { in: [false], if: :merge_request_event? }
+
+ validates :external_pull_request, presence: { if: :external_pull_request_event? }
+ validates :external_pull_request, absence: { unless: :external_pull_request_event? }
+ validates :tag, inclusion: { in: [false], if: :external_pull_request_event? }
+
validates :status, presence: { unless: :importing? }
validate :valid_commit_sha, unless: :importing?
validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create
@@ -203,6 +209,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)
@@ -224,6 +231,14 @@ module Ci
where('EXISTS (?)', ::Ci::Build.latest.with_reports(reports_scope).where('ci_pipelines.id=ci_builds.commit_id').select(1))
end
+ scope :without_interruptible_builds, -> do
+ where('NOT EXISTS (?)',
+ Ci::Build.where('ci_builds.commit_id = ci_pipelines.id')
+ .with_status(:running, :success, :failed)
+ .not_interruptible
+ )
+ end
+
# Returns the pipelines in descending order (= newest first), optionally
# limited to a number of references.
#
@@ -459,8 +474,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 +483,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,10 +684,15 @@ 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)
end
+
+ if external_pull_request_event? && external_pull_request
+ variables.concat(external_pull_request.predefined_variables)
+ end
end
end
@@ -771,10 +791,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 +831,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/pipeline_enums.rb b/app/models/ci/pipeline_enums.rb
index 571c4271475..0c2bd0aa8eb 100644
--- a/app/models/ci/pipeline_enums.rb
+++ b/app/models/ci/pipeline_enums.rb
@@ -23,7 +23,8 @@ module Ci
api: 5,
external: 6,
chat: 8,
- merge_request_event: 10
+ merge_request_event: 10,
+ external_pull_request_event: 11
}
end
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..ec65482a846 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'
@@ -85,7 +85,8 @@ module Clusters
"clientId" => oauth_application.uid,
"clientSecret" => oauth_application.secret,
"callbackUrl" => callback_url,
- "gitlabProjectIdWhitelist" => [project_id]
+ "gitlabProjectIdWhitelist" => cluster.projects.ids,
+ "gitlabGroupWhitelist" => cluster.groups.map(&:to_param)
}
},
"singleuser" => {
@@ -101,10 +102,6 @@ module Clusters
@crypto_key ||= SecureRandom.hex(32)
end
- def project_id
- cluster&.project&.id
- end
-
def gitlab_url
Gitlab.config.gitlab.url
end
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 6533b7a186e..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.7.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..a976093ac0c 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -10,18 +10,18 @@ module Clusters
self.table_name = 'clusters'
PROJECT_ONLY_APPLICATIONS = {
- Applications::Jupyter.application_name => Applications::Jupyter,
- Applications::Knative.application_name => Applications::Knative
}.freeze
APPLICATIONS = {
Applications::Helm.application_name => Applications::Helm,
Applications::Ingress.application_name => Applications::Ingress,
Applications::CertManager.application_name => Applications::CertManager,
+ Applications::Prometheus.application_name => Applications::Prometheus,
Applications::Runner.application_name => Applications::Runner,
- Applications::Prometheus.application_name => Applications::Prometheus
+ Applications::Jupyter.application_name => Applications::Jupyter,
+ Applications::Knative.application_name => Applications::Knative
}.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/commit.rb b/app/models/commit.rb
index 0889ce7e287..1470b50f396 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -35,6 +35,7 @@ class Commit
MIN_SHA_LENGTH = Gitlab::Git::Commit::MIN_SHA_LENGTH
COMMIT_SHA_PATTERN = /\h{#{MIN_SHA_LENGTH},40}/.freeze
+ EXACT_COMMIT_SHA_PATTERN = /\A#{COMMIT_SHA_PATTERN}\z/.freeze
# Used by GFM to match and present link extensions on node texts and hrefs.
LINK_EXTENSION_PATTERN = /(patch)/.freeze
@@ -90,7 +91,7 @@ class Commit
end
def valid_hash?(key)
- !!(/\A#{COMMIT_SHA_PATTERN}\z/ =~ key)
+ !!(EXACT_COMMIT_SHA_PATTERN =~ key)
end
def lazy(project, oid)
@@ -139,6 +140,10 @@ class Commit
'@'
end
+ def self.reference_valid?(reference)
+ !!(reference =~ EXACT_COMMIT_SHA_PATTERN)
+ end
+
# Pattern used to extract commit references from text
#
# This pattern supports cross-project references.
diff --git a/app/models/concerns/analytics/cycle_analytics/stage.rb b/app/models/concerns/analytics/cycle_analytics/stage.rb
new file mode 100644
index 00000000000..0c603c2d5e6
--- /dev/null
+++ b/app/models/concerns/analytics/cycle_analytics/stage.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module Analytics
+ module CycleAnalytics
+ module Stage
+ extend ActiveSupport::Concern
+
+ included do
+ validates :name, presence: true
+ validates :start_event_identifier, presence: true
+ validates :end_event_identifier, presence: true
+ validate :validate_stage_event_pairs
+
+ enum start_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents.to_enum, _prefix: :start_event_identifier
+ enum end_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents.to_enum, _prefix: :end_event_identifier
+
+ alias_attribute :custom_stage?, :custom
+ end
+
+ def parent=(_)
+ raise NotImplementedError
+ end
+
+ def parent
+ raise NotImplementedError
+ end
+
+ def start_event
+ Gitlab::Analytics::CycleAnalytics::StageEvents[start_event_identifier].new(params_for_start_event)
+ end
+
+ def end_event
+ Gitlab::Analytics::CycleAnalytics::StageEvents[end_event_identifier].new(params_for_end_event)
+ end
+
+ def params_for_start_event
+ {}
+ end
+
+ def params_for_end_event
+ {}
+ end
+
+ def default_stage?
+ !custom
+ end
+
+ # The model that is going to be queried, Issue or MergeRequest
+ def subject_model
+ start_event.object_type
+ end
+
+ private
+
+ def validate_stage_event_pairs
+ return if start_event_identifier.nil? || end_event_identifier.nil?
+
+ unless pairing_rules.fetch(start_event.class, []).include?(end_event.class)
+ errors.add(:end_event, :not_allowed_for_the_given_start_event)
+ end
+ end
+
+ def pairing_rules
+ Gitlab::Analytics::CycleAnalytics::StageEvents.pairing_rules
+ end
+ end
+ end
+end
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/ci/metadatable.rb b/app/models/concerns/ci/metadatable.rb
index 304cc71e9dc..a0ca8a34c6d 100644
--- a/app/models/concerns/ci/metadatable.rb
+++ b/app/models/concerns/ci/metadatable.rb
@@ -15,6 +15,7 @@ module Ci
autosave: true
delegate :timeout, to: :metadata, prefix: true, allow_nil: true
+ delegate :interruptible, to: :metadata, prefix: false, allow_nil: true
before_create :ensure_metadata
end
@@ -50,6 +51,14 @@ module Ci
write_metadata_attribute(:yaml_variables, :config_variables, value)
end
+ def interruptible
+ metadata&.interruptible
+ end
+
+ def interruptible=(value)
+ ensure_metadata.interruptible = value
+ end
+
private
def read_metadata_attribute(legacy_key, metadata_key, default_value = nil)
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 71ebb586c13..bcbbb27a9a8 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
@@ -102,6 +102,7 @@ module HasStatus
scope :manual, -> { with_status(:manual) }
scope :scheduled, -> { with_status(:scheduled) }
scope :alive, -> { with_status(:created, :preparing, :pending, :running) }
+ scope :alive_or_scheduled, -> { with_status(:created, :preparing, :pending, :running, :scheduled) }
scope :created_or_pending, -> { with_status(:created, :pending) }
scope :running_or_pending, -> { with_status(:running, :pending) }
scope :finished, -> { with_status(:success, :failed, :canceled) }
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 e60b6497cb7..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) }
@@ -186,16 +187,15 @@ module Issuable
def sort_by_attribute(method, excluded_labels: [])
sorted =
case method.to_s
- when 'downvotes_desc' then order_downvotes_desc
- when 'label_priority' then order_labels_priority(excluded_labels: excluded_labels)
- when 'label_priority_desc' then order_labels_priority('DESC', excluded_labels: excluded_labels)
- when 'milestone', 'milestone_due_asc' then order_milestone_due_asc
- when 'milestone_due_desc' then order_milestone_due_desc
- when 'popularity', 'popularity_desc' then order_upvotes_desc
- when 'popularity_asc' then order_upvotes_asc
- when 'priority', 'priority_asc' then order_due_date_and_labels_priority(excluded_labels: excluded_labels)
- when 'priority_desc' then order_due_date_and_labels_priority('DESC', excluded_labels: excluded_labels)
- when 'upvotes_desc' then order_upvotes_desc
+ when 'downvotes_desc' then order_downvotes_desc
+ when 'label_priority', 'label_priority_asc' then order_labels_priority(excluded_labels: excluded_labels)
+ when 'label_priority_desc' then order_labels_priority('DESC', excluded_labels: excluded_labels)
+ when 'milestone', 'milestone_due_asc' then order_milestone_due_asc
+ when 'milestone_due_desc' then order_milestone_due_desc
+ when 'popularity_asc' then order_upvotes_asc
+ when 'popularity', 'popularity_desc', 'upvotes_desc' then order_upvotes_desc
+ when 'priority', 'priority_asc' then order_due_date_and_labels_priority(excluded_labels: excluded_labels)
+ when 'priority_desc' then order_due_date_and_labels_priority('DESC', excluded_labels: excluded_labels)
else order_by(method)
end
@@ -277,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/noteable.rb b/app/models/concerns/noteable.rb
index 4b428b0af83..6a44bc7c401 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -73,6 +73,10 @@ module Noteable
.discussions(self)
end
+ def capped_notes_count(max)
+ notes.limit(max).count
+ end
+
def grouped_diff_discussions(*args)
# Doesn't use `discussion_notes`, because this may include commit diff notes
# besides MR diff notes, that we do not want to display on the MR Changes tab.
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..8b011bca72c 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
+ routable_calls_counter.increment(method: '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,18 @@ module Routable
def where_full_path_in(paths)
return none if paths.empty?
+ routable_calls_counter.increment(method: '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 routable_calls_counter
+ @routable_calls_counter ||= Gitlab::Metrics.counter(:gitlab_routable_calls_total, 'Number of calls to Routable by method')
end
end
diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb
index df1a9e3fe6e..c4af1b1fab2 100644
--- a/app/models/concerns/sortable.rb
+++ b/app/models/concerns/sortable.rb
@@ -27,14 +27,18 @@ module Sortable
def simple_sorts
{
'created_asc' => -> { order_created_asc },
+ 'created_at_asc' => -> { order_created_asc },
'created_date' => -> { order_created_desc },
'created_desc' => -> { order_created_desc },
+ 'created_at_desc' => -> { order_created_desc },
'id_asc' => -> { order_id_asc },
'id_desc' => -> { order_id_desc },
'name_asc' => -> { order_name_asc },
'name_desc' => -> { order_name_desc },
'updated_asc' => -> { order_updated_asc },
- 'updated_desc' => -> { order_updated_desc }
+ 'updated_at_asc' => -> { order_updated_asc },
+ 'updated_desc' => -> { order_updated_desc },
+ 'updated_at_desc' => -> { order_updated_desc }
}
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 33f0be91632..20e1d802178 100644
--- a/app/models/deploy_token.rb
+++ b/app/models/deploy_token.rb
@@ -5,10 +5,10 @@ class DeployToken < ApplicationRecord
include TokenAuthenticatable
include PolicyActor
include Gitlab::Utils::StrongMemoize
- add_authentication_token_field :token
+ 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/deployment.rb b/app/models/deployment.rb
index 68586e7a1fd..bff5d348ca0 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -162,6 +162,14 @@ class Deployment < ApplicationRecord
deployed_at&.to_time&.in_time_zone&.to_s(:medium)
end
+ def deployed_by
+ # We use deployable's user if available because Ci::PlayBuildService
+ # does not update the deployment's user, just the one for the deployable.
+ # TODO: use deployment's user once https://gitlab.com/gitlab-org/gitlab-ce/issues/66442
+ # is completed.
+ deployable&.user || user
+ end
+
private
def ref_path
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/external_pull_request.rb b/app/models/external_pull_request.rb
new file mode 100644
index 00000000000..65ae8d95500
--- /dev/null
+++ b/app/models/external_pull_request.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+# This model stores pull requests coming from external providers, such as
+# GitHub, when GitLab project is set as CI/CD only and remote mirror.
+#
+# When setting up a remote mirror with GitHub we subscribe to push and
+# pull_request webhook events. When a pull request is opened on GitHub,
+# a webhook is sent out, we create or update the status of the pull
+# request locally.
+#
+# When the mirror is updated and changes are pushed to branches we check
+# if there are open pull requests for the source and target branch.
+# If so, we create pipelines for external pull requests.
+class ExternalPullRequest < ApplicationRecord
+ include Gitlab::Utils::StrongMemoize
+ include ShaAttribute
+
+ belongs_to :project
+
+ sha_attribute :source_sha
+ sha_attribute :target_sha
+
+ validates :source_branch, presence: true
+ validates :target_branch, presence: true
+ validates :source_sha, presence: true
+ validates :target_sha, presence: true
+ validates :source_repository, presence: true
+ validates :target_repository, presence: true
+ validates :status, presence: true
+
+ enum status: {
+ open: 1,
+ closed: 2
+ }
+
+ # We currently don't support pull requests from fork, so
+ # we are going to return an error to the webhook
+ validate :not_from_fork
+
+ scope :by_source_branch, ->(branch) { where(source_branch: branch) }
+ scope :by_source_repository, -> (repository) { where(source_repository: repository) }
+
+ def self.create_or_update_from_params(params)
+ find_params = params.slice(:project_id, :source_branch, :target_branch)
+
+ safe_find_or_initialize_and_update(find: find_params, update: params) do |pull_request|
+ yield(pull_request) if block_given?
+ end
+ end
+
+ def actual_branch_head?
+ actual_source_branch_sha == source_sha
+ end
+
+ def from_fork?
+ source_repository != target_repository
+ end
+
+ def source_ref
+ Gitlab::Git::BRANCH_REF_PREFIX + source_branch
+ end
+
+ def predefined_variables
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_IID', value: pull_request_iid.to_s)
+ variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_SHA', value: source_sha)
+ variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_SHA', value: target_sha)
+ variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_NAME', value: source_branch)
+ variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME', value: target_branch)
+ end
+ end
+
+ private
+
+ def actual_source_branch_sha
+ project.commit(source_ref)&.sha
+ end
+
+ def not_from_fork
+ if from_fork?
+ errors.add(:base, 'Pull requests from fork are not supported')
+ end
+ end
+
+ def self.safe_find_or_initialize_and_update(find:, update:)
+ safe_ensure_unique(retries: 1) do
+ model = find_or_initialize_by(find)
+
+ if model.update(update)
+ yield(model) if block_given?
+ end
+
+ model
+ end
+ end
+end
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 c5a18f0af0f..75d4fc8c1c5 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -128,11 +128,10 @@ class Issue < ApplicationRecord
def self.sort_by_attribute(method, excluded_labels: [])
case method.to_s
- when 'closest_future_date' then order_closest_future_date
- when 'due_date' then order_due_date_asc
- when 'due_date_asc' then order_due_date_asc
- when 'due_date_desc' then order_due_date_desc
- when 'relative_position' then order_relative_position_asc.with_order_id_desc
+ when 'closest_future_date', 'closest_future_date_asc' then order_closest_future_date
+ when 'due_date', 'due_date_asc' then order_due_date_asc
+ when 'due_date_desc' then order_due_date_desc
+ when 'relative_position', 'relative_position_asc' then order_relative_position_asc.with_order_id_desc
else
super
end
@@ -179,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/lfs_object.rb b/app/models/lfs_object.rb
index 79a376ff0fd..40695a97d97 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -2,6 +2,7 @@
class LfsObject < ApplicationRecord
include AfterCommitQueue
+ include EachBatch
include ObjectStorage::BackgroundMove
has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
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..95daa48d4bc 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -174,6 +174,7 @@ class MergeRequest < ApplicationRecord
scope :from_project, ->(project) { where(source_project_id: project.id) }
scope :merged, -> { with_state(:merged) }
scope :closed_and_merged, -> { with_states(:closed, :merged) }
+ scope :open_and_closed, -> { with_states(:opened, :closed) }
scope :from_source_branches, ->(branches) { where(source_branch: branches) }
scope :by_commit_sha, ->(sha) do
where('EXISTS (?)', MergeRequestDiff.select(1).where('merge_requests.latest_merge_request_diff_id = merge_request_diffs.id').by_commit_sha(sha)).reorder(nil)
@@ -187,6 +188,11 @@ class MergeRequest < ApplicationRecord
target_project: [:route, { namespace: :route }],
source_project: [:route, { namespace: :route }])
}
+ scope :by_target_branch_wildcard, ->(wildcard_branch_name) do
+ where("target_branch LIKE ?", ApplicationRecord.sanitize_sql_like(wildcard_branch_name).tr('*', '%'))
+ end
+ scope :by_target_branch, ->(branch_name) { where(target_branch: branch_name) }
+ scope :preload_source_project, -> { preload(:source_project) }
after_save :keep_around_commit
@@ -1142,6 +1148,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
@@ -1228,9 +1238,9 @@ class MergeRequest < ApplicationRecord
compare_reports(Ci::CompareTestReportsService)
end
- def compare_reports(service_class)
+ def compare_reports(service_class, current_user = nil)
with_reactive_cache(service_class.name) do |data|
- unless service_class.new(project)
+ unless service_class.new(project, current_user)
.latest?(base_pipeline, actual_head_pipeline, data)
raise InvalidateReactiveCache
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 2c9dbf2585c..4db2b7a74e5 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
@@ -198,7 +197,7 @@ class MergeRequestDiff < ApplicationRecord
def lines_count
strong_memoize(:lines_count) do
- diffs.diff_files.sum(&:line_count)
+ raw_diffs(limits: false).line_count
end
end
@@ -223,6 +222,10 @@ class MergeRequestDiff < ApplicationRecord
commits.last
end
+ def last_commit
+ commits.first
+ end
+
def base_commit
return unless base_commit_sha
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 2ad2838111e..915978d37b8 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -16,6 +16,7 @@ class Milestone < ApplicationRecord
include Referable
include StripAttribute
include Milestoneish
+ include FromUnion
include Gitlab::SQL::Pattern
cache_markdown_field :title, pipeline: :single_line
@@ -24,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) }
@@ -59,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
@@ -253,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 10679fb1f85..d948410e397 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
@@ -287,6 +291,8 @@ class Project < ApplicationRecord
has_many :remote_mirrors, inverse_of: :project
has_many :cycle_analytics_stages, class_name: 'Analytics::CycleAnalytics::ProjectStage'
+ has_many :external_pull_requests, inverse_of: :project
+
accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature, update_only: true
accepts_nested_attributes_for :import_data
@@ -497,6 +503,7 @@ class Project < ApplicationRecord
# We require an alias to the project_mirror_data_table in order to use import_state in our queries
scope :joins_import_state, -> { joins("INNER JOIN project_mirror_data import_state ON import_state.project_id = projects.id") }
scope :for_group, -> (group) { where(group: group) }
+ scope :for_group_and_its_subgroups, ->(group) { where(namespace_id: group.self_and_descendants.select(:id)) }
class << self
# Searches for a list of projects based on the query given in `query`.
@@ -2175,8 +2182,7 @@ class Project < ApplicationRecord
hashed_storage?(:repository) &&
public? &&
repository_exists? &&
- Gitlab::CurrentSettings.hashed_storage_enabled &&
- Feature.enabled?(:object_pools, self, default_enabled: true)
+ Gitlab::CurrentSettings.hashed_storage_enabled
end
def leave_pool_repository
@@ -2191,6 +2197,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/protected_branch.rb b/app/models/protected_branch.rb
index ee0c94c20af..9fd929371f8 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -3,6 +3,9 @@
class ProtectedBranch < ApplicationRecord
include ProtectedRef
+ scope :requiring_code_owner_approval,
+ -> { where(code_owner_approval_required: true) }
+
protected_ref_access_levels :merge, :push
def self.protected_ref_accessible_to?(ref, user, project:, action:, protected_refs: nil)
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 240c91da5b6..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
@@ -186,9 +187,9 @@ class Todo < ApplicationRecord
def target_reference
if for_commit?
- target.reference_link_text(full: true)
+ target.reference_link_text
else
- target.to_reference(full: true)
+ target.to_reference
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 6131a8dc710..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
@@ -161,6 +162,8 @@ class User < ApplicationRecord
#
# Note: devise :validatable above adds validations for :email and :password
validates :name, presence: true, length: { maximum: 128 }
+ validates :first_name, length: { maximum: 255 }
+ validates :last_name, length: { maximum: 255 }
validates :email, confirmation: true
validates :notification_email, presence: true
validates :notification_email, devise_email: true, if: ->(user) { user.notification_email != user.email }
@@ -491,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
@@ -643,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'))
@@ -881,7 +891,15 @@ class User < ApplicationRecord
end
def first_name
- name.split.first unless name.blank?
+ read_attribute(:first_name) || begin
+ name.split(' ').first unless name.blank?
+ end
+ end
+
+ def last_name
+ read_attribute(:last_name) || begin
+ name.split(' ').drop(1).join(' ') unless name.blank?
+ end
end
def projects_limit_left
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/board_policy.rb b/app/policies/board_policy.rb
index 4bf1e7bd3e1..b8435dad3f1 100644
--- a/app/policies/board_policy.rb
+++ b/app/policies/board_policy.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class BoardPolicy < BasePolicy
+ include FindGroupProjects
+
delegate { @subject.parent }
condition(:is_group_board) { @subject.group_board? }
@@ -13,4 +15,20 @@ class BoardPolicy < BasePolicy
enable :read_milestone
enable :read_issue
end
+
+ condition(:reporter_of_group_projects) do
+ next unless @user
+
+ group_projects_for(user: @user, group: @subject.parent)
+ .visible_to_user_and_access_level(@user, ::Gitlab::Access::REPORTER)
+ .exists?
+ end
+
+ rule { is_group_board & reporter_of_group_projects }.policy do
+ enable :create_non_backlog_issues
+ end
+
+ rule { is_project_board & can?(:admin_issue) }.policy do
+ enable :create_non_backlog_issues
+ end
end
diff --git a/app/policies/concerns/find_group_projects.rb b/app/policies/concerns/find_group_projects.rb
new file mode 100644
index 00000000000..e2cb90079c7
--- /dev/null
+++ b/app/policies/concerns/find_group_projects.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module FindGroupProjects
+ extend ActiveSupport::Concern
+
+ def group_projects_for(user:, group:)
+ GroupProjectsFinder.new(
+ group: group,
+ current_user: user,
+ options: { include_subgroups: true, only_owned: true }
+ ).execute
+ end
+end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index 5d2b74b17a2..c726c7c24a7 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class GroupPolicy < BasePolicy
+ include FindGroupProjects
+
desc "Group is public"
with_options scope: :subject, score: 0
condition(:public_group) { @subject.public? }
@@ -22,7 +24,7 @@ class GroupPolicy < BasePolicy
condition(:can_change_parent_share_with_group_lock) { can?(:change_share_with_group_lock, @subject.parent) }
condition(:has_projects) do
- GroupProjectsFinder.new(group: @subject, current_user: @user, options: { include_subgroups: true, only_owned: true }).execute.any?
+ group_projects_for(user: @user, group: @subject).any?
end
with_options scope: :subject, score: 0
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/policies/project_policy.rb b/app/policies/project_policy.rb
index b8dee1b0789..5c36b59f07b 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -294,6 +294,7 @@ class ProjectPolicy < BasePolicy
enable :destroy_release
enable :destroy_artifacts
enable :daily_statistics
+ enable :admin_operations
end
rule { (mirror_available & can?(:admin_project)) | admin }.enable :admin_remote_mirror
@@ -502,6 +503,8 @@ class ProjectPolicy < BasePolicy
end
def feature_available?(feature)
+ return false unless project.project_feature
+
case project.project_feature.access_level(feature)
when ProjectFeature::DISABLED
false
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/build_runner_presenter.rb b/app/presenters/ci/build_runner_presenter.rb
index b928988ed8c..5231a8efa55 100644
--- a/app/presenters/ci/build_runner_presenter.rb
+++ b/app/presenters/ci/build_runner_presenter.rb
@@ -4,8 +4,8 @@ module Ci
class BuildRunnerPresenter < SimpleDelegator
include Gitlab::Utils::StrongMemoize
- RUNNER_REMOTE_TAG_PREFIX = 'refs/tags/'.freeze
- RUNNER_REMOTE_BRANCH_PREFIX = 'refs/remotes/origin/'.freeze
+ RUNNER_REMOTE_TAG_PREFIX = 'refs/tags/'
+ RUNNER_REMOTE_BRANCH_PREFIX = 'refs/remotes/origin/'
def artifacts
return unless options[:artifacts]
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 d6466ad1cdd..94a827658f0 100644
--- a/app/serializers/deployment_entity.rb
+++ b/app/serializers/deployment_entity.rb
@@ -21,9 +21,9 @@ class DeploymentEntity < Grape::Entity
expose :deployed_at
expose :tag
expose :last?
- expose :user, using: UserEntity
+ 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..9504fdd8eac
--- /dev/null
+++ b/app/serializers/merge_request_noteable_entity.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+class MergeRequestNoteableEntity < IssuableEntity
+ 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 471df6e2d0c..e06a87c4763 100644
--- a/app/services/application_settings/update_service.rb
+++ b/app/services/application_settings/update_service.rb
@@ -6,8 +6,10 @@ 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)
+ validate_classification_label(application_setting, :external_authorization_service_default_label) unless bypass_external_auth?
if application_setting.errors.any?
return false
@@ -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?
@@ -59,5 +72,9 @@ module ApplicationSettings
Group.find_by_full_path(group_full_path)&.id if group_full_path.present?
end
+
+ def bypass_external_auth?
+ params.key?(:external_authorization_service_enabled) && !Gitlab::Utils.to_boolean(params[:external_authorization_service_enabled])
+ end
end
end
diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb
index 73f3408a240..22ade0b0658 100644
--- a/app/services/audit_event_service.rb
+++ b/app/services/audit_event_service.rb
@@ -21,6 +21,10 @@ class AuditEventService
log_security_event_to_database
end
+ def log_security_event_to_file
+ file_logger.info(base_payload.merge(formatted_details))
+ end
+
private
def base_payload
@@ -39,10 +43,6 @@ class AuditEventService
@details.merge(@details.slice(:from, :to).transform_values(&:to_s))
end
- def log_security_event_to_file
- file_logger.info(base_payload.merge(formatted_details))
- end
-
def log_security_event_to_database
SecurityEvent.create(base_payload.merge(details: @details))
end
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index 0a069320936..9c210f3a1f5 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -2,7 +2,7 @@
module Auth
class ContainerRegistryAuthenticationService < BaseService
- AUDIENCE = 'container_registry'.freeze
+ AUDIENCE = 'container_registry'
def execute(authentication_abilities:)
@authentication_abilities = authentication_abilities
@@ -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/auto_merge_service.rb b/app/services/auto_merge_service.rb
index 95bf2db2018..053c6d71fbb 100644
--- a/app/services/auto_merge_service.rb
+++ b/app/services/auto_merge_service.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class AutoMergeService < BaseService
- STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS = 'merge_when_pipeline_succeeds'.freeze
+ STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS = 'merge_when_pipeline_succeeds'
STRATEGIES = [STRATEGY_MERGE_WHEN_PIPELINE_SUCCEEDS].freeze
class << self
diff --git a/app/services/base_count_service.rb b/app/services/base_count_service.rb
index ad1647842b8..cfad2dd9265 100644
--- a/app/services/base_count_service.rb
+++ b/app/services/base_count_service.rb
@@ -35,7 +35,7 @@ class BaseCountService
end
def cache_key
- raise NotImplementedError, 'cache_key must be implemented and return a String'
+ raise NotImplementedError, 'cache_key must be implemented and return a String, Array, or Hash'
end
# subclasses can override to add any specific options, such as
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..4a7f62de9e1 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -15,9 +15,11 @@ 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)
+ # rubocop: disable Metrics/ParameterLists
+ def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, external_pull_request: nil, **options, &block)
@pipeline = Ci::Pipeline.new
command = Gitlab::Ci::Pipeline::Chain::Command.new(
@@ -31,6 +33,7 @@ module Ci
trigger_request: trigger_request,
schedule: schedule,
merge_request: merge_request,
+ external_pull_request: external_pull_request,
ignore_skip_ci: ignore_skip_ci,
save_incompleted: save_on_errors,
seeds_block: block,
@@ -61,6 +64,7 @@ module Ci
pipeline
end
+ # rubocop: enable Metrics/ParameterLists
def execute!(*args, &block)
execute(*args, &block).tap do |pipeline|
@@ -90,11 +94,21 @@ module Ci
# rubocop: disable CodeReuse/ActiveRecord
def auto_cancelable_pipelines
- project.ci_pipelines
- .where(ref: pipeline.ref)
- .where.not(id: pipeline.id)
- .where.not(sha: project.commit(pipeline.ref).try(:id))
- .created_or_pending
+ # TODO: Introduced by https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23464
+ if Feature.enabled?(:ci_support_interruptible_pipelines, project, default_enabled: true)
+ project.ci_pipelines
+ .where(ref: pipeline.ref)
+ .where.not(id: pipeline.id)
+ .where.not(sha: project.commit(pipeline.ref).try(:id))
+ .alive_or_scheduled
+ .without_interruptible_builds
+ else
+ project.ci_pipelines
+ .where(ref: pipeline.ref)
+ .where.not(id: pipeline.id)
+ .where.not(sha: project.commit(pipeline.ref).try(:id))
+ .created_or_pending
+ end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/clusters/applications/check_installation_progress_service.rb b/app/services/clusters/applications/check_installation_progress_service.rb
index 3c6803d24e6..1ce6e0c1cb0 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
@@ -58,14 +37,6 @@ module Clusters
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..fe9c488bdfd 100644
--- a/app/services/clusters/applications/check_uninstall_progress_service.rb
+++ b/app/services/clusters/applications/check_uninstall_progress_service.rb
@@ -2,37 +2,20 @@
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!
rescue StandardError => e
app.make_errored!(_('Application uninstalled but failed to destroy: %{error_message}') % { error_message: e.message })
ensure
- 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 })
+ remove_uninstallation_pod
end
def check_timeout
@@ -51,13 +34,9 @@ module Clusters
Time.now.utc - app.updated_at.utc > WaitForUninstallAppWorker::TIMEOUT
end
- def remove_installation_pod
+ def remove_uninstallation_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/external_pull_requests/create_pipeline_service.rb b/app/services/external_pull_requests/create_pipeline_service.rb
new file mode 100644
index 00000000000..36411465ff1
--- /dev/null
+++ b/app/services/external_pull_requests/create_pipeline_service.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+# This service is responsible for creating a pipeline for a given
+# ExternalPullRequest coming from other providers such as GitHub.
+
+module ExternalPullRequests
+ class CreatePipelineService < BaseService
+ def execute(pull_request)
+ return unless pull_request.open? && pull_request.actual_branch_head?
+
+ create_pipeline_for(pull_request)
+ end
+
+ private
+
+ def create_pipeline_for(pull_request)
+ Ci::CreatePipelineService.new(project, current_user, create_params(pull_request))
+ .execute(:external_pull_request_event, external_pull_request: pull_request)
+ end
+
+ def create_params(pull_request)
+ {
+ ref: pull_request.source_ref,
+ source_sha: pull_request.source_sha,
+ target_sha: pull_request.target_sha
+ }
+ end
+ end
+end
diff --git a/app/services/git/base_hooks_service.rb b/app/services/git/base_hooks_service.rb
index 47c308c8280..35a4d2015fa 100644
--- a/app/services/git/base_hooks_service.rb
+++ b/app/services/git/base_hooks_service.rb
@@ -57,7 +57,9 @@ module Git
Ci::CreatePipelineService
.new(project, current_user, pipeline_params)
- .execute(:push, pipeline_options)
+ .execute!(:push, pipeline_options)
+ rescue Ci::CreatePipelineService::CreateError => ex
+ log_pipeline_errors(ex)
end
def execute_project_hooks
@@ -125,5 +127,29 @@ module Git
project.mark_stuck_remote_mirrors_as_failed!
project.update_remote_mirrors
end
+
+ def log_pipeline_errors(exception)
+ data = {
+ class: self.class.name,
+ correlation_id: Labkit::Correlation::CorrelationId.current_id.to_s,
+ project_id: project.id,
+ project_path: project.full_path,
+ message: "Error creating pipeline",
+ errors: exception.to_s,
+ pipeline_params: pipeline_params
+ }
+
+ logger.warn(data)
+ end
+
+ def logger
+ if Sidekiq.server?
+ Sidekiq.logger
+ else
+ # This service runs in Sidekiq, so this shouldn't ever be
+ # called, but this is included just in case.
+ Gitlab::ProjectServiceLogger
+ end
+ end
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/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 2ab6e88599f..900e5063621 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -76,13 +76,16 @@ class IssuableBaseService < BaseService
end
def filter_labels
- params[:add_label_ids] = labels_service.filter_labels_ids_in_param(:add_label_ids) if params[:add_label_ids]
- params[:remove_label_ids] = labels_service.filter_labels_ids_in_param(:remove_label_ids) if params[:remove_label_ids]
+ label_ids_to_filter(:add_label_ids, :add_labels, false)
+ label_ids_to_filter(:remove_label_ids, :remove_labels, true)
+ label_ids_to_filter(:label_ids, :labels, false)
+ end
- if params[:label_ids]
- params[:label_ids] = labels_service.filter_labels_ids_in_param(:label_ids)
- elsif params[:labels]
- params[:label_ids] = labels_service.find_or_create_by_titles.map(&:id)
+ def label_ids_to_filter(label_id_key, label_key, find_only)
+ if params[label_id_key]
+ params[label_id_key] = labels_service.filter_labels_ids_in_param(label_id_key)
+ elsif params[label_key]
+ params[label_id_key] = labels_service.find_or_create_by_titles(label_key, find_only: find_only).map(&:id)
end
end
@@ -221,6 +224,7 @@ class IssuableBaseService < BaseService
# We have to perform this check before saving the issuable as Rails resets
# the changed fields upon calling #save.
update_project_counters = issuable.project && update_project_counter_caches?(issuable)
+ ensure_milestone_available(issuable)
if issuable.with_transaction_returning_status { issuable.save(touch: should_touch) }
# We do not touch as it will affect a update on updated_at field
diff --git a/app/services/labels/available_labels_service.rb b/app/services/labels/available_labels_service.rb
index fe477d96970..8886e58d6ef 100644
--- a/app/services/labels/available_labels_service.rb
+++ b/app/services/labels/available_labels_service.rb
@@ -9,8 +9,8 @@ module Labels
@params = params
end
- def find_or_create_by_titles
- labels = params.delete(:labels)
+ def find_or_create_by_titles(key = :labels, find_only: false)
+ labels = params.delete(key)
return [] unless labels
@@ -23,7 +23,7 @@ module Labels
include_ancestor_groups: true,
title: label_name.strip,
available_labels: available_labels
- ).execute
+ ).execute(find_only: find_only)
label
end.compact
diff --git a/app/services/labels/find_or_create_service.rb b/app/services/labels/find_or_create_service.rb
index 628873519d7..a47dd42aea0 100644
--- a/app/services/labels/find_or_create_service.rb
+++ b/app/services/labels/find_or_create_service.rb
@@ -9,9 +9,9 @@ module Labels
@params = params.dup.with_indifferent_access
end
- def execute(skip_authorization: false)
+ def execute(skip_authorization: false, find_only: false)
@skip_authorization = skip_authorization
- find_or_create_label
+ find_or_create_label(find_only: find_only)
end
private
@@ -30,9 +30,11 @@ module Labels
# Only creates the label if current_user can do so, if the label does not exist
# and the user can not create the label, nil is returned
# rubocop: disable CodeReuse/ActiveRecord
- def find_or_create_label
+ def find_or_create_label(find_only: false)
new_label = available_labels.find_by(title: title)
+ return new_label if find_only
+
if new_label.nil? && (skip_authorization || Ability.allowed?(current_user, :admin_label, parent))
create_params = params.except(:include_ancestor_groups)
new_label = Labels::CreateService.new(create_params).execute(parent_type.to_sym => parent)
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index b28f80939ae..308a3a10d1a 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -18,6 +18,18 @@ module MergeRequests
merge_request.target_project = find_target_project
filter_params(merge_request)
+
+ # merge_request.assign_attributes(...) below is a Rails
+ # method that only work if all the params it is passed have
+ # corresponding fields in the database. As there are no fields
+ # in the database for :add_label_ids and :remove_label_ids, we
+ # need to remove them from the params before the call to
+ # merge_request.assign_attributes(...)
+ #
+ # IssuableBaseService#process_label_ids takes care
+ # of the removal.
+ params[:label_ids] = process_label_ids(params, extra_label_ids: merge_request.label_ids.to_a)
+
merge_request.assign_attributes(params.to_h.compact)
merge_request.compare_commits = []
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index 06e46595b95..a69678a4422 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -27,6 +27,7 @@ module MergeRequests
issuable.cache_merge_request_closes_issues!(current_user)
create_pipeline_for(issuable, current_user)
issuable.update_head_pipeline
+ Gitlab::UsageDataCounters::MergeRequestCounter.count(:create)
super
end
diff --git a/app/services/merge_requests/push_options_handler_service.rb b/app/services/merge_requests/push_options_handler_service.rb
index b210004e6e1..0168b31005e 100644
--- a/app/services/merge_requests/push_options_handler_service.rb
+++ b/app/services/merge_requests/push_options_handler_service.rb
@@ -100,7 +100,8 @@ module MergeRequests
merge_request = ::MergeRequests::CreateService.new(
project,
current_user,
- merge_request.attributes.merge(assignees: merge_request.assignees)
+ merge_request.attributes.merge(assignees: merge_request.assignees,
+ label_ids: merge_request.label_ids)
).execute
end
@@ -122,7 +123,9 @@ module MergeRequests
title: push_options[:title],
description: push_options[:description],
target_branch: push_options[:target],
- force_remove_source_branch: push_options[:remove_source_branch]
+ force_remove_source_branch: push_options[:remove_source_branch],
+ label: push_options[:label],
+ unlabel: push_options[:unlabel]
}
params.compact!
@@ -134,6 +137,9 @@ module MergeRequests
)
end
+ params[:add_labels] = params.delete(:label).keys if params.has_key?(:label)
+ params[:remove_labels] = params.delete(:unlabel).keys if params.has_key?(:unlabel)
+
params
end
diff --git a/app/services/merge_requests/rebase_service.rb b/app/services/merge_requests/rebase_service.rb
index 27c16ba1777..4d36dd4feae 100644
--- a/app/services/merge_requests/rebase_service.rb
+++ b/app/services/merge_requests/rebase_service.rb
@@ -2,7 +2,7 @@
module MergeRequests
class RebaseService < MergeRequests::WorkingCopyBaseService
- REBASE_ERROR = 'Rebase failed. Please rebase locally'.freeze
+ REBASE_ERROR = 'Rebase failed. Please rebase locally'
def execute(merge_request)
@merge_request = merge_request
diff --git a/app/services/milestones/find_or_create_service.rb b/app/services/milestones/find_or_create_service.rb
new file mode 100644
index 00000000000..881011e5106
--- /dev/null
+++ b/app/services/milestones/find_or_create_service.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Milestones
+ class FindOrCreateService
+ attr_accessor :project, :current_user, :params
+
+ def initialize(project, user, params = {})
+ @project, @current_user, @params = project, user, params.dup
+ end
+
+ def execute
+ find_milestone || create_milestone
+ end
+
+ private
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def find_milestone
+ groups = project.group&.self_and_ancestors_ids
+ Milestone.for_projects_and_groups([project.id], groups).find_by(title: params["title"])
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def create_milestone
+ return unless current_user.can?(:admin_milestone, project)
+
+ new_milestone if new_milestone.persisted?
+ end
+
+ def new_milestone
+ @new_milestone ||= CreateService.new(project, current_user, params).execute
+ end
+ end
+end
diff --git a/app/services/milestones/transfer_service.rb b/app/services/milestones/transfer_service.rb
new file mode 100644
index 00000000000..1efbfed4853
--- /dev/null
+++ b/app/services/milestones/transfer_service.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+# Milestones::TransferService class
+#
+# Used for recreating the missing group milestones at project level when
+# transferring a project to a new namespace
+#
+module Milestones
+ class TransferService
+ attr_reader :current_user, :old_group, :project
+
+ def initialize(current_user, old_group, project)
+ @current_user = current_user
+ @old_group = old_group
+ @project = project
+ end
+
+ def execute
+ return unless old_group.present?
+
+ Milestone.transaction do
+ milestones_to_transfer.find_each do |milestone|
+ new_milestone = find_or_create_milestone(milestone)
+
+ update_issues_milestone(milestone.id, new_milestone&.id)
+ update_merge_requests_milestone(milestone.id, new_milestone&.id)
+ end
+ end
+ end
+
+ private
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def milestones_to_transfer
+ Milestone.from_union([
+ group_milestones_applied_to_issues,
+ group_milestones_applied_to_merge_requests
+ ])
+ .reorder(nil)
+ .distinct
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def group_milestones_applied_to_issues
+ Milestone.joins(:issues)
+ .where(
+ issues: { project_id: project.id },
+ group_id: old_group.id
+ )
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def group_milestones_applied_to_merge_requests
+ Milestone.joins(:merge_requests)
+ .where(
+ merge_requests: { target_project_id: project.id },
+ group_id: old_group.id
+ )
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def find_or_create_milestone(milestone)
+ params = milestone.attributes.slice('title', 'description', 'start_date', 'due_date')
+
+ FindOrCreateService.new(project, current_user, params).execute
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def update_issues_milestone(old_milestone_id, new_milestone_id)
+ Issue.where(project: project, milestone_id: old_milestone_id)
+ .update_all(milestone_id: new_milestone_id)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def update_merge_requests_milestone(old_milestone_id, new_milestone_id)
+ MergeRequest.where(project: project, milestone_id: old_milestone_id)
+ .update_all(milestone_id: new_milestone_id)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+end
diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb
index 384d1dd2e50..853faed9d85 100644
--- a/app/services/notes/update_service.rb
+++ b/app/services/notes/update_service.rb
@@ -8,24 +8,70 @@ module Notes
old_mentioned_users = note.mentioned_users.to_a
note.update(params.merge(updated_by: current_user))
- note.create_new_cross_references!(current_user)
- if note.previous_changes.include?('note')
- TodoService.new.update_note(note, current_user, old_mentioned_users)
+ only_commands = false
+
+ quick_actions_service = QuickActionsService.new(project, current_user)
+ if quick_actions_service.supported?(note)
+ content, update_params, message = quick_actions_service.execute(note, {})
+
+ only_commands = content.empty?
+
+ note.note = content
+ end
+
+ unless only_commands
+ note.create_new_cross_references!(current_user)
+
+ update_todos(note, old_mentioned_users)
+
+ update_suggestions(note)
end
- if note.supports_suggestion?
- Suggestion.transaction do
- note.suggestions.delete_all
- Suggestions::CreateService.new(note).execute
+ if quick_actions_service.commands_executed_count.to_i > 0
+ if update_params.present?
+ quick_actions_service.apply_updates(update_params, note)
+ note.commands_changes = update_params
end
- # We need to refresh the previous suggestions call cache
- # in order to get the new records.
- note.reset
+ if only_commands
+ delete_note(note, message)
+ note = nil
+ else
+ note.save
+ end
end
note
end
+
+ private
+
+ def delete_note(note, message)
+ # We must add the error after we call #save because errors are reset
+ # when #save is called
+ note.errors.add(:commands_only, message.presence || _('Commands did not apply'))
+
+ Notes::DestroyService.new(project, current_user).execute(note)
+ end
+
+ def update_suggestions(note)
+ return unless note.supports_suggestion?
+
+ Suggestion.transaction do
+ note.suggestions.delete_all
+ Suggestions::CreateService.new(note).execute
+ end
+
+ # We need to refresh the previous suggestions call cache
+ # in order to get the new records.
+ note.reset
+ end
+
+ def update_todos(note, old_mentioned_users)
+ return unless note.previous_changes.include?('note')
+
+ TodoService.new.update_note(note, current_user, old_mentioned_users)
+ 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/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 5893b8eedff..108c4a79cde 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -6,7 +6,7 @@ module Projects
DestroyError = Class.new(StandardError)
- DELETED_FLAG = '+deleted'.freeze
+ DELETED_FLAG = '+deleted'
REPO_REMOVAL_DELAY = 5.minutes.to_i
def async_execute
diff --git a/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb b/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb
index 9b72480d18b..5ef7e03ea02 100644
--- a/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb
+++ b/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb
@@ -5,7 +5,7 @@
module Projects
module LfsPointers
class LfsDownloadLinkListService < BaseService
- DOWNLOAD_ACTION = 'download'.freeze
+ DOWNLOAD_ACTION = 'download'
DownloadLinksError = Class.new(StandardError)
DownloadLinkNotFound = Class.new(StandardError)
diff --git a/app/services/projects/lfs_pointers/lfs_link_service.rb b/app/services/projects/lfs_pointers/lfs_link_service.rb
index e3c956250f0..38de2af9c1e 100644
--- a/app/services/projects/lfs_pointers/lfs_link_service.rb
+++ b/app/services/projects/lfs_pointers/lfs_link_service.rb
@@ -4,6 +4,8 @@
module Projects
module LfsPointers
class LfsLinkService < BaseService
+ BATCH_SIZE = 1000
+
# Accept an array of oids to link
#
# Returns an array with the oid of the existent lfs objects
@@ -18,16 +20,33 @@ module Projects
# rubocop: disable CodeReuse/ActiveRecord
def link_existing_lfs_objects(oids)
- existent_lfs_objects = LfsObject.where(oid: oids)
+ all_existing_objects = []
+ iterations = 0
+
+ LfsObject.where(oid: oids).each_batch(of: BATCH_SIZE) do |existent_lfs_objects|
+ next unless existent_lfs_objects.any?
+
+ iterations += 1
+ not_linked_lfs_objects = existent_lfs_objects.where.not(id: project.all_lfs_objects)
+ project.all_lfs_objects << not_linked_lfs_objects
- return [] unless existent_lfs_objects.any?
+ all_existing_objects += existent_lfs_objects.pluck(:oid)
+ end
- not_linked_lfs_objects = existent_lfs_objects.where.not(id: project.all_lfs_objects)
- project.all_lfs_objects << not_linked_lfs_objects
+ log_lfs_link_results(all_existing_objects.count, iterations)
- existent_lfs_objects.pluck(:oid)
+ all_existing_objects
end
# rubocop: enable CodeReuse/ActiveRecord
+
+ def log_lfs_link_results(lfs_objects_linked_count, iterations)
+ Gitlab::Import::Logger.info(
+ class: self.class.name,
+ project_id: project.id,
+ project_path: project.full_path,
+ lfs_objects_linked_count: lfs_objects_linked_count,
+ iterations: iterations)
+ end
end
end
end
diff --git a/app/services/projects/lfs_pointers/lfs_list_service.rb b/app/services/projects/lfs_pointers/lfs_list_service.rb
index 22160017f4f..a07fa93a279 100644
--- a/app/services/projects/lfs_pointers/lfs_list_service.rb
+++ b/app/services/projects/lfs_pointers/lfs_list_service.rb
@@ -4,7 +4,7 @@
module Projects
module LfsPointers
class LfsListService < BaseService
- REV = 'HEAD'.freeze
+ REV = 'HEAD'
# Retrieve all lfs blob pointers and returns a hash
# with the structure { lfs_file_oid => lfs_file_size }
diff --git a/app/services/projects/lfs_pointers/lfs_object_download_list_service.rb b/app/services/projects/lfs_pointers/lfs_object_download_list_service.rb
index 5ba0f50f2ff..4224b56c42c 100644
--- a/app/services/projects/lfs_pointers/lfs_object_download_list_service.rb
+++ b/app/services/projects/lfs_pointers/lfs_object_download_list_service.rb
@@ -8,9 +8,9 @@ module Projects
class LfsObjectDownloadListService < BaseService
include Gitlab::Utils::StrongMemoize
- HEAD_REV = 'HEAD'.freeze
+ HEAD_REV = 'HEAD'
LFS_ENDPOINT_PATTERN = /^\t?url\s*=\s*(.+)$/.freeze
- LFS_BATCH_API_ENDPOINT = '/info/lfs/objects/batch'.freeze
+ LFS_BATCH_API_ENDPOINT = '/info/lfs/objects/batch'
LfsObjectDownloadListError = Class.new(StandardError)
diff --git a/app/services/projects/open_issues_count_service.rb b/app/services/projects/open_issues_count_service.rb
index ee9884e9042..bc8f7f342f7 100644
--- a/app/services/projects/open_issues_count_service.rb
+++ b/app/services/projects/open_issues_count_service.rb
@@ -7,8 +7,8 @@ module Projects
include Gitlab::Utils::StrongMemoize
# Cache keys used to store issues count
- PUBLIC_COUNT_KEY = 'public_open_issues_count'.freeze
- TOTAL_COUNT_KEY = 'total_open_issues_count'.freeze
+ PUBLIC_COUNT_KEY = 'public_open_issues_count'
+ TOTAL_COUNT_KEY = 'total_open_issues_count'
def initialize(project, user = nil)
@user = user
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 233dcf37e35..078a751025f 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -72,6 +72,9 @@ module Projects
# Move missing group labels to project
Labels::TransferService.new(current_user, @old_group, project).execute
+ # Move missing group milestones
+ Milestones::TransferService.new(current_user, @old_group, project).execute
+
# Move uploads
move_project_uploads(project)
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index 5caeb4cfa5f..fa7a4f0ed82 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -7,11 +7,11 @@ module Projects
BLOCK_SIZE = 32.kilobytes
MAX_SIZE = 1.terabyte
- PUBLIC_DIR = 'public'.freeze
+ PUBLIC_DIR = 'public'
# this has to be invalid group name,
# as it shares the namespace with groups
- TMP_EXTRACT_PATH = '@pages.tmp'.freeze
+ TMP_EXTRACT_PATH = '@pages.tmp'
attr_reader :build
diff --git a/app/services/protected_branches/create_service.rb b/app/services/protected_branches/create_service.rb
index 87aaf4672a4..6b2836bba39 100644
--- a/app/services/protected_branches/create_service.rb
+++ b/app/services/protected_branches/create_service.rb
@@ -5,7 +5,8 @@ module ProtectedBranches
def execute(skip_authorization: false)
raise Gitlab::Access::AccessDeniedError unless skip_authorization || authorized?
- protected_branch.save
+ save_protected_branch
+
protected_branch
end
@@ -15,6 +16,10 @@ module ProtectedBranches
private
+ def save_protected_branch
+ protected_branch.save
+ end
+
def protected_branch
@protected_branch ||= project.protected_branches.new(params)
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/self_monitoring/project/create_service.rb b/app/services/self_monitoring/project/create_service.rb
deleted file mode 100644
index c925c6a1610..00000000000
--- a/app/services/self_monitoring/project/create_service.rb
+++ /dev/null
@@ -1,219 +0,0 @@
-# frozen_string_literal: true
-
-module SelfMonitoring
- module Project
- class CreateService < ::BaseService
- include Stepable
- include Gitlab::Utils::StrongMemoize
-
- VISIBILITY_LEVEL = Gitlab::VisibilityLevel::INTERNAL
- PROJECT_NAME = 'GitLab Instance Administration'
- PROJECT_DESCRIPTION = <<~HEREDOC
- This project is automatically generated and will be used to help monitor this GitLab instance.
- HEREDOC
-
- GROUP_NAME = 'GitLab Instance Administrators'
- GROUP_PATH = 'gitlab-instance-administrators'
-
- steps :validate_admins,
- :create_group,
- :create_project,
- :save_project_id,
- :add_group_members,
- :add_to_whitelist,
- :add_prometheus_manual_configuration
-
- def initialize
- super(nil)
- end
-
- def execute
- execute_steps
- end
-
- private
-
- def validate_admins
- unless instance_admins.any?
- log_error('No active admin user found')
- return error('No active admin user found')
- end
-
- success
- end
-
- def create_group
- if project_created?
- log_info(_('Instance administrators group already exists'))
- @group = application_settings.instance_administration_project.owner
- return success(group: @group)
- end
-
- admin_user = group_owner
- @group = ::Groups::CreateService.new(admin_user, create_group_params).execute
-
- if @group.persisted?
- success(group: @group)
- else
- error('Could not create group')
- end
- end
-
- def create_project
- if project_created?
- log_info(_('Instance administration project already exists'))
- @project = application_settings.instance_administration_project
- return success(project: project)
- end
-
- admin_user = group_owner
- @project = ::Projects::CreateService.new(admin_user, create_project_params).execute
-
- if project.persisted?
- success(project: project)
- else
- log_error(_("Could not create instance administration project. Errors: %{errors}") % { errors: project.errors.full_messages })
- error(_('Could not create project'))
- end
- end
-
- def save_project_id
- return success if project_created?
-
- result = ApplicationSettings::UpdateService.new(
- application_settings,
- group_owner,
- { instance_administration_project_id: @project.id }
- ).execute
-
- if result
- success
- else
- 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
-
- def add_group_members
- members = @group.add_users(group_maintainers, Gitlab::Access::MAINTAINER)
- 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}")
- error('Could not add admins as members')
- else
- success
- end
- end
-
- def add_to_whitelist
- return success unless prometheus_enabled?
- 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
-
- result = ApplicationSettings::UpdateService.new(
- application_settings,
- group_owner,
- add_to_outbound_local_requests_whitelist: [uri.normalized_host]
- ).execute
-
- if result
- success
- else
- 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
-
- def add_prometheus_manual_configuration
- return success unless prometheus_enabled?
- return success unless prometheus_listen_address.present?
-
- 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: #{service.errors.full_messages}")
- return error('Could not save prometheus manual configuration')
- end
-
- success
- end
-
- def application_settings
- strong_memoize(:application_settings) do
- Gitlab::CurrentSettings.expire_current_application_settings
- Gitlab::CurrentSettings.current_application_settings
- end
- end
-
- def project_created?
- application_settings.instance_administration_project.present?
- end
-
- def parse_url(uri_string)
- Addressable::URI.parse(uri_string)
- rescue Addressable::URI::InvalidURIError, TypeError
- end
-
- def prometheus_enabled?
- Gitlab.config.prometheus.enable
- rescue Settingslogic::MissingSetting
- false
- end
-
- def prometheus_listen_address
- Gitlab.config.prometheus.listen_address
- rescue Settingslogic::MissingSetting
- end
-
- def instance_admins
- @instance_admins ||= User.admins.active
- end
-
- def group_owner
- instance_admins.first
- end
-
- def group_maintainers
- # Exclude the first so that the group_owner is not added again as a member.
- instance_admins - [group_owner]
- end
-
- def create_group_params
- {
- name: GROUP_NAME,
- path: "#{GROUP_PATH}-#{SecureRandom.hex(4)}",
- visibility_level: VISIBILITY_LEVEL
- }
- end
-
- def create_project_params
- {
- initialize_with_readme: true,
- visibility_level: VISIBILITY_LEVEL,
- name: PROJECT_NAME,
- description: PROJECT_DESCRIPTION,
- namespace_id: @group.id
- }
- end
-
- def internal_prometheus_listen_address_uri
- if prometheus_listen_address.starts_with?('http')
- prometheus_listen_address
- else
- 'http://' + prometheus_listen_address
- end
- end
-
- def prometheus_service_attributes
- {
- api_url: internal_prometheus_listen_address_uri,
- manual_configuration: true,
- active: true
- }
- end
- end
- end
-end
diff --git a/app/services/submit_usage_ping_service.rb b/app/services/submit_usage_ping_service.rb
index 4f10f220298..415a02ab337 100644
--- a/app/services/submit_usage_ping_service.rb
+++ b/app/services/submit_usage_ping_service.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class SubmitUsagePingService
- URL = 'https://version.gitlab.com/usage_data'.freeze
+ URL = 'https://version.gitlab.com/usage_data'
METRICS = %w[leader_issues instance_issues percentage_issues leader_notes instance_notes
percentage_notes leader_milestones instance_milestones percentage_milestones
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/services/wikis/create_attachment_service.rb b/app/services/wikis/create_attachment_service.rb
index df31ad7c8ea..6ef6cbc3c12 100644
--- a/app/services/wikis/create_attachment_service.rb
+++ b/app/services/wikis/create_attachment_service.rb
@@ -2,7 +2,7 @@
module Wikis
class CreateAttachmentService < Files::CreateService
- ATTACHMENT_PATH = 'uploads'.freeze
+ ATTACHMENT_PATH = 'uploads'
MAX_FILENAME_LENGTH = 255
delegate :wiki, to: :project
diff --git a/app/uploaders/object_storage.rb b/app/uploaders/object_storage.rb
index 0a44d60778d..499807d1438 100644
--- a/app/uploaders/object_storage.rb
+++ b/app/uploaders/object_storage.rb
@@ -23,7 +23,7 @@ module ObjectStorage
end
end
- TMP_UPLOAD_PATH = 'tmp/uploads'.freeze
+ TMP_UPLOAD_PATH = 'tmp/uploads'
module Store
LOCAL = 1
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/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb
index bb445499cee..f292730441c 100644
--- a/app/validators/addressable_url_validator.rb
+++ b/app/validators/addressable_url_validator.rb
@@ -42,6 +42,11 @@
class AddressableUrlValidator < ActiveModel::EachValidator
attr_reader :record
+ # By default, we avoid checking the dns rebinding protection
+ # when saving/updating a record. Sometimes, the url
+ # is not resolvable at that point, and some automated
+ # tasks that uses that url won't work.
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/66723
BLOCKER_VALIDATE_OPTIONS = {
schemes: %w(http https),
ports: [],
@@ -49,7 +54,8 @@ class AddressableUrlValidator < ActiveModel::EachValidator
allow_local_network: true,
ascii_only: false,
enforce_user: false,
- enforce_sanitization: false
+ enforce_sanitization: false,
+ dns_rebind_protection: false
}.freeze
DEFAULT_OPTIONS = BLOCKER_VALIDATE_OPTIONS.merge({
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/_ip_limits.html.haml b/app/views/admin/application_settings/_ip_limits.html.haml
index 67a04fcf698..9512c1837bf 100644
--- a/app/views/admin/application_settings/_ip_limits.html.haml
+++ b/app/views/admin/application_settings/_ip_limits.html.haml
@@ -4,7 +4,7 @@
%fieldset
.form-group
.form-check
- = f.check_box :throttle_unauthenticated_enabled, class: 'form-check-input'
+ = f.check_box :throttle_unauthenticated_enabled, class: 'form-check-input', data: { qa_selector: 'throttle_unauthenticated_checkbox' }
= f.label :throttle_unauthenticated_enabled, class: 'form-check-label' do
Enable unauthenticated request rate limit
%span.form-text.text-muted
@@ -17,7 +17,7 @@
= f.number_field :throttle_unauthenticated_period_in_seconds, class: 'form-control'
.form-group
.form-check
- = f.check_box :throttle_authenticated_api_enabled, class: 'form-check-input'
+ = f.check_box :throttle_authenticated_api_enabled, class: 'form-check-input', data: { qa_selector: 'throttle_authenticated_api_checkbox' }
= f.label :throttle_authenticated_api_enabled, class: 'form-check-label' do
Enable authenticated API request rate limit
%span.form-text.text-muted
@@ -30,7 +30,7 @@
= f.number_field :throttle_authenticated_api_period_in_seconds, class: 'form-control'
.form-group
.form-check
- = f.check_box :throttle_authenticated_web_enabled, class: 'form-check-input'
+ = f.check_box :throttle_authenticated_web_enabled, class: 'form-check-input', data: { qa_selector: 'throttle_authenticated_web_checkbox' }
= f.label :throttle_authenticated_web_enabled, class: 'form-check-label' do
Enable authenticated web request rate limit
%span.form-text.text-muted
@@ -42,4 +42,4 @@
= f.label :throttle_authenticated_web_period_in_seconds, 'Rate limit period in seconds', class: 'label-bold'
= f.number_field :throttle_authenticated_web_period_in_seconds, class: 'form-control'
- = 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/network.html.haml b/app/views/admin/application_settings/network.html.haml
index 26fd745f45f..3a4d901ca1d 100644
--- a/app/views/admin/application_settings/network.html.haml
+++ b/app/views/admin/application_settings/network.html.haml
@@ -13,7 +13,7 @@
.settings-content
= render 'performance'
-%section.settings.as-ip-limits.no-animate#js-ip-limits-settings{ class: ('expanded' if expanded_by_default?) }
+%section.settings.as-ip-limits.no-animate#js-ip-limits-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'ip_limits_section' } }
.settings-header
%h4
= _('User and IP Rate Limits')
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/ci/status/_icon.html.haml b/app/views/ci/status/_icon.html.haml
index 1249b98221f..fdaacb732c7 100644
--- a/app/views/ci/status/_icon.html.haml
+++ b/app/views/ci/status/_icon.html.haml
@@ -1,13 +1,10 @@
- status = local_assigns.fetch(:status)
- size = local_assigns.fetch(:size, 16)
-- type = local_assigns.fetch(:type, 'pipeline')
- tooltip_placement = local_assigns.fetch(:tooltip_placement, "left")
- path = local_assigns.fetch(:path, status.has_details? ? status.details_path : nil)
- option_css_classes = local_assigns.fetch(:option_css_classes, '')
- css_classes = "ci-status-link ci-status-icon ci-status-icon-#{status.group} has-tooltip #{option_css_classes}"
- title = s_("PipelineStatusTooltip|Pipeline: %{ci_status}") % {ci_status: status.label}
-- if type == 'commit'
- - title = s_("PipelineStatusTooltip|Commit: %{ci_status}") % {ci_status: status.label}
- if path
= link_to path, class: css_classes, title: title, data: { placement: tooltip_placement } do
diff --git a/app/views/clusters/clusters/_configure.html.haml b/app/views/clusters/clusters/_configure.html.haml
new file mode 100644
index 00000000000..4ce00c67866
--- /dev/null
+++ b/app/views/clusters/clusters/_configure.html.haml
@@ -0,0 +1,26 @@
+%section#cluster-integration
+ - unless @cluster.status_name.in? %i/scheduled creating/
+ = render 'form'
+
+- unless @cluster.status_name.in? %i/scheduled creating/
+ = render_if_exists 'projects/clusters/prometheus_graphs'
+
+ .cluster-applications-table#js-cluster-applications
+
+ %section.settings#js-cluster-details{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4= s_('ClusterIntegration|Kubernetes cluster details')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = expanded ? _('Collapse') : _('Expand')
+ %p= s_('ClusterIntegration|See and edit the details for your Kubernetes cluster')
+ .settings-content
+ = render 'clusters/platforms/kubernetes/form', cluster: @cluster, platform: @cluster.platform_kubernetes, update_cluster_url_path: clusterable.cluster_path(@cluster)
+
+ %section.settings.no-animate#js-cluster-advanced-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4= _('Advanced settings')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = expanded ? _('Collapse') : _('Expand')
+ %p= s_("ClusterIntegration|Advanced options on this Kubernetes cluster's integration")
+ .settings-content#advanced-settings-section
+ = render 'advanced_settings'
diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml
index 913d4caa0bc..6052e5d96f2 100644
--- a/app/views/clusters/clusters/show.html.haml
+++ b/app/views/clusters/clusters/show.html.haml
@@ -24,38 +24,19 @@
help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'),
ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-endpoint'),
ingress_dns_help_path: help_page_path('user/project/clusters/index.md', anchor: 'manually-determining-the-external-endpoint'),
+ environments_help_path: help_page_path('ci/environments', anchor: 'defining-environments'),
+ clusters_help_path: help_page_path('user/project/clusters/index.md', anchor: 'deploying-to-a-kubernetes-cluster'),
+ deploy_boards_help_path: help_page_path('user/project/deploy_boards.html', anchor: 'enabling-deploy-boards'),
manage_prometheus_path: manage_prometheus_path,
cluster_id: @cluster.id } }
.js-cluster-application-notice
.flash-container
- %section#cluster-integration
- %h4= @cluster.name
- = render 'banner'
+ %h4= @cluster.name
+ = render 'banner'
- - unless @cluster.status_name.in? %i/scheduled creating/
- = render 'form'
+ = render_if_exists 'clusters/clusters/group_cluster_environments', expanded: expanded
- - unless @cluster.status_name.in? %i/scheduled creating/
- = render_if_exists 'projects/clusters/prometheus_graphs'
-
- .cluster-applications-table#js-cluster-applications
-
- %section.settings#js-cluster-details{ class: ('expanded' if expanded) }
- .settings-header
- %h4= s_('ClusterIntegration|Kubernetes cluster details')
- %button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p= s_('ClusterIntegration|See and edit the details for your Kubernetes cluster')
- .settings-content
- = render 'clusters/platforms/kubernetes/form', cluster: @cluster, platform: @cluster.platform_kubernetes, update_cluster_url_path: clusterable.cluster_path(@cluster)
-
- %section.settings.no-animate#js-cluster-advanced-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4= _('Advanced settings')
- %button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p= s_("ClusterIntegration|Advanced options on this Kubernetes cluster's integration")
- .settings-content#advanced-settings-section
- = render 'advanced_settings'
+ - unless Gitlab.ee?
+ = render 'configure', expanded: expanded
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/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index 8cdfc7369a0..fdb71d3a221 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -2,41 +2,49 @@
.todo-avatar
= author_avatar(todo, size: 40)
- .todo-item.todo-block
- .todo-title.title
+ .todo-item.todo-block.align-self-center
+ .todo-title
- unless todo.build_failed? || todo.unmergeable?
= todo_target_state_pill(todo)
- .title-item.author-name
+ %span.title-item.author-name.bold
- if todo.author
= link_to_author(todo, self_added: todo.self_added?)
- else
(removed)
- .title-item.action-name
+ %span.title-item.action-name
= todo_action_name(todo)
- .title-item.todo-label
+ %span.title-item.todo-label.todo-target-link
- if todo.target
= todo_target_link(todo)
- else
- (removed)
+ = _("(removed)")
+
+ %span.title-item.todo-target-title
+ = todo_target_title(todo)
+
+ %span.title-item.todo-project.todo-label
+ at
+ = todo_parent_path(todo)
- if todo.self_assigned?
- .title-item.action-name
+ %span.title-item.action-name
to yourself
- .title-item
+ %span.title-item
&middot;
- .title-item
+ %span.title-item.todo-timestamp
#{time_ago_with_tooltip(todo.created_at)}
= todo_due_date(todo)
- .todo-body
- .todo-note.break-word
- .md
- = first_line_in_markdown(todo, :body, 150, project: todo.project)
+ - if todo.note.present?
+ .todo-body
+ .todo-note.break-word
+ .md
+ = first_line_in_markdown(todo, :body, 150, project: todo.project)
- if todo.pending?
.todo-actions
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/edit.html.haml b/app/views/groups/edit.html.haml
index 0c8f86c2822..6d06bb246cb 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -14,7 +14,7 @@
.settings-content
= render 'groups/settings/general'
-%section.settings.gs-permissions.no-animate#js-permissions-settings{ class: ('expanded' if expanded) }
+%section.settings.gs-permissions.no-animate#js-permissions-settings{ class: ('expanded' if expanded), data: { qa_selector: 'permission_lfs_2fa_section' } }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only{ role: 'button' }
= _('Permissions, LFS, 2FA')
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 94a938021f9..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
@@ -31,4 +31,4 @@
= render 'groups/settings/two_factor_auth', f: f
= render_if_exists 'groups/member_lock_setting', f: f, group: @group
- = f.submit _('Save changes'), class: 'btn btn-success prepend-top-default js-dirty-submit'
+ = f.submit _('Save changes'), class: 'btn btn-success prepend-top-default js-dirty-submit', data: { qa_selector: 'save_permissions_changes_button' }
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/_head.html.haml b/app/views/layouts/_head.html.haml
index 271b73326fa..68abfd3f61f 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -36,7 +36,6 @@
= stylesheet_link_tag "print", media: "print"
= stylesheet_link_tag "test", media: "all" if Rails.env.test?
= stylesheet_link_tag 'performance_bar' if performance_bar_enabled?
- = stylesheet_link_tag 'csslab' if Feature.enabled?(:csslab)
= stylesheet_link_tag "highlight/themes/#{user_color_scheme}", media: "all"
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/_piwik.html.haml b/app/views/layouts/_piwik.html.haml
index 2cb2e23433d..361a7b03180 100644
--- a/app/views/layouts/_piwik.html.haml
+++ b/app/views/layouts/_piwik.html.haml
@@ -11,5 +11,5 @@
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
})();
- <noscript><p><img src="//#{extra_config.piwik_url}/piwik.php?idsite=#{extra_config.piwik_site_id}" style="border:0;" alt="" /></p></noscript>
- <!-- End Piwik Code -->
+<noscript><p><img src="//#{extra_config.piwik_url}/piwik.php?idsite=#{extra_config.piwik_site_id}" style="border:0;" alt="" /></p></noscript>
+<!-- End Piwik Code -->
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/_snowplow.html.haml b/app/views/layouts/_snowplow.html.haml
index 5f5c5e984c5..d7ff5ad1094 100644
--- a/app/views/layouts/_snowplow.html.haml
+++ b/app/views/layouts/_snowplow.html.haml
@@ -7,23 +7,4 @@
};p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1;
n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","#{asset_url('snowplow/sp.js')}","snowplow"));
- window.snowplow('newTracker', '#{Gitlab::SnowplowTracker::NAMESPACE}', '#{Gitlab::CurrentSettings.snowplow_collector_hostname}', {
- appId: '#{Gitlab::CurrentSettings.snowplow_site_id}',
- cookieDomain: '#{Gitlab::CurrentSettings.snowplow_cookie_domain}',
- userFingerprint: false,
- respectDoNotTrack: true,
- forceSecureTracker: true,
- post: true,
- contexts: { webPage: true },
- stateStorageStrategy: "localStorage"
- });
-
- window.snowplow('enableActivityTracking', 30, 30);
- window.snowplow('trackPageView');
-
-- return unless Feature.enabled?(:additional_snowplow_tracking, @group)
-
-= javascript_tag nonce: true do
- :plain
- window.snowplow('enableFormTracking');
- window.snowplow('enableLinkClickTracking');
+ window.snowplowOptions = #{Gitlab::Tracking.snowplow_options(@group).to_json}
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 cb39c830170..f76268bc29b 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -261,7 +261,7 @@
%span
= _('Metrics and profiling')
= nav_link(path: 'application_settings#network') do
- = link_to network_admin_application_settings_path, title: _('Network') do
+ = link_to network_admin_application_settings_path, title: _('Network'), data: { qa_selector: 'admin_settings_network_item' } do
%span
= _('Network')
- if template_exists?('admin/application_settings/geo')
@@ -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/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 48c9f19f89f..7cc7d1783c4 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -48,14 +48,14 @@
- if group_sidebar_link?(:issues)
= nav_link(path: group_issues_sub_menu_items) do
- = link_to issues_group_path(@group) do
+ = link_to issues_group_path(@group), data: { qa_selector: 'group_issues_item' } do
.nav-icon-container
= sprite_icon('issues')
%span.nav-item-name
= _('Issues')
%span.badge.badge-pill.count= number_with_delimiter(issues_count)
- %ul.sidebar-sub-level-items
+ %ul.sidebar-sub-level-items{ data: { qa_selector: 'group_issues_sidebar_submenu'} }
= nav_link(path: ['groups#issues', 'labels#index', 'milestones#index'], html_options: { class: "fly-out-top-item" } ) do
= link_to issues_group_path(@group) do
%strong.fly-out-top-item-name
@@ -70,7 +70,7 @@
- if group_sidebar_link?(:boards)
= nav_link(path: ['boards#index', 'boards#show']) do
- = link_to group_boards_path(@group), title: boards_link_text do
+ = link_to group_boards_path(@group), title: boards_link_text, data: { qa_selector: 'group_issue_boards_link' } do
%span
= boards_link_text
@@ -147,7 +147,7 @@
= _('Settings')
%li.divider.fly-out-top-item
= nav_link(path: 'groups#edit') do
- = link_to edit_group_path(@group), title: _('General') do
+ = link_to edit_group_path(@group), title: _('General'), data: { qa_selector: 'general_settings_link' } do
%span
= _('General')
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 02ecf816e90..48fea2bbecf 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -115,7 +115,7 @@
= _('List')
= nav_link(controller: :boards) do
- = link_to project_boards_path(@project), title: boards_link_text do
+ = link_to project_boards_path(@project), title: boards_link_text, data: { qa_selector: "issue_boards_link" } do
%span
= boards_link_text
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/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index d99063e344f..0887e8e64da 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -47,7 +47,7 @@
= s_('Preferences|Layout width')
= f.select :layout, layout_choices, {}, class: 'select2'
.form-text.text-muted
- = s_('Preferences|Choose between fixed (max. 1280px) and fluid (100%%) application layout.')
+ = s_('Preferences|Choose between fixed (max. 1280px) and fluid (%{percentage}) application layout.').html_safe % { percentage: '100%' }
.form-group
= f.label :dashboard, class: 'label-bold' do
= s_('Preferences|Default dashboard')
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/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 95c5eb32c7f..cf273aab108 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -9,6 +9,6 @@
= render "projects/blob/auxiliary_viewer", blob: blob
#blob-content-holder.blob-content-holder
- %article.file-holder{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
+ %article.file-holder
= render 'projects/blob/header', blob: blob
= render 'projects/blob/content', blob: blob
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/preview.html.haml b/app/views/projects/blob/preview.html.haml
index 3e893343165..46e76e4d175 100644
--- a/app/views/projects/blob/preview.html.haml
+++ b/app/views/projects/blob/preview.html.haml
@@ -1,5 +1,5 @@
- if markup?(@blob.name)
- .file-content.md.md-file{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
+ .file-content.md.md-file
= markup(@blob.name, @content)
- else
.diff-file
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/blob/viewers/_markup.html.haml b/app/views/projects/blob/viewers/_markup.html.haml
index abc74b66e90..c71df29354b 100644
--- a/app/views/projects/blob/viewers/_markup.html.haml
+++ b/app/views/projects/blob/viewers/_markup.html.haml
@@ -1,4 +1,4 @@
- blob = viewer.blob
- context = blob.respond_to?(:rendered_markup) ? { rendered: blob.rendered_markup } : {}
-.file-content.md.md-file{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
+.file-content.md.md-file
= markup(blob.name, blob.data, context)
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 a11e23b6daa..ef2ab4c698e 100644
--- a/app/views/projects/deployments/_deployment.html.haml
+++ b/app/views/projects/deployments/_deployment.html.haml
@@ -15,14 +15,15 @@
.flex-truncate-child
= link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: 'build-link' do
#{deployment.deployable.name} (##{deployment.deployable.id})
- - if deployment.user
+ - if deployment.deployed_by
%div
by
- = user_avatar(user: deployment.user, size: 20, css_class: "mr-0 float-none")
+ = user_avatar(user: deployment.deployed_by, size: 20, css_class: "mr-0 float-none")
.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/error_tracking/index.html.haml b/app/views/projects/error_tracking/index.html.haml
index bc02c5f0e5a..96f61584a99 100644
--- a/app/views/projects/error_tracking/index.html.haml
+++ b/app/views/projects/error_tracking/index.html.haml
@@ -1,3 +1,3 @@
- page_title _('Errors')
-#js-error_tracking{ data: error_tracking_data(@project) }
+#js-error_tracking{ data: error_tracking_data(@current_user, @project) }
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..2e322c7db23 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,4 @@
- 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: jobs_data }
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/_how_to_merge.html.haml b/app/views/projects/merge_requests/_how_to_merge.html.haml
index 15499c89ffb..928b54ea28f 100644
--- a/app/views/projects/merge_requests/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/_how_to_merge.html.haml
@@ -13,12 +13,13 @@
%pre.dark#merge-info-1
- if @merge_request.for_fork?
:preserve
- git fetch #{h default_url_to_repo(@merge_request.source_project)} #{h @merge_request.source_branch}
- git checkout -b #{h @merge_request.source_project_path}-#{h @merge_request.source_branch} FETCH_HEAD
+ -# All repo/branch refs have been quoted to allow support for special characters (such as #my-branch)
+ git fetch "#{h default_url_to_repo(@merge_request.source_project)}" "#{h @merge_request.source_branch}"
+ git checkout -b "#{h @merge_request.source_project_path}-#{h @merge_request.source_branch}" FETCH_HEAD
- else
:preserve
git fetch origin
- git checkout -b #{h @merge_request.source_branch} origin/#{h @merge_request.source_branch}
+ git checkout -b "#{h @merge_request.source_branch}" "origin/#{h @merge_request.source_branch}"
%p
%strong Step 2.
Review the changes locally
@@ -31,20 +32,20 @@
- if @merge_request.for_fork?
:preserve
git fetch origin
- git checkout origin/#{h @merge_request.target_branch}
- git merge --no-ff #{h @merge_request.source_project_path}-#{h @merge_request.source_branch}
+ git checkout "origin/#{h @merge_request.target_branch}"
+ git merge --no-ff "#{h @merge_request.source_project_path}-#{h @merge_request.source_branch}"
- else
:preserve
git fetch origin
- git checkout origin/#{h @merge_request.target_branch}
- git merge --no-ff #{h @merge_request.source_branch}
+ git checkout "origin/#{h @merge_request.target_branch}"
+ git merge --no-ff "#{h @merge_request.source_branch}"
%p
%strong Step 4.
Push the result of the merge to GitLab
= clipboard_button(target: "pre#merge-info-4", title: "Copy commands to clipboard")
%pre.dark#merge-info-4
:preserve
- git push origin #{h @merge_request.target_branch}
+ git push origin "#{h @merge_request.target_branch}"
- unless @merge_request.can_be_merged_by?(current_user)
%p
Note that pushing to GitLab requires write access to this repository.
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/_instructions.html.haml b/app/views/projects/mirrors/_instructions.html.haml
index 1a163cc4a54..7ff6c0a2019 100644
--- a/app/views/projects/mirrors/_instructions.html.haml
+++ b/app/views/projects/mirrors/_instructions.html.haml
@@ -3,6 +3,7 @@
%li
= _('The repository must be accessible over <code>http://</code>,
<code>https://</code>, <code>ssh://</code> or <code>git://</code>.').html_safe
+ %li= _('When using the <code>http://</code> or <code>https://</code> protocols, please provide the exact URL to the repository. HTTP redirects will not be followed.').html_safe
%li= _('Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>.').html_safe
%li
- minutes = Gitlab.config.gitlab_shell.git_timeout / 60
diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml
index eb100e5cf47..84f0900d9c1 100644
--- a/app/views/projects/mirrors/_mirror_repos.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos.html.haml
@@ -13,8 +13,6 @@
.settings-content
= form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'new-password', data: mirrors_form_data_attributes } do |f|
.panel.panel-default
- .panel-heading
- %h3.panel-title= _('Mirror a repository')
.panel-body
%div= form_errors(@project)
@@ -52,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..7b6d46964a2 100644
--- a/app/views/projects/pages/_access.html.haml
+++ b/app/views/projects/pages/_access.html.haml
@@ -5,9 +5,13 @@
.card-body
%p
%strong
- Congratulations! Your pages are served under:
+ = _("Your pages are served under:")
- %p= link_to @project.pages_url, @project.pages_url
+ %p
+ = external_link(@project.pages_url, @project.pages_url)
- @project.pages_domains.each do |domain|
- %p= link_to domain.url, domain.url
+ %p
+ = external_link(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/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml
index 2427b4d7611..c4285e7f3d2 100644
--- a/app/views/projects/pages/_list.html.haml
+++ b/app/views/projects/pages/_list.html.haml
@@ -12,9 +12,7 @@
.domain-status.ci-status-icon.has-tooltip{ class: "ci-status-icon-#{status}", title: tooltip }
= sprite_icon("status_#{status}", size: 16 )
.domain-name
- = link_to domain.url do
- = domain.url
- = icon('external-link')
+ = external_link(domain.url, domain.url)
- if domain.subject
%div
%span.badge.badge-gray Certificate: #{domain.subject}
diff --git a/app/views/projects/pages_domains/show.html.haml b/app/views/projects/pages_domains/show.html.haml
index d0b54946f7e..33837e21c8d 100644
--- a/app/views/projects/pages_domains/show.html.haml
+++ b/app/views/projects/pages_domains/show.html.haml
@@ -21,9 +21,7 @@
%td
= _("Domain")
%td
- = link_to @domain.url do
- = @domain.url
- = icon('external-link')
+ = external_link(@domain.url, @domain.url)
%tr
%td
= _("DNS")
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 9c69aedfbfc..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,9 +9,9 @@
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
- .top-area.adjust
+ .top-area.adjust.d-flex.justify-content-center
.serverless-functions-table#js-serverless-functions
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/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index da48cb207a4..f495b4eaf30 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -6,7 +6,7 @@
= render 'shared/snippets/header'
.project-snippets
- %article.file-holder.snippet-file-content{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
+ %article.file-holder.snippet-file-content
= render 'shared/snippets/blob'
.row-content-block.top-block.content-component-block
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/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index c6197fe576e..51b7f2dd4b4 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -26,7 +26,7 @@
= (s_("WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}.") % { most_recent_link: most_recent_link, history_link: history_link }).html_safe
.prepend-top-default.append-bottom-default
- .md.md-file{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
+ .md.md-file
= render_wiki_content(@page)
= render 'sidebar'
diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml
index d0f9374e832..b2ea45d6f1a 100644
--- a/app/views/shared/_import_form.html.haml
+++ b/app/views/shared/_import_form.html.haml
@@ -27,6 +27,7 @@
%ul
%li
= _('The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>.').html_safe
+ %li= _('When using the <code>http://</code> or <code>https://</code> protocols, please provide the exact URL to the repository. HTTP redirects will not be followed.').html_safe
%li
= _('If your HTTP repository is not publicly accessible, add your credentials.')
%li
diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml
index 4f0be117035..93fc839a371 100644
--- a/app/views/shared/boards/_show.html.haml
+++ b/app/views/shared/boards/_show.html.haml
@@ -4,7 +4,7 @@
- @no_container = true
- @content_class = "issue-boards-content js-focus-mode-board"
- breadcrumb_title _("Issue Boards")
-- page_title _("Boards")
+- page_title("#{board.name}", _("Boards"))
- content_for :page_specific_javascripts do
diff --git a/app/views/shared/boards/components/_board.html.haml b/app/views/shared/boards/components/_board.html.haml
index 5abd4ce0fb9..ffa24d1c041 100644
--- a/app/views/shared/boards/components/_board.html.haml
+++ b/app/views/shared/boards/components/_board.html.haml
@@ -1,4 +1,4 @@
-.board.d-inline-block.h-100.px-2.align-top.ws-normal{ ":class" => '{ "is-draggable": !list.preset, "is-expandable": list.isExpandable, "is-collapsed": !list.isExpanded, "board-type-assignee": list.type === "assignee" }',
+.board.h-100.px-2.align-top.ws-normal{ ":class" => '{ "is-draggable": !list.preset, "is-expandable": list.isExpandable, "is-collapsed": !list.isExpanded, "board-type-assignee": list.type === "assignee" }',
":data-id" => "list.id", data: { qa_selector: "board_list" } }
.board-inner.d-flex.flex-column.position-relative.h-100.rounded
%header.board-header{ ":class" => '{ "has-border": list.label && list.label.color, "position-relative": list.isExpanded, "position-absolute position-top-0 position-left-0 w-100 h-100": !list.isExpanded }', ":style" => "{ borderTopColor: (list.label && list.label.color ? list.label.color : null) }", data: { qa_selector: "board_list_header" } }
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/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index aea09bf8d4d..837707707a9 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -139,7 +139,9 @@
- if signed_in
- if issuable_sidebar[:project_emails_disabled]
.block.js-emails-disabled
- = notification_description(:owner_disabled)
+ .sidebar-collapsed-icon.has-tooltip{ title: notification_description(:owner_disabled), data: { placement: "left", container: "body", boundary: 'viewport' } }
+ = notification_setting_icon
+ .hide-collapsed= notification_description(:owner_disabled)
- else
.js-sidebar-subscriptions-entry-point
diff --git a/app/views/shared/members/_sort_dropdown.html.haml b/app/views/shared/members/_sort_dropdown.html.haml
index feca109dade..5f3d49adff7 100644
--- a/app/views/shared/members/_sort_dropdown.html.haml
+++ b/app/views/shared/members/_sort_dropdown.html.haml
@@ -1,4 +1,4 @@
-= label_tag :sort_by, 'Sort by', class: 'col-form-label label-bold pr-2'
+= label_tag :sort_by, 'Sort by', class: 'col-form-label label-bold px-2'
.dropdown.inline.qa-user-sort-dropdown
= dropdown_toggle(member_sort_options_hash[@sort], { toggle: 'dropdown' })
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index b7474d891dc..573ed36d7f4 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -89,7 +89,7 @@
- if pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status? && can?(current_user, :read_build, project)
- pipeline_path = pipelines_project_commit_path(project.pipeline_status.project, project.pipeline_status.sha, ref: project.pipeline_status.ref)
%span.icon-wrapper.pipeline-status
- = render 'ci/status/icon', status: project.commit.last_pipeline.detailed_status(current_user), type: 'commit', tooltip_placement: 'top', path: pipeline_path
+ = render 'ci/status/icon', status: project.commit.last_pipeline.detailed_status(current_user), tooltip_placement: 'top', path: pipeline_path
.updated-note
%span
= _('Updated')
diff --git a/app/views/shared/runners/_form.html.haml b/app/views/shared/runners/_form.html.haml
index 559b5aa9c1e..24b4eae0c58 100644
--- a/app/views/shared/runners/_form.html.haml
+++ b/app/views/shared/runners/_form.html.haml
@@ -26,11 +26,6 @@
= f.check_box :locked, { class: 'form-check-input' }
%label.light{ for: :runner_locked }= _('When a runner is locked, it cannot be assigned to other projects')
.form-group.row
- = label_tag :token, class: 'col-form-label col-sm-2' do
- = _('Token')
- .col-sm-10
- = f.text_field :token, class: 'form-control', readonly: true
- .form-group.row
= label_tag :ip_address, class: 'col-form-label col-sm-2' do
= _('IP Address')
.col-sm-10
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/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 991a177018e..a33afd436b0 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -160,6 +160,7 @@
- repository_import
- repository_remove_remote
- system_hook_push
+- update_external_pull_requests
- update_merge_requests
- update_project_statistics
- upload_checksum
diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb
index 489d6215774..5499e12e49b 100644
--- a/app/workers/git_garbage_collect_worker.rb
+++ b/app/workers/git_garbage_collect_worker.rb
@@ -24,7 +24,7 @@ class GitGarbageCollectWorker
task = task.to_sym
- ::Projects::GitDeduplicationService.new(project).execute
+ ::Projects::GitDeduplicationService.new(project).execute if task == :gc
gitaly_call(task, project.repository.raw_repository)
diff --git a/app/workers/hashed_storage/base_worker.rb b/app/workers/hashed_storage/base_worker.rb
index 816e0504db6..237e278c537 100644
--- a/app/workers/hashed_storage/base_worker.rb
+++ b/app/workers/hashed_storage/base_worker.rb
@@ -5,7 +5,7 @@ module HashedStorage
include ExclusiveLeaseGuard
LEASE_TIMEOUT = 30.seconds.to_i
- LEASE_KEY_SEGMENT = 'project_migrate_hashed_storage_worker'.freeze
+ LEASE_KEY_SEGMENT = 'project_migrate_hashed_storage_worker'
protected
diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb
index 30fba038937..7e002d8822c 100644
--- a/app/workers/stuck_ci_jobs_worker.rb
+++ b/app/workers/stuck_ci_jobs_worker.rb
@@ -4,7 +4,7 @@ class StuckCiJobsWorker
include ApplicationWorker
include CronjobQueue
- EXCLUSIVE_LEASE_KEY = 'stuck_ci_builds_worker_lease'.freeze
+ EXCLUSIVE_LEASE_KEY = 'stuck_ci_builds_worker_lease'
BUILD_RUNNING_OUTDATED_TIMEOUT = 1.hour
BUILD_PENDING_OUTDATED_TIMEOUT = 1.day
diff --git a/app/workers/update_external_pull_requests_worker.rb b/app/workers/update_external_pull_requests_worker.rb
new file mode 100644
index 00000000000..c5acfa82ada
--- /dev/null
+++ b/app/workers/update_external_pull_requests_worker.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class UpdateExternalPullRequestsWorker
+ include ApplicationWorker
+
+ def perform(project_id, user_id, ref)
+ project = Project.find_by_id(project_id)
+ return unless project
+
+ user = User.find_by_id(user_id)
+ return unless user
+
+ branch = Gitlab::Git.branch_name(ref)
+ return unless branch
+
+ external_pull_requests = project.external_pull_requests
+ .by_source_repository(project.import_source)
+ .by_source_branch(branch)
+
+ external_pull_requests.find_each do |pull_request|
+ ExternalPullRequests::CreatePipelineService.new(project, user)
+ .execute(pull_request)
+ end
+ end
+end
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/21505-quickactions-update-pd.yml b/changelogs/unreleased/21505-quickactions-update-pd.yml
new file mode 100644
index 00000000000..243f8eda4e3
--- /dev/null
+++ b/changelogs/unreleased/21505-quickactions-update-pd.yml
@@ -0,0 +1,5 @@
+---
+title: Apply quickactions when modifying comments
+merge_request: 31136
+author:
+type: added
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/31735-only-show-copy_metadata-when-usable.yml b/changelogs/unreleased/31735-only-show-copy_metadata-when-usable.yml
new file mode 100644
index 00000000000..9f34a912dc5
--- /dev/null
+++ b/changelogs/unreleased/31735-only-show-copy_metadata-when-usable.yml
@@ -0,0 +1,5 @@
+---
+title: Only show /copy_metadata quick action when usable
+merge_request: 31735
+author: Lee Tickett
+type: fixed
diff --git a/changelogs/unreleased/35060-remove-token-field.yml b/changelogs/unreleased/35060-remove-token-field.yml
new file mode 100644
index 00000000000..93a7b459dd8
--- /dev/null
+++ b/changelogs/unreleased/35060-remove-token-field.yml
@@ -0,0 +1,5 @@
+---
+title: Remove token field from runners edit form
+merge_request: 32231
+author:
+type: fixed
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/55360-redundant-index-in-the-releases-table_v2.yml b/changelogs/unreleased/55360-redundant-index-in-the-releases-table_v2.yml
new file mode 100644
index 00000000000..91a1fb5e6c9
--- /dev/null
+++ b/changelogs/unreleased/55360-redundant-index-in-the-releases-table_v2.yml
@@ -0,0 +1,5 @@
+---
+title: Removed redundant index on releases table
+merge_request: 31487
+author:
+type: removed
diff --git a/changelogs/unreleased/55999-misleading-pipeline-tooltip-messages-and-misleading-ci-status-icons.yml b/changelogs/unreleased/55999-misleading-pipeline-tooltip-messages-and-misleading-ci-status-icons.yml
new file mode 100644
index 00000000000..a937614be38
--- /dev/null
+++ b/changelogs/unreleased/55999-misleading-pipeline-tooltip-messages-and-misleading-ci-status-icons.yml
@@ -0,0 +1,5 @@
+---
+title: Remove "Commit" from pipeline status tooltips
+merge_request: 31861
+author:
+type: fixed
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/56883-migration.yml b/changelogs/unreleased/56883-migration.yml
new file mode 100644
index 00000000000..d6eb49e62e1
--- /dev/null
+++ b/changelogs/unreleased/56883-migration.yml
@@ -0,0 +1,5 @@
+---
+title: Create a project for self-monitoring the GitLab instance
+merge_request: 31389
+author:
+type: added
diff --git a/changelogs/unreleased/57402-upate-issues-list-sort-options.yml b/changelogs/unreleased/57402-upate-issues-list-sort-options.yml
new file mode 100644
index 00000000000..900e71e3204
--- /dev/null
+++ b/changelogs/unreleased/57402-upate-issues-list-sort-options.yml
@@ -0,0 +1,5 @@
+---
+title: Updates issues REST API to allow extended sort options
+merge_request: 31849
+author:
+type: changed
diff --git a/changelogs/unreleased/57538-not-null-constraint-on-users-private-profile.yml b/changelogs/unreleased/57538-not-null-constraint-on-users-private-profile.yml
new file mode 100644
index 00000000000..bcbd8e3f11e
--- /dev/null
+++ b/changelogs/unreleased/57538-not-null-constraint-on-users-private-profile.yml
@@ -0,0 +1,5 @@
+---
+title: Setting NOT NULL constraint to users.private_profile column
+merge_request: 14838
+author:
+type: other
diff --git a/changelogs/unreleased/57657-promote-label-to-group-label-via-api-endpoint.yml b/changelogs/unreleased/57657-promote-label-to-group-label-via-api-endpoint.yml
new file mode 100644
index 00000000000..572bce34f45
--- /dev/null
+++ b/changelogs/unreleased/57657-promote-label-to-group-label-via-api-endpoint.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Promote project labels to group labels'
+merge_request: 25218
+author: Robert Schilling
+type: added
diff --git a/changelogs/unreleased/59729-estimate-quick-action-does-not-produce-correct-time-for-1mo.yml b/changelogs/unreleased/59729-estimate-quick-action-does-not-produce-correct-time-for-1mo.yml
new file mode 100644
index 00000000000..1962a662179
--- /dev/null
+++ b/changelogs/unreleased/59729-estimate-quick-action-does-not-produce-correct-time-for-1mo.yml
@@ -0,0 +1,5 @@
+---
+title: Fix parsing of months in time tracking commands
+merge_request: 32165
+author:
+type: fixed
diff --git a/changelogs/unreleased/60141-mr-resolve-conflicts-file-headers-bad-positioning-on-scroll.yml b/changelogs/unreleased/60141-mr-resolve-conflicts-file-headers-bad-positioning-on-scroll.yml
new file mode 100644
index 00000000000..b6d6d596de9
--- /dev/null
+++ b/changelogs/unreleased/60141-mr-resolve-conflicts-file-headers-bad-positioning-on-scroll.yml
@@ -0,0 +1,5 @@
+---
+title: Fix file header style and position during scroll in a merge conflict resolution
+merge_request: 31991
+author:
+type: fixed
diff --git a/changelogs/unreleased/60372-milestone-link-prevent-delete-issue-after-move-it-to-another-projec.yml b/changelogs/unreleased/60372-milestone-link-prevent-delete-issue-after-move-it-to-another-projec.yml
new file mode 100644
index 00000000000..d9f4c17a668
--- /dev/null
+++ b/changelogs/unreleased/60372-milestone-link-prevent-delete-issue-after-move-it-to-another-projec.yml
@@ -0,0 +1,5 @@
+---
+title: Add service to transfer Group Milestones when transferring a Project
+merge_request: 31778
+author:
+type: added
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/62322-add-optional-id-to-label-api-put-delete-pd.yml b/changelogs/unreleased/62322-add-optional-id-to-label-api-put-delete-pd.yml
new file mode 100644
index 00000000000..7e1eeddef32
--- /dev/null
+++ b/changelogs/unreleased/62322-add-optional-id-to-label-api-put-delete-pd.yml
@@ -0,0 +1,5 @@
+---
+title: Add optional label_id parameter to label API for PUT and DELETE
+merge_request: 31804
+author:
+type: changed
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/63502-encrypt-deploy-token.yml b/changelogs/unreleased/63502-encrypt-deploy-token.yml
new file mode 100644
index 00000000000..81ce1e6c3dd
--- /dev/null
+++ b/changelogs/unreleased/63502-encrypt-deploy-token.yml
@@ -0,0 +1,5 @@
+---
+title: Encrypt existing and new deploy tokens
+merge_request: 30679
+author:
+type: other
diff --git a/changelogs/unreleased/64009-show-a-meaningful-error-message-when-due-quick_actions-command-fail.yml b/changelogs/unreleased/64009-show-a-meaningful-error-message-when-due-quick_actions-command-fail.yml
new file mode 100644
index 00000000000..8624b868686
--- /dev/null
+++ b/changelogs/unreleased/64009-show-a-meaningful-error-message-when-due-quick_actions-command-fail.yml
@@ -0,0 +1,5 @@
+---
+title: Show meaningful message on /due quick action with invalid date
+merge_request: 32349
+author: Jacopo Beschi @jacopo-beschi
+type: changed
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/64269-pipeline-api-fails-with-401.yml b/changelogs/unreleased/64269-pipeline-api-fails-with-401.yml
new file mode 100644
index 00000000000..582339901ae
--- /dev/null
+++ b/changelogs/unreleased/64269-pipeline-api-fails-with-401.yml
@@ -0,0 +1,5 @@
+---
+title: Read pipelines from public projects through API without an access token
+merge_request: 31816
+author:
+type: fixed
diff --git a/changelogs/unreleased/64764-fix-serverless-layout.yml b/changelogs/unreleased/64764-fix-serverless-layout.yml
new file mode 100644
index 00000000000..9717062df93
--- /dev/null
+++ b/changelogs/unreleased/64764-fix-serverless-layout.yml
@@ -0,0 +1,5 @@
+---
+title: Fix serverless entry page layout
+merge_request: 32029
+author:
+type: fixed
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/66037-deployment-user.yml b/changelogs/unreleased/66037-deployment-user.yml
new file mode 100644
index 00000000000..8a61b8145af
--- /dev/null
+++ b/changelogs/unreleased/66037-deployment-user.yml
@@ -0,0 +1,5 @@
+---
+title: Return correct user for manual deployments
+merge_request: 32004
+author:
+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/66067-pages-domain-doesnt-set-target-blank.yml b/changelogs/unreleased/66067-pages-domain-doesnt-set-target-blank.yml
new file mode 100644
index 00000000000..726d4b163d2
--- /dev/null
+++ b/changelogs/unreleased/66067-pages-domain-doesnt-set-target-blank.yml
@@ -0,0 +1,5 @@
+---
+title: Makes custom Pages domain open as external link in new tab
+merge_request: 32130
+author: jakeburden
+type: fixed
diff --git a/changelogs/unreleased/66073-use-time-series-chart-instead-of-area-chart-in-panel_types.yml b/changelogs/unreleased/66073-use-time-series-chart-instead-of-area-chart-in-panel_types.yml
new file mode 100644
index 00000000000..8d7af96a7d8
--- /dev/null
+++ b/changelogs/unreleased/66073-use-time-series-chart-instead-of-area-chart-in-panel_types.yml
@@ -0,0 +1,5 @@
+---
+title: Enable line charts in dashbaord panels and embedded charts
+merge_request: 31920
+author:
+type: added
diff --git a/changelogs/unreleased/66150-remove-dynamically-constructed-feature-flags-starting-with-promethe.yml b/changelogs/unreleased/66150-remove-dynamically-constructed-feature-flags-starting-with-promethe.yml
new file mode 100644
index 00000000000..e6dc1f1bec6
--- /dev/null
+++ b/changelogs/unreleased/66150-remove-dynamically-constructed-feature-flags-starting-with-promethe.yml
@@ -0,0 +1,5 @@
+---
+title: Remove dynamically constructed feature flags starting with prometheus_transaction_
+merge_request: 32395
+author: Jacopo Beschi @jacopo-beschi
+type: changed
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/66402-use-visual-review-tools-npm-package.yml b/changelogs/unreleased/66402-use-visual-review-tools-npm-package.yml
new file mode 100644
index 00000000000..46773e12002
--- /dev/null
+++ b/changelogs/unreleased/66402-use-visual-review-tools-npm-package.yml
@@ -0,0 +1,5 @@
+---
+title: Move visual review toolbar code to NPM
+merge_request: 32159
+author:
+type: fixed
diff --git a/changelogs/unreleased/66443-unrecoverable-configuration-loop-in-external-auth-control.yml b/changelogs/unreleased/66443-unrecoverable-configuration-loop-in-external-auth-control.yml
new file mode 100644
index 00000000000..ab52e3e5a2c
--- /dev/null
+++ b/changelogs/unreleased/66443-unrecoverable-configuration-loop-in-external-auth-control.yml
@@ -0,0 +1,5 @@
+---
+title: Don't check external authorization when disabling the service
+merge_request: 32102
+author: Robert Schilling
+type: fixed
diff --git a/changelogs/unreleased/66454-base-components.yml b/changelogs/unreleased/66454-base-components.yml
new file mode 100644
index 00000000000..7a40a66f122
--- /dev/null
+++ b/changelogs/unreleased/66454-base-components.yml
@@ -0,0 +1,5 @@
+---
+title: Creates base components for the new job log
+merge_request:
+author:
+type: added
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/66467-enable-error-tracking-only-user-can-read-sentry-logs.yml b/changelogs/unreleased/66467-enable-error-tracking-only-user-can-read-sentry-logs.yml
new file mode 100644
index 00000000000..b152943942f
--- /dev/null
+++ b/changelogs/unreleased/66467-enable-error-tracking-only-user-can-read-sentry-logs.yml
@@ -0,0 +1,5 @@
+---
+title: Display `more information` docs link on error tracking page when users do not have permissions to enable that feature
+merge_request: 32365
+author: Romain Maneschi
+type: fixed
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/66616-follow-up-documentation-for-merge-trains-cancel-when-running.yml b/changelogs/unreleased/66616-follow-up-documentation-for-merge-trains-cancel-when-running.yml
new file mode 100644
index 00000000000..54bec43815c
--- /dev/null
+++ b/changelogs/unreleased/66616-follow-up-documentation-for-merge-trains-cancel-when-running.yml
@@ -0,0 +1,5 @@
+---
+title: Update merge train documentation
+merge_request: 32218
+author:
+type: changed
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/67037-user-content-gitlab-static-net-brings-back-404-only.yml b/changelogs/unreleased/67037-user-content-gitlab-static-net-brings-back-404-only.yml
new file mode 100644
index 00000000000..8d4f816af2e
--- /dev/null
+++ b/changelogs/unreleased/67037-user-content-gitlab-static-net-brings-back-404-only.yml
@@ -0,0 +1,5 @@
+---
+title: Default the asset proxy whitelist to the installation domain
+merge_request: 32703
+author:
+type: fixed
diff --git a/changelogs/unreleased/ab-add-index-for-ci-builds-metrics.yml b/changelogs/unreleased/ab-add-index-for-ci-builds-metrics.yml
new file mode 100644
index 00000000000..03a37dc0c04
--- /dev/null
+++ b/changelogs/unreleased/ab-add-index-for-ci-builds-metrics.yml
@@ -0,0 +1,5 @@
+---
+title: Create partial index for gitlab-monitor CI metrics
+merge_request: 32546
+author:
+type: performance
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/ab-unconfirmed-email-index.yml b/changelogs/unreleased/ab-unconfirmed-email-index.yml
new file mode 100644
index 00000000000..3887cd87e41
--- /dev/null
+++ b/changelogs/unreleased/ab-unconfirmed-email-index.yml
@@ -0,0 +1,5 @@
+---
+title: Create index for users.unconfirmed_email
+merge_request: 32664
+author:
+type: performance
diff --git a/changelogs/unreleased/ac-accelerate-wiki-attachments.yml b/changelogs/unreleased/ac-accelerate-wiki-attachments.yml
new file mode 100644
index 00000000000..347a570488e
--- /dev/null
+++ b/changelogs/unreleased/ac-accelerate-wiki-attachments.yml
@@ -0,0 +1,5 @@
+---
+title: Preprocess wiki attachments with GitLab-Workhorse
+merge_request: 32663
+author:
+type: performance
diff --git a/changelogs/unreleased/add-label-push-opts.yml b/changelogs/unreleased/add-label-push-opts.yml
new file mode 100644
index 00000000000..1289020e4e5
--- /dev/null
+++ b/changelogs/unreleased/add-label-push-opts.yml
@@ -0,0 +1,5 @@
+---
+title: Support adding and removing labels w/ push opts
+merge_request: 31831
+author:
+type: added
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/bump-pages-1-8.yml b/changelogs/unreleased/bump-pages-1-8.yml
new file mode 100644
index 00000000000..0201ff0fd5f
--- /dev/null
+++ b/changelogs/unreleased/bump-pages-1-8.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade pages to 1.8.0
+merge_request: 32334
+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/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/fix-regression-remove-installation-pod.yml b/changelogs/unreleased/fix-regression-remove-installation-pod.yml
new file mode 100644
index 00000000000..1ed72f1189d
--- /dev/null
+++ b/changelogs/unreleased/fix-regression-remove-installation-pod.yml
@@ -0,0 +1,5 @@
+---
+title: Fix removal of install pods
+merge_request: 32667
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-search-input-dropdown.yml b/changelogs/unreleased/fix-search-input-dropdown.yml
new file mode 100644
index 00000000000..a86f7eacfdb
--- /dev/null
+++ b/changelogs/unreleased/fix-search-input-dropdown.yml
@@ -0,0 +1,5 @@
+---
+title: Fix top-nav search bar dropdown on xl displays
+merge_request: 31864
+author: Kemais Ehlers
+type: fixed
diff --git a/changelogs/unreleased/fj-66723-add-dns-rebinding-protection-check.yml b/changelogs/unreleased/fj-66723-add-dns-rebinding-protection-check.yml
new file mode 100644
index 00000000000..c1372a4a73e
--- /dev/null
+++ b/changelogs/unreleased/fj-66723-add-dns-rebinding-protection-check.yml
@@ -0,0 +1,5 @@
+---
+title: Allow not resolvable urls when dns rebind protection is disabled
+merge_request: 32523
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-remove-dns-protection-when-validating.yml b/changelogs/unreleased/fj-remove-dns-protection-when-validating.yml
new file mode 100644
index 00000000000..9c74f8d69c7
--- /dev/null
+++ b/changelogs/unreleased/fj-remove-dns-protection-when-validating.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid checking dns rebind protection when validating
+merge_request: 32577
+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/georgekoltsov-54023-fogbugz-visibility-level.yml b/changelogs/unreleased/georgekoltsov-54023-fogbugz-visibility-level.yml
new file mode 100644
index 00000000000..d292958c92a
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-54023-fogbugz-visibility-level.yml
@@ -0,0 +1,5 @@
+---
+title: Change default visibility level for FogBugz imported projects to Private
+merge_request: 32142
+author:
+type: fixed
diff --git a/changelogs/unreleased/group_level_jupyterhub.yml b/changelogs/unreleased/group_level_jupyterhub.yml
new file mode 100644
index 00000000000..81fc7600e0e
--- /dev/null
+++ b/changelogs/unreleased/group_level_jupyterhub.yml
@@ -0,0 +1,5 @@
+---
+title: Group level JupyterHub
+merge_request: 32512
+author:
+type: added
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-autosave-for-new-mr.yml b/changelogs/unreleased/id-autosave-for-new-mr.yml
new file mode 100644
index 00000000000..8f269094715
--- /dev/null
+++ b/changelogs/unreleased/id-autosave-for-new-mr.yml
@@ -0,0 +1,5 @@
+---
+title: Fix sharing localStorage with all MRs
+merge_request: 32699
+author:
+type: fixed
diff --git a/changelogs/unreleased/id-code-review-smau.yml b/changelogs/unreleased/id-code-review-smau.yml
new file mode 100644
index 00000000000..0bc7bca789b
--- /dev/null
+++ b/changelogs/unreleased/id-code-review-smau.yml
@@ -0,0 +1,5 @@
+---
+title: Add usage pings for merge request creating
+merge_request: 32059
+author:
+type: added
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/instance-group-level-knative.yml b/changelogs/unreleased/instance-group-level-knative.yml
new file mode 100644
index 00000000000..5108334a3ea
--- /dev/null
+++ b/changelogs/unreleased/instance-group-level-knative.yml
@@ -0,0 +1,5 @@
+---
+title: Allow Knative to be installed on group and instance level clusters
+merge_request: 32128
+author:
+type: added
diff --git a/changelogs/unreleased/issue-32741.yml b/changelogs/unreleased/issue-32741.yml
new file mode 100644
index 00000000000..5d0a3ee2cc7
--- /dev/null
+++ b/changelogs/unreleased/issue-32741.yml
@@ -0,0 +1,5 @@
+---
+title: New interruptible attribute for CI/CD jobs
+merge_request: 23464
+author: Cédric Tabin
+type: added
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/issue_54042.yml b/changelogs/unreleased/issue_54042.yml
new file mode 100644
index 00000000000..465c7426e93
--- /dev/null
+++ b/changelogs/unreleased/issue_54042.yml
@@ -0,0 +1,5 @@
+---
+title: Let project reporters create issue from group boards
+merge_request: 29866
+author:
+type: fixed
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/je-add-group-deployments-page-fe.yml b/changelogs/unreleased/je-add-group-deployments-page-fe.yml
new file mode 100644
index 00000000000..91333087eb5
--- /dev/null
+++ b/changelogs/unreleased/je-add-group-deployments-page-fe.yml
@@ -0,0 +1,5 @@
+---
+title: Add ability to see project deployments at cluster level (FE)
+merge_request: 31575
+author:
+type: added
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/oauth_bypass_two_factor.yml b/changelogs/unreleased/oauth_bypass_two_factor.yml
new file mode 100644
index 00000000000..7261e65a9f3
--- /dev/null
+++ b/changelogs/unreleased/oauth_bypass_two_factor.yml
@@ -0,0 +1,5 @@
+---
+title: Add option to allow OAuth providers to bypass two factor
+merge_request: 31996
+author: Dodocat
+type: added
diff --git a/changelogs/unreleased/persist-needs-error.yml b/changelogs/unreleased/persist-needs-error.yml
new file mode 100644
index 00000000000..96aaa4d11a3
--- /dev/null
+++ b/changelogs/unreleased/persist-needs-error.yml
@@ -0,0 +1,5 @@
+---
+title: Persist `needs:` validation as config error
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/quote-branch-names-in-instructions.yml b/changelogs/unreleased/quote-branch-names-in-instructions.yml
new file mode 100644
index 00000000000..fe5964c8918
--- /dev/null
+++ b/changelogs/unreleased/quote-branch-names-in-instructions.yml
@@ -0,0 +1,5 @@
+---
+title: Quote branch names in how to merge instructions
+merge_request: 32639
+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-add-margin-member-list.yml b/changelogs/unreleased/sh-add-margin-member-list.yml
new file mode 100644
index 00000000000..35d35aad8bf
--- /dev/null
+++ b/changelogs/unreleased/sh-add-margin-member-list.yml
@@ -0,0 +1,5 @@
+---
+title: Add padding to left of "Sort by" in members dropdown
+merge_request: 32602
+author:
+type: other
diff --git a/changelogs/unreleased/sh-add-sidekiq-logging-for-bad-ci.yml b/changelogs/unreleased/sh-add-sidekiq-logging-for-bad-ci.yml
new file mode 100644
index 00000000000..b334355cab6
--- /dev/null
+++ b/changelogs/unreleased/sh-add-sidekiq-logging-for-bad-ci.yml
@@ -0,0 +1,5 @@
+---
+title: Log errors for failed pipeline creation in PostReceive
+merge_request: 32633
+author:
+type: other
diff --git a/changelogs/unreleased/sh-eliminate-gitaly-nplus-one-notes.yml b/changelogs/unreleased/sh-eliminate-gitaly-nplus-one-notes.yml
new file mode 100644
index 00000000000..00523f53dd7
--- /dev/null
+++ b/changelogs/unreleased/sh-eliminate-gitaly-nplus-one-notes.yml
@@ -0,0 +1,5 @@
+---
+title: Eliminate Gitaly N+1 queries with notes API
+merge_request: 32089
+author:
+type: performance
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-issue-move-api.yml b/changelogs/unreleased/sh-fix-issue-move-api.yml
new file mode 100644
index 00000000000..e98d04b02d9
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-issue-move-api.yml
@@ -0,0 +1,5 @@
+---
+title: Fix moving issues API failing when text includes commit URLs
+merge_request: 32317
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-issues-api-gitaly-nplusone.yml b/changelogs/unreleased/sh-fix-issues-api-gitaly-nplusone.yml
deleted file mode 100644
index 3177cb8d18c..00000000000
--- a/changelogs/unreleased/sh-fix-issues-api-gitaly-nplusone.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix Gitaly N+1 calls with listing issues/MRs via API
-merge_request: 31938
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-guard-against-orphaned-project-feature.yml b/changelogs/unreleased/sh-guard-against-orphaned-project-feature.yml
new file mode 100644
index 00000000000..99c8732c5b0
--- /dev/null
+++ b/changelogs/unreleased/sh-guard-against-orphaned-project-feature.yml
@@ -0,0 +1,5 @@
+---
+title: Guard against deleted project feature entry in project permissions
+merge_request: 32187
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-lfs-object-batches.yml b/changelogs/unreleased/sh-lfs-object-batches.yml
new file mode 100644
index 00000000000..09043e286be
--- /dev/null
+++ b/changelogs/unreleased/sh-lfs-object-batches.yml
@@ -0,0 +1,5 @@
+---
+title: Makes LFS object linker process OIDs in batches
+merge_request: 32268
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-project-feature-nplus-one.yml b/changelogs/unreleased/sh-project-feature-nplus-one.yml
new file mode 100644
index 00000000000..425ae7815c1
--- /dev/null
+++ b/changelogs/unreleased/sh-project-feature-nplus-one.yml
@@ -0,0 +1,5 @@
+---
+title: Remove N+1 SQL query loading project feature in dashboard
+merge_request: 32169
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-revert-redis-cache-store.yml b/changelogs/unreleased/sh-revert-redis-cache-store.yml
deleted file mode 100644
index 710b6e7941d..00000000000
--- a/changelogs/unreleased/sh-revert-redis-cache-store.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix "ERR value is not an integer or out of range" errors
-merge_request: 32126
-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/ss-add-board-name-to-page-title.yml b/changelogs/unreleased/ss-add-board-name-to-page-title.yml
new file mode 100644
index 00000000000..8a04972fa32
--- /dev/null
+++ b/changelogs/unreleased/ss-add-board-name-to-page-title.yml
@@ -0,0 +1,5 @@
+---
+title: Added board name to page title in boards view
+merge_request:
+author:
+type: other
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/todos-include-issue-mr-titles.yml b/changelogs/unreleased/todos-include-issue-mr-titles.yml
new file mode 100644
index 00000000000..a9fb9116070
--- /dev/null
+++ b/changelogs/unreleased/todos-include-issue-mr-titles.yml
@@ -0,0 +1,5 @@
+---
+title: Add Issue and Merge Request titles to Todo items
+merge_request: 30435
+author: Arun Kumar Mohan
+type: changed
diff --git a/changelogs/unreleased/tr-param-undefined-fix.yml b/changelogs/unreleased/tr-param-undefined-fix.yml
deleted file mode 100644
index 0a9051485bd..00000000000
--- a/changelogs/unreleased/tr-param-undefined-fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix for embedded metrics undefined params
-merge_request: 31975
-author:
-type: fixed
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-8-0.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-8-0.yml
new file mode 100644
index 00000000000..719a273b30c
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-8-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Runner Helm Chart to 0.8.0
+merge_request: 32289
+author:
+type: other
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/changelogs/unreleased/use_default_external_auth_label_empty.yml b/changelogs/unreleased/use_default_external_auth_label_empty.yml
new file mode 100644
index 00000000000..9c1039b0875
--- /dev/null
+++ b/changelogs/unreleased/use_default_external_auth_label_empty.yml
@@ -0,0 +1,6 @@
+---
+title: Prevent empty external authorization classification labels from overriding
+ the default label
+merge_request: 32517
+author: Will Chandler
+type: fixed
diff --git a/changelogs/unreleased/user_name_migration.yml b/changelogs/unreleased/user_name_migration.yml
new file mode 100644
index 00000000000..11d6ff2e8b2
--- /dev/null
+++ b/changelogs/unreleased/user_name_migration.yml
@@ -0,0 +1,5 @@
+---
+title: Add First and Last name columns to User model
+merge_request: 31985
+author:
+type: added
diff --git a/changelogs/unreleased/winh-deduplicate-board-headers.yml b/changelogs/unreleased/winh-deduplicate-board-headers.yml
new file mode 100644
index 00000000000..009ce59b6bc
--- /dev/null
+++ b/changelogs/unreleased/winh-deduplicate-board-headers.yml
@@ -0,0 +1,5 @@
+---
+title: Hide duplicate board list while dragging
+merge_request: 32099
+author:
+type: fixed
diff --git a/config/application.rb b/config/application.rb
index 294ed470298..81889561473 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -165,7 +165,6 @@ module Gitlab
config.assets.precompile << "locale/**/app.js"
config.assets.precompile << "emoji_sprites.css"
config.assets.precompile << "errors.css"
- config.assets.precompile << "csslab.css"
config.assets.precompile << "highlight/themes/*.css"
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 973c2747838..e3693f612e3 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -158,6 +158,7 @@ production: &base
# The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
# The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
+ # Please be aware that a placeholder is required for the Service Desk feature to work.
address: "gitlab-incoming+%{key}@gmail.com"
# Email account username
@@ -431,6 +432,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:
@@ -780,6 +782,14 @@ production: &base
# (default: [])
external_providers: []
+ # CAUTION!
+ # This allows users to login with the specified providers without two factor. Define the allowed providers
+ # using an array, e.g. ["twitter", 'google_oauth2'], or as true/false to allow all providers or none.
+ # This option should only be configured for providers which already have two factor.
+ # This configration dose not apply to SAML.
+ # (default: false)
+ allow_bypass_two_factor: ["twitter", 'google_oauth2']
+
## Auth providers
# Uncomment the following lines and fill in the data of the auth provider you want to use
# If your favorite auth provider is not listed you can use others:
diff --git a/config/initializers/0_inject_enterprise_edition_module.rb b/config/initializers/0_inject_enterprise_edition_module.rb
index 39595e23abe..b3ebb44ef25 100644
--- a/config/initializers/0_inject_enterprise_edition_module.rb
+++ b/config/initializers/0_inject_enterprise_edition_module.rb
@@ -3,8 +3,15 @@
require 'active_support/inflector'
module InjectEnterpriseEditionModule
- def prepend_if_ee(constant)
- prepend(constant.constantize) if Gitlab.ee?
+ def prepend_if_ee(constant, with_descendants: false)
+ return unless Gitlab.ee?
+
+ ee_module = constant.constantize
+ prepend(ee_module)
+
+ if with_descendants
+ descendants.each { |descendant| descendant.prepend(ee_module) }
+ end
end
def extend_if_ee(constant)
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index fdc6b0a05ab..4160f488a7a 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -84,6 +84,7 @@ Settings['omniauth'] ||= Settingslogic.new({})
Settings.omniauth['enabled'] = true if Settings.omniauth['enabled'].nil?
Settings.omniauth['auto_sign_in_with_provider'] = false if Settings.omniauth['auto_sign_in_with_provider'].nil?
Settings.omniauth['allow_single_sign_on'] = false if Settings.omniauth['allow_single_sign_on'].nil?
+Settings.omniauth['allow_bypass_two_factor'] = false if Settings.omniauth['allow_bypass_two_factor'].nil?
Settings.omniauth['external_providers'] = [] if Settings.omniauth['external_providers'].nil?
Settings.omniauth['block_auto_created_users'] = true if Settings.omniauth['block_auto_created_users'].nil?
Settings.omniauth['auto_link_ldap_user'] = false if Settings.omniauth['auto_link_ldap_user'].nil?
@@ -514,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/chronic_duration.rb b/config/initializers/chronic_duration.rb
index b65b06c813a..aa43eef434c 100644
--- a/config/initializers/chronic_duration.rb
+++ b/config/initializers/chronic_duration.rb
@@ -1 +1,5 @@
+# frozen_string_literal: true
+
ChronicDuration.raise_exceptions = true
+
+ChronicDuration.prepend Gitlab::Patch::ChronicDuration
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..a622ce268da 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -67,7 +67,7 @@ Rails.application.routes.draw do
get 'health_check(/:checks)' => 'health_check#index', as: :health_check
scope path: '-' do
- # '/-/health' implemented by BasicHealthMiddleware
+ # '/-/health' implemented by BasicHealthCheck middleware
get 'liveness' => 'health#liveness'
get 'readiness' => 'health#readiness'
resources :metrics, only: [:index]
@@ -107,9 +107,11 @@ Rails.application.routes.draw do
draw :instance_statistics
Gitlab.ee do
+ draw :security
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/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index ea165508d29..e89e9657314 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -102,6 +102,7 @@
- [create_github_webhook, 2]
- [geo, 1]
- [repository_update_mirror, 1]
+ - [repository_push_audit_event, 1]
- [new_epic, 2]
- [project_import_schedule, 1]
- [project_update_repository_storage, 1]
@@ -114,3 +115,4 @@
- [export_csv, 1]
- [incident_management, 2]
- [jira_connect, 1]
+ - [update_external_pull_requests, 3]
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 442b4b4c21e..969a84e85dd 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -298,6 +298,13 @@ module.exports = {
from: path.join(ROOT_PATH, 'node_modules/pdfjs-dist/cmaps/'),
to: path.join(ROOT_PATH, 'public/assets/webpack/cmaps/'),
},
+ {
+ from: path.join(
+ ROOT_PATH,
+ 'node_modules/@gitlab/visual-review-tools/dist/visual_review_toolbar.js',
+ ),
+ to: path.join(ROOT_PATH, 'public/assets/webpack'),
+ },
]),
// compression can require a lot of compute time and is disabled in CI
diff --git a/config/webpack.config.review_toolbar.js b/config/webpack.config.review_toolbar.js
deleted file mode 100644
index baaba7ed387..00000000000
--- a/config/webpack.config.review_toolbar.js
+++ /dev/null
@@ -1,58 +0,0 @@
-const path = require('path');
-const CompressionPlugin = require('compression-webpack-plugin');
-
-const ROOT_PATH = path.resolve(__dirname, '..');
-const CACHE_PATH = process.env.WEBPACK_CACHE_PATH || path.join(ROOT_PATH, 'tmp/cache');
-const NO_SOURCEMAPS = process.env.NO_SOURCEMAPS;
-const IS_PRODUCTION = process.env.NODE_ENV === 'production';
-
-const devtool = IS_PRODUCTION ? 'source-map' : 'cheap-module-eval-source-map';
-
-const alias = {
- vendor: path.join(ROOT_PATH, 'vendor/assets/javascripts'),
- spec: path.join(ROOT_PATH, 'spec/javascripts'),
-};
-
-module.exports = {
- mode: IS_PRODUCTION ? 'production' : 'development',
-
- context: path.join(ROOT_PATH, 'app/assets/javascripts'),
-
- name: 'visual_review_toolbar',
-
- entry: './visual_review_toolbar',
-
- output: {
- path: path.join(ROOT_PATH, 'public/assets/webpack'),
- filename: 'visual_review_toolbar.js',
- library: 'VisualReviewToolbar',
- libraryTarget: 'var',
- },
-
- resolve: {
- alias,
- },
-
- module: {
- rules: [
- {
- test: /\.js$/,
- loader: 'babel-loader',
- options: {
- cacheDirectory: path.join(CACHE_PATH, 'babel-loader'),
- },
- },
- {
- test: /\.css$/,
- use: ['style-loader', 'css-loader'],
- },
- ],
- },
-
- plugins: [
- // compression can require a lot of compute time and is disabled in CI
- new CompressionPlugin(),
- ].filter(Boolean),
-
- devtool: NO_SOURCEMAPS ? false : devtool,
-};
diff --git a/danger/database/Dangerfile b/danger/database/Dangerfile
index 3550cb7eabf..5cdad09db6e 100644
--- a/danger/database/Dangerfile
+++ b/danger/database/Dangerfile
@@ -1,7 +1,13 @@
# frozen_string_literal: true
-SCHEMA_NOT_UPDATED_MESSAGE = <<~MSG
-**New %<migrations>s added but %<schema>s wasn't updated.**
+gitlab_danger = GitlabDanger.new(helper.gitlab_helper)
+
+SCHEMA_NOT_UPDATED_MESSAGE_SHORT = <<~MSG
+New %<migrations>s added but %<schema>s wasn't updated.
+MSG
+
+SCHEMA_NOT_UPDATED_MESSAGE_FULL = <<~MSG
+**#{SCHEMA_NOT_UPDATED_MESSAGE_SHORT}**
Usually, when adding new %<migrations>s, %<schema>s should be
updated too (unless the migration isn't changing the DB schema
@@ -29,14 +35,18 @@ geo_db_schema_updated = !git.modified_files.grep(%r{\Aee/db/geo/schema\.rb}).emp
non_geo_migration_created = !git.added_files.grep(%r{\A(db/(post_)?migrate)/}).empty?
geo_migration_created = !git.added_files.grep(%r{\Aee/db/geo/(post_)?migrate/}).empty?
+format_str = gitlab_danger.ci? ? SCHEMA_NOT_UPDATED_MESSAGE_FULL : SCHEMA_NOT_UPDATED_MESSAGE_SHORT
+
if non_geo_migration_created && !non_geo_db_schema_updated
- warn format(SCHEMA_NOT_UPDATED_MESSAGE, migrations: 'migrations', schema: gitlab.html_link("db/schema.rb"))
+ warn format(format_str, migrations: 'migrations', schema: gitlab_danger.html_link("db/schema.rb"))
end
if geo_migration_created && !geo_db_schema_updated
- warn format(SCHEMA_NOT_UPDATED_MESSAGE, migrations: 'Geo migrations', schema: gitlab.html_link("ee/db/geo/schema.rb"))
+ warn format(format_str, migrations: 'Geo migrations', schema: gitlab_danger.html_link("ee/db/geo/schema.rb"))
end
+return unless gitlab_danger.ci?
+
db_paths_to_review = helper.changes_by_category[:database]
if gitlab.mr_labels.include?('database') || db_paths_to_review.any?
diff --git a/danger/documentation/Dangerfile b/danger/documentation/Dangerfile
index 96c0d9b7478..ad64c3a6c60 100644
--- a/danger/documentation/Dangerfile
+++ b/danger/documentation/Dangerfile
@@ -6,20 +6,22 @@ unless docs_paths_to_review.empty?
message 'This merge request adds or changes files that require a review ' \
'from the Technical Writing team.'
- markdown(<<~MARKDOWN)
-## Documentation review
+ if GitlabDanger.new(helper.gitlab_helper).ci?
+ markdown(<<~MARKDOWN)
+ ## Documentation review
-The following files require a review from a technical writer:
+ The following files require a review from a technical writer:
-* #{docs_paths_to_review.map { |path| "`#{path}`" }.join("\n* ")}
+ * #{docs_paths_to_review.map { |path| "`#{path}`" }.join("\n* ")}
-The review does not need to block merging this merge request. See the:
+ The review does not need to block merging this merge request. See the:
-- [DevOps stages](https://about.gitlab.com/handbook/product/categories/#devops-stages) for the appropriate technical writer for this review.
-- [Documentation workflows](https://docs.gitlab.com/ee/development/documentation/workflow.html) for information on when to assign a merge request for review.
- MARKDOWN
+ - [DevOps stages](https://about.gitlab.com/handbook/product/categories/#devops-stages) for the appropriate technical writer for this review.
+ - [Documentation workflows](https://docs.gitlab.com/ee/development/documentation/workflow.html) for information on when to assign a merge request for review.
+ MARKDOWN
- unless gitlab.mr_labels.include?('Documentation')
- warn 'This merge request is missing the ~Documentation label.'
+ unless gitlab.mr_labels.include?('Documentation')
+ warn 'This merge request is missing the ~Documentation label.'
+ end
end
end
diff --git a/danger/duplicate_yarn_dependencies/Dangerfile b/danger/duplicate_yarn_dependencies/Dangerfile
index 25f81ec86a4..492b888d00e 100644
--- a/danger/duplicate_yarn_dependencies/Dangerfile
+++ b/danger/duplicate_yarn_dependencies/Dangerfile
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-return unless helper.all_changed_files.include? 'yarn.lock'
+return unless helper.all_changed_files.include?('yarn.lock')
duplicate = `node_modules/.bin/yarn-deduplicate --list --strategy fewer yarn.lock`
.split(/$/)
@@ -11,17 +11,19 @@ return if duplicate.empty?
warn 'This merge request has introduced duplicated yarn dependencies.'
-markdown(<<~MARKDOWN)
- ## Duplicate yarn dependencies
+if GitlabDanger.new(helper.gitlab_helper).ci?
+ markdown(<<~MARKDOWN)
+ ## Duplicate yarn dependencies
- The following dependencies should be de-duplicated:
+ The following dependencies should be de-duplicated:
- * #{duplicate.map { |path| "`#{path}`" }.join("\n* ")}
+ * #{duplicate.map { |path| "`#{path}`" }.join("\n* ")}
- Please run the following command and commit the changes to `yarn.lock`:
+ Please run the following command and commit the changes to `yarn.lock`:
- ```
- node_modules/.bin/yarn-deduplicate --strategy fewer yarn.lock \\
- && yarn install
- ```
-MARKDOWN
+ ```
+ node_modules/.bin/yarn-deduplicate --strategy fewer yarn.lock \\
+ && yarn install
+ ```
+ MARKDOWN
+end
diff --git a/danger/eslint/Dangerfile b/danger/eslint/Dangerfile
index 4916cacfd7e..92830bd7706 100644
--- a/danger/eslint/Dangerfile
+++ b/danger/eslint/Dangerfile
@@ -13,17 +13,19 @@ return if eslint_candidates.empty?
warn 'This merge request changed files with disabled eslint rules. Please consider fixing them.'
-markdown(<<~MARKDOWN)
- ## Disabled eslint rules
+if GitlabDanger.new(helper.gitlab_helper).ci?
+ markdown(<<~MARKDOWN)
+ ## Disabled eslint rules
- The following files have disabled `eslint` rules. Please consider fixing them:
+ The following files have disabled `eslint` rules. Please consider fixing them:
- * #{eslint_candidates.map { |path| "`#{path}`" }.join("\n* ")}
+ * #{eslint_candidates.map { |path| "`#{path}`" }.join("\n* ")}
- Run the following command for more details
+ Run the following command for more details
- ```
- node_modules/.bin/eslint --report-unused-disable-directives --no-inline-config \\
- #{eslint_candidates.map { |path| " '#{path}'" }.join(" \\\n")}
- ```
-MARKDOWN
+ ```
+ node_modules/.bin/eslint --report-unused-disable-directives --no-inline-config \\
+ #{eslint_candidates.map { |path| " '#{path}'" }.join(" \\\n")}
+ ```
+ MARKDOWN
+end
diff --git a/danger/frozen_string/Dangerfile b/danger/frozen_string/Dangerfile
index b9687ef6b83..8d3ac3dee68 100644
--- a/danger/frozen_string/Dangerfile
+++ b/danger/frozen_string/Dangerfile
@@ -16,11 +16,13 @@ if files_to_fix.any?
warn 'This merge request adds files that do not enforce frozen string literal. ' \
'See https://gitlab.com/gitlab-org/gitlab-ce/issues/47424 for more information.'
- markdown(<<~MARKDOWN)
- ## Enable Frozen String Literal
+ if GitlabDanger.new(helper.gitlab_helper).ci?
+ markdown(<<~MARKDOWN)
+ ## Enable Frozen String Literal
- The following files should have `#{MAGIC_COMMENT}` on the first line:
+ The following files should have `#{MAGIC_COMMENT}` on the first line:
- * #{files_to_fix.map { |path| "`#{path}`" }.join("\n* ")}
- MARKDOWN
+ * #{files_to_fix.map { |path| "`#{path}`" }.join("\n* ")}
+ MARKDOWN
+ end
end
diff --git a/danger/gemfile/Dangerfile b/danger/gemfile/Dangerfile
index dfe64f79d7b..07c4c07cfe8 100644
--- a/danger/gemfile/Dangerfile
+++ b/danger/gemfile/Dangerfile
@@ -1,5 +1,9 @@
-GEMFILE_LOCK_NOT_UPDATED_MESSAGE = <<~MSG.freeze
-**%<gemfile>s was updated but %<gemfile_lock>s wasn't updated.**
+GEMFILE_LOCK_NOT_UPDATED_MESSAGE_SHORT = <<~MSG.freeze
+%<gemfile>s was updated but %<gemfile_lock>s wasn't updated.
+MSG
+
+GEMFILE_LOCK_NOT_UPDATED_MESSAGE_FULL = <<~MSG.freeze
+**#{GEMFILE_LOCK_NOT_UPDATED_MESSAGE_SHORT}**
Usually, when %<gemfile>s is updated, you should run
```
@@ -19,5 +23,14 @@ gemfile_modified = git.modified_files.include?("Gemfile")
gemfile_lock_modified = git.modified_files.include?("Gemfile.lock")
if gemfile_modified && !gemfile_lock_modified
- warn format(GEMFILE_LOCK_NOT_UPDATED_MESSAGE, gemfile: gitlab.html_link("Gemfile"), gemfile_lock: gitlab.html_link("Gemfile.lock"))
+ gitlab_danger = GitlabDanger.new(helper.gitlab_helper)
+
+ format_str = gitlab_danger.ci? ? GEMFILE_LOCK_NOT_UPDATED_MESSAGE_FULL : GEMFILE_LOCK_NOT_UPDATED_MESSAGE_SHORT
+
+ message = format(format_str,
+ gemfile: gitlab_danger.html_link("Gemfile"),
+ gemfile_lock: gitlab_danger.html_link("Gemfile.lock")
+ )
+
+ warn(message)
end
diff --git a/danger/only_documentation/Dangerfile b/danger/only_documentation/Dangerfile
index dad12c0d29c..ce7ede26d9a 100644
--- a/danger/only_documentation/Dangerfile
+++ b/danger/only_documentation/Dangerfile
@@ -1,7 +1,7 @@
# rubocop:disable Style/SignalException
# frozen_string_literal: true
-has_only_docs_changes = helper.all_changed_files.all? { |file| file.start_with?('doc/', '.gitlab/ci/docs.gitlab-ci.yml', '.mdlrc') || file.end_with?('.md') }
+has_only_docs_changes = helper.all_changed_files.all? { |file| file.start_with?('doc/', '.gitlab/ci/docs.gitlab-ci.yml', '.markdownlint.json') || file.end_with?('.md') }
is_docs_only_branch = gitlab.branch_for_head =~ /(^docs[\/-].*|.*-docs$)/
if is_docs_only_branch && !has_only_docs_changes
diff --git a/danger/prettier/Dangerfile b/danger/prettier/Dangerfile
index 37c4b78a213..0be75db8baa 100644
--- a/danger/prettier/Dangerfile
+++ b/danger/prettier/Dangerfile
@@ -19,21 +19,23 @@ return if unpretty.empty?
warn 'This merge request changed frontend files without pretty printing them.'
-markdown(<<~MARKDOWN)
- ## Pretty print Frontend files
+if GitlabDanger.new(helper.gitlab_helper).ci?
+ markdown(<<~MARKDOWN)
+ ## Pretty print Frontend files
- The following files should have been pretty printed with `prettier`:
+ The following files should have been pretty printed with `prettier`:
- * #{unpretty.map { |path| "`#{path}`" }.join("\n* ")}
+ * #{unpretty.map { |path| "`#{path}`" }.join("\n* ")}
- Please run
+ Please run
- ```
- node_modules/.bin/prettier --write \\
- #{unpretty.map { |path| " '#{path}'" }.join(" \\\n")}
- ```
+ ```
+ node_modules/.bin/prettier --write \\
+ #{unpretty.map { |path| " '#{path}'" }.join(" \\\n")}
+ ```
- Also consider auto-formatting [on-save].
+ Also consider auto-formatting [on-save].
- [on-save]: https://docs.gitlab.com/ee/development/new_fe_guide/style/prettier.html
-MARKDOWN
+ [on-save]: https://docs.gitlab.com/ee/development/new_fe_guide/style/prettier.html
+ MARKDOWN
+end
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/20171230123729_init_schema.rb b/db/migrate/20171230123729_init_schema.rb
index fa90b37954f..a474ea2f925 100644
--- a/db/migrate/20171230123729_init_schema.rb
+++ b/db/migrate/20171230123729_init_schema.rb
@@ -5,6 +5,7 @@
# rubocop:disable Metrics/AbcSize
# rubocop:disable Migration/AddConcurrentForeignKey
# rubocop:disable Style/WordArray
+# rubocop:disable Migration/AddLimitToStringColumns
class InitSchema < ActiveRecord::Migration[4.2]
DOWNTIME = false
@@ -1852,3 +1853,4 @@ class InitSchema < ActiveRecord::Migration[4.2]
raise ActiveRecord::IrreversibleMigration, "The initial migration is not revertable"
end
end
+# rubocop:enable Migration/AddLimitToStringColumns
diff --git a/db/migrate/20180101160629_create_prometheus_metrics.rb b/db/migrate/20180101160629_create_prometheus_metrics.rb
index e3b1ed710d6..a098b999a0a 100644
--- a/db/migrate/20180101160629_create_prometheus_metrics.rb
+++ b/db/migrate/20180101160629_create_prometheus_metrics.rb
@@ -4,6 +4,7 @@ class CreatePrometheusMetrics < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :prometheus_metrics do |t|
t.references :project, index: true, foreign_key: { on_delete: :cascade }, null: false
t.string :title, null: false
@@ -14,5 +15,6 @@ class CreatePrometheusMetrics < ActiveRecord::Migration[4.2]
t.integer :group, null: false, index: true
t.timestamps_with_timezone null: false
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180122162010_add_auto_devops_domain_to_application_settings.rb b/db/migrate/20180122162010_add_auto_devops_domain_to_application_settings.rb
index c76dc5b3a68..eb446ad0d72 100644
--- a/db/migrate/20180122162010_add_auto_devops_domain_to_application_settings.rb
+++ b/db/migrate/20180122162010_add_auto_devops_domain_to_application_settings.rb
@@ -8,6 +8,6 @@ class AddAutoDevopsDomainToApplicationSettings < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
- add_column :application_settings, :auto_devops_domain, :string
+ add_column :application_settings, :auto_devops_domain, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180129193323_add_uploads_builder_context.rb b/db/migrate/20180129193323_add_uploads_builder_context.rb
index c7227bf0f1e..710fa7b3ba8 100644
--- a/db/migrate/20180129193323_add_uploads_builder_context.rb
+++ b/db/migrate/20180129193323_add_uploads_builder_context.rb
@@ -8,7 +8,9 @@ class AddUploadsBuilderContext < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
add_column :uploads, :mount_point, :string
add_column :uploads, :secret, :string
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180212030105_add_external_ip_to_clusters_applications_ingress.rb b/db/migrate/20180212030105_add_external_ip_to_clusters_applications_ingress.rb
index e2a9a68b1ad..78aa2014601 100644
--- a/db/migrate/20180212030105_add_external_ip_to_clusters_applications_ingress.rb
+++ b/db/migrate/20180212030105_add_external_ip_to_clusters_applications_ingress.rb
@@ -4,6 +4,6 @@ class AddExternalIpToClustersApplicationsIngress < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
- add_column :clusters_applications_ingress, :external_ip, :string
+ add_column :clusters_applications_ingress, :external_ip, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180214093516_create_badges.rb b/db/migrate/20180214093516_create_badges.rb
index 66e017b115a..fe27d465571 100644
--- a/db/migrate/20180214093516_create_badges.rb
+++ b/db/migrate/20180214093516_create_badges.rb
@@ -2,6 +2,7 @@ class CreateBadges < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :badges do |t|
t.string :link_url, null: false
t.string :image_url, null: false
@@ -11,6 +12,7 @@ class CreateBadges < ActiveRecord::Migration[4.2]
t.timestamps_with_timezone null: false
end
+ # rubocop:enable Migration/AddLimitToStringColumns
# rubocop:disable Migration/AddConcurrentForeignKey
add_foreign_key :badges, :namespaces, column: :group_id, on_delete: :cascade
diff --git a/db/migrate/20180214155405_create_clusters_applications_runners.rb b/db/migrate/20180214155405_create_clusters_applications_runners.rb
index ce594c91890..f611fefbb0d 100644
--- a/db/migrate/20180214155405_create_clusters_applications_runners.rb
+++ b/db/migrate/20180214155405_create_clusters_applications_runners.rb
@@ -13,7 +13,7 @@ class CreateClustersApplicationsRunners < ActiveRecord::Migration[4.2]
t.index :cluster_id, unique: true
t.integer :status, null: false
t.timestamps_with_timezone null: false
- t.string :version, null: false
+ t.string :version, null: false # rubocop:disable Migration/AddLimitToStringColumns
t.text :status_reason
end
diff --git a/db/migrate/20180216120000_add_pages_domain_verification.rb b/db/migrate/20180216120000_add_pages_domain_verification.rb
index f709f5a5809..b2f19f2e1a9 100644
--- a/db/migrate/20180216120000_add_pages_domain_verification.rb
+++ b/db/migrate/20180216120000_add_pages_domain_verification.rb
@@ -3,6 +3,6 @@ class AddPagesDomainVerification < ActiveRecord::Migration[4.2]
def change
add_column :pages_domains, :verified_at, :datetime_with_timezone
- add_column :pages_domains, :verification_code, :string
+ add_column :pages_domains, :verification_code, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180222043024_add_ip_address_to_runner.rb b/db/migrate/20180222043024_add_ip_address_to_runner.rb
index b52366c0be1..08fb0bd900c 100644
--- a/db/migrate/20180222043024_add_ip_address_to_runner.rb
+++ b/db/migrate/20180222043024_add_ip_address_to_runner.rb
@@ -4,6 +4,6 @@ class AddIpAddressToRunner < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
- add_column :ci_runners, :ip_address, :string
+ add_column :ci_runners, :ip_address, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180308125206_add_user_internal_regex_to_application_setting.rb b/db/migrate/20180308125206_add_user_internal_regex_to_application_setting.rb
index 5e4bf96f86f..9bdd44baf58 100644
--- a/db/migrate/20180308125206_add_user_internal_regex_to_application_setting.rb
+++ b/db/migrate/20180308125206_add_user_internal_regex_to_application_setting.rb
@@ -4,7 +4,7 @@ class AddUserInternalRegexToApplicationSetting < ActiveRecord::Migration[4.2]
DOWNTIME = false
def up
- add_column :application_settings, :user_default_internal_regex, :string, null: true
+ add_column :application_settings, :user_default_internal_regex, :string, null: true # rubocop:disable Migration/AddLimitToStringColumns
end
def down
diff --git a/db/migrate/20180315160435_add_external_auth_mutual_tls_fields_to_project_settings.rb b/db/migrate/20180315160435_add_external_auth_mutual_tls_fields_to_project_settings.rb
index ee3d1078f5e..c379d207ff0 100644
--- a/db/migrate/20180315160435_add_external_auth_mutual_tls_fields_to_project_settings.rb
+++ b/db/migrate/20180315160435_add_external_auth_mutual_tls_fields_to_project_settings.rb
@@ -2,6 +2,7 @@ class AddExternalAuthMutualTlsFieldsToProjectSettings < ActiveRecord::Migration[
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
add_column :application_settings,
:external_auth_client_cert, :text
add_column :application_settings,
@@ -12,5 +13,6 @@ class AddExternalAuthMutualTlsFieldsToProjectSettings < ActiveRecord::Migration[
:encrypted_external_auth_client_key_pass, :string
add_column :application_settings,
:encrypted_external_auth_client_key_pass_iv, :string
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180319190020_create_deploy_tokens.rb b/db/migrate/20180319190020_create_deploy_tokens.rb
index a4d797679c5..f444521b3ae 100644
--- a/db/migrate/20180319190020_create_deploy_tokens.rb
+++ b/db/migrate/20180319190020_create_deploy_tokens.rb
@@ -2,6 +2,7 @@ class CreateDeployTokens < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :deploy_tokens do |t|
t.boolean :revoked, default: false
t.boolean :read_repository, null: false, default: false
@@ -15,5 +16,6 @@ class CreateDeployTokens < ActiveRecord::Migration[4.2]
t.index [:token, :expires_at, :id], where: "(revoked IS FALSE)"
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180502122856_create_project_mirror_data.rb b/db/migrate/20180502122856_create_project_mirror_data.rb
index 9781815a97b..04367e1c98b 100644
--- a/db/migrate/20180502122856_create_project_mirror_data.rb
+++ b/db/migrate/20180502122856_create_project_mirror_data.rb
@@ -3,6 +3,7 @@ class CreateProjectMirrorData < ActiveRecord::Migration[4.2]
DOWNTIME = false
+ # rubocop:disable Migration/AddLimitToStringColumns
def up
if table_exists?(:project_mirror_data)
add_column :project_mirror_data, :status, :string unless column_exists?(:project_mirror_data, :status)
@@ -17,6 +18,7 @@ class CreateProjectMirrorData < ActiveRecord::Migration[4.2]
end
end
end
+ # rubocop:enable Migration/AddLimitToStringColumns
def down
remove_column :project_mirror_data, :status
diff --git a/db/migrate/20180503131624_create_remote_mirrors.rb b/db/migrate/20180503131624_create_remote_mirrors.rb
index 288ae365f0f..a079c1b3328 100644
--- a/db/migrate/20180503131624_create_remote_mirrors.rb
+++ b/db/migrate/20180503131624_create_remote_mirrors.rb
@@ -5,6 +5,7 @@ class CreateRemoteMirrors < ActiveRecord::Migration[4.2]
disable_ddl_transaction!
+ # rubocop:disable Migration/AddLimitToStringColumns
def up
return if table_exists?(:remote_mirrors)
@@ -27,6 +28,7 @@ class CreateRemoteMirrors < ActiveRecord::Migration[4.2]
t.timestamps null: false
end
end
+ # rubocop:enable Migration/AddLimitToStringColumns
def down
# ee/db/migrate/20160321161032_create_remote_mirrors_ee.rb will remove the table
diff --git a/db/migrate/20180503175053_ensure_missing_columns_to_project_mirror_data.rb b/db/migrate/20180503175053_ensure_missing_columns_to_project_mirror_data.rb
index 3775b3a08c9..f00493ed515 100644
--- a/db/migrate/20180503175053_ensure_missing_columns_to_project_mirror_data.rb
+++ b/db/migrate/20180503175053_ensure_missing_columns_to_project_mirror_data.rb
@@ -4,8 +4,8 @@ class EnsureMissingColumnsToProjectMirrorData < ActiveRecord::Migration[4.2]
DOWNTIME = false
def up
- add_column :project_mirror_data, :status, :string unless column_exists?(:project_mirror_data, :status)
- add_column :project_mirror_data, :jid, :string unless column_exists?(:project_mirror_data, :jid)
+ add_column :project_mirror_data, :status, :string unless column_exists?(:project_mirror_data, :status) # rubocop:disable Migration/AddLimitToStringColumns
+ add_column :project_mirror_data, :jid, :string unless column_exists?(:project_mirror_data, :jid) # rubocop:disable Migration/AddLimitToStringColumns
add_column :project_mirror_data, :last_error, :text unless column_exists?(:project_mirror_data, :last_error)
end
diff --git a/db/migrate/20180511131058_create_clusters_applications_jupyter.rb b/db/migrate/20180511131058_create_clusters_applications_jupyter.rb
index 749aeeb4792..4633d930e2d 100644
--- a/db/migrate/20180511131058_create_clusters_applications_jupyter.rb
+++ b/db/migrate/20180511131058_create_clusters_applications_jupyter.rb
@@ -7,17 +7,19 @@ class CreateClustersApplicationsJupyter < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :clusters_applications_jupyter do |t|
t.references :cluster, null: false, unique: true, foreign_key: { on_delete: :cascade }
t.references :oauth_application, foreign_key: { on_delete: :nullify }
t.integer :status, null: false
- t.string :version, null: false
- t.string :hostname
+ t.string :version, null: false # rubocop:disable Migration/AddLimitToStringColumns
+ t.string :hostname # rubocop:disable Migration/AddLimitToStringColumns
t.timestamps_with_timezone null: false
t.text :status_reason
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180515121227_create_notes_diff_files.rb b/db/migrate/20180515121227_create_notes_diff_files.rb
index e50324d8599..5f6dba11ff9 100644
--- a/db/migrate/20180515121227_create_notes_diff_files.rb
+++ b/db/migrate/20180515121227_create_notes_diff_files.rb
@@ -4,6 +4,7 @@ class CreateNotesDiffFiles < ActiveRecord::Migration[4.2]
disable_ddl_transaction!
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :note_diff_files do |t|
t.references :diff_note, references: :notes, null: false, index: { unique: true }
t.text :diff, null: false
@@ -18,5 +19,6 @@ class CreateNotesDiffFiles < ActiveRecord::Migration[4.2]
# rubocop:disable Migration/AddConcurrentForeignKey
add_foreign_key :note_diff_files, :notes, column: :diff_note_id, on_delete: :cascade
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180529093006_ensure_remote_mirror_columns.rb b/db/migrate/20180529093006_ensure_remote_mirror_columns.rb
index 207e1f089fb..a0a1150f022 100644
--- a/db/migrate/20180529093006_ensure_remote_mirror_columns.rb
+++ b/db/migrate/20180529093006_ensure_remote_mirror_columns.rb
@@ -8,7 +8,7 @@ class EnsureRemoteMirrorColumns < ActiveRecord::Migration[4.2]
def up
# rubocop:disable Migration/Datetime
add_column :remote_mirrors, :last_update_started_at, :datetime unless column_exists?(:remote_mirrors, :last_update_started_at)
- add_column :remote_mirrors, :remote_name, :string unless column_exists?(:remote_mirrors, :remote_name)
+ add_column :remote_mirrors, :remote_name, :string unless column_exists?(:remote_mirrors, :remote_name) # rubocop:disable Migration/AddLimitToStringColumns
unless column_exists?(:remote_mirrors, :only_protected_branches)
add_column_with_default(:remote_mirrors,
diff --git a/db/migrate/20180531185349_add_repository_languages.rb b/db/migrate/20180531185349_add_repository_languages.rb
index 26a01c3bb26..d517c21c26c 100644
--- a/db/migrate/20180531185349_add_repository_languages.rb
+++ b/db/migrate/20180531185349_add_repository_languages.rb
@@ -4,6 +4,7 @@ class AddRepositoryLanguages < ActiveRecord::Migration[4.2]
DOWNTIME = false
def up
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table(:programming_languages) do |t|
t.string :name, null: false
t.string :color, null: false
@@ -19,6 +20,7 @@ class AddRepositoryLanguages < ActiveRecord::Migration[4.2]
add_index :programming_languages, :name, unique: true
add_index :repository_languages, [:project_id, :programming_language_id],
unique: true, name: "index_repository_languages_on_project_and_languages_id"
+ # rubocop:enable Migration/AddLimitToStringColumns
end
def down
diff --git a/db/migrate/20180613081317_create_ci_builds_runner_session.rb b/db/migrate/20180613081317_create_ci_builds_runner_session.rb
index eb41f76b105..68af38834d2 100644
--- a/db/migrate/20180613081317_create_ci_builds_runner_session.rb
+++ b/db/migrate/20180613081317_create_ci_builds_runner_session.rb
@@ -8,6 +8,7 @@ class CreateCiBuildsRunnerSession < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :ci_builds_runner_session, id: :bigserial do |t|
t.integer :build_id, null: false
t.string :url, null: false
@@ -17,5 +18,6 @@ class CreateCiBuildsRunnerSession < ActiveRecord::Migration[4.2]
t.foreign_key :ci_builds, column: :build_id, on_delete: :cascade
t.index :build_id, unique: true
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180713092803_create_user_statuses.rb b/db/migrate/20180713092803_create_user_statuses.rb
index 43b96805c1e..3abab4e45a9 100644
--- a/db/migrate/20180713092803_create_user_statuses.rb
+++ b/db/migrate/20180713092803_create_user_statuses.rb
@@ -6,6 +6,7 @@ class CreateUserStatuses < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :user_statuses, id: false, primary_key: :user_id do |t|
t.references :user,
foreign_key: { on_delete: :cascade },
@@ -16,5 +17,6 @@ class CreateUserStatuses < ActiveRecord::Migration[4.2]
t.string :message, limit: 100
t.string :message_html
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180814153625_add_commit_email_to_users.rb b/db/migrate/20180814153625_add_commit_email_to_users.rb
index 4d9217ea504..98bafc14a03 100644
--- a/db/migrate/20180814153625_add_commit_email_to_users.rb
+++ b/db/migrate/20180814153625_add_commit_email_to_users.rb
@@ -28,6 +28,6 @@ class AddCommitEmailToUsers < ActiveRecord::Migration[4.2]
# disable_ddl_transaction!
def change
- add_column :users, :commit_email, :string
+ add_column :users, :commit_email, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180831164908_add_identifier_to_prometheus_metric.rb b/db/migrate/20180831164908_add_identifier_to_prometheus_metric.rb
index 7aa5950249c..8f30363c310 100644
--- a/db/migrate/20180831164908_add_identifier_to_prometheus_metric.rb
+++ b/db/migrate/20180831164908_add_identifier_to_prometheus_metric.rb
@@ -6,6 +6,6 @@ class AddIdentifierToPrometheusMetric < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
- add_column :prometheus_metrics, :identifier, :string
+ add_column :prometheus_metrics, :identifier, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180910115836_add_attr_encrypted_columns_to_web_hook.rb b/db/migrate/20180910115836_add_attr_encrypted_columns_to_web_hook.rb
index ca8dbdba2bb..9757f7fdc79 100644
--- a/db/migrate/20180910115836_add_attr_encrypted_columns_to_web_hook.rb
+++ b/db/migrate/20180910115836_add_attr_encrypted_columns_to_web_hook.rb
@@ -6,10 +6,12 @@ class AddAttrEncryptedColumnsToWebHook < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
add_column :web_hooks, :encrypted_token, :string
add_column :web_hooks, :encrypted_token_iv, :string
add_column :web_hooks, :encrypted_url, :string
add_column :web_hooks, :encrypted_url_iv, :string
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180910153412_add_token_digest_to_personal_access_tokens.rb b/db/migrate/20180910153412_add_token_digest_to_personal_access_tokens.rb
index 142e454832f..52923f52499 100644
--- a/db/migrate/20180910153412_add_token_digest_to_personal_access_tokens.rb
+++ b/db/migrate/20180910153412_add_token_digest_to_personal_access_tokens.rb
@@ -8,7 +8,7 @@ class AddTokenDigestToPersonalAccessTokens < ActiveRecord::Migration[4.2]
def up
change_column :personal_access_tokens, :token, :string, null: true
- add_column :personal_access_tokens, :token_digest, :string
+ add_column :personal_access_tokens, :token_digest, :string # rubocop:disable Migration/AddLimitToStringColumns
end
def down
diff --git a/db/migrate/20180912111628_add_knative_application.rb b/db/migrate/20180912111628_add_knative_application.rb
index 86d9100d2e7..7c55de02d1c 100644
--- a/db/migrate/20180912111628_add_knative_application.rb
+++ b/db/migrate/20180912111628_add_knative_application.rb
@@ -6,6 +6,7 @@ class AddKnativeApplication < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table "clusters_applications_knative" do |t|
t.references :cluster, null: false, unique: true, foreign_key: { on_delete: :cascade }
@@ -16,5 +17,6 @@ class AddKnativeApplication < ActiveRecord::Migration[4.2]
t.string "hostname"
t.text "status_reason"
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb b/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb
index 62ad6c63d0a..b6ffb2866aa 100644
--- a/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb
+++ b/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb
@@ -5,6 +5,7 @@ class CreateClustersKubernetesNamespaces < ActiveRecord::Migration[4.2]
INDEX_NAME = 'kubernetes_namespaces_cluster_and_namespace'
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :clusters_kubernetes_namespaces, id: :bigserial do |t|
t.references :cluster, null: false, index: true, foreign_key: { on_delete: :cascade }
t.references :project, index: true, foreign_key: { on_delete: :nullify }
@@ -20,5 +21,6 @@ class CreateClustersKubernetesNamespaces < ActiveRecord::Migration[4.2]
t.index [:cluster_id, :namespace], name: INDEX_NAME, unique: true
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181019032400_add_shards_table.rb b/db/migrate/20181019032400_add_shards_table.rb
index e31af97cc94..82287e5c3b5 100644
--- a/db/migrate/20181019032400_add_shards_table.rb
+++ b/db/migrate/20181019032400_add_shards_table.rb
@@ -5,7 +5,7 @@ class AddShardsTable < ActiveRecord::Migration[4.2]
def change
create_table :shards do |t|
- t.string :name, null: false, index: { unique: true }
+ t.string :name, null: false, index: { unique: true } # rubocop:disable Migration/AddLimitToStringColumns
end
end
end
diff --git a/db/migrate/20181019032408_add_repositories_table.rb b/db/migrate/20181019032408_add_repositories_table.rb
index 2153c1c9fc6..dd510b44084 100644
--- a/db/migrate/20181019032408_add_repositories_table.rb
+++ b/db/migrate/20181019032408_add_repositories_table.rb
@@ -6,7 +6,7 @@ class AddRepositoriesTable < ActiveRecord::Migration[4.2]
def change
create_table :repositories, id: :bigserial do |t|
t.references :shard, null: false, index: true, foreign_key: { on_delete: :restrict }
- t.string :disk_path, null: false, index: { unique: true }
+ t.string :disk_path, null: false, index: { unique: true } # rubocop:disable Migration/AddLimitToStringColumns
end
add_column :projects, :pool_repository_id, :bigint
diff --git a/db/migrate/20181025115728_add_private_commit_email_hostname_to_application_settings.rb b/db/migrate/20181025115728_add_private_commit_email_hostname_to_application_settings.rb
index 052a344f182..c0e4897b8d7 100644
--- a/db/migrate/20181025115728_add_private_commit_email_hostname_to_application_settings.rb
+++ b/db/migrate/20181025115728_add_private_commit_email_hostname_to_application_settings.rb
@@ -6,6 +6,6 @@ class AddPrivateCommitEmailHostnameToApplicationSettings < ActiveRecord::Migrati
DOWNTIME = false
def change
- add_column(:application_settings, :commit_email_hostname, :string, null: true)
+ add_column(:application_settings, :commit_email_hostname, :string, null: true) # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181031190559_drop_gcp_clusters_table.rb b/db/migrate/20181031190559_drop_gcp_clusters_table.rb
index 597fe49f4c8..850fa93c75a 100644
--- a/db/migrate/20181031190559_drop_gcp_clusters_table.rb
+++ b/db/migrate/20181031190559_drop_gcp_clusters_table.rb
@@ -10,6 +10,7 @@ class DropGcpClustersTable < ActiveRecord::Migration[4.2]
end
def down
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :gcp_clusters do |t|
# Order columns by best align scheme
t.references :project, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade }
@@ -49,5 +50,6 @@ class DropGcpClustersTable < ActiveRecord::Migration[4.2]
t.text :encrypted_gcp_token
t.string :encrypted_gcp_token_iv
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181101191341_create_clusters_applications_cert_manager.rb b/db/migrate/20181101191341_create_clusters_applications_cert_manager.rb
index 0b6155356d9..3bc20046311 100644
--- a/db/migrate/20181101191341_create_clusters_applications_cert_manager.rb
+++ b/db/migrate/20181101191341_create_clusters_applications_cert_manager.rb
@@ -6,6 +6,7 @@ class CreateClustersApplicationsCertManager < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :clusters_applications_cert_managers do |t|
t.references :cluster, null: false, index: false, foreign_key: { on_delete: :cascade }
t.integer :status, null: false
@@ -15,5 +16,6 @@ class CreateClustersApplicationsCertManager < ActiveRecord::Migration[4.2]
t.text :status_reason
t.index :cluster_id, unique: true
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181115140140_add_encrypted_runners_token_to_settings.rb b/db/migrate/20181115140140_add_encrypted_runners_token_to_settings.rb
index 5b2bb4f6b08..124eedf7933 100644
--- a/db/migrate/20181115140140_add_encrypted_runners_token_to_settings.rb
+++ b/db/migrate/20181115140140_add_encrypted_runners_token_to_settings.rb
@@ -6,6 +6,6 @@ class AddEncryptedRunnersTokenToSettings < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
- add_column :application_settings, :runners_registration_token_encrypted, :string
+ add_column :application_settings, :runners_registration_token_encrypted, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181116050532_knative_external_ip.rb b/db/migrate/20181116050532_knative_external_ip.rb
index 5645b040a23..4634b411108 100644
--- a/db/migrate/20181116050532_knative_external_ip.rb
+++ b/db/migrate/20181116050532_knative_external_ip.rb
@@ -9,6 +9,6 @@ class KnativeExternalIp < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
- add_column :clusters_applications_knative, :external_ip, :string
+ add_column :clusters_applications_knative, :external_ip, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181116141415_add_encrypted_runners_token_to_namespaces.rb b/db/migrate/20181116141415_add_encrypted_runners_token_to_namespaces.rb
index dcf565cd6c0..0a8ed912891 100644
--- a/db/migrate/20181116141415_add_encrypted_runners_token_to_namespaces.rb
+++ b/db/migrate/20181116141415_add_encrypted_runners_token_to_namespaces.rb
@@ -6,6 +6,6 @@ class AddEncryptedRunnersTokenToNamespaces < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
- add_column :namespaces, :runners_token_encrypted, :string
+ add_column :namespaces, :runners_token_encrypted, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181116141504_add_encrypted_runners_token_to_projects.rb b/db/migrate/20181116141504_add_encrypted_runners_token_to_projects.rb
index 13cd80e5c8b..3083dff49b8 100644
--- a/db/migrate/20181116141504_add_encrypted_runners_token_to_projects.rb
+++ b/db/migrate/20181116141504_add_encrypted_runners_token_to_projects.rb
@@ -6,6 +6,6 @@ class AddEncryptedRunnersTokenToProjects < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
- add_column :projects, :runners_token_encrypted, :string
+ add_column :projects, :runners_token_encrypted, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181120151656_add_token_encrypted_to_ci_runners.rb b/db/migrate/20181120151656_add_token_encrypted_to_ci_runners.rb
index 8b990451adc..2270246dfb4 100644
--- a/db/migrate/20181120151656_add_token_encrypted_to_ci_runners.rb
+++ b/db/migrate/20181120151656_add_token_encrypted_to_ci_runners.rb
@@ -6,6 +6,6 @@ class AddTokenEncryptedToCiRunners < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
- add_column :ci_runners, :token_encrypted, :string
+ add_column :ci_runners, :token_encrypted, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181122160027_create_project_repositories.rb b/db/migrate/20181122160027_create_project_repositories.rb
index e42cef9b1c6..3f123daa150 100644
--- a/db/migrate/20181122160027_create_project_repositories.rb
+++ b/db/migrate/20181122160027_create_project_repositories.rb
@@ -11,7 +11,7 @@ class CreateProjectRepositories < ActiveRecord::Migration[5.0]
def change
create_table :project_repositories, id: :bigserial do |t|
t.references :shard, null: false, index: true, foreign_key: { on_delete: :restrict }
- t.string :disk_path, null: false, index: { unique: true }
+ t.string :disk_path, null: false, index: { unique: true } # rubocop:disable Migration/AddLimitToStringColumns
t.references :project, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade }
end
end
diff --git a/db/migrate/20181123144235_create_suggestions.rb b/db/migrate/20181123144235_create_suggestions.rb
index 1723f6de7eb..78888517db5 100644
--- a/db/migrate/20181123144235_create_suggestions.rb
+++ b/db/migrate/20181123144235_create_suggestions.rb
@@ -8,7 +8,7 @@ class CreateSuggestions < ActiveRecord::Migration[5.0]
t.references :note, foreign_key: { on_delete: :cascade }, null: false
t.integer :relative_order, null: false, limit: 2
t.boolean :applied, null: false, default: false
- t.string :commit_id
+ t.string :commit_id # rubocop:disable Migration/AddLimitToStringColumns
t.text :from_content, null: false
t.text :to_content, null: false
diff --git a/db/migrate/20181128123704_add_state_to_pool_repository.rb b/db/migrate/20181128123704_add_state_to_pool_repository.rb
index 714232ede56..04bbcf24f62 100644
--- a/db/migrate/20181128123704_add_state_to_pool_repository.rb
+++ b/db/migrate/20181128123704_add_state_to_pool_repository.rb
@@ -9,7 +9,7 @@ class AddStateToPoolRepository < ActiveRecord::Migration[5.0]
# the transactions don't have to be disabled
# rubocop: disable Migration/AddConcurrentForeignKey, Migration/AddIndex
def change
- add_column(:pool_repositories, :state, :string, null: true)
+ add_column(:pool_repositories, :state, :string, null: true) # rubocop:disable Migration/AddLimitToStringColumns
add_column :pool_repositories, :source_project_id, :integer
add_index :pool_repositories, :source_project_id, unique: true
diff --git a/db/migrate/20181129104854_add_token_encrypted_to_ci_builds.rb b/db/migrate/20181129104854_add_token_encrypted_to_ci_builds.rb
index 11b98203793..62a7421eae0 100644
--- a/db/migrate/20181129104854_add_token_encrypted_to_ci_builds.rb
+++ b/db/migrate/20181129104854_add_token_encrypted_to_ci_builds.rb
@@ -6,6 +6,6 @@ class AddTokenEncryptedToCiBuilds < ActiveRecord::Migration[5.0]
DOWNTIME = false
def change
- add_column :ci_builds, :token_encrypted, :string
+ add_column :ci_builds, :token_encrypted, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181203002526_add_project_bfg_object_map_column.rb b/db/migrate/20181203002526_add_project_bfg_object_map_column.rb
index 8b42cd6f941..5e6d416895c 100644
--- a/db/migrate/20181203002526_add_project_bfg_object_map_column.rb
+++ b/db/migrate/20181203002526_add_project_bfg_object_map_column.rb
@@ -4,6 +4,6 @@ class AddProjectBfgObjectMapColumn < ActiveRecord::Migration[5.0]
DOWNTIME = false
def change
- add_column :projects, :bfg_object_map, :string
+ add_column :projects, :bfg_object_map, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181211092510_add_name_author_id_and_sha_to_releases.rb b/db/migrate/20181211092510_add_name_author_id_and_sha_to_releases.rb
index 60815e0c31a..3ab808ba667 100644
--- a/db/migrate/20181211092510_add_name_author_id_and_sha_to_releases.rb
+++ b/db/migrate/20181211092510_add_name_author_id_and_sha_to_releases.rb
@@ -7,7 +7,7 @@ class AddNameAuthorIdAndShaToReleases < ActiveRecord::Migration[5.0]
def change
add_column :releases, :author_id, :integer
- add_column :releases, :name, :string
- add_column :releases, :sha, :string
+ add_column :releases, :name, :string # rubocop:disable Migration/AddLimitToStringColumns
+ add_column :releases, :sha, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181212171634_create_error_tracking_settings.rb b/db/migrate/20181212171634_create_error_tracking_settings.rb
index 18c38bd2c47..950b9a88005 100644
--- a/db/migrate/20181212171634_create_error_tracking_settings.rb
+++ b/db/migrate/20181212171634_create_error_tracking_settings.rb
@@ -6,6 +6,7 @@ class CreateErrorTrackingSettings < ActiveRecord::Migration[5.0]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :project_error_tracking_settings, id: :int, primary_key: :project_id, default: nil do |t|
t.boolean :enabled, null: false, default: true
t.string :api_url, null: false
@@ -13,5 +14,6 @@ class CreateErrorTrackingSettings < ActiveRecord::Migration[5.0]
t.string :encrypted_token_iv
t.foreign_key :projects, column: :project_id, on_delete: :cascade
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181228175414_create_releases_link_table.rb b/db/migrate/20181228175414_create_releases_link_table.rb
index 03558202137..168c4722cc1 100644
--- a/db/migrate/20181228175414_create_releases_link_table.rb
+++ b/db/migrate/20181228175414_create_releases_link_table.rb
@@ -6,6 +6,7 @@ class CreateReleasesLinkTable < ActiveRecord::Migration[5.0]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :release_links, id: :bigserial do |t|
t.references :release, null: false, index: false, foreign_key: { on_delete: :cascade }
t.string :url, null: false
@@ -15,5 +16,6 @@ class CreateReleasesLinkTable < ActiveRecord::Migration[5.0]
t.index [:release_id, :url], unique: true
t.index [:release_id, :name], unique: true
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190109153125_add_merge_request_external_diffs.rb b/db/migrate/20190109153125_add_merge_request_external_diffs.rb
index c67903c7f67..a680856a3d8 100644
--- a/db/migrate/20190109153125_add_merge_request_external_diffs.rb
+++ b/db/migrate/20190109153125_add_merge_request_external_diffs.rb
@@ -11,7 +11,7 @@ class AddMergeRequestExternalDiffs < ActiveRecord::Migration[5.0]
def change
# Allow the merge request diff to store details about an external file
- add_column :merge_request_diffs, :external_diff, :string
+ add_column :merge_request_diffs, :external_diff, :string # rubocop:disable Migration/AddLimitToStringColumns
add_column :merge_request_diffs, :external_diff_store, :integer
add_column :merge_request_diffs, :stored_externally, :boolean
diff --git a/db/migrate/20190114172110_add_domain_to_cluster.rb b/db/migrate/20190114172110_add_domain_to_cluster.rb
index 58d7664b8c0..d8f10af9cad 100644
--- a/db/migrate/20190114172110_add_domain_to_cluster.rb
+++ b/db/migrate/20190114172110_add_domain_to_cluster.rb
@@ -4,6 +4,6 @@ class AddDomainToCluster < ActiveRecord::Migration[5.0]
DOWNTIME = false
def change
- add_column :clusters, :domain, :string
+ add_column :clusters, :domain, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190115092821_add_columns_project_error_tracking_settings.rb b/db/migrate/20190115092821_add_columns_project_error_tracking_settings.rb
index 190b6f958fd..afed929cce4 100644
--- a/db/migrate/20190115092821_add_columns_project_error_tracking_settings.rb
+++ b/db/migrate/20190115092821_add_columns_project_error_tracking_settings.rb
@@ -6,8 +6,8 @@ class AddColumnsProjectErrorTrackingSettings < ActiveRecord::Migration[5.0]
DOWNTIME = false
def change
- add_column :project_error_tracking_settings, :project_name, :string
- add_column :project_error_tracking_settings, :organization_name, :string
+ add_column :project_error_tracking_settings, :project_name, :string # rubocop:disable Migration/AddLimitToStringColumns
+ add_column :project_error_tracking_settings, :organization_name, :string # rubocop:disable Migration/AddLimitToStringColumns
change_column_default :project_error_tracking_settings, :enabled, from: true, to: false
diff --git a/db/migrate/20190116234221_add_sorting_fields_to_user_preference.rb b/db/migrate/20190116234221_add_sorting_fields_to_user_preference.rb
index 7bf581fe9b0..39aab600546 100644
--- a/db/migrate/20190116234221_add_sorting_fields_to_user_preference.rb
+++ b/db/migrate/20190116234221_add_sorting_fields_to_user_preference.rb
@@ -10,8 +10,8 @@ class AddSortingFieldsToUserPreference < ActiveRecord::Migration[5.0]
DOWNTIME = false
def up
- add_column :user_preferences, :issues_sort, :string
- add_column :user_preferences, :merge_requests_sort, :string
+ add_column :user_preferences, :issues_sort, :string # rubocop:disable Migration/AddLimitToStringColumns
+ add_column :user_preferences, :merge_requests_sort, :string # rubocop:disable Migration/AddLimitToStringColumns
end
def down
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/20190301182457_add_external_hostname_to_ingress_and_knative.rb b/db/migrate/20190301182457_add_external_hostname_to_ingress_and_knative.rb
index 2c3a54b12a9..37ba1090cf0 100644
--- a/db/migrate/20190301182457_add_external_hostname_to_ingress_and_knative.rb
+++ b/db/migrate/20190301182457_add_external_hostname_to_ingress_and_knative.rb
@@ -4,7 +4,7 @@ class AddExternalHostnameToIngressAndKnative < ActiveRecord::Migration[5.0]
DOWNTIME = false
def change
- add_column :clusters_applications_ingress, :external_hostname, :string
- add_column :clusters_applications_knative, :external_hostname, :string
+ add_column :clusters_applications_ingress, :external_hostname, :string # rubocop:disable Migration/AddLimitToStringColumns
+ add_column :clusters_applications_knative, :external_hostname, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190320174702_add_lets_encrypt_notification_email_to_application_settings.rb b/db/migrate/20190320174702_add_lets_encrypt_notification_email_to_application_settings.rb
index e9cf2af84a5..aeabf4e3cb4 100644
--- a/db/migrate/20190320174702_add_lets_encrypt_notification_email_to_application_settings.rb
+++ b/db/migrate/20190320174702_add_lets_encrypt_notification_email_to_application_settings.rb
@@ -10,6 +10,6 @@ class AddLetsEncryptNotificationEmailToApplicationSettings < ActiveRecord::Migra
DOWNTIME = false
def change
- add_column :application_settings, :lets_encrypt_notification_email, :string
+ add_column :application_settings, :lets_encrypt_notification_email, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190325105715_add_fields_to_user_preferences.rb b/db/migrate/20190325105715_add_fields_to_user_preferences.rb
index 9ea3b4f9cd8..4da5c496147 100644
--- a/db/migrate/20190325105715_add_fields_to_user_preferences.rb
+++ b/db/migrate/20190325105715_add_fields_to_user_preferences.rb
@@ -11,7 +11,7 @@ class AddFieldsToUserPreferences < ActiveRecord::Migration[5.0]
DOWNTIME = false
def up
- add_column(:user_preferences, :timezone, :string)
+ add_column(:user_preferences, :timezone, :string) # rubocop:disable Migration/AddLimitToStringColumns
add_column(:user_preferences, :time_display_relative, :boolean)
add_column(:user_preferences, :time_format_in_24h, :boolean)
end
diff --git a/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb b/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb
index 2f3069032a1..d912f922510 100644
--- a/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb
+++ b/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb
@@ -6,6 +6,6 @@ class AddNotificationEmailToNotificationSettings < ActiveRecord::Migration[5.0]
DOWNTIME = false
def change
- add_column :notification_settings, :notification_email, :string
+ add_column :notification_settings, :notification_email, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190402150158_backport_enterprise_schema.rb b/db/migrate/20190402150158_backport_enterprise_schema.rb
index 8762cc53ed7..3f13b68c2f3 100644
--- a/db/migrate/20190402150158_backport_enterprise_schema.rb
+++ b/db/migrate/20190402150158_backport_enterprise_schema.rb
@@ -2,6 +2,7 @@
# rubocop: disable Metrics/AbcSize
# rubocop: disable Migration/Datetime
+# rubocop: disable Migration/AddLimitToStringColumns
class BackportEnterpriseSchema < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
@@ -2190,3 +2191,4 @@ class BackportEnterpriseSchema < ActiveRecord::Migration[5.0]
end
# rubocop: enable Metrics/AbcSize
# rubocop: enable Migration/Datetime
+# rubocop: enable Migration/AddLimitToStringColumns
diff --git a/db/migrate/20190409224933_add_name_to_geo_nodes.rb b/db/migrate/20190409224933_add_name_to_geo_nodes.rb
index 2dff81b429c..65c01683995 100644
--- a/db/migrate/20190409224933_add_name_to_geo_nodes.rb
+++ b/db/migrate/20190409224933_add_name_to_geo_nodes.rb
@@ -10,7 +10,7 @@ class AddNameToGeoNodes < ActiveRecord::Migration[5.0]
DOWNTIME = false
def up
- add_column :geo_nodes, :name, :string
+ add_column :geo_nodes, :name, :string # rubocop:disable Migration/AddLimitToStringColumns
# url is also unique, and its type and size is identical to the name column,
# so this is safe.
diff --git a/db/migrate/20190422082247_create_project_metrics_settings.rb b/db/migrate/20190422082247_create_project_metrics_settings.rb
index 3e21dd0a934..a0a2ed64820 100644
--- a/db/migrate/20190422082247_create_project_metrics_settings.rb
+++ b/db/migrate/20190422082247_create_project_metrics_settings.rb
@@ -7,7 +7,7 @@ class CreateProjectMetricsSettings < ActiveRecord::Migration[5.0]
def change
create_table :project_metrics_settings, id: :int, primary_key: :project_id, default: nil do |t|
- t.string :external_dashboard_url, null: false
+ t.string :external_dashboard_url, null: false # rubocop:disable Migration/AddLimitToStringColumns
t.foreign_key :projects, column: :project_id, on_delete: :cascade
end
end
diff --git a/db/migrate/20190429082448_create_pages_domain_acme_orders.rb b/db/migrate/20190429082448_create_pages_domain_acme_orders.rb
index af811e83518..ca1796d054c 100644
--- a/db/migrate/20190429082448_create_pages_domain_acme_orders.rb
+++ b/db/migrate/20190429082448_create_pages_domain_acme_orders.rb
@@ -10,6 +10,7 @@ class CreatePagesDomainAcmeOrders < ActiveRecord::Migration[5.1]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :pages_domain_acme_orders do |t|
t.references :pages_domain, null: false, index: true, foreign_key: { on_delete: :cascade }, type: :integer
@@ -24,5 +25,6 @@ class CreatePagesDomainAcmeOrders < ActiveRecord::Migration[5.1]
t.text :encrypted_private_key, null: false
t.text :encrypted_private_key_iv, null: false
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190430131225_create_issue_tracker_data.rb b/db/migrate/20190430131225_create_issue_tracker_data.rb
index 7859bea9c22..d2134ad82c7 100644
--- a/db/migrate/20190430131225_create_issue_tracker_data.rb
+++ b/db/migrate/20190430131225_create_issue_tracker_data.rb
@@ -9,6 +9,7 @@ class CreateIssueTrackerData < ActiveRecord::Migration[5.1]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :issue_tracker_data do |t|
t.references :service, foreign_key: { on_delete: :cascade }, type: :integer, index: true, null: false
t.timestamps_with_timezone
@@ -19,5 +20,6 @@ class CreateIssueTrackerData < ActiveRecord::Migration[5.1]
t.string :encrypted_new_issue_url
t.string :encrypted_new_issue_url_iv
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190430142025_create_jira_tracker_data.rb b/db/migrate/20190430142025_create_jira_tracker_data.rb
index d328ad63854..5e53e5a701a 100644
--- a/db/migrate/20190430142025_create_jira_tracker_data.rb
+++ b/db/migrate/20190430142025_create_jira_tracker_data.rb
@@ -9,6 +9,7 @@ class CreateJiraTrackerData < ActiveRecord::Migration[5.1]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :jira_tracker_data do |t|
t.references :service, foreign_key: { on_delete: :cascade }, type: :integer, index: true, null: false
t.timestamps_with_timezone
@@ -22,5 +23,6 @@ class CreateJiraTrackerData < ActiveRecord::Migration[5.1]
t.string :encrypted_password_iv
t.string :jira_issue_transition_id
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190514105711_create_ip_restriction.rb b/db/migrate/20190514105711_create_ip_restriction.rb
index dfafbe32ad1..69f8c1b8c4e 100644
--- a/db/migrate/20190514105711_create_ip_restriction.rb
+++ b/db/migrate/20190514105711_create_ip_restriction.rb
@@ -12,7 +12,7 @@ class CreateIpRestriction < ActiveRecord::Migration[5.1]
type: :integer,
null: false,
index: true
- t.string :range, null: false
+ t.string :range, null: false # rubocop:disable Migration/AddLimitToStringColumns
end
add_foreign_key(:ip_restrictions, :namespaces, column: :group_id, on_delete: :cascade) # rubocop: disable Migration/AddConcurrentForeignKey
diff --git a/db/migrate/20190527011309_add_required_template_name_to_application_settings.rb b/db/migrate/20190527011309_add_required_template_name_to_application_settings.rb
index 6cbac0ed507..5c47e6f33c2 100644
--- a/db/migrate/20190527011309_add_required_template_name_to_application_settings.rb
+++ b/db/migrate/20190527011309_add_required_template_name_to_application_settings.rb
@@ -10,6 +10,6 @@ class AddRequiredTemplateNameToApplicationSettings < ActiveRecord::Migration[5.1
DOWNTIME = false
def change
- add_column :application_settings, :required_instance_ci_template, :string, null: true
+ add_column :application_settings, :required_instance_ci_template, :string, null: true # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190606054742_add_token_encrypted_to_operations_feature_flags_clients.rb b/db/migrate/20190606054742_add_token_encrypted_to_operations_feature_flags_clients.rb
index 024b5bd2ba5..2db4dc85750 100644
--- a/db/migrate/20190606054742_add_token_encrypted_to_operations_feature_flags_clients.rb
+++ b/db/migrate/20190606054742_add_token_encrypted_to_operations_feature_flags_clients.rb
@@ -6,6 +6,6 @@ class AddTokenEncryptedToOperationsFeatureFlagsClients < ActiveRecord::Migration
DOWNTIME = false
def change
- add_column :operations_feature_flags_clients, :token_encrypted, :string
+ add_column :operations_feature_flags_clients, :token_encrypted, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190613044655_add_username_to_deploy_tokens.rb b/db/migrate/20190613044655_add_username_to_deploy_tokens.rb
index 793553afe35..a0acb02013b 100644
--- a/db/migrate/20190613044655_add_username_to_deploy_tokens.rb
+++ b/db/migrate/20190613044655_add_username_to_deploy_tokens.rb
@@ -4,6 +4,6 @@ class AddUsernameToDeployTokens < ActiveRecord::Migration[5.1]
DOWNTIME = false
def change
- add_column :deploy_tokens, :username, :string
+ add_column :deploy_tokens, :username, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190613073003_create_project_aliases.rb b/db/migrate/20190613073003_create_project_aliases.rb
index 5a2c2ba0cf2..896d3ca5813 100644
--- a/db/migrate/20190613073003_create_project_aliases.rb
+++ b/db/migrate/20190613073003_create_project_aliases.rb
@@ -8,7 +8,7 @@ class CreateProjectAliases < ActiveRecord::Migration[5.1]
def change
create_table :project_aliases do |t|
t.references :project, null: false, index: true, foreign_key: { on_delete: :cascade }, type: :integer
- t.string :name, null: false, index: { unique: true }
+ t.string :name, null: false, index: { unique: true } # rubocop:disable Migration/AddLimitToStringColumns
t.timestamps_with_timezone null: false
end
diff --git a/db/migrate/20190621151636_add_merge_request_rebase_jid.rb b/db/migrate/20190621151636_add_merge_request_rebase_jid.rb
index 1fed5690ead..6c1081732e8 100644
--- a/db/migrate/20190621151636_add_merge_request_rebase_jid.rb
+++ b/db/migrate/20190621151636_add_merge_request_rebase_jid.rb
@@ -4,6 +4,6 @@ class AddMergeRequestRebaseJid < ActiveRecord::Migration[5.1]
DOWNTIME = false
def change
- add_column :merge_requests, :rebase_jid, :string
+ add_column :merge_requests, :rebase_jid, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190624123615_add_grafana_url_to_settings.rb b/db/migrate/20190624123615_add_grafana_url_to_settings.rb
index 61efe64a7a1..835ec4e9094 100644
--- a/db/migrate/20190624123615_add_grafana_url_to_settings.rb
+++ b/db/migrate/20190624123615_add_grafana_url_to_settings.rb
@@ -8,8 +8,10 @@ class AddGrafanaUrlToSettings < ActiveRecord::Migration[5.1]
DOWNTIME = false
def up
+ # rubocop:disable Migration/AddLimitToStringColumns
add_column_with_default(:application_settings, :grafana_url, :string,
default: '/-/grafana', allow_null: false)
+ # rubocop:enable Migration/AddLimitToStringColumns
end
def down
diff --git a/db/migrate/20190711124721_create_job_variables.rb b/db/migrate/20190711124721_create_job_variables.rb
index a860522f39e..4ff4b031d8f 100644
--- a/db/migrate/20190711124721_create_job_variables.rb
+++ b/db/migrate/20190711124721_create_job_variables.rb
@@ -10,6 +10,7 @@ class CreateJobVariables < ActiveRecord::Migration[5.1]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :ci_job_variables do |t|
t.string :key, null: false
t.text :encrypted_value
@@ -17,6 +18,7 @@ class CreateJobVariables < ActiveRecord::Migration[5.1]
t.references :job, null: false, index: true, foreign_key: { to_table: :ci_builds, on_delete: :cascade }
t.integer :variable_type, null: false, limit: 2, default: 1
end
+ # rubocop:enable Migration/AddLimitToStringColumns
add_index :ci_job_variables, [:key, :job_id], unique: true
end
diff --git a/db/migrate/20190711200053_change_deploy_tokens_token_not_null.rb b/db/migrate/20190711200053_change_deploy_tokens_token_not_null.rb
new file mode 100644
index 00000000000..14ccf544d0b
--- /dev/null
+++ b/db/migrate/20190711200053_change_deploy_tokens_token_not_null.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class ChangeDeployTokensTokenNotNull < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ change_column_null :deploy_tokens, :token, true
+ end
+end
diff --git a/db/migrate/20190711200508_add_token_encrypted_to_deploy_tokens.rb b/db/migrate/20190711200508_add_token_encrypted_to_deploy_tokens.rb
new file mode 100644
index 00000000000..ea0956fdf7f
--- /dev/null
+++ b/db/migrate/20190711200508_add_token_encrypted_to_deploy_tokens.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddTokenEncryptedToDeployTokens < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :deploy_tokens, :token_encrypted, :string, limit: 255
+ 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/20190719174505_add_index_to_deploy_tokens_token_encrypted.rb b/db/migrate/20190719174505_add_index_to_deploy_tokens_token_encrypted.rb
new file mode 100644
index 00000000000..d58d1d8348c
--- /dev/null
+++ b/db/migrate/20190719174505_add_index_to_deploy_tokens_token_encrypted.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexToDeployTokensTokenEncrypted < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :deploy_tokens, :token_encrypted, unique: true, name: "index_deploy_tokens_on_token_encrypted"
+ end
+
+ def down
+ remove_concurrent_index_by_name :deploy_tokens, "index_deploy_tokens_on_token_encrypted"
+ 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/20190726101050_rename_allow_local_requests_from_hooks_and_services_application_setting.rb b/db/migrate/20190726101050_rename_allow_local_requests_from_hooks_and_services_application_setting.rb
index ac65e8d745c..cce8942128c 100644
--- a/db/migrate/20190726101050_rename_allow_local_requests_from_hooks_and_services_application_setting.rb
+++ b/db/migrate/20190726101050_rename_allow_local_requests_from_hooks_and_services_application_setting.rb
@@ -12,6 +12,6 @@ class RenameAllowLocalRequestsFromHooksAndServicesApplicationSetting < ActiveRec
end
def down
- cleanup_concurrent_column_rename :application_settings, :allow_local_requests_from_web_hooks_and_services, :allow_local_requests_from_hooks_and_services
+ undo_rename_column_concurrently :application_settings, :allow_local_requests_from_hooks_and_services, :allow_local_requests_from_web_hooks_and_services
end
end
diff --git a/db/migrate/20190729180447_add_merge_requests_require_code_owner_approval_to_protected_branches.rb b/db/migrate/20190729180447_add_merge_requests_require_code_owner_approval_to_protected_branches.rb
new file mode 100644
index 00000000000..098fcff9ace
--- /dev/null
+++ b/db/migrate/20190729180447_add_merge_requests_require_code_owner_approval_to_protected_branches.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class AddMergeRequestsRequireCodeOwnerApprovalToProtectedBranches < 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
+ add_column_with_default(
+ :protected_branches,
+ :code_owner_approval_required,
+ :boolean,
+ default: false
+ )
+
+ add_concurrent_index(
+ :protected_branches,
+ [:project_id, :code_owner_approval_required],
+ name: "code_owner_approval_required",
+ where: "code_owner_approval_required = #{Gitlab::Database.true_value}")
+ end
+
+ def down
+ remove_concurrent_index(:protected_branches, name: "code_owner_approval_required")
+
+ remove_column(:protected_branches, :code_owner_approval_required)
+ end
+end
diff --git a/db/migrate/20190805140353_remove_rendundant_index_from_releases.rb b/db/migrate/20190805140353_remove_rendundant_index_from_releases.rb
new file mode 100644
index 00000000000..fc4bc1a423b
--- /dev/null
+++ b/db/migrate/20190805140353_remove_rendundant_index_from_releases.rb
@@ -0,0 +1,21 @@
+# 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 RemoveRendundantIndexFromReleases < 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 :releases, :project_id
+ end
+
+ def down
+ add_concurrent_index :releases, :project_id
+ 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/20190820163320_add_first_last_name_to_user.rb b/db/migrate/20190820163320_add_first_last_name_to_user.rb
new file mode 100644
index 00000000000..0ea465fc2e2
--- /dev/null
+++ b/db/migrate/20190820163320_add_first_last_name_to_user.rb
@@ -0,0 +1,14 @@
+# 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 AddFirstLastNameToUser < ActiveRecord::Migration[5.2]
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ add_column(:users, :first_name, :string, null: true, limit: 255)
+ add_column(:users, :last_name, :string, null: true, limit: 255)
+ 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/20190828083843_add_index_to_ci_job_artifacts_on_project_id_for_security_reports.rb b/db/migrate/20190828083843_add_index_to_ci_job_artifacts_on_project_id_for_security_reports.rb
new file mode 100644
index 00000000000..5253f25aab4
--- /dev/null
+++ b/db/migrate/20190828083843_add_index_to_ci_job_artifacts_on_project_id_for_security_reports.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class AddIndexToCiJobArtifactsOnProjectIdForSecurityReports < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :ci_job_artifacts,
+ :project_id,
+ name: "index_ci_job_artifacts_on_project_id_for_security_reports",
+ where: "file_type IN (5, 6, 7, 8)"
+ end
+
+ def down
+ remove_concurrent_index :ci_job_artifacts,
+ :project_id,
+ name: "index_ci_job_artifacts_on_project_id_for_security_reports"
+ 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/20190829131130_create_external_pull_requests.rb b/db/migrate/20190829131130_create_external_pull_requests.rb
new file mode 100644
index 00000000000..0c3168807ec
--- /dev/null
+++ b/db/migrate/20190829131130_create_external_pull_requests.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class CreateExternalPullRequests < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX = 'index_external_pull_requests_on_project_and_branches'
+
+ def change
+ create_table :external_pull_requests do |t|
+ t.timestamps_with_timezone null: false
+ t.references :project, null: false, foreign_key: { on_delete: :cascade }, index: false
+ t.integer :pull_request_iid, null: false
+ t.integer :status, null: false, limit: 2
+ t.string :source_branch, null: false, limit: 255
+ t.string :target_branch, null: false, limit: 255
+ t.string :source_repository, null: false, limit: 255
+ t.string :target_repository, null: false, limit: 255
+ t.binary :source_sha, null: false
+ t.binary :target_sha, null: false
+
+ t.index [:project_id, :source_branch, :target_branch], unique: true, name: INDEX
+ end
+ end
+end
diff --git a/db/migrate/20190830075508_add_external_pull_request_id_to_ci_pipelines.rb b/db/migrate/20190830075508_add_external_pull_request_id_to_ci_pipelines.rb
new file mode 100644
index 00000000000..5abf56742b1
--- /dev/null
+++ b/db/migrate/20190830075508_add_external_pull_request_id_to_ci_pipelines.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class AddExternalPullRequestIdToCiPipelines < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ add_column :ci_pipelines, :external_pull_request_id, :bigint
+ end
+
+ def down
+ remove_column :ci_pipelines, :external_pull_request_id
+ end
+end
diff --git a/db/migrate/20190830080123_add_index_to_ci_pipelines_external_pull_request.rb b/db/migrate/20190830080123_add_index_to_ci_pipelines_external_pull_request.rb
new file mode 100644
index 00000000000..d2f5ad7a420
--- /dev/null
+++ b/db/migrate/20190830080123_add_index_to_ci_pipelines_external_pull_request.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexToCiPipelinesExternalPullRequest < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :ci_pipelines, :external_pull_request_id, where: 'external_pull_request_id IS NOT NULL'
+ end
+
+ def down
+ remove_concurrent_index :ci_pipelines, :external_pull_request_id
+ end
+end
diff --git a/db/migrate/20190830080626_add_foreign_key_to_ci_pipelines_external_pull_request.rb b/db/migrate/20190830080626_add_foreign_key_to_ci_pipelines_external_pull_request.rb
new file mode 100644
index 00000000000..b38fda83047
--- /dev/null
+++ b/db/migrate/20190830080626_add_foreign_key_to_ci_pipelines_external_pull_request.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddForeignKeyToCiPipelinesExternalPullRequest < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :ci_pipelines, :external_pull_requests, column: :external_pull_request_id, on_delete: :nullify
+ end
+
+ def down
+ remove_foreign_key :ci_pipelines, :external_pull_requests
+ 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/migrate/20190902152329_add_index_for_ci_builds_metrics.rb b/db/migrate/20190902152329_add_index_for_ci_builds_metrics.rb
new file mode 100644
index 00000000000..b755d60e311
--- /dev/null
+++ b/db/migrate/20190902152329_add_index_for_ci_builds_metrics.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddIndexForCiBuildsMetrics < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'ci_builds_gitlab_monitor_metrics'
+
+ def up
+ add_concurrent_index(:ci_builds, [:status, :created_at, :project_id], where: "type = 'Ci::Build'", name: INDEX_NAME)
+ end
+
+ def down
+ remove_concurrent_index_by_name(:ci_builds, INDEX_NAME)
+ end
+end
diff --git a/db/migrate/20190904173203_add_index_on_users_unconfirmed_email.rb b/db/migrate/20190904173203_add_index_on_users_unconfirmed_email.rb
new file mode 100644
index 00000000000..e78d47f023f
--- /dev/null
+++ b/db/migrate/20190904173203_add_index_on_users_unconfirmed_email.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexOnUsersUnconfirmedEmail < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :users, :unconfirmed_email, where: 'unconfirmed_email IS NOT NULL'
+ end
+
+ def down
+ remove_concurrent_index :users, :unconfirmed_email, where: 'unconfirmed_email IS NOT NULL'
+ end
+end
diff --git a/db/migrate/20190905223800_add_interruptible_to_builds_metadata.rb b/db/migrate/20190905223800_add_interruptible_to_builds_metadata.rb
new file mode 100644
index 00000000000..a666b11013b
--- /dev/null
+++ b/db/migrate/20190905223800_add_interruptible_to_builds_metadata.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddInterruptibleToBuildsMetadata < ActiveRecord::Migration[5.0]
+ DOWNTIME = false
+
+ def change
+ add_column :ci_builds_metadata, :interruptible, :boolean
+ end
+end
diff --git a/db/migrate/20190905223900_add_concurrent_index_to_builds_metadata.rb b/db/migrate/20190905223900_add_concurrent_index_to_builds_metadata.rb
new file mode 100644
index 00000000000..d394f995673
--- /dev/null
+++ b/db/migrate/20190905223900_add_concurrent_index_to_builds_metadata.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddConcurrentIndexToBuildsMetadata < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :ci_builds_metadata, [:build_id],
+ where: "interruptible = false",
+ name: "index_ci_builds_metadata_on_build_id_and_interruptible_false"
+ end
+
+ def down
+ remove_concurrent_index_by_name(:ci_builds_metadata, 'index_ci_builds_metadata_on_build_id_and_interruptible_false')
+ end
+end
diff --git a/db/post_migrate/20181013005024_remove_koding_from_application_settings.rb b/db/post_migrate/20181013005024_remove_koding_from_application_settings.rb
index 550ad94f4ab..b6e5473e896 100644
--- a/db/post_migrate/20181013005024_remove_koding_from_application_settings.rb
+++ b/db/post_migrate/20181013005024_remove_koding_from_application_settings.rb
@@ -12,6 +12,6 @@ class RemoveKodingFromApplicationSettings < ActiveRecord::Migration[4.2]
def down
add_column :application_settings, :koding_enabled, :boolean # rubocop:disable Migration/SaferBooleanColumn
- add_column :application_settings, :koding_url, :string
+ add_column :application_settings, :koding_url, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/post_migrate/20190404231137_remove_alternate_url_from_geo_nodes.rb b/db/post_migrate/20190404231137_remove_alternate_url_from_geo_nodes.rb
index 785ceb2fb28..8e7ef0ec54f 100644
--- a/db/post_migrate/20190404231137_remove_alternate_url_from_geo_nodes.rb
+++ b/db/post_migrate/20190404231137_remove_alternate_url_from_geo_nodes.rb
@@ -16,6 +16,6 @@ class RemoveAlternateUrlFromGeoNodes < ActiveRecord::Migration[5.0]
end
def down
- add_column :geo_nodes, :alternate_url, :string
+ add_column :geo_nodes, :alternate_url, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/post_migrate/20190625184066_remove_sentry_from_application_settings.rb b/db/post_migrate/20190625184066_remove_sentry_from_application_settings.rb
index 427df343193..9d71bfafffb 100644
--- a/db/post_migrate/20190625184066_remove_sentry_from_application_settings.rb
+++ b/db/post_migrate/20190625184066_remove_sentry_from_application_settings.rb
@@ -32,7 +32,7 @@ class RemoveSentryFromApplicationSettings < ActiveRecord::Migration[5.0]
end
SENTRY_DSN_COLUMNS.each do |column|
- add_column(:application_settings, column, :string) unless column_exists?(:application_settings, column)
+ add_column(:application_settings, column, :string) unless column_exists?(:application_settings, column) # rubocop:disable Migration/AddLimitToStringColumns
end
end
end
diff --git a/db/post_migrate/20190711201818_encrypt_deploy_tokens_tokens.rb b/db/post_migrate/20190711201818_encrypt_deploy_tokens_tokens.rb
new file mode 100644
index 00000000000..2eb8d1ee11c
--- /dev/null
+++ b/db/post_migrate/20190711201818_encrypt_deploy_tokens_tokens.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class EncryptDeployTokensTokens < ActiveRecord::Migration[5.1]
+ DOWNTIME = false
+
+ class DeploymentTokens < ActiveRecord::Base
+ self.table_name = 'deploy_tokens'
+ end
+
+ def up
+ say_with_time("Encrypting tokens from deploy_tokens") do
+ DeploymentTokens.where('token_encrypted is NULL AND token IS NOT NULL').find_each(batch_size: 10000) do |deploy_token|
+ token_encrypted = Gitlab::CryptoHelper.aes256_gcm_encrypt(deploy_token.token)
+ deploy_token.update!(token_encrypted: token_encrypted)
+ end
+ end
+ end
+
+ def down
+ say_with_time("Decrypting tokens from deploy_tokens") do
+ DeploymentTokens.where('token_encrypted IS NOT NULL AND token IS NULL').find_each(batch_size: 10000) do |deploy_token|
+ token = Gitlab::CryptoHelper.aes256_gcm_decrypt(deploy_token.token_encrypted)
+ deploy_token.update!(token: token)
+ end
+ end
+ end
+end
diff --git a/db/post_migrate/20190725080128_set_not_null_on_users_private_profile.rb b/db/post_migrate/20190725080128_set_not_null_on_users_private_profile.rb
new file mode 100644
index 00000000000..db42e949d3f
--- /dev/null
+++ b/db/post_migrate/20190725080128_set_not_null_on_users_private_profile.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class SetNotNullOnUsersPrivateProfile < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ Gitlab::BackgroundMigration.steal('MigrateNullPrivateProfileToFalse')
+
+ # rubocop:disable Migration/UpdateLargeTable
+ # rubocop:disable Migration/UpdateColumnInBatches
+ # Data has been migrated previously, count should be close to 0
+ update_column_in_batches(:users, :private_profile, false) do |table, query|
+ query.where(table[:private_profile].eq(nil))
+ end
+
+ change_column_null :users, :private_profile, false
+ end
+
+ def down
+ change_column_null :users, :private_profile, true
+ end
+end
diff --git a/db/post_migrate/20190801072937_add_gitlab_instance_administration_project.rb b/db/post_migrate/20190801072937_add_gitlab_instance_administration_project.rb
new file mode 100644
index 00000000000..8b2cf7b3d76
--- /dev/null
+++ b/db/post_migrate/20190801072937_add_gitlab_instance_administration_project.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddGitlabInstanceAdministrationProject < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def up
+ Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService.new.execute!
+ end
+
+ def down
+ ApplicationSetting.current_without_cache
+ &.instance_administration_project
+ &.owner
+ &.destroy!
+ end
+end
diff --git a/db/post_migrate/20190801114109_cleanup_allow_local_requests_from_hooks_and_services_application_setting_rename.rb b/db/post_migrate/20190801114109_cleanup_allow_local_requests_from_hooks_and_services_application_setting_rename.rb
index 127e44254ac..cb86f843f9c 100644
--- a/db/post_migrate/20190801114109_cleanup_allow_local_requests_from_hooks_and_services_application_setting_rename.rb
+++ b/db/post_migrate/20190801114109_cleanup_allow_local_requests_from_hooks_and_services_application_setting_rename.rb
@@ -12,6 +12,6 @@ class CleanupAllowLocalRequestsFromHooksAndServicesApplicationSettingRename < Ac
end
def down
- rename_column_concurrently :application_settings, :allow_local_requests_from_web_hooks_and_services, :allow_local_requests_from_hooks_and_services
+ undo_cleanup_concurrent_column_rename :application_settings, :allow_local_requests_from_hooks_and_services, :allow_local_requests_from_web_hooks_and_services
end
end
diff --git a/db/post_migrate/20190809072552_set_self_monitoring_project_alerting_token.rb b/db/post_migrate/20190809072552_set_self_monitoring_project_alerting_token.rb
new file mode 100644
index 00000000000..0c4faebc548
--- /dev/null
+++ b/db/post_migrate/20190809072552_set_self_monitoring_project_alerting_token.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+class SetSelfMonitoringProjectAlertingToken < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ module Migratable
+ module Alerting
+ class ProjectAlertingSetting < ApplicationRecord
+ self.table_name = 'project_alerting_settings'
+
+ belongs_to :project
+
+ validates :token, presence: true
+
+ attr_encrypted :token,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-gcm'
+
+ before_validation :ensure_token
+
+ private
+
+ def ensure_token
+ self.token ||= generate_token
+ end
+
+ def generate_token
+ SecureRandom.hex
+ end
+ end
+ end
+
+ class Project < ApplicationRecord
+ has_one :alerting_setting, inverse_of: :project, class_name: 'Alerting::ProjectAlertingSetting'
+ end
+
+ class ApplicationSetting < ApplicationRecord
+ self.table_name = 'application_settings'
+
+ belongs_to :instance_administration_project, class_name: 'Project'
+
+ def self.current_without_cache
+ last
+ end
+ end
+ end
+
+ def setup_alertmanager_token(project)
+ return unless License.feature_available?(:prometheus_alerts)
+
+ project.create_alerting_setting!
+ end
+
+ def up
+ Gitlab.ee do
+ project = Migratable::ApplicationSetting.current_without_cache&.instance_administration_project
+
+ if project
+ setup_alertmanager_token(project)
+ end
+ end
+ end
+
+ def down
+ Gitlab.ee do
+ Migratable::ApplicationSetting.current_without_cache
+ &.instance_administration_project
+ &.alerting_setting
+ &.destroy!
+ end
+ 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 3f7917654cf..6ddfb8bcb39 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_15_093949) do
+ActiveRecord::Schema.define(version: 2019_09_05_223900) 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_15_093949) 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"
@@ -599,6 +605,7 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) do
t.index ["scheduled_at"], name: "partial_index_ci_builds_on_scheduled_at_with_scheduled_jobs", where: "((scheduled_at IS NOT NULL) AND ((type)::text = 'Ci::Build'::text) AND ((status)::text = 'scheduled'::text))"
t.index ["stage_id", "stage_idx"], name: "tmp_build_stage_position_index", where: "(stage_idx IS NOT NULL)"
t.index ["stage_id"], name: "index_ci_builds_on_stage_id"
+ t.index ["status", "created_at", "project_id"], name: "ci_builds_gitlab_monitor_metrics", where: "((type)::text = 'Ci::Build'::text)"
t.index ["status", "type", "runner_id"], name: "index_ci_builds_on_status_and_type_and_runner_id"
t.index ["token"], name: "index_ci_builds_on_token", unique: true
t.index ["token_encrypted"], name: "index_ci_builds_on_token_encrypted", unique: true, where: "(token_encrypted IS NOT NULL)"
@@ -612,9 +619,11 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) do
t.integer "project_id", null: false
t.integer "timeout"
t.integer "timeout_source", default: 1, null: false
+ t.boolean "interruptible"
t.jsonb "config_options"
t.jsonb "config_variables"
t.index ["build_id"], name: "index_ci_builds_metadata_on_build_id", unique: true
+ t.index ["build_id"], name: "index_ci_builds_metadata_on_build_id_and_interruptible_false", where: "(interruptible = false)"
t.index ["project_id"], name: "index_ci_builds_metadata_on_project_id"
end
@@ -658,6 +667,7 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) do
t.index ["file_store"], name: "index_ci_job_artifacts_on_file_store"
t.index ["job_id", "file_type"], name: "index_ci_job_artifacts_on_job_id_and_file_type", unique: true
t.index ["project_id"], name: "index_ci_job_artifacts_on_project_id"
+ t.index ["project_id"], name: "index_ci_job_artifacts_on_project_id_for_security_reports", where: "(file_type = ANY (ARRAY[5, 6, 7, 8]))"
end
create_table "ci_job_variables", force: :cascade do |t|
@@ -744,7 +754,9 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) do
t.integer "merge_request_id"
t.binary "source_sha"
t.binary "target_sha"
+ t.bigint "external_pull_request_id"
t.index ["auto_canceled_by_id"], name: "index_ci_pipelines_on_auto_canceled_by_id"
+ t.index ["external_pull_request_id"], name: "index_ci_pipelines_on_external_pull_request_id", where: "(external_pull_request_id IS NOT NULL)"
t.index ["merge_request_id"], name: "index_ci_pipelines_on_merge_request_id", where: "(merge_request_id IS NOT NULL)"
t.index ["pipeline_schedule_id"], name: "index_ci_pipelines_on_pipeline_schedule_id"
t.index ["project_id", "iid"], name: "index_ci_pipelines_on_project_id_and_iid", unique: true, where: "(iid IS NOT NULL)"
@@ -928,7 +940,7 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) 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
@@ -1121,10 +1133,12 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) do
t.datetime_with_timezone "expires_at", null: false
t.datetime_with_timezone "created_at", null: false
t.string "name", null: false
- t.string "token", null: false
+ t.string "token"
t.string "username"
+ t.string "token_encrypted", limit: 255
t.index ["token", "expires_at", "id"], name: "index_deploy_tokens_on_token_and_expires_at_and_id", where: "(revoked IS FALSE)"
t.index ["token"], name: "index_deploy_tokens_on_token", unique: true
+ t.index ["token_encrypted"], name: "index_deploy_tokens_on_token_encrypted", unique: true
end
create_table "deployments", id: :serial, force: :cascade do |t|
@@ -1143,7 +1157,7 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) 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"
@@ -1277,11 +1291,11 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) 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"
@@ -1301,14 +1315,31 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) 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"
end
+ create_table "external_pull_requests", force: :cascade do |t|
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.bigint "project_id", null: false
+ t.integer "pull_request_iid", null: false
+ t.integer "status", limit: 2, null: false
+ t.string "source_branch", limit: 255, null: false
+ t.string "target_branch", limit: 255, null: false
+ t.string "source_repository", limit: 255, null: false
+ t.string "target_repository", limit: 255, null: false
+ t.binary "source_sha", null: false
+ t.binary "target_sha", null: false
+ t.index ["project_id", "source_branch", "target_branch"], name: "index_external_pull_requests_on_project_and_branches", unique: true
+ end
+
create_table "feature_gates", id: :serial, force: :cascade do |t|
t.string "feature_key", null: false
t.string "key", null: false
@@ -1919,6 +1950,17 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) 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"
@@ -2138,6 +2180,13 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) 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"
@@ -2500,6 +2549,7 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) 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
@@ -2848,8 +2898,8 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) 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
@@ -2901,6 +2951,8 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) do
t.string "name", null: false
t.datetime "created_at"
t.datetime "updated_at"
+ t.boolean "code_owner_approval_required", default: false, null: false
+ t.index ["project_id", "code_owner_approval_required"], name: "code_owner_approval_required", where: "(code_owner_approval_required = true)"
t.index ["project_id"], name: "index_protected_branches_on_project_id"
end
@@ -3015,7 +3067,6 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) do
t.datetime_with_timezone "released_at", null: false
t.index ["author_id"], name: "index_releases_on_author_id"
t.index ["project_id", "tag"], name: "index_releases_on_project_id_and_tag"
- t.index ["project_id"], name: "index_releases_on_project_id"
end
create_table "remote_mirrors", id: :serial, force: :cascade do |t|
@@ -3498,7 +3549,7 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) do
t.integer "theme_id", limit: 2
t.integer "accepted_term_id"
t.string "feed_token"
- t.boolean "private_profile", default: false
+ t.boolean "private_profile", default: false, null: false
t.boolean "include_private_contributions"
t.string "commit_email"
t.boolean "auditor", default: false, null: false
@@ -3512,6 +3563,8 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) do
t.text "note"
t.integer "roadmap_layout", limit: 2
t.integer "bot_type", limit: 2
+ t.string "first_name", limit: 255
+ t.string "last_name", limit: 255
t.index ["accepted_term_id"], name: "index_users_on_accepted_term_id"
t.index ["admin"], name: "index_users_on_admin"
t.index ["bot_type"], name: "index_users_on_bot_type"
@@ -3529,7 +3582,9 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) 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 ["unconfirmed_email"], name: "index_users_on_unconfirmed_email", where: "(unconfirmed_email IS NOT 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
@@ -3747,6 +3802,7 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) do
add_foreign_key "ci_pipeline_variables", "ci_pipelines", column: "pipeline_id", name: "fk_f29c5f4380", on_delete: :cascade
add_foreign_key "ci_pipelines", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_3d34ab2e06", on_delete: :nullify
add_foreign_key "ci_pipelines", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_262d4c2d19", on_delete: :nullify
+ add_foreign_key "ci_pipelines", "external_pull_requests", name: "fk_190998ef09", on_delete: :nullify
add_foreign_key "ci_pipelines", "merge_requests", name: "fk_a23be95014", on_delete: :cascade
add_foreign_key "ci_pipelines", "projects", name: "fk_86635dbd80", on_delete: :cascade
add_foreign_key "ci_runner_namespaces", "ci_runners", column: "runner_id", on_delete: :cascade
@@ -3808,8 +3864,10 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) 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 "external_pull_requests", "projects", on_delete: :cascade
add_foreign_key "fork_network_members", "fork_networks", on_delete: :cascade
add_foreign_key "fork_network_members", "projects", column: "forked_from_project_id", name: "fk_b01280dae4", on_delete: :nullify
add_foreign_key "fork_network_members", "projects", on_delete: :cascade
@@ -3876,6 +3934,8 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) 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
@@ -3907,6 +3967,8 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) 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/README.md b/doc/README.md
index f12c06199c2..c704bedc7d6 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -357,9 +357,10 @@ The following documentation relates to the DevOps **Secure** stage:
| [Dependency List](user/application_security/dependency_list/index.md) **(ULTIMATE)** | View your project's dependencies and their known vulnerabilities. |
| [Dependency Scanning](user/application_security/dependency_scanning/index.md) **(ULTIMATE)** | Analyze your dependencies for known vulnerabilities. |
| [Dynamic Application Security Testing (DAST)](user/application_security/dast/index.md) **(ULTIMATE)** | Analyze running web applications for known vulnerabilities. |
-| [Group Security Dashboard](user/application_security/security_dashboard/index.md) **(ULTIMATE)** | View vulnerabilities in all the projects in a group and its subgroups. |
-| [License Compliance](user/application_security/license_management/index.md) **(ULTIMATE)** | Search your project's dependencies for their licenses. |
-| [Project Security Dashboard](user/application_security/security_dashboard/index.md) **(ULTIMATE)** | View the latest security reports for your project. |
+| [Group Security Dashboard](user/application_security/security_dashboard/index.md#group-security-dashboard) **(ULTIMATE)** | View vulnerabilities in all the projects in a group and its subgroups. |
+| [License Compliance](user/application_security/license_compliance/index.md) **(ULTIMATE)** | Search your project's dependencies for their licenses. |
+| [Pipeline Security Dashboard](user/application_security/security_dashboard/index.md#pipeline-security-dashboard) **(ULTIMATE)** | View the security reports for your project's pipelines. |
+| [Project Security Dashboard](user/application_security/security_dashboard/index.md#project-security-dashboard) **(ULTIMATE)** | View the latest security reports for your project. |
| [Static Application Security Testing (SAST)](user/application_security/sast/index.md) **(ULTIMATE)** | Analyze source code for known vulnerabilities. |
## New to Git and GitLab?
diff --git a/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
index 86dd398343b..7c14d4004db 100644
--- a/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
@@ -32,7 +32,7 @@ For example, [Active Directory](https://technet.microsoft.com/en-us/library/hh83
We won't cover the installation and configuration of Windows Server or Active Directory Domain Services in this tutorial. There are a number of resources online to guide you through this process:
-- Install Windows Server 2012 - (_technet.microsoft.com_) - [Installing Windows Server 2012 ](https://technet.microsoft.com/en-us/library/jj134246(v=ws.11).aspx)
+- Install Windows Server 2012 - (_technet.microsoft.com_) - [Installing Windows Server 2012](https://technet.microsoft.com/en-us/library/jj134246(v=ws.11).aspx)
- Install Active Directory Domain Services (AD DS) (_technet.microsoft.com_)- [Install Active Directory Domain Services](https://technet.microsoft.com/windows-server-docs/identity/ad-ds/deploy/install-active-directory-domain-services--level-100-#BKMK_PS)
@@ -249,7 +249,7 @@ After configuring LDAP, basic authentication will be available. Users can then l
Users that are removed from the LDAP base group (e.g `OU=GitLab INT,DC=GitLab,DC=org`) will be **blocked** in GitLab. [More information](../ldap.md#security) on LDAP security.
-If `allow_username_or_email_login` is enabled in the LDAP configuration, GitLab will ignore everything after the first '@' in the LDAP username used on login. Example: The username `` jon.doe@example.com `` is converted to `jon.doe` when authenticating with the LDAP server. Disable this setting if you use `userPrincipalName` as the `uid`.
+If `allow_username_or_email_login` is enabled in the LDAP configuration, GitLab will ignore everything after the first '@' in the LDAP username used on login. Example: The username `jon.doe@example.com` is converted to `jon.doe` when authenticating with the LDAP server. Disable this setting if you use `userPrincipalName` as the `uid`.
## LDAP extended features on GitLab EE
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/auth/okta.md b/doc/administration/auth/okta.md
index 5524c3ba092..41745e8caae 100644
--- a/doc/administration/auth/okta.md
+++ b/doc/administration/auth/okta.md
@@ -98,20 +98,20 @@ Now that the Okta app is configured, it's time to enable it in GitLab.
>**Notes:**
>
>- Change the value for `assertion_consumer_service_url` to match the HTTPS endpoint
- of GitLab (append `users/auth/saml/callback` to the HTTPS URL of your GitLab
- installation to generate the correct value).
+ > of GitLab (append `users/auth/saml/callback` to the HTTPS URL of your GitLab
+ > installation to generate the correct value).
>
>- To get the `idp_cert_fingerprint` fingerprint, first download the
- certificate from the Okta app you registered and then run:
- `openssl x509 -in okta.cert -noout -fingerprint`. Substitute `okta.cert`
- with the location of your certificate.
+ > certificate from the Okta app you registered and then run:
+ > `openssl x509 -in okta.cert -noout -fingerprint`. Substitute `okta.cert`
+ > with the location of your certificate.
>
>- Change the value of `idp_sso_target_url`, with the value of the
- **Identity Provider Single Sign-On URL** from the step when you
- configured the Okta app.
+ > **Identity Provider Single Sign-On URL** from the step when you
+ > configured the Okta app.
>
>- Change the value of `issuer` to the value of the **Audience Restriction** from your Okta app configuration. This will identify GitLab
- to the IdP.
+ > to the IdP.
>
>- Leave `name_identifier_format` as-is.
diff --git a/doc/administration/database_load_balancing.md b/doc/administration/database_load_balancing.md
index dc4cc401fca..f643d853d10 100644
--- a/doc/administration/database_load_balancing.md
+++ b/doc/administration/database_load_balancing.md
@@ -122,6 +122,7 @@ production:
discover:
nameserver: localhost
record: secondary.postgresql.service.consul
+ record_type: A
port: 8600
interval: 60
disconnect_timeout: 120
@@ -137,12 +138,20 @@ The following options can be set:
| Option | Description | Default |
|----------------------|---------------------------------------------------------------------------------------------------|-----------|
| `nameserver` | The nameserver to use for looking up the DNS record. | localhost |
-| `record` | The A record to look up. This option is required for service discovery to work. | |
+| `record` | The record to look up. This option is required for service discovery to work. | |
+| `record_type` | Optional record type to look up, this can be either A or SRV (since GitLab 12.3) | A |
| `port` | The port of the nameserver. | 8600 |
| `interval` | The minimum time in seconds between checking the DNS record. | 60 |
| `disconnect_timeout` | The time in seconds after which an old connection is closed, after the list of hosts was updated. | 120 |
| `use_tcp` | Lookup DNS resources using TCP instead of UDP | false |
+If `record_type` is set to `SRV`, GitLab will continue to use a round-robin algorithm
+and will ignore the `weight` and `priority` in the record. Since SRV records usually
+return hostnames instead of IPs, GitLab will look for the IPs of returned hostnames
+in the additional section of the SRV response. If no IP is found for a hostname, Gitlab
+will query the configured `nameserver` for ANY record for each such hostname looking for A or AAAA records, eventually
+dropping this hostname from rotation if it can't resolve its IP.
+
The `interval` value specifies the _minimum_ time between checks. If the A
record has a TTL greater than this value, then service discovery will honor said
TTL. For example, if the TTL of the A record is 90 seconds, then service
@@ -207,28 +216,25 @@ without it immediately leading to errors being presented to the users.
## Logging
-The load balancer logs various messages, such as:
+The load balancer logs various events in
+[`database_load_balancing.log`](logs.md#database_load_balancinglog-premium-only), such as
- When a host is marked as offline
- When a host comes back online
- When all secondaries are offline
+- When a read is retried on a different host due to a query conflict
-Each log message contains the tag `[DB-LB]` to make searching/filtering of such
-log entries easier. For example:
+The log is structured with each entry a JSON object containing at least:
-```
-[DB-LB] Host 10.123.2.5 came back online
-[DB-LB] Marking host 10.123.2.7 as offline
-[DB-LB] Marking host 10.123.2.7 as offline
-[DB-LB] Marking host 10.123.2.7 as offline
-[DB-LB] Marking host 10.123.2.7 as offline
-[DB-LB] Marking host 10.123.2.7 as offline
-[DB-LB] Host 10.123.2.6 came back online
-[DB-LB] Marking host 10.123.2.7 as offline
-[DB-LB] Marking host 10.123.2.7 as offline
-[DB-LB] Marking host 10.123.2.7 as offline
-[DB-LB] Host 10.123.2.7 came back online
-[DB-LB] Host 10.123.2.7 came back online
+- An `event` field useful for filtering.
+- A human-readable `message` field.
+- Some event-specific metadata. For example, `db_host`
+- Contextual information that is always logged. For example, `severity` and `time`.
+
+For example:
+
+```json
+{"severity":"INFO","time":"2019-09-02T12:12:01.728Z","correlation_id":"abcdefg","event":"host_online","message":"Host came back online","db_host":"111.222.333.444","db_port":null,"tag":"rails.database_load_balancing","environment":"production","hostname":"web-example-1","fqdn":"gitlab.example.com","path":null,"params":null}
```
## Handling Stale Reads
diff --git a/doc/administration/geo/disaster_recovery/index.md b/doc/administration/geo/disaster_recovery/index.md
index ba95843b0b0..7228cc6948e 100644
--- a/doc/administration/geo/disaster_recovery/index.md
+++ b/doc/administration/geo/disaster_recovery/index.md
@@ -297,7 +297,7 @@ for another **primary** node. All the old replication settings will be overwritt
## Troubleshooting
-### I followed the disaster recovery instructions and now two-factor auth is broken!
+### I followed the disaster recovery instructions and now two-factor auth is broken
The setup instructions for Geo prior to 10.5 failed to replicate the
`otp_key_base` secret, which is used to encrypt the two-factor authentication
@@ -315,7 +315,7 @@ section to resolve the error. Otherwise, the secret is lost and you'll need to
[geo-limitations]: ../replication/index.md#current-limitations
[planned-failover]: planned_failover.md
[setup-geo]: ../replication/index.md#setup-instructions
-[updating-geo]: ../replication/updating_the_geo_nodes.md#upgrading-to-gitlab-105
+[updating-geo]: ../replication/version_specific_updates.md#updating-to-gitlab-105
[sec-tfa]: ../../../security/two_factor_authentication.md#disabling-2fa-for-everyone
[gitlab-org/omnibus-gitlab#3058]: https://gitlab.com/gitlab-org/omnibus-gitlab/issues/3058
[gitlab-org/gitlab-ee#4284]: https://gitlab.com/gitlab-org/gitlab-ee/issues/4284
diff --git a/doc/administration/geo/replication/troubleshooting.md b/doc/administration/geo/replication/troubleshooting.md
index fe1557fd8b5..3ae92b07736 100644
--- a/doc/administration/geo/replication/troubleshooting.md
+++ b/doc/administration/geo/replication/troubleshooting.md
@@ -538,7 +538,7 @@ can simply reset the existing tracking database with:
sudo gitlab-rake geo:db:reset
```
-### Geo node has a database that is writable which is an indication it is not configured for replication with the primary node.
+### Geo node has a database that is writable which is an indication it is not configured for replication with the primary node
This error refers to a problem with the database replica on a **secondary** node,
which Geo expects to have access to. It usually means, either:
@@ -552,7 +552,7 @@ PostgreSQL instances:
- A read-only replica of the **primary** node.
- A regular, writable instance that holds replication metadata. That is, the Geo tracking database.
-### Geo node does not appear to be replicating the database from the primary node.
+### Geo node does not appear to be replicating the database from the primary node
The most common problems that prevent the database from replicating correctly are:
diff --git a/doc/administration/geo/replication/updating_the_geo_nodes.md b/doc/administration/geo/replication/updating_the_geo_nodes.md
index 39174780e24..fda0ebbbeac 100644
--- a/doc/administration/geo/replication/updating_the_geo_nodes.md
+++ b/doc/administration/geo/replication/updating_the_geo_nodes.md
@@ -1,15 +1,38 @@
# Updating the Geo nodes **(PREMIUM ONLY)**
+Updating Geo nodes involves performing:
+
+1. [Version-specific update steps](#version-specific-update-steps), depending on the
+ version being updated to or from.
+1. [General update steps](#general-update-steps), for all updates.
+
+## Version specific update steps
+
Depending on which version of Geo you are updating to/from, there may be
different steps.
+- [Updating to GitLab 12.1](version_specific_updates.md#updating-to-gitlab-121)
+- [Updating to GitLab 10.8](version_specific_updates.md#updating-to-gitlab-108)
+- [Updating to GitLab 10.6](version_specific_updates.md#updating-to-gitlab-106)
+- [Updating to GitLab 10.5](version_specific_updates.md#updating-to-gitlab-105)
+- [Updating to GitLab 10.3](version_specific_updates.md#updating-to-gitlab-103)
+- [Updating to GitLab 10.2](version_specific_updates.md#updating-to-gitlab-102)
+- [Updating to GitLab 10.1](version_specific_updates.md#updating-to-gitlab-101)
+- [Updating to GitLab 10.0](version_specific_updates.md#updating-to-gitlab-100)
+- [Updating from GitLab 9.3 or older](version_specific_updates.md#updating-from-gitlab-93-or-older)
+- [Updating to GitLab 9.0](version_specific_updates.md#updating-to-gitlab-90)
+
## 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).
+
+To update the Geo nodes when a new GitLab version is released, update **primary**
+and all **secondary** nodes:
-1. Log into each node (**primary** and **secondary** nodes).
-1. [Update GitLab][update].
+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,427 +50,4 @@ 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.
-## 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.
-
-This can be temporarily disabled by running the following before ugprading:
-
-```sh
-sudo touch /etc/gitlab/disable-postgresql-upgrade
-```
-
-## Upgrading to GitLab 10.8
-
-Before 10.8, broadcast messages would not propagate without flushing the cache on the **secondary** nodes. This has been fixed in 10.8, but requires one last cache flush on each **secondary** node:
-
-```sh
-sudo gitlab-rake cache:clear
-```
-
-## Upgrading to GitLab 10.6
-
-In 10.4, we started to recommend that you define a password for database user (`gitlab`).
-
-We now require this change as we use this password to enable the Foreign Data Wrapper, as a way to optimize
-the Geo Tracking Database. We are also improving security by disabling the use of **trust**
-authentication method.
-
-1. **(primary)** Login to your **primary** node and run:
-
- ```sh
- gitlab-ctl pg-password-md5 gitlab
- # Enter password: <your_password_here>
- # Confirm password: <your_password_here>
- # fca0b89a972d69f00eb3ec98a5838484
- ```
-
- Copy the generated hash and edit `/etc/gitlab/gitlab.rb`:
-
- ```ruby
- # Fill with the hash generated by `gitlab-ctl pg-password-md5 gitlab`
- postgresql['sql_user_password'] = '<md5_hash_of_your_password>'
-
- # Every node that runs Unicorn or Sidekiq needs to have the database
- # password specified as below. If you have a high-availability setup, this
- # must be present in all application nodes.
- gitlab_rails['db_password'] = '<your_password_here>'
- ```
-
- Still in the configuration file, locate and remove the `trust_auth_cidr_address`:
-
- ```ruby
- postgresql['trust_auth_cidr_addresses'] = ['127.0.0.1/32','1.2.3.4/32'] # <- Remove this
- ```
-
-1. **(primary)** Reconfigure and restart:
-
- ```sh
- sudo gitlab-ctl reconfigure
- sudo gitlab-ctl restart
- ```
-
-1. **(secondary)** Login to all **secondary** nodes and edit `/etc/gitlab/gitlab.rb`:
-
- ```ruby
- # Fill with the hash generated by `gitlab-ctl pg-password-md5 gitlab`
- postgresql['sql_user_password'] = '<md5_hash_of_your_password>'
-
- # Every node that runs Unicorn or Sidekiq needs to have the database
- # password specified as below. If you have a high-availability setup, this
- # must be present in all application nodes.
- gitlab_rails['db_password'] = '<your_password_here>'
-
- # Enable Foreign Data Wrapper
- geo_secondary['db_fdw'] = true
-
- # Secondary address in CIDR format, for example '5.6.7.8/32'
- postgresql['md5_auth_cidr_addresses'] = ['<secondary_node_ip>/32']
- ```
-
- Still in the configuration file, locate and remove the `trust_auth_cidr_address`:
-
- ```ruby
- postgresql['trust_auth_cidr_addresses'] = ['127.0.0.1/32','5.6.7.8/32'] # <- Remove this
- ```
-
-1. **(secondary)** Reconfigure and restart:
-
- ```sh
- sudo gitlab-ctl reconfigure
- sudo gitlab-ctl restart
- ```
-
-## Upgrading to GitLab 10.5
-
-For Geo Disaster Recovery to work with minimum downtime, your **secondary** node
-should use the same set of secrets as the **primary** node. However, setup instructions
-prior to the 10.5 release only synchronized the `db_key_base` secret.
-
-To rectify this error on existing installations, you should **overwrite** the
-contents of `/etc/gitlab/gitlab-secrets.json` on each **secondary** node with the
-contents of `/etc/gitlab/gitlab-secrets.json` on the **primary** node, then run the
-following command on each **secondary** node:
-
-```sh
-sudo gitlab-ctl reconfigure
-```
-
-If you do not perform this step, you may find that two-factor authentication
-[is broken following DR](../disaster_recovery/index.html#i-followed-the-disaster-recovery-instructions-and-now-two-factor-auth-is-broken).
-
-To prevent SSH requests to the newly promoted **primary** node from failing
-due to SSH host key mismatch when updating the **primary** node domain's DNS record
-you should perform the step to [Manually replicate **primary** SSH host keys](configuration.md#step-2-manually-replicate-the-primary-nodes-ssh-host-keys) in each
-**secondary** node.
-
-## Upgrading to GitLab 10.4
-
-There are no Geo-specific steps to take!
-
-## Upgrading to GitLab 10.3
-
-### Support for SSH repository synchronization removed
-
-In GitLab 10.2, synchronizing secondaries over SSH was deprecated. In 10.3,
-support is removed entirely. All installations will switch to the HTTP/HTTPS
-cloning method instead. Before upgrading, ensure that all your Geo nodes are
-configured to use this method and that it works for your installation. In
-particular, ensure that [Git access over HTTP/HTTPS is enabled](configuration.md#step-6-enable-git-access-over-httphttps).
-
-Synchronizing repositories over the public Internet using HTTP is insecure, so
-you should ensure that you have HTTPS configured before upgrading. Note that
-file synchronization is **also** insecure in these cases!
-
-## Upgrading to GitLab 10.2
-
-### Secure PostgreSQL replication
-
-Support for TLS-secured PostgreSQL replication has been added. If you are
-currently using PostgreSQL replication across the open internet without an
-external means of securing the connection (e.g., a site-to-site VPN), then you
-should immediately reconfigure your **primary** and **secondary** PostgreSQL instances
-according to the [updated instructions][database].
-
-If you *are* securing the connections externally and wish to continue doing so,
-ensure you include the new option `--sslmode=prefer` in future invocations of
-`gitlab-ctl replicate-geo-database`.
-
-### HTTPS repository sync
-
-Support for replicating repositories and wikis over HTTP/HTTPS has been added.
-Replicating over SSH has been deprecated, and support for this option will be
-removed in a future release.
-
-To switch to HTTP/HTTPS replication, log into the **primary** node as an admin and visit
-**Admin Area > Geo** (`/admin/geo/nodes`). For each **secondary** node listed,
-press the "Edit" button, change the "Repository cloning" setting from
-"SSH (deprecated)" to "HTTP/HTTPS", and press "Save changes". This should take
-effect immediately.
-
-Any new secondaries should be created using HTTP/HTTPS replication - this is the
-default setting.
-
-After you've verified that HTTP/HTTPS replication is working, you should remove
-the now-unused SSH keys from your secondaries, as they may cause problems if the
-**secondary** node if ever promoted to a **primary** node:
-
-1. **(secondary)** Login to **all** your **secondary** nodes and run:
-
- ```ruby
- sudo -u git -H rm ~git/.ssh/id_rsa ~git/.ssh/id_rsa.pub
- ```
-
-### Hashed Storage
-
-CAUTION: **Warning:**
-Hashed storage is in **Alpha**. It is considered experimental and not
-production-ready. See [Hashed Storage] for more detail.
-
-If you previously enabled Hashed Storage and migrated all your existing
-projects to Hashed Storage, disabling hashed storage will not migrate projects
-to their previous project based storage path. As such, once enabled and
-migrated we recommend leaving Hashed Storage enabled.
-
-## Upgrading to GitLab 10.1
-
-CAUTION: **Warning:**
-Hashed storage is in **Alpha**. It is considered experimental and not
-production-ready. See [Hashed Storage] for more detail.
-
-[Hashed storage] was introduced in GitLab 10.0, and a [migration path][hashed-migration]
-for existing repositories was added in GitLab 10.1.
-
-## Upgrading to GitLab 10.0
-
-Since GitLab 10.0, we require all **Geo** systems to [use SSH key lookups via
-the database][ssh-fast-lookup] to avoid having to maintain consistency of the
-`authorized_keys` file for SSH access. Failing to do this will prevent users
-from being able to clone via SSH.
-
-Note that in older versions of Geo, attachments downloaded on the **secondary**
-nodes would be saved to the wrong directory. We recommend that you do the
-following to clean this up.
-
-On the **secondary** Geo nodes, run as root:
-
-```sh
-mv /var/opt/gitlab/gitlab-rails/working /var/opt/gitlab/gitlab-rails/working.old
-mkdir /var/opt/gitlab/gitlab-rails/working
-chmod 700 /var/opt/gitlab/gitlab-rails/working
-chown git:git /var/opt/gitlab/gitlab-rails/working
-```
-
-You may delete `/var/opt/gitlab/gitlab-rails/working.old` any time.
-
-Once this is done, we advise restarting GitLab on the **secondary** nodes for the
-new working directory to be used:
-
-```sh
-sudo gitlab-ctl restart
-```
-
-## Upgrading from GitLab 9.3 or older
-
-If you started running Geo on GitLab 9.3 or older, we recommend that you
-resync your **secondary** PostgreSQL databases to use replication slots. If you
-started using Geo with GitLab 9.4 or 10.x, no further action should be
-required because replication slots are used by default. However, if you
-started with GitLab 9.3 and upgraded later, you should still follow the
-instructions below.
-
-When in doubt, it does not hurt to do a resync. The easiest way to do this in
-Omnibus is the following:
-
-1. Make sure you have Omnibus GitLab on the **primary** server.
-1. Run `gitlab-ctl reconfigure` and `gitlab-ctl restart postgresql`. This will enable replication slots on the **primary** database.
-1. Check the steps about defining `postgresql['sql_user_password']`, `gitlab_rails['db_password']`.
-1. Make sure `postgresql['max_replication_slots']` matches the number of **secondary** Geo nodes locations.
-1. Install GitLab on the **secondary** server.
-1. Re-run the [database replication process](database.md#step-3-initiate-the-replication-process).
-
-## Special update notes for 9.0.x
-
-> **IMPORTANT**:
-With GitLab 9.0, the PostgreSQL version is upgraded to 9.6 and manual steps are
-required in order to update the **secondary** nodes and keep the Streaming
-Replication working. Downtime is required, so plan ahead.
-
-The following steps apply only if you upgrade from a 8.17 GitLab version to
-9.0+. For previous versions, update to GitLab 8.17 first before attempting to
-upgrade to 9.0+.
-
----
-
-Make sure to follow the steps in the exact order as they appear below and pay
-extra attention in what node (either **primary** or **secondary**) you execute them! Each step
-is prepended with the relevant node for better clarity:
-
-1. **(secondary)** Login to **all** your **secondary** nodes and stop all services:
-
- ```ruby
- sudo gitlab-ctl stop
- ```
-
-1. **(secondary)** Make a backup of the `recovery.conf` file on **all**
- **secondary** nodes to preserve PostgreSQL's credentials:
-
- ```sh
- sudo cp /var/opt/gitlab/postgresql/data/recovery.conf /var/opt/gitlab/
- ```
-
-1. **(primary)** Update the **primary** node to GitLab 9.0 following the
- [regular update docs][update]. At the end of the update, the **primary** node
- will be running with PostgreSQL 9.6.
-
-1. **(primary)** To prevent a de-synchronization of the repository replication,
- stop all services except `postgresql` as we will use it to re-initialize the
- **secondary** node's database:
-
- ```sh
- sudo gitlab-ctl stop
- sudo gitlab-ctl start postgresql
- ```
-
-1. **(secondary)** Run the following steps on each of the **secondary** nodes:
-
- 1. **(secondary)** Stop all services:
-
- ```sh
- sudo gitlab-ctl stop
- ```
-
- 1. **(secondary)** Prevent running database migrations:
-
- ```sh
- sudo touch /etc/gitlab/skip-auto-migrations
- ```
-
- 1. **(secondary)** Move the old database to another directory:
-
- ```sh
- sudo mv /var/opt/gitlab/postgresql{,.bak}
- ```
-
- 1. **(secondary)** Update to GitLab 9.0 following the [regular update docs][update].
- At the end of the update, the node will be running with PostgreSQL 9.6.
-
- 1. **(secondary)** Make sure all services are up:
-
- ```sh
- sudo gitlab-ctl start
- ```
-
- 1. **(secondary)** Reconfigure GitLab:
-
- ```sh
- sudo gitlab-ctl reconfigure
- ```
-
- 1. **(secondary)** Run the PostgreSQL upgrade command:
-
- ```sh
- sudo gitlab-ctl pg-upgrade
- ```
-
- 1. **(secondary)** See the stored credentials for the database that you will
- need to re-initialize the replication:
-
- ```sh
- sudo grep -s primary_conninfo /var/opt/gitlab/recovery.conf
- ```
-
- 1. **(secondary)** Save the snippet below in a file, let's say `/tmp/replica.sh`. Modify the
- embedded paths if necessary:
-
- ```
- #!/bin/bash
-
- PORT="5432"
- USER="gitlab_replicator"
- echo ---------------------------------------------------------------
- echo WARNING: Make sure this script is run from the secondary server
- echo ---------------------------------------------------------------
- echo
- echo Enter the IP or FQDN of the primary PostgreSQL server
- read HOST
- echo Enter the password for $USER@$HOST
- read -s PASSWORD
- echo Enter the required sslmode
- read SSLMODE
-
- echo Stopping PostgreSQL and all GitLab services
- sudo service gitlab stop
- sudo service postgresql stop
-
- echo Backing up postgresql.conf
- sudo -u postgres mv /var/opt/gitlab/postgresql/data/postgresql.conf /var/opt/gitlab/postgresql/
-
- echo Cleaning up old cluster directory
- sudo -u postgres rm -rf /var/opt/gitlab/postgresql/data
-
- echo Starting base backup as the replicator user
- echo Enter the password for $USER@$HOST
- sudo -u postgres /opt/gitlab/embedded/bin/pg_basebackup -h $HOST -D /var/opt/gitlab/postgresql/data -U gitlab_replicator -v -x -P
-
- echo Writing recovery.conf file
- sudo -u postgres bash -c "cat > /var/opt/gitlab/postgresql/data/recovery.conf <<- _EOF1_
- standby_mode = 'on'
- primary_conninfo = 'host=$HOST port=$PORT user=$USER password=$PASSWORD sslmode=$SSLMODE'
- _EOF1_
- "
-
- echo Restoring postgresql.conf
- sudo -u postgres mv /var/opt/gitlab/postgresql/postgresql.conf /var/opt/gitlab/postgresql/data/
-
- echo Starting PostgreSQL
- sudo service postgresql start
- ```
-
- 1. **(secondary)** Run the recovery script using the credentials from the
- previous step:
-
- ```sh
- sudo bash /tmp/replica.sh
- ```
-
- 1. **(secondary)** Reconfigure GitLab:
-
- ```sh
- sudo gitlab-ctl reconfigure
- ```
-
- 1. **(secondary)** Start all services:
-
- ```sh
- sudo gitlab-ctl start
- ```
-
- 1. **(secondary)** Repeat the steps for the remaining **secondary** nodes.
-
-1. **(primary)** After all **secondary** nodes are updated, start all services in
- **primary** node:
-
- ```sh
- sudo gitlab-ctl start
- ```
-
-### Update tracking database on **secondary** node
-
-After updating a **secondary** node, you might need to run migrations on
-the tracking database. The tracking database was added in GitLab 9.1,
-and it is required since 10.0.
-
-1. Run database migrations on tracking database:
-
- ```sh
- sudo gitlab-rake geo:db:migrate
- ```
-
-1. Repeat this step for each **secondary** node.
-
-[update]: ../../../update/README.md
-[database]: database.md
-[Hashed Storage]: ../../repository_storage_types.md
-[hashed-migration]: ../../raketasks/storage.md
-[ssh-fast-lookup]: ../../operations/fast_ssh_key_lookup.md
+If you encounter any issues, please consult the [Geo troubleshooting guide](troubleshooting.md).
diff --git a/doc/administration/geo/replication/version_specific_updates.md b/doc/administration/geo/replication/version_specific_updates.md
new file mode 100644
index 00000000000..6d550a49df4
--- /dev/null
+++ b/doc/administration/geo/replication/version_specific_updates.md
@@ -0,0 +1,426 @@
+# Version specific update instructions
+
+Check this document if it includes instructions for the version you are updating.
+These steps go together with the [general steps](updating_the_geo_nodes.md#general-update-steps)
+for updating Geo nodes.
+
+## Updating to GitLab 12.1
+
+By default, GitLab 12.1 will attempt to automatically update 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.
+
+This can be temporarily disabled by running the following before updating:
+
+```sh
+sudo touch /etc/gitlab/disable-postgresql-upgrade
+```
+
+## Updating to GitLab 10.8
+
+Before 10.8, broadcast messages would not propagate without flushing
+the cache on the **secondary** nodes. This has been fixed in 10.8, but
+requires one last cache flush on each **secondary** node:
+
+```sh
+sudo gitlab-rake cache:clear
+```
+
+## Updating to GitLab 10.6
+
+In 10.4, we started to recommend that you define a password for database user (`gitlab`).
+
+We now require this change as we use this password to enable the Foreign Data Wrapper, as a way to optimize
+the Geo Tracking Database. We are also improving security by disabling the use of **trust**
+authentication method.
+
+1. **(primary)** Login to your **primary** node and run:
+
+ ```sh
+ gitlab-ctl pg-password-md5 gitlab
+ # Enter password: <your_password_here>
+ # Confirm password: <your_password_here>
+ # fca0b89a972d69f00eb3ec98a5838484
+ ```
+
+ Copy the generated hash and edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ # Fill with the hash generated by `gitlab-ctl pg-password-md5 gitlab`
+ postgresql['sql_user_password'] = '<md5_hash_of_your_password>'
+
+ # Every node that runs Unicorn or Sidekiq needs to have the database
+ # password specified as below. If you have a high-availability setup, this
+ # must be present in all application nodes.
+ gitlab_rails['db_password'] = '<your_password_here>'
+ ```
+
+ Still in the configuration file, locate and remove the `trust_auth_cidr_address`:
+
+ ```ruby
+ postgresql['trust_auth_cidr_addresses'] = ['127.0.0.1/32','1.2.3.4/32'] # <- Remove this
+ ```
+
+1. **(primary)** Reconfigure and restart:
+
+ ```sh
+ sudo gitlab-ctl reconfigure
+ sudo gitlab-ctl restart
+ ```
+
+1. **(secondary)** Login to all **secondary** nodes and edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ # Fill with the hash generated by `gitlab-ctl pg-password-md5 gitlab`
+ postgresql['sql_user_password'] = '<md5_hash_of_your_password>'
+
+ # Every node that runs Unicorn or Sidekiq needs to have the database
+ # password specified as below. If you have a high-availability setup, this
+ # must be present in all application nodes.
+ gitlab_rails['db_password'] = '<your_password_here>'
+
+ # Enable Foreign Data Wrapper
+ geo_secondary['db_fdw'] = true
+
+ # Secondary address in CIDR format, for example '5.6.7.8/32'
+ postgresql['md5_auth_cidr_addresses'] = ['<secondary_node_ip>/32']
+ ```
+
+ Still in the configuration file, locate and remove the `trust_auth_cidr_address`:
+
+ ```ruby
+ postgresql['trust_auth_cidr_addresses'] = ['127.0.0.1/32','5.6.7.8/32'] # <- Remove this
+ ```
+
+1. **(secondary)** Reconfigure and restart:
+
+ ```sh
+ sudo gitlab-ctl reconfigure
+ sudo gitlab-ctl restart
+ ```
+
+## Updating to GitLab 10.5
+
+For Geo Disaster Recovery to work with minimum downtime, your **secondary** node
+should use the same set of secrets as the **primary** node. However, setup instructions
+prior to the 10.5 release only synchronized the `db_key_base` secret.
+
+To rectify this error on existing installations, you should **overwrite** the
+contents of `/etc/gitlab/gitlab-secrets.json` on each **secondary** node with the
+contents of `/etc/gitlab/gitlab-secrets.json` on the **primary** node, then run the
+following command on each **secondary** node:
+
+```sh
+sudo gitlab-ctl reconfigure
+```
+
+If you do not perform this step, you may find that two-factor authentication
+[is broken following DR](../disaster_recovery/index.html#i-followed-the-disaster-recovery-instructions-and-now-two-factor-auth-is-broken).
+
+To prevent SSH requests to the newly promoted **primary** node from failing
+due to SSH host key mismatch when updating the **primary** node domain's DNS record
+you should perform the step to [Manually replicate **primary** SSH host keys](configuration.md#step-2-manually-replicate-the-primary-nodes-ssh-host-keys) in each
+**secondary** node.
+
+## Updating to GitLab 10.3
+
+### Support for SSH repository synchronization removed
+
+In GitLab 10.2, synchronizing secondaries over SSH was deprecated. In 10.3,
+support is removed entirely. All installations will switch to the HTTP/HTTPS
+cloning method instead. Before updating, ensure that all your Geo nodes are
+configured to use this method and that it works for your installation. In
+particular, ensure that [Git access over HTTP/HTTPS is enabled](configuration.md#step-6-enable-git-access-over-httphttps).
+
+Synchronizing repositories over the public Internet using HTTP is insecure, so
+you should ensure that you have HTTPS configured before updating. Note that
+file synchronization is **also** insecure in these cases!
+
+## Updating to GitLab 10.2
+
+### Secure PostgreSQL replication
+
+Support for TLS-secured PostgreSQL replication has been added. If you are
+currently using PostgreSQL replication across the open internet without an
+external means of securing the connection (e.g., a site-to-site VPN), then you
+should immediately reconfigure your **primary** and **secondary** PostgreSQL instances
+according to the [updated instructions](database.md).
+
+If you *are* securing the connections externally and wish to continue doing so,
+ensure you include the new option `--sslmode=prefer` in future invocations of
+`gitlab-ctl replicate-geo-database`.
+
+### HTTPS repository sync
+
+Support for replicating repositories and wikis over HTTP/HTTPS has been added.
+Replicating over SSH has been deprecated, and support for this option will be
+removed in a future release.
+
+To switch to HTTP/HTTPS replication, log into the **primary** node as an admin and visit
+**Admin Area > Geo** (`/admin/geo/nodes`). For each **secondary** node listed,
+press the "Edit" button, change the "Repository cloning" setting from
+"SSH (deprecated)" to "HTTP/HTTPS", and press "Save changes". This should take
+effect immediately.
+
+Any new secondaries should be created using HTTP/HTTPS replication - this is the
+default setting.
+
+After you've verified that HTTP/HTTPS replication is working, you should remove
+the now-unused SSH keys from your secondaries, as they may cause problems if the
+**secondary** node if ever promoted to a **primary** node:
+
+1. **(secondary)** Login to **all** your **secondary** nodes and run:
+
+ ```ruby
+ sudo -u git -H rm ~git/.ssh/id_rsa ~git/.ssh/id_rsa.pub
+ ```
+
+### Hashed Storage
+
+CAUTION: **Warning:**
+Hashed storage is in **Alpha**. It is considered experimental and not
+production-ready. See [Hashed Storage](../../repository_storage_types.md) for more detail.
+
+If you previously enabled Hashed Storage and migrated all your existing
+projects to Hashed Storage, disabling hashed storage will not migrate projects
+to their previous project based storage path. As such, once enabled and
+migrated we recommend leaving Hashed Storage enabled.
+
+## Updating to GitLab 10.1
+
+CAUTION: **Warning:**
+Hashed storage is in **Alpha**. It is considered experimental and not
+production-ready. See [Hashed Storage](../../repository_storage_types.md) for more detail.
+
+[Hashed storage](../../repository_storage_types.md) was introduced in
+GitLab 10.0, and a [migration path](../../raketasks/storage.md) for
+existing repositories was added in GitLab 10.1.
+
+## Updating to GitLab 10.0
+
+Since GitLab 10.0, we require all **Geo** systems to [use SSH key lookups via
+the database](../../operations/fast_ssh_key_lookup.md) to avoid having to maintain consistency of the
+`authorized_keys` file for SSH access. Failing to do this will prevent users
+from being able to clone via SSH.
+
+Note that in older versions of Geo, attachments downloaded on the **secondary**
+nodes would be saved to the wrong directory. We recommend that you do the
+following to clean this up.
+
+On the **secondary** Geo nodes, run as root:
+
+```sh
+mv /var/opt/gitlab/gitlab-rails/working /var/opt/gitlab/gitlab-rails/working.old
+mkdir /var/opt/gitlab/gitlab-rails/working
+chmod 700 /var/opt/gitlab/gitlab-rails/working
+chown git:git /var/opt/gitlab/gitlab-rails/working
+```
+
+You may delete `/var/opt/gitlab/gitlab-rails/working.old` any time.
+
+Once this is done, we advise restarting GitLab on the **secondary** nodes for the
+new working directory to be used:
+
+```sh
+sudo gitlab-ctl restart
+```
+
+## Updating from GitLab 9.3 or older
+
+If you started running Geo on GitLab 9.3 or older, we recommend that you
+resync your **secondary** PostgreSQL databases to use replication slots. If you
+started using Geo with GitLab 9.4 or 10.x, no further action should be
+required because replication slots are used by default. However, if you
+started with GitLab 9.3 and updated later, you should still follow the
+instructions below.
+
+When in doubt, it does not hurt to do a resync. The easiest way to do this in
+Omnibus is the following:
+
+1. Make sure you have Omnibus GitLab on the **primary** server.
+1. Run `gitlab-ctl reconfigure` and `gitlab-ctl restart postgresql`. This will enable replication slots on the **primary** database.
+1. Check the steps about defining `postgresql['sql_user_password']`, `gitlab_rails['db_password']`.
+1. Make sure `postgresql['max_replication_slots']` matches the number of **secondary** Geo nodes locations.
+1. Install GitLab on the **secondary** server.
+1. Re-run the [database replication process](database.md#step-3-initiate-the-replication-process).
+
+## Updating to GitLab 9.0
+
+> **IMPORTANT**:
+With GitLab 9.0, the PostgreSQL version is updated to 9.6 and manual steps are
+required in order to update the **secondary** nodes and keep the Streaming
+Replication working. Downtime is required, so plan ahead.
+
+The following steps apply only if you update from a 8.17 GitLab version to
+9.0+. For previous versions, update to GitLab 8.17 first before attempting to
+update to 9.0+.
+
+---
+
+Make sure to follow the steps in the exact order as they appear below and pay
+extra attention in what node (either **primary** or **secondary**) you execute them! Each step
+is prepended with the relevant node for better clarity:
+
+1. **(secondary)** Log in to **all** your **secondary** nodes and stop all services:
+
+ ```ruby
+ sudo gitlab-ctl stop
+ ```
+
+1. **(secondary)** Make a backup of the `recovery.conf` file on **all**
+ **secondary** nodes to preserve PostgreSQL's credentials:
+
+ ```sh
+ sudo cp /var/opt/gitlab/postgresql/data/recovery.conf /var/opt/gitlab/
+ ```
+
+1. **(primary)** Update the **primary** node to GitLab 9.0 following the
+ [regular update docs](../../../update/README.md). At the end of the
+ update, the **primary** node will be running with PostgreSQL 9.6.
+
+1. **(primary)** To prevent a de-synchronization of the repository replication,
+ stop all services except `postgresql` as we will use it to re-initialize the
+ **secondary** node's database:
+
+ ```sh
+ sudo gitlab-ctl stop
+ sudo gitlab-ctl start postgresql
+ ```
+
+1. **(secondary)** Run the following steps on each of the **secondary** nodes:
+
+ 1. **(secondary)** Stop all services:
+
+ ```sh
+ sudo gitlab-ctl stop
+ ```
+
+ 1. **(secondary)** Prevent running database migrations:
+
+ ```sh
+ sudo touch /etc/gitlab/skip-auto-migrations
+ ```
+
+ 1. **(secondary)** Move the old database to another directory:
+
+ ```sh
+ sudo mv /var/opt/gitlab/postgresql{,.bak}
+ ```
+
+ 1. **(secondary)** Update to GitLab 9.0 following the [regular update docs](../../../update/README.md).
+ At the end of the update, the node will be running with PostgreSQL 9.6.
+
+ 1. **(secondary)** Make sure all services are up:
+
+ ```sh
+ sudo gitlab-ctl start
+ ```
+
+ 1. **(secondary)** Reconfigure GitLab:
+
+ ```sh
+ sudo gitlab-ctl reconfigure
+ ```
+
+ 1. **(secondary)** Run the PostgreSQL upgrade command:
+
+ ```sh
+ sudo gitlab-ctl pg-upgrade
+ ```
+
+ 1. **(secondary)** See the stored credentials for the database that you will
+ need to re-initialize the replication:
+
+ ```sh
+ sudo grep -s primary_conninfo /var/opt/gitlab/recovery.conf
+ ```
+
+ 1. **(secondary)** Save the snippet below in a file, let's say `/tmp/replica.sh`. Modify the
+ embedded paths if necessary:
+
+ ```
+ #!/bin/bash
+
+ PORT="5432"
+ USER="gitlab_replicator"
+ echo ---------------------------------------------------------------
+ echo WARNING: Make sure this script is run from the secondary server
+ echo ---------------------------------------------------------------
+ echo
+ echo Enter the IP or FQDN of the primary PostgreSQL server
+ read HOST
+ echo Enter the password for $USER@$HOST
+ read -s PASSWORD
+ echo Enter the required sslmode
+ read SSLMODE
+
+ echo Stopping PostgreSQL and all GitLab services
+ sudo service gitlab stop
+ sudo service postgresql stop
+
+ echo Backing up postgresql.conf
+ sudo -u postgres mv /var/opt/gitlab/postgresql/data/postgresql.conf /var/opt/gitlab/postgresql/
+
+ echo Cleaning up old cluster directory
+ sudo -u postgres rm -rf /var/opt/gitlab/postgresql/data
+
+ echo Starting base backup as the replicator user
+ echo Enter the password for $USER@$HOST
+ sudo -u postgres /opt/gitlab/embedded/bin/pg_basebackup -h $HOST -D /var/opt/gitlab/postgresql/data -U gitlab_replicator -v -x -P
+
+ echo Writing recovery.conf file
+ sudo -u postgres bash -c "cat > /var/opt/gitlab/postgresql/data/recovery.conf <<- _EOF1_
+ standby_mode = 'on'
+ primary_conninfo = 'host=$HOST port=$PORT user=$USER password=$PASSWORD sslmode=$SSLMODE'
+ _EOF1_
+ "
+
+ echo Restoring postgresql.conf
+ sudo -u postgres mv /var/opt/gitlab/postgresql/postgresql.conf /var/opt/gitlab/postgresql/data/
+
+ echo Starting PostgreSQL
+ sudo service postgresql start
+ ```
+
+ 1. **(secondary)** Run the recovery script using the credentials from the
+ previous step:
+
+ ```sh
+ sudo bash /tmp/replica.sh
+ ```
+
+ 1. **(secondary)** Reconfigure GitLab:
+
+ ```sh
+ sudo gitlab-ctl reconfigure
+ ```
+
+ 1. **(secondary)** Start all services:
+
+ ```sh
+ sudo gitlab-ctl start
+ ```
+
+ 1. **(secondary)** Repeat the steps for the remaining **secondary** nodes.
+
+1. **(primary)** After all **secondary** nodes are updated, start all services in
+ **primary** node:
+
+ ```sh
+ sudo gitlab-ctl start
+ ```
+
+### Update tracking database on **secondary** node
+
+After updating a **secondary** node, you might need to run migrations on
+the tracking database. The tracking database was added in GitLab 9.1,
+and it is required since 10.0.
+
+1. Run database migrations on tracking database:
+
+ ```sh
+ sudo gitlab-rake geo:db:migrate
+ ```
+
+1. Repeat this step for each **secondary** node.
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index e0a06e71316..53b354d2f92 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -139,7 +139,7 @@ Git operations in GitLab will result in an API error.
NOTE: **Note:**
In most or all cases, the storage paths below end in `/repositories` which is
-not that case with `path` in `git_data_dirs` of Omnibus GitLab installations.
+not the case with `path` in `git_data_dirs` of Omnibus GitLab installations.
Check the directory layout on your Gitaly server to be sure.
**For Omnibus GitLab**
@@ -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
@@ -185,7 +185,7 @@ Check the directory layout on your Gitaly server to be sure.
```
1. Append the following to `/etc/gitlab/gitlab.rb` for each respective server:
-
+
For `gitaly1.internal`:
```
@@ -194,7 +194,7 @@ Check the directory layout on your Gitaly server to be sure.
{ 'name' => 'storage1' },
]
```
-
+
For `gitaly2.internal`:
```
@@ -226,7 +226,7 @@ Check the directory layout on your Gitaly server to be sure.
```
1. Append the following to `/home/git/gitaly/config.toml` for each respective server:
-
+
For `gitaly1.internal`:
```toml
@@ -236,7 +236,7 @@ Check the directory layout on your Gitaly server to be sure.
[[storage]]
name = 'storage1'
```
-
+
For `gitaly2.internal`:
```toml
@@ -262,12 +262,12 @@ Additionally, you need to
[disable Rugged if previously manually enabled](../high_availability/nfs.md#improving-nfs-performance-with-gitlab).
We assume that your `gitaly1.internal` Gitaly server can be reached at
-`gitaly1.internal:8075` from your GitLab server, and that Gitaly server
+`gitaly1.internal:8075` from your GitLab server, and that Gitaly server
can read and write to `/mnt/gitlab/default` and `/mnt/gitlab/storage1`.
We assume also that your `gitaly2.internal` Gitaly server can be reached at
-`gitaly2.internal:8075` from your GitLab server, and that Gitaly server
-can read and write to `/mnt/gitlab/storage2`.
+`gitaly2.internal:8075` from your GitLab server, and that Gitaly server
+can read and write to `/mnt/gitlab/storage2`.
**For Omnibus GitLab**
@@ -283,10 +283,6 @@ can read and write to `/mnt/gitlab/storage2`.
gitlab_rails['gitaly_token'] = 'abc123secret'
```
- NOTE: **Note:**
- In some cases, you'll have to set `path` for each `git_data_dirs` in the
- format `'path' => '/mnt/gitlab/<storage name>'`.
-
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
1. Tail the logs to see the requests:
@@ -304,18 +300,22 @@ can read and write to `/mnt/gitlab/storage2`.
storages:
default:
gitaly_address: tcp://gitaly1.internal:8075
+ path: /some/dummy/path
storage1:
gitaly_address: tcp://gitaly1.internal:8075
+ path: /some/dummy/path
storage2:
gitaly_address: tcp://gitaly2.internal:8075
+ path: /some/dummy/path
gitaly:
token: 'abc123secret'
```
NOTE: **Note:**
- In some cases, you'll have to set `path` for each of the `storages` in the
- format `path: /mnt/gitlab/<storage name>/repositories`.
+ `/some/dummy/path` should be set to a local folder that exists, however no
+ data will be stored in this folder. This will no longer be necessary after
+ [this issue](https://gitlab.com/gitlab-org/gitaly/issues/1282) is resolved.
1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
1. Tail the logs to see the requests:
@@ -416,18 +416,22 @@ To configure Gitaly with TLS:
storages:
default:
gitaly_address: tls://gitaly1.internal:9999
+ path: /some/dummy/path
storage1:
gitaly_address: tls://gitaly1.internal:9999
+ path: /some/dummy/path
storage2:
gitaly_address: tls://gitaly2.internal:9999
+ path: /some/dummy/path
gitaly:
token: 'abc123secret'
```
NOTE: **Note:**
- In some cases, you'll have to set `path` for each of the `storages` in the
- format `path: /mnt/gitlab/<storage name>/repositories`.
+ `/some/dummy/path` should be set to a local folder that exists, however no
+ data will be stored in this folder. This will no longer be necessary after
+ [this issue](https://gitlab.com/gitlab-org/gitaly/issues/1282) is resolved.
1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
1. On the Gitaly server nodes, edit `/home/git/gitaly/config.toml`:
@@ -514,6 +518,48 @@ One current feature of GitLab that still requires a shared directory (NFS) is
There is [work in progress](https://gitlab.com/gitlab-org/gitlab-pages/issues/196)
to eliminate the need for NFS to support GitLab Pages.
+## Limiting RPC concurrency
+
+It can happen that CI clone traffic puts a large strain on your Gitaly
+service. The bulk of the work gets done in the SSHUploadPack (for Git
+SSH) and PostUploadPack (for Git HTTP) RPC's. To prevent such workloads
+from overcrowding your Gitaly server you can set concurrency limits in
+Gitaly's configuration file.
+
+```ruby
+# in /etc/gitlab/gitlab.rb
+
+gitaly['concurrency'] = [
+ {
+ 'rpc' => "/gitaly.SmartHTTPService/PostUploadPack",
+ 'max_per_repo' => 20
+ },
+ {
+ 'rpc' => "/gitaly.SSHService/SSHUploadPack",
+ 'max_per_repo' => 20
+ }
+]
+```
+
+This will limit the number of in-flight RPC calls for the given RPC's.
+The limit is applied per repository. In the example above, each on the
+Gitaly server can have at most 20 simultaneous PostUploadPack calls in
+flight, and the same for SSHUploadPack. If another request comes in for
+a repository that hase used up its 20 slots, that request will get
+queued.
+
+You can observe the behavior of this queue via the Gitaly logs and via
+Prometheus. In the Gitaly logs, you can look for the string (or
+structured log field) `acquire_ms`. Messages that have this field are
+reporting about the concurrency limiter. In Prometheus, look for the
+`gitaly_rate_limiting_in_progress`, `gitaly_rate_limiting_queued` and
+`gitaly_rate_limiting_seconds` metrics.
+
+The name of the Prometheus metric is not quite right because this is a
+concurrency limiter, not a rate limiter. If a client makes 1000 requests
+in a row in a very short timespan, the concurrency will not exceed 1,
+and this mechanism (the concurrency limiter) will do nothing.
+
## Troubleshooting Gitaly
### Commits, pushes, and clones return a 401
@@ -684,4 +730,4 @@ To remove the proxy setting, run the following commands (depending on which vari
```bash
unset http_proxy
unset https_proxy
-``` \ No newline at end of file
+```
diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md
index 8818a9606de..0d1dd06871a 100644
--- a/doc/administration/high_availability/gitlab.md
+++ b/doc/administration/high_availability/gitlab.md
@@ -4,9 +4,9 @@ type: reference
# Configuring GitLab for Scaling and High Availability
-> **Note:** There is some additional configuration near the bottom for
- additional GitLab application servers. It's important to read and understand
- these additional steps before proceeding with GitLab installation.
+NOTE: **Note:** There is some additional configuration near the bottom for
+additional GitLab application servers. It's important to read and understand
+these additional steps before proceeding with GitLab installation.
1. If necessary, install the NFS client utility packages using the following
commands:
@@ -82,19 +82,19 @@ type: reference
1. [Enable monitoring](#enable-monitoring)
- > **Note:** To maintain uniformity of links across HA clusters, the `external_url`
+ NOTE: **Note:** To maintain uniformity of links across HA clusters, the `external_url`
on the first application server as well as the additional application
servers should point to the external url that users will use to access GitLab.
In a typical HA setup, this will be the url of the load balancer which will
route traffic to all GitLab application servers in the HA cluster.
- >
- > **Note:** When you specify `https` in the `external_url`, as in the example
+
+ NOTE: **Note:** When you specify `https` in the `external_url`, as in the example
above, GitLab assumes you have SSL certificates in `/etc/gitlab/ssl/`. If
certificates are not present, Nginx will fail to start. See
[Nginx documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
for more information.
- >
- > **Note:** It is best to set the `uid` and `gid`s prior to the initial reconfigure
+
+ NOTE: **Note:** It is best to set the `uid` and `gid`s prior to the initial reconfigure
of GitLab. Omnibus will not recursively `chown` directories if set after the initial reconfigure.
## First GitLab application server
@@ -105,8 +105,8 @@ Do not run this on additional application servers.
1. Initialize the database by running `sudo gitlab-rake gitlab:setup`.
1. Run `sudo gitlab-ctl reconfigure` to compile the configuration.
-> **WARNING:** Only run this setup task on **NEW** GitLab instances because it
- will wipe any existing data.
+ CAUTION: **WARNING:** Only run this setup task on **NEW** GitLab instances because it
+ will wipe any existing data.
## Extra configuration for additional GitLab application servers
@@ -173,9 +173,10 @@ If you enable Monitoring, it must be enabled on **all** GitLab servers.
1. Run `sudo gitlab-ctl reconfigure` to compile the configuration.
-> **Warning:** After changing `unicorn['listen']` in `gitlab.rb`, and running `sudo gitlab-ctl reconfigure`,
- it can take an extended period of time for unicorn to complete reloading after receiving a `HUP`.
- For more information, see the [issue](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/4401).
+ CAUTION: **Warning:**
+ After changing `unicorn['listen']` in `gitlab.rb`, and running `sudo gitlab-ctl reconfigure`,
+ it can take an extended period of time for unicorn to complete reloading after receiving a `HUP`.
+ For more information, see the [issue](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/4401).
## Troubleshooting
diff --git a/doc/administration/index.md b/doc/administration/index.md
index 650cb10a64a..d557068e6c8 100644
--- a/doc/administration/index.md
+++ b/doc/administration/index.md
@@ -139,6 +139,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Repository checks](repository_checks.md): Periodic Git repository checks.
- [Repository storage paths](repository_storage_paths.md): Manage the paths used to store repositories.
+- [Repository storage types](repository_storage_types.md): Information about the different repository storage types.
- [Repository storage rake tasks](raketasks/storage.md): A collection of rake tasks to list and migrate existing projects and attachments associated with it from Legacy storage to Hashed storage.
- [Limit repository size](../user/admin_area/settings/account_and_limit_settings.md): Set a hard limit for your repositories' size. **(STARTER ONLY)**
@@ -186,13 +187,29 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Debugging tips](troubleshooting/debug.md): Tips to debug problems when things go wrong
- [Log system](logs.md): Where to look for logs.
- [Sidekiq Troubleshooting](troubleshooting/sidekiq.md): Debug when Sidekiq appears hung and is not processing jobs.
-- Useful [diagnostics tools](troubleshooting/diagnostics_tools.md) that are sometimes used by the GitLab
- Support team.
-- [Troubleshooting ElasticSearch](troubleshooting/elasticsearch.md): Tips to troubleshoot ElasticSearch.
-- [Kubernetes troubleshooting](troubleshooting/kubernetes_cheat_sheet.md): Commands and tips useful
- for troubleshooting Kubernetes-related issues.
-- Useful links from the Support Team:
- - [GitLab Developer Docs](https://docs.gitlab.com/ee/development/README.html).
- - [Repairing and recovering broken Git repositories](https://git.seveas.net/repairing-and-recovering-broken-git-repositories.html).
- - [Testing with OpenSSL](https://www.feistyduck.com/library/openssl-cookbook/online/ch-testing-with-openssl.html).
- - [Strace zine](https://wizardzines.com/zines/strace/).
+- [Troubleshooting ElasticSearch](troubleshooting/elasticsearch.md)
+
+### Support Team Docs
+
+The GitLab Support Team has collected a lot of information about troubleshooting GitLab
+instances. These documents are normally used by the Support Team itself, or by customers
+with direct guidance from a Support Team member. GitLab administrators may find the
+information useful for troubleshooting, but if you are experiencing trouble with your
+GitLab instance, you should check your [support options](https://about.gitlab.com/support/)
+before referring to these documents.
+
+CAUTION: **Warning:**
+Using the commands listed in the documentation below could result in data loss or
+other damage to a GitLab instance, and should only be used by experienced administrators
+who are aware of the risks.
+
+- [Useful diagnostics tools](troubleshooting/diagnostics_tools.md)
+- [Useful Linux commands](troubleshooting/linux_cheat_sheet.md)
+- [Troubleshooting Kubernetes](troubleshooting/kubernetes_cheat_sheet.md)
+- [Guide to test environments](troubleshooting/test_environments.md) (for Support Engineers)
+- [GitLab rails console commands](troubleshooting/gitlab_rails_cheat_sheet.md) (for Support Engineers)
+- Useful links:
+ - [GitLab Developer Docs](../development/README.md)
+ - [Repairing and recovering broken Git repositories](https://git.seveas.net/repairing-and-recovering-broken-git-repositories.html)
+ - [Testing with OpenSSL](https://www.feistyduck.com/library/openssl-cookbook/online/ch-testing-with-openssl.html)
+ - [Strace zine](https://wizardzines.com/zines/strace/)
diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md
index 16a193550a1..df6c554decb 100644
--- a/doc/administration/integration/plantuml.md
+++ b/doc/administration/integration/plantuml.md
@@ -1,7 +1,6 @@
# PlantUML & GitLab
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8537) in
-> GitLab 8.16.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8537) in GitLab 8.16.
When [PlantUML](http://plantuml.com) integration is enabled and configured in
GitLab we are able to create simple diagrams in AsciiDoc and Markdown documents
diff --git a/doc/administration/issue_closing_pattern.md b/doc/administration/issue_closing_pattern.md
index 66ab3ef8d81..f9be06c6daf 100644
--- a/doc/administration/issue_closing_pattern.md
+++ b/doc/administration/issue_closing_pattern.md
@@ -1,8 +1,8 @@
# Issue closing pattern **(CORE ONLY)**
>**Note:**
-This is the administration documentation.
-There is a separate [user documentation] on issue closing pattern.
+This is the administration documentation. There is a separate [user documentation](../user/project/issues/managing_issues.md#closing-issues-automatically)
+on issue closing pattern.
When a commit or merge request resolves one or more issues, it is possible to
automatically have these issues closed when the commit or merge request lands
@@ -13,8 +13,8 @@ in the project's default branch.
In order to change the pattern you need to have access to the server that GitLab
is installed on.
-The default pattern can be located in [`gitlab.yml.example`] under the
-"Automatic issue closing" section.
+The default pattern can be located in [`gitlab.yml.example`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example)
+under the "Automatic issue closing" section.
> **Tip:**
You are advised to use <http://rubular.com> to test the issue closing pattern.
@@ -31,7 +31,7 @@ Because Rubular doesn't understand `%{issue_ref}`, you can replace this by
gitlab_rails['gitlab_issue_closing_pattern'] = "\b((?:[Cc]los(?:e[sd]|ing)|\b[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)"
```
-1. [Reconfigure] GitLab for the changes to take effect.
+1. [Reconfigure](restart_gitlab.md#omnibus-gitlab-reconfigure) GitLab for the changes to take effect.
**For installations from source**
@@ -42,9 +42,4 @@ Because Rubular doesn't understand `%{issue_ref}`, you can replace this by
issue_closing_pattern: "\b((?:[Cc]los(?:e[sd]|ing)|\b[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)"
```
-1. [Restart] GitLab for the changes to take effect.
-
-[gitlab.yml.example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example
-[reconfigure]: restart_gitlab.md#omnibus-gitlab-reconfigure
-[restart]: restart_gitlab.md#installations-from-source
-[user documentation]: ../user/project/issues/managing_issues.md#closing-issues-automatically
+1. [Restart](restart_gitlab.md#installations-from-source) GitLab for the changes to take effect.
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 9030c941cad..c51b53c596e 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -338,3 +338,13 @@ installations from source.
[repocheck]: repository_checks.md
[Rack Attack]: ../security/rack_attack.md
[Rate Limit]: ../user/admin_area/settings/rate_limits_on_raw_endpoints.md
+
+## `database_load_balancing.log` **(PREMIUM ONLY)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/15442) in GitLab 12.3.
+
+Contains details of GitLab's [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/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index ec26c0b2e7e..6dbfd5404d0 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -1,7 +1,7 @@
# GitLab Prometheus metrics
>**Note:**
-Available since [Omnibus GitLab 9.3][29118]. For
+Available since [Omnibus GitLab 9.3](https://gitlab.com/gitlab-org/gitlab-ce/issues/29118). For
installations from source you'll have to configure it yourself.
To enable the GitLab Prometheus metrics:
@@ -9,125 +9,184 @@ To enable the GitLab Prometheus metrics:
1. Log into GitLab as an administrator, and go to the Admin area.
1. Click on the gear, then click on Settings.
1. Find the `Metrics - Prometheus` section, and click `Enable Prometheus Metrics`
-1. [Restart GitLab][restart] for the changes to take effect
+1. [Restart GitLab](../../restart_gitlab.md#omnibus-gitlab-restart) for the changes to take effect
## Collecting the metrics
GitLab monitors its own internal service metrics, and makes them available at the
-`/-/metrics` endpoint. Unlike other [Prometheus] exporters, in order to access
-it, the client IP needs to be [included in a whitelist][whitelist].
+`/-/metrics` endpoint. Unlike other [Prometheus](https://prometheus.io) exporters, in order to access
+it, the client IP needs to be [included in a whitelist](../ip_whitelist.md).
For Omnibus and Chart installations, these metrics are automatically enabled and collected as of [GitLab 9.4](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1702). For source installations or earlier versions, these metrics will need to be enabled manually and collected by a Prometheus server.
-## Unicorn Metrics available
+## Metrics available
The following metrics are available:
-| Metric | Type | Since | Description |
-|:--------------------------------- |:--------- |:----- |:----------- |
-| db_ping_timeout | Gauge | 9.4 | Whether or not the last database ping timed out |
-| db_ping_success | Gauge | 9.4 | Whether or not the last database ping succeeded |
-| db_ping_latency_seconds | Gauge | 9.4 | Round trip time of the database ping |
-| filesystem_access_latency_seconds | Gauge | 9.4 | Latency in accessing a specific filesystem |
-| filesystem_accessible | Gauge | 9.4 | Whether or not a specific filesystem is accessible |
-| filesystem_write_latency_seconds | Gauge | 9.4 | Write latency of a specific filesystem |
-| filesystem_writable | Gauge | 9.4 | Whether or not the filesystem is writable |
-| filesystem_read_latency_seconds | Gauge | 9.4 | Read latency of a specific filesystem |
-| filesystem_readable | Gauge | 9.4 | Whether or not the filesystem is readable |
-| gitlab_cache_misses_total | Counter | 10.2 | Cache read miss |
-| gitlab_cache_operation_duration_seconds | Histogram | 10.2 | Cache access time |
-| gitlab_cache_operations_total | Counter | 12.2 | Cache operations by controller/action |
-| http_requests_total | Counter | 9.4 | Rack request count |
-| http_request_duration_seconds | Histogram | 9.4 | HTTP response time from rack middleware |
-| pipelines_created_total | Counter | 9.4 | Counter of pipelines created |
-| rack_uncaught_errors_total | Counter | 9.4 | Rack connections handling uncaught errors count |
-| redis_ping_timeout | Gauge | 9.4 | Whether or not the last redis ping timed out |
-| redis_ping_success | Gauge | 9.4 | Whether or not the last redis ping succeeded |
-| redis_ping_latency_seconds | Gauge | 9.4 | Round trip time of the redis ping |
-| user_session_logins_total | Counter | 9.4 | Counter of how many users have logged in |
-| upload_file_does_not_exist | Counter | 10.7 in EE, 11.5 in CE | Number of times an upload record could not find its file |
-| failed_login_captcha_total | Gauge | 11.0 | Counter of failed CAPTCHA attempts during login |
-| successful_login_captcha_total | Gauge | 11.0 | Counter of successful CAPTCHA attempts during login |
-| unicorn_active_connections | Gauge | 11.0 | The number of active Unicorn connections (workers) |
-| unicorn_queued_connections | Gauge | 11.0 | The number of queued Unicorn connections |
-| unicorn_workers | Gauge | 12.0 | The number of Unicorn workers |
+| Metric | Type | Since | Description | Labels |
+|:---------------------------------------------------------------|:----------|-----------------------:|:----------------------------------------------------------------------------------------------------|:----------------------------------------------------|
+| `gitlab_banzai_cached_render_real_duration_seconds` | Histogram | 9.4 | Duration of rendering markdown into HTML when cached output exists | controller, action |
+| `gitlab_banzai_cacheless_render_real_duration_seconds` | Histogram | 9.4 | Duration of rendering markdown into HTML when cached outupt does not exist | controller, action |
+| `gitlab_cache_misses_total` | Counter | 10.2 | Cache read miss | controller, action |
+| `gitlab_cache_operation_duration_seconds` | Histogram | 10.2 | Cache access time | |
+| `gitlab_cache_operations_total` | Counter | 12.2 | Cache operations by controller/action | controller, action, operation |
+| `gitlab_database_transaction_seconds` | Histogram | 12.1 | Time spent in database transactions, in seconds | |
+| `gitlab_method_call_duration_seconds` | Histogram | 10.2 | Method calls real duration | controller, action, module, method |
+| `gitlab_rails_queue_duration_seconds` | Histogram | 9.4 | Measures latency between GitLab Workhorse forwarding a request to Rails | |
+| `gitlab_sql_duration_seconds` | Histogram | 10.2 | SQL execution time, excluding SCHEMA operations and BEGIN / COMMIT | |
+| `gitlab_transaction_allocated_memory_bytes` | Histogram | 10.2 | Allocated memory for all transactions (gitlab_transaction_* metrics) | |
+| `gitlab_transaction_cache_<key>_count_total` | Counter | 10.2 | Counter for total Rails cache calls (per key) | |
+| `gitlab_transaction_cache_<key>_duration_total` | Counter | 10.2 | Counter for total time (seconds) spent in Rails cache calls (per key) | |
+| `gitlab_transaction_cache_count_total` | Counter | 10.2 | Counter for total Rails cache calls (aggregate) | |
+| `gitlab_transaction_cache_duration_total` | Counter | 10.2 | Counter for total time (seconds) spent in Rails cache calls (aggregate) | |
+| `gitlab_transaction_cache_read_hit_count_total` | Counter | 10.2 | Counter for cache hits for Rails cache calls | controller, action |
+| `gitlab_transaction_cache_read_miss_count_total` | Counter | 10.2 | Counter for cache misses for Rails cache calls | controller, action |
+| `gitlab_transaction_duration_seconds` | Histogram | 10.2 | Duration for all transactions (gitlab_transaction_* metrics) | controller, action |
+| `gitlab_transaction_event_build_found_total` | Counter | 9.4 | Counter for build found for api /jobs/request | |
+| `gitlab_transaction_event_build_invalid_total` | Counter | 9.4 | Counter for build invalid due to concurrency conflict for api /jobs/request | |
+| `gitlab_transaction_event_build_not_found_cached_total` | Counter | 9.4 | Counter for cached response of build not found for api /jobs/request | |
+| `gitlab_transaction_event_build_not_found_total` | Counter | 9.4 | Counter for build not found for api /jobs/request | |
+| `gitlab_transaction_event_change_default_branch_total` | Counter | 9.4 | Counter when default branch is changed for any repository | |
+| `gitlab_transaction_event_create_repository_total` | Counter | 9.4 | Counter when any repository is created | |
+| `gitlab_transaction_event_etag_caching_cache_hit_total` | Counter | 9.4 | Counter for etag cache hit. | endpoint |
+| `gitlab_transaction_event_etag_caching_header_missing_total` | Counter | 9.4 | Counter for etag cache miss - header missing | endpoint |
+| `gitlab_transaction_event_etag_caching_key_not_found_total` | Counter | 9.4 | Counter for etag cache miss - key not found | endpoint |
+| `gitlab_transaction_event_etag_caching_middleware_used_total` | Counter | 9.4 | Counter for etag middleware accessed | endpoint |
+| `gitlab_transaction_event_etag_caching_resource_changed_total` | Counter | 9.4 | Counter for etag cache miss - resource changed | endpoint |
+| `gitlab_transaction_event_fork_repository_total` | Counter | 9.4 | Counter for repository forks (RepositoryForkWorker). Only incremented when source repository exists | |
+| `gitlab_transaction_event_import_repository_total` | Counter | 9.4 | Counter for repository imports (RepositoryImportWorker) | |
+| `gitlab_transaction_event_push_branch_total` | Counter | 9.4 | Counter for all branch pushes | |
+| `gitlab_transaction_event_push_commit_total` | Counter | 9.4 | Counter for commits | branch |
+| `gitlab_transaction_event_push_tag_total` | Counter | 9.4 | Counter for tag pushes | |
+| `gitlab_transaction_event_rails_exception_total` | Counter | 9.4 | Counter for number of rails exceptions | |
+| `gitlab_transaction_event_receive_email_total` | Counter | 9.4 | Counter for recieved emails | handler |
+| `gitlab_transaction_event_remote_mirrors_failed_total` | Counter | 10.8 | Counter for failed remote mirrors | |
+| `gitlab_transaction_event_remote_mirrors_finished_total` | Counter | 10.8 | Counter for finished remote mirrors | |
+| `gitlab_transaction_event_remote_mirrors_running_total` | Counter | 10.8 | Counter for running remote mirrors | |
+| `gitlab_transaction_event_remove_branch_total` | Counter | 9.4 | Counter when a branch is removed for any repository | |
+| `gitlab_transaction_event_remove_repository_total` | Counter | 9.4 | Counter when a repository is removed | |
+| `gitlab_transaction_event_remove_tag_total` | Counter | 9.4 | Counter when a tag is remove for any repository | |
+| `gitlab_transaction_event_sidekiq_exception_total` | Counter | 9.4 | Counter of sidekiq exceptions | |
+| `gitlab_transaction_event_stuck_import_jobs_total` | Counter | 9.4 | Count of stuck import jobs | projects_without_jid_count, projects_with_jid_count |
+| `gitlab_transaction_event_update_build_total` | Counter | 9.4 | Counter for update build for api /jobs/request/:id | |
+| `gitlab_transaction_new_redis_connections_total` | Counter | 9.4 | Counter for new redis connections | |
+| `gitlab_transaction_queue_duration_total` | Counter | 9.4 | Duration jobs were enqueued before processing | |
+| `gitlab_transaction_rails_queue_duration_total` | Counter | 9.4 | Measures latency between GitLab Workhorse forwarding a request to Rails | controller, action |
+| `gitlab_transaction_view_duration_total` | Counter | 9.4 | Duration for views | controller, action, view |
+| `gitlab_view_rendering_duration_seconds` | Histogram | 10.2 | Duration for views (histogram) | controller, action, view |
+| `http_requests_total` | Counter | 9.4 | Rack request count | method |
+| `http_request_duration_seconds` | Histogram | 9.4 | HTTP response time from rack middleware | method, status |
+| `pipelines_created_total` | Counter | 9.4 | Counter of pipelines created | |
+| `rack_uncaught_errors_total` | Counter | 9.4 | Rack connections handling uncaught errors count | |
+| `user_session_logins_total` | Counter | 9.4 | Counter of how many users have logged in | |
+| `upload_file_does_not_exist` | Counter | 10.7 in EE, 11.5 in CE | Number of times an upload record could not find its file | |
+| `failed_login_captcha_total` | Gauge | 11.0 | Counter of failed CAPTCHA attempts during login | |
+| `successful_login_captcha_total` | Gauge | 11.0 | Counter of successful CAPTCHA attempts during login | |
+
+## Metrics controlled by a feature flag
+
+The following metrics can be controlled by feature flags:
+
+| Metric | Feature Flag |
+|:---------------------------------------------------------------|:-------------------------------------------------------------------|
+| `gitlab_method_call_duration_seconds` | `prometheus_metrics_method_instrumentation` |
+| `gitlab_transaction_allocated_memory_bytes` | `prometheus_metrics_transaction_allocated_memory` |
+| `gitlab_view_rendering_duration_seconds` | `prometheus_metrics_view_instrumentation` |
## Sidekiq Metrics available for Geo **(PREMIUM)**
Sidekiq jobs may also gather metrics, and these metrics can be accessed if the Sidekiq exporter is enabled (e.g. via
the `monitoring.sidekiq_exporter` configuration option in `gitlab.yml`.
-| Metric | Type | Since | Description | Labels |
-|:-------------------------------------------- |:------- |:----- |:----------- |:------ |
-| geo_db_replication_lag_seconds | Gauge | 10.2 | Database replication lag (seconds) | url
-| geo_repositories | Gauge | 10.2 | Total number of repositories available on primary | url
-| geo_repositories_synced | Gauge | 10.2 | Number of repositories synced on secondary | url
-| geo_repositories_failed | Gauge | 10.2 | Number of repositories failed to sync on secondary | url
-| geo_lfs_objects | Gauge | 10.2 | Total number of LFS objects available on primary | url
-| geo_lfs_objects_synced | Gauge | 10.2 | Number of LFS objects synced on secondary | url
-| geo_lfs_objects_failed | Gauge | 10.2 | Number of LFS objects failed to sync on secondary | url
-| geo_attachments | Gauge | 10.2 | Total number of file attachments available on primary | url
-| geo_attachments_synced | Gauge | 10.2 | Number of attachments synced on secondary | url
-| geo_attachments_failed | Gauge | 10.2 | Number of attachments failed to sync on secondary | url
-| geo_last_event_id | Gauge | 10.2 | Database ID of the latest event log entry on the primary | url
-| geo_last_event_timestamp | Gauge | 10.2 | UNIX timestamp of the latest event log entry on the primary | url
-| geo_cursor_last_event_id | Gauge | 10.2 | Last database ID of the event log processed by the secondary | url
-| geo_cursor_last_event_timestamp | Gauge | 10.2 | Last UNIX timestamp of the event log processed by the secondary | url
-| geo_status_failed_total | Counter | 10.2 | Number of times retrieving the status from the Geo Node failed | url
-| geo_last_successful_status_check_timestamp | Gauge | 10.2 | Last timestamp when the status was successfully updated | url
-| geo_lfs_objects_synced_missing_on_primary | Gauge | 10.7 | Number of LFS objects marked as synced due to the file missing on the primary | url
-| geo_job_artifacts_synced_missing_on_primary | Gauge | 10.7 | Number of job artifacts marked as synced due to the file missing on the primary | url
-| geo_attachments_synced_missing_on_primary | Gauge | 10.7 | Number of attachments marked as synced due to the file missing on the primary | url
-| geo_repositories_checksummed_count | Gauge | 10.7 | Number of repositories checksummed on primary | url
-| geo_repositories_checksum_failed_count | Gauge | 10.7 | Number of repositories failed to calculate the checksum on primary | url
-| geo_wikis_checksummed_count | Gauge | 10.7 | Number of wikis checksummed on primary | url
-| geo_wikis_checksum_failed_count | Gauge | 10.7 | Number of wikis failed to calculate the checksum on primary | url
-| geo_repositories_verified_count | Gauge | 10.7 | Number of repositories verified on secondary | url
-| geo_repositories_verification_failed_count | Gauge | 10.7 | Number of repositories failed to verify on secondary | url
-| geo_repositories_checksum_mismatch_count | Gauge | 10.7 | Number of repositories that checksum mismatch on secondary | url
-| geo_wikis_verified_count | Gauge | 10.7 | Number of wikis verified on secondary | url
-| geo_wikis_verification_failed_count | Gauge | 10.7 | Number of wikis failed to verify on secondary | url
-| geo_wikis_checksum_mismatch_count | Gauge | 10.7 | Number of wikis that checksum mismatch on secondary | url
-| geo_repositories_checked_count | Gauge | 11.1 | Number of repositories that have been checked via `git fsck` | url
-| geo_repositories_checked_failed_count | Gauge | 11.1 | Number of repositories that have a failure from `git fsck` | url
-| geo_repositories_retrying_verification_count | Gauge | 11.2 | Number of repositories verification failures that Geo is actively trying to correct on secondary | url
-| geo_wikis_retrying_verification_count | Gauge | 11.2 | Number of wikis verification failures that Geo is actively trying to correct on secondary | url
-
-### Ruby metrics
+| Metric | Type | Since | Description | Labels |
+|:---------------------------------------------- |:------- |:----- |:----------- |:------ |
+| `geo_db_replication_lag_seconds` | Gauge | 10.2 | Database replication lag (seconds) | url |
+| `geo_repositories` | Gauge | 10.2 | Total number of repositories available on primary | url |
+| `geo_repositories_synced` | Gauge | 10.2 | Number of repositories synced on secondary | url |
+| `geo_repositories_failed` | Gauge | 10.2 | Number of repositories failed to sync on secondary | url |
+| `geo_lfs_objects` | Gauge | 10.2 | Total number of LFS objects available on primary | url |
+| `geo_lfs_objects_synced` | Gauge | 10.2 | Number of LFS objects synced on secondary | url |
+| `geo_lfs_objects_failed` | Gauge | 10.2 | Number of LFS objects failed to sync on secondary | url |
+| `geo_attachments` | Gauge | 10.2 | Total number of file attachments available on primary | url |
+| `geo_attachments_synced` | Gauge | 10.2 | Number of attachments synced on secondary | url |
+| `geo_attachments_failed` | Gauge | 10.2 | Number of attachments failed to sync on secondary | url |
+| `geo_last_event_id` | Gauge | 10.2 | Database ID of the latest event log entry on the primary | url |
+| `geo_last_event_timestamp` | Gauge | 10.2 | UNIX timestamp of the latest event log entry on the primary | url |
+| `geo_cursor_last_event_id` | Gauge | 10.2 | Last database ID of the event log processed by the secondary | url |
+| `geo_cursor_last_event_timestamp` | Gauge | 10.2 | Last UNIX timestamp of the event log processed by the secondary | url |
+| `geo_status_failed_total` | Counter | 10.2 | Number of times retrieving the status from the Geo Node failed | url |
+| `geo_last_successful_status_check_timestamp` | Gauge | 10.2 | Last timestamp when the status was successfully updated | url |
+| `geo_lfs_objects_synced_missing_on_primary` | Gauge | 10.7 | Number of LFS objects marked as synced due to the file missing on the primary | url |
+| `geo_job_artifacts_synced_missing_on_primary` | Gauge | 10.7 | Number of job artifacts marked as synced due to the file missing on the primary | url |
+| `geo_attachments_synced_missing_on_primary` | Gauge | 10.7 | Number of attachments marked as synced due to the file missing on the primary | url |
+| `geo_repositories_checksummed_count` | Gauge | 10.7 | Number of repositories checksummed on primary | url |
+| `geo_repositories_checksum_failed_count` | Gauge | 10.7 | Number of repositories failed to calculate the checksum on primary | url |
+| `geo_wikis_checksummed_count` | Gauge | 10.7 | Number of wikis checksummed on primary | url |
+| `geo_wikis_checksum_failed_count` | Gauge | 10.7 | Number of wikis failed to calculate the checksum on primary | url |
+| `geo_repositories_verified_count` | Gauge | 10.7 | Number of repositories verified on secondary | url |
+| `geo_repositories_verification_failed_count` | Gauge | 10.7 | Number of repositories failed to verify on secondary | url |
+| `geo_repositories_checksum_mismatch_count` | Gauge | 10.7 | Number of repositories that checksum mismatch on secondary | url |
+| `geo_wikis_verified_count` | Gauge | 10.7 | Number of wikis verified on secondary | url |
+| `geo_wikis_verification_failed_count` | Gauge | 10.7 | Number of wikis failed to verify on secondary | url |
+| `geo_wikis_checksum_mismatch_count` | Gauge | 10.7 | Number of wikis that checksum mismatch on secondary | url |
+| `geo_repositories_checked_count` | Gauge | 11.1 | Number of repositories that have been checked via `git fsck` | url |
+| `geo_repositories_checked_failed_count` | Gauge | 11.1 | Number of repositories that have a failure from `git fsck` | url |
+| `geo_repositories_retrying_verification_count` | Gauge | 11.2 | Number of repositories verification failures that Geo is actively trying to correct on secondary | url |
+| `geo_wikis_retrying_verification_count` | Gauge | 11.2 | Number of wikis verification failures that Geo is actively trying to correct on secondary | url |
+
+## Database load balancing metrics **(PREMIUM ONLY)**
+
+The following metrics are available:
+
+| Metric | Type | Since | Description |
+|:--------------------------------- |:--------- |:------------------------------------------------------------- |:-------------------------------------- |
+| `db_load_balancing_hosts` | Gauge | [12.3](https://gitlab.com/gitlab-org/gitlab-ee/issues/13630) | Current number of load balancing hosts |
+| `db_load_balancing_index` | Gauge | [12.3](https://gitlab.com/gitlab-org/gitlab-ee/issues/13630) | Current load balancing host index |
+
+## Ruby metrics
Some basic Ruby runtime metrics are available:
-| Metric | Type | Since | Description |
-|:-------------------------------------- |:--------- |:----- |:----------- |
-| ruby_gc_duration_seconds_total | Counter | 11.1 | Time spent by Ruby in GC |
-| ruby_gc_stat_... | Gauge | 11.1 | Various metrics from [GC.stat] |
-| ruby_file_descriptors | Gauge | 11.1 | File descriptors per process |
-| ruby_memory_bytes | Gauge | 11.1 | Memory usage by process |
-| ruby_sampler_duration_seconds_total | Counter | 11.1 | Time spent collecting stats |
-| ruby_process_cpu_seconds_total | Gauge | 12.0 | Total amount of CPU time per process |
-| ruby_process_max_fds | Gauge | 12.0 | Maximum number of open file descriptors per process |
-| ruby_process_resident_memory_bytes | Gauge | 12.0 | Memory usage by process, measured in bytes |
-| ruby_process_start_time_seconds | Gauge | 12.0 | UNIX timestamp of process start time |
+| Metric | Type | Since | Description |
+|:------------------------------------ |:--------- |:----- |:----------- |
+| `ruby_gc_duration_seconds` | Counter | 11.1 | Time spent by Ruby in GC |
+| `ruby_gc_stat_...` | Gauge | 11.1 | Various metrics from [GC.stat] |
+| `ruby_file_descriptors` | Gauge | 11.1 | File descriptors per process |
+| `ruby_memory_bytes` | Gauge | 11.1 | Memory usage by process |
+| `ruby_sampler_duration_seconds` | Counter | 11.1 | Time spent collecting stats |
+| `ruby_process_cpu_seconds_total` | Gauge | 12.0 | Total amount of CPU time per process |
+| `ruby_process_max_fds` | Gauge | 12.0 | Maximum number of open file descriptors per process |
+| `ruby_process_resident_memory_bytes` | Gauge | 12.0 | Memory usage by process, measured in bytes |
+| `ruby_process_start_time_seconds` | Gauge | 12.0 | UNIX timestamp of process start time |
-[GC.stat]: https://ruby-doc.org/core-2.3.0/GC.html#method-c-stat
+[GC.stat]: https://ruby-doc.org/core-2.6.3/GC.html#method-c-stat
+
+## Unicorn Metrics
+
+Unicorn specific metrics, when Unicorn is used.
+
+| Metric | Type | Since | Description |
+|:-----------------------------|:------|:------|:---------------------------------------------------|
+| `unicorn_active_connections` | Gauge | 11.0 | The number of active Unicorn connections (workers) |
+| `unicorn_queued_connections` | Gauge | 11.0 | The number of queued Unicorn connections |
+| `unicorn_workers` | Gauge | 12.0 | The number of Unicorn workers |
## Puma Metrics **(EXPERIMENTAL)**
-When Puma is used instead of Unicorn, following metrics are available:
-
-| Metric | Type | Since | Description |
-|:-------------------------------------------- |:------- |:----- |:----------- |
-| puma_workers | Gauge | 12.0 | Total number of workers |
-| puma_running_workers | Gauge | 12.0 | Number of booted workers |
-| puma_stale_workers | Gauge | 12.0 | Number of old workers |
-| puma_running | Gauge | 12.0 | Number of running threads |
-| puma_queued_connections | Gauge | 12.0 | Number of connections in that worker's "todo" set waiting for a worker thread |
-| puma_active_connections | Gauge | 12.0 | Number of threads processing a request |
-| puma_pool_capacity | Gauge | 12.0 | Number of requests the worker is capable of taking right now |
-| puma_max_threads | Gauge | 12.0 | Maximum number of worker threads |
-| puma_idle_threads | Gauge | 12.0 | Number of spawned threads which are not processing a request |
-| rack_state_total | Gauge | 12.0 | Number of requests in a given rack state |
-| puma_killer_terminations_total | Gauge | 12.0 | Number of workers terminated by PumaWorkerKiller |
+When Puma is used instead of Unicorn, the following metrics are available:
+
+| Metric | Type | Since | Description |
+|:---------------------------------------------- |:------- |:----- |:----------- |
+| `puma_workers` | Gauge | 12.0 | Total number of workers |
+| `puma_running_workers` | Gauge | 12.0 | Number of booted workers |
+| `puma_stale_workers` | Gauge | 12.0 | Number of old workers |
+| `puma_running` | Gauge | 12.0 | Number of running threads |
+| `puma_queued_connections` | Gauge | 12.0 | Number of connections in that worker's "todo" set waiting for a worker thread |
+| `puma_active_connections` | Gauge | 12.0 | Number of threads processing a request |
+| `puma_pool_capacity` | Gauge | 12.0 | Number of requests the worker is capable of taking right now |
+| `puma_max_threads` | Gauge | 12.0 | Maximum number of worker threads |
+| `puma_idle_threads` | Gauge | 12.0 | Number of spawned threads which are not processing a request |
+| `puma_killer_terminations_total` | Gauge | 12.0 | Number of workers terminated by PumaWorkerKiller |
## Metrics shared directory
@@ -144,9 +203,3 @@ If GitLab is installed using Omnibus and `tmpfs` is available then metrics
directory will be automatically configured.
[← Back to the main Prometheus page](index.md)
-
-[29118]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29118
-[Prometheus]: https://prometheus.io
-[restart]: ../../restart_gitlab.md#omnibus-gitlab-restart
-[whitelist]: ../ip_whitelist.md
-[reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure
diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md
index c77a1a9638f..fdfde22647d 100644
--- a/doc/administration/pages/source.md
+++ b/doc/administration/pages/source.md
@@ -343,21 +343,6 @@ world. Custom domains and TLS are supported.
1. Restart NGINX
1. [Restart GitLab][restart]
-## Change storage path
-
-Follow the steps below to change the default path where GitLab Pages' contents
-are stored.
-
-1. Pages are stored by default in `/var/opt/gitlab/gitlab-rails/shared/pages`.
- If you wish to store them in another location you must set it up in
- `/etc/gitlab/gitlab.rb`:
-
- ```ruby
- gitlab_rails['pages_path'] = "/mnt/storage/pages"
- ```
-
-1. [Reconfigure GitLab][reconfigure]
-
## NGINX caveats
>**Note:**
@@ -468,7 +453,6 @@ than GitLab to prevent XSS attacks.
[NGINX configs]: https://gitlab.com/gitlab-org/gitlab-ee/tree/8-5-stable-ee/lib/support/nginx
[pages-readme]: https://gitlab.com/gitlab-org/gitlab-pages/blob/master/README.md
[pages-userguide]: ../../user/project/pages/index.md
-[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
[restart]: ../restart_gitlab.md#installations-from-source
[gitlab-pages]: https://gitlab.com/gitlab-org/gitlab-pages/tree/v0.4.0
[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/init.d/gitlab.default.example
diff --git a/doc/administration/raketasks/github_import.md b/doc/administration/raketasks/github_import.md
index ccd9c0afb1d..f8eecc97c33 100644
--- a/doc/administration/raketasks/github_import.md
+++ b/doc/administration/raketasks/github_import.md
@@ -1,6 +1,6 @@
# GitHub import
-> [Introduced]( https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10308) in GitLab 9.1.
+> [Introduced]( https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10308) in GitLab 9.1.
In order to retrieve and import GitHub repositories, you will need a
[GitHub personal access token](https://github.com/settings/tokens).
diff --git a/doc/administration/raketasks/ldap.md b/doc/administration/raketasks/ldap.md
index e880d76e756..d0ebe272b6d 100644
--- a/doc/administration/raketasks/ldap.md
+++ b/doc/administration/raketasks/ldap.md
@@ -19,8 +19,6 @@ sudo gitlab-rake gitlab:ldap:check
sudo -u git -H bundle exec rake gitlab:ldap:check RAILS_ENV=production
```
-------
-
By default, the task will return a sample of 100 LDAP users. Change this
limit by passing a number to the check task:
@@ -135,8 +133,6 @@ What is the old provider? Ex. 'ldapmain': ldapmain
What is the new provider? Ex. 'ldapcustom': ldapmycompany
```
-------
-
This tasks also accepts the `force` environment variable which will skip the
confirmation dialog:
diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md
index 8d0b5b42515..89335fcd2a8 100644
--- a/doc/administration/raketasks/maintenance.md
+++ b/doc/administration/raketasks/maintenance.md
@@ -260,7 +260,7 @@ To check the status of migrations, you can use the following rake task:
sudo gitlab-rake db:migrate:status
```
-This will output a table with a `Status` of `up` or `down` for
+This will output a table with a `Status` of `up` or `down` for
each Migration ID.
```bash
@@ -269,4 +269,4 @@ database: gitlabhq_production
Status Migration ID Migration Name
--------------------------------------------------
up migration_id migration_name
-``` \ No newline at end of file
+```
diff --git a/doc/administration/raketasks/uploads/migrate.md b/doc/administration/raketasks/uploads/migrate.md
index 86e8b763f51..d9b4c9b3369 100644
--- a/doc/administration/raketasks/uploads/migrate.md
+++ b/doc/administration/raketasks/uploads/migrate.md
@@ -108,7 +108,7 @@ sudo -u git -H bundle exec rake "gitlab:uploads:migrate[FileUploader, MergeReque
> Introduced in GitLab 12.3.
-To migrate all uploads created by legacy uploaders, run:
+To migrate all uploads created by legacy uploaders, run:
```shell
bundle exec rake gitlab:uploads:legacy:migrate
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/repository_storage_types.md b/doc/administration/repository_storage_types.md
index 142bcc65561..3ee8673b297 100644
--- a/doc/administration/repository_storage_types.md
+++ b/doc/administration/repository_storage_types.md
@@ -82,19 +82,17 @@ by another folder with the next 2 characters. They are both stored in a special
> [Introduced](https://gitlab.com/gitlab-org/gitaly/issues/1606) in GitLab 12.1.
-Forks of public projects are deduplicated by creating a third repository, the object pool, containing the objects from the source project. Using `objects/info/alternates`, the source project and forks use the object pool for shared objects. Objects are moved from the source project to the object pool when housekeeping is run on the source project.
+Forks of public projects are deduplicated by creating a third repository, the
+object pool, containing the objects from the source project. Using
+`objects/info/alternates`, the source project and forks use the object pool for
+shared objects. Objects are moved from the source project to the object pool
+when housekeeping is run on the source project.
```ruby
# object pool paths
"@pools/#{hash[0..1]}/#{hash[2..3]}/#{hash}.git"
```
-Object pools can be disabled using the `object_pools` feature flag, and can be
-disabled for individual projects by executing
-`Feature.disable(:object_pools, Project.find(<id>))`. Disabling object pools
-will not change existing deduplicated forks, but will prevent new forks from
-being deduplicated.
-
DANGER: **Danger:**
Do not run `git prune` or `git gc` in pool repositories! This can
cause data loss in "real" repositories that depend on the pool in
diff --git a/doc/administration/smime_signing_email.md b/doc/administration/smime_signing_email.md
index 9f719088f25..b2e3bf8487b 100644
--- a/doc/administration/smime_signing_email.md
+++ b/doc/administration/smime_signing_email.md
@@ -22,7 +22,7 @@ email_smime:
```
- Both files must be provided PEM-encoded.
-- The key file must be unencrypted so that Gitlab can read it without user
+- The key file must be unencrypted so that Gitlab can read it without user
intervention.
NOTE: **Note:** Be mindful of the access levels for your private keys and visibility to
diff --git a/doc/administration/troubleshooting/elasticsearch.md b/doc/administration/troubleshooting/elasticsearch.md
index c4a7ba01fae..13b9c30b29d 100644
--- a/doc/administration/troubleshooting/elasticsearch.md
+++ b/doc/administration/troubleshooting/elasticsearch.md
@@ -266,9 +266,9 @@ ElasticSearch administrator.
Generally speaking, ensure:
-* The ElasticSearch server **is not** running on the same node as GitLab.
-* The ElasticSearch server have enough RAM and CPU cores.
-* That sharding **is** being used.
+- The ElasticSearch server **is not** running on the same node as GitLab.
+- The ElasticSearch server have enough RAM and CPU cores.
+- That sharding **is** being used.
Going into some more detail here, if ElasticSearch is running on the same server as GitLab, resource contention is **very** likely to occur. Ideally, ElasticSearch, which requires ample resources, should be running on its own server (maybe coupled with logstash and kibana).
diff --git a/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md
new file mode 100644
index 00000000000..0c5611aa6cd
--- /dev/null
+++ b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md
@@ -0,0 +1,1040 @@
+---
+type: reference
+---
+
+# GitLab Rails Console Cheat Sheet
+
+This is the GitLab Support Team's collection of information regarding the GitLab rails
+console, for use while troubleshooting. It is listed here for transparency,
+and it may be useful for users with experience with these tools. If you are currently
+having an issue with GitLab, it is highly recommended that you check your
+[support options](https://about.gitlab.com/support/) first, before attempting to use
+this information.
+
+CAUTION: **CAUTION:**
+Please note that some of these scripts could be damaging if not run correctly,
+or under the right conditions. We highly recommend running them under the
+guidance of a Support Engineer, or running them in a test environment with a
+backup of the instance ready to be restored, just in case.
+
+CAUTION: **CAUTION:**
+Please also note that as GitLab changes, changes to the code are inevitable,
+and so some scripts may not work as they once used to. These are not kept
+up-to-date as these scripts/commands were added as they were found/needed. As
+mentioned above, we recommend running these scripts under the supervision of a
+Support Engineer, who can also verify that they will continue to work as they
+should and, if needed, update the script for the latest version of GitLab.
+
+## Use the Rails Runner
+
+If the script you want to run is short, you can use the Rails Runner to avoid
+entering the rails console in the first place. Here's an example of its use:
+
+```bash
+gitlab-rails runner "RAILS_COMMAND"
+
+# Example with a 2-line script
+gitlab-rails runner "user = User.first; puts user.username"
+```
+
+## Enable debug logging on rails console
+
+```ruby
+Rails.logger.level = 0
+```
+
+## Enable debug logging for ActiveRecord (db issues)
+
+```ruby
+ActiveRecord::Base.logger = Logger.new(STDOUT)
+```
+
+## Temporarily Disable Timeout
+
+```ruby
+ActiveRecord::Base.connection.execute('SET statement_timeout TO 0')
+```
+
+## Find specific methods for an object
+
+```ruby
+Array.methods.select { |m| m.to_s.include? "sing" }
+Array.methods.grep(/sing/)
+```
+
+## Find method source
+
+Works for [non-instrumented methods](https://docs.gitlab.com/ce/development/instrumentation.html#checking-instrumented-methods):
+
+```ruby
+instance_of_object.method(:foo).source_location
+
+# Example for when we would call project.private?
+project.method(:private?).source_location
+```
+
+## Query an object
+
+```ruby
+o = Object.where('attribute like ?', 'ex')
+```
+
+## View all keys in cache
+
+```ruby
+Rails.cache.instance_variable_get(:@data).keys
+```
+
+## Rails console history
+
+```ruby
+puts Readline::HISTORY.to_a
+```
+
+## Profile a page
+
+```ruby
+# Before 11.6.0
+logger = Logger.new(STDOUT)
+admin_token = User.find_by_username('ADMIN_USERNAME').personal_access_tokens.first.token
+app.get("URL/?private_token=#{admin_token}")
+
+# From 11.6.0
+admin = User.find_by_username('ADMIN_USERNAME')
+url = "/url/goes/here"
+Gitlab::Profiler.with_user(admin) { app.get(url) }
+```
+
+## Using the GitLab profiler inside console (used as of 10.5)
+
+```ruby
+logger = Logger.new(STDOUT)
+admin = User.find_by_username('ADMIN_USERNAME')
+Gitlab::Profiler.profile('URL', logger: logger, user: admin)
+```
+
+## Time an operation
+
+```ruby
+# A single operation
+Benchmark.measure { <operation> }
+
+# A breakdown of multiple operations
+Benchmark.bm do |x|
+ x.report(:label1) { <operation_1> }
+ x.report(:label2) { <operation_2> }
+end
+```
+
+## Command Line
+
+### Check the GitLab version fast
+
+```bash
+grep -m 1 gitlab /opt/gitlab/version-manifest.txt
+```
+
+### Debugging SSH
+
+```bash
+GIT_SSH_COMMAND="ssh -vvv" git clone <repository>
+```
+
+### Debugging over HTTPS
+
+```bash
+GIT_CURL_VERBOSE=1 GIT_TRACE=1 git clone <repository>
+```
+
+## Projects
+
+### Find projects
+
+```ruby
+# A single project
+project = Project.find_by_full_path('PROJECT_PATH')
+
+# All projects in a particular namespace. Can be a username, a group
+# ('gitlab-org'), or even include subgroups ('gitlab-org/distribution')
+namespace = Namespace.find_by_full_path('NAMESPACE_PATH')
+projects = namespace.all_projects
+```
+
+### Clear a project's cache
+
+```ruby
+ProjectCacheWorker.perform_async(project.id)
+```
+
+### Expire the .exists? cache
+
+```ruby
+project.repository.expire_exists_cache
+```
+
+### Make all projects private
+
+```ruby
+Project.update_all(visibility_level: 0)
+```
+
+### Find & remove projects that are pending deletion
+
+```ruby
+#
+# This section will list all the projects which are pending deletion
+#
+projects = Project.where(pending_delete: true)
+projects.each do |p|
+ puts "Project name: #{p.id}"
+ puts "Project name: #{p.name}"
+ puts "Repository path: #{p.repository.storage_path}"
+end
+
+#
+# Assign a user (the root user will do)
+#
+user = User.find_by_username('root')
+
+#
+# For each project listed repeat these two commands
+#
+
+# Find the project, update the xxx-changeme values from above
+project = Project.find_by_full_path('group-changeme/project-changeme')
+
+# Delete the project
+::Projects::DestroyService.new(project, user, {}).execute
+```
+
+Next, run `sudo gitlab-rake gitlab:cleanup:repos` on the command line to finish.
+
+### Destroy a project
+
+```ruby
+project = Project.find_by_full_path('')
+user = User.find_by_username('')
+ProjectDestroyWorker.perform_async(project.id, user.id, {})
+# or ProjectDestroyWorker.new.perform(project.id, user.id, {})
+# or Projects::DestroyService.new(project, user).execute
+```
+
+### Remove fork relationship manually
+
+```ruby
+p = Project.find_by_full_path('')
+u = User.find_by_username('')
+::Projects::UnlinkForkService.new(p, u).execute
+```
+
+### Make a project read-only (can only be done in the console)
+
+```ruby
+# Make a project read-only
+project.repository_read_only = true; project.save
+
+# OR
+project.update!(repository_read_only: true)
+```
+
+### Bulk update service integration password for _all_ projects
+
+For example, change the Jira user's password for all projects that have the Jira
+integration active:
+
+```ruby
+p = Project.find_by_sql("SELECT p.id FROM projects p LEFT JOIN services s ON p.id = s.project_id WHERE s.type = 'JiraService' AND s.active = true")
+
+p.each do |project|
+ project.jira_service.update_attribute(:password, '<your-new-password>')
+end
+```
+
+### Identify un-indexed projects
+
+```ruby
+Project.find_each do |project|
+ puts "id #{project.id}: #{project.namespace.name.to_s}/#{project.name.to_s}" if project.index_status.nil?
+end
+```
+
+## Imports / Exports
+
+```ruby
+# Find the project and get the error
+p = Project.find_by_full_path('<username-or-group>/<project-name>')
+
+p.import_error
+
+# To finish the import on GitLab running version before 11.6
+p.import_finish
+
+# To finish the import on GitLab running version 11.6 or after
+p.import_state.mark_as_failed("Failed manually through console.")
+```
+
+### Rename imported repository
+
+In a specific situation, an imported repository needed to be renamed. The Support
+Team was informed of a backup restore that failed on a single repository, which created
+the project with an empty repository. The project was successfully restored to a dev
+instance, then exported, and imported into a new project under a different name.
+
+The Support Team was able to transfer the incorrectly named imported project into the
+correctly named empty project using the steps below.
+
+Move the new repository to the empty repository:
+
+```bash
+mv /var/opt/gitlab/git-data/repositories/<group>/<new-project> /var/opt/gitlab/git-data/repositories/<group>/<empty-project>
+```
+
+Make sure the permissions are correct:
+
+```bash
+chown -R git:git <path-to-directory>.git
+```
+
+Clear the cache:
+
+```bash
+sudo gitlab-rake cache:clear
+```
+
+## Repository
+
+### Search sequence of pushes to a repository
+
+If it seems that a commit has gone "missing", search the sequence of pushes to a repository.
+[This StackOverflow article](https://stackoverflow.com/questions/13468027/the-mystery-of-the-missing-commit-across-merges)
+describes how you can end up in this state without a force push.
+
+If you look at the output from the sample code below for the target branch, you will
+see a discontinuity in the from/to commits as you step through the output. Each new
+push should be "from" the "to" SHA of the previous push. When this discontinuity happens,
+you will see two pushes with the same "from" SHA:
+
+```ruby
+p = Project.find_with_namespace('u/p')
+p.events.code_push.last(100).each do |e|
+ printf "%-20.20s %8s...%8s (%s)\n", e.data[:ref], e.data[:before], e.data[:after], e.author.try(:username)
+end
+```
+
+GitLab 9.5 and above:
+
+```ruby
+p = Project.find_by_full_path('u/p')
+p.events.code_push.last(100).each do |e|
+ printf "%-20.20s %8s...%8s (%s)\n", e.push_event_payload[:ref], e.push_event_payload[:commit_from], e.push_event_payload[:commit_to], e.author.try(:username)
+end
+```
+
+## Mirrors
+
+### Find mirrors with "bad decrypt" errors
+
+```ruby
+total = 0
+bad = []
+ProjectImportData.find_each do |data|
+ begin
+ total += 1
+ data.credentials
+ rescue => e
+ bad << data
+ end
+end
+
+puts "Bad count: #{bad.count} / #{total}"
+bad.each do |repo|
+ puts Project.find(repo.project_id).full_path
+end; bad.count
+```
+
+### Transfer mirror users and tokens to a single service account
+
+Use case: If you have multiple users using their own GitHub credentials to set up
+repository mirroring, mirroring breaks when people leave the company. Use this
+script to migrate disparate mirroring users and tokens into a single service account:
+
+```ruby
+svc_user = User.find_by(username: 'ourServiceUser')
+token = 'githubAccessToken'
+
+Project.where(mirror: true).each do |project|
+ import_url = project.import_url
+
+ # The url we want is https://token@project/path.git
+ repo_url = if import_url.include?('@')
+ # Case 1: The url is something like https://23423432@project/path.git
+ import_url.split('@').last
+ elsif import_url.include?('//')
+ # Case 2: The url is something like https://project/path.git
+ import_url.split('//').last
+ end
+
+ next unless repo_url
+
+ final_url = "https://#{token}@#{repo_url}"
+
+ project.mirror_user = svc_user
+ project.import_url = final_url
+ project.username_only_import_url = final_url
+ project.save
+end
+```
+
+## Users
+
+### Finding users
+
+```ruby
+# By username
+user = User.find_by(username: '')
+
+# By primary email
+user = User.find_by(email: '')
+
+# By any email (primary or secondary)
+user = User.find_by_any_email('')
+
+# Admins
+User.admins
+admin = User.admins.first
+```
+
+### Block
+
+```ruby
+User.find_by_username().block!
+```
+
+### Unblock
+
+```ruby
+User.find_by_username().active
+```
+
+### Skip reconfirmation
+
+```ruby
+user = User.find_by_username ''
+user.skip_reconfirmation!
+```
+
+### Get an admin token
+
+```ruby
+# Get the first admin's first access token (no longer works on 11.9+. see: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22743)
+User.where(admin:true).first.personal_access_tokens.first.token
+
+# Get the first admin's private token (no longer works on 10.2+)
+User.where(admin:true).private_token
+```
+
+### Create personal access token
+
+```ruby
+personal_access_token = User.find(123).personal_access_tokens.create(
+ name: 'apitoken',
+ impersonation: false,
+ scopes: [:api]
+)
+
+personal_access_token.token
+```
+
+You might also want to manually set the token string:
+
+```ruby
+User.find(123).personal_access_tokens.create(
+ name: 'apitoken',
+ token_digest: Gitlab::CryptoHelper.sha256('some-token-string-here'),
+ impersonation: false,
+ scopes: [:api]
+)
+```
+
+### Disable 2FA on a user
+
+```ruby
+user = User.find_by_username('username')
+user.disable_two_factor!
+```
+
+### Active users & Historical users
+
+```ruby
+# Active users on the instance, now
+User.active.count
+
+# The historical max on the instance as of the past year
+::HistoricalData.max_historical_user_count
+```
+
+```bash
+# Using curl and jq (up to a max 100, see [pagination](https://docs.gitlab.com/ee/api/#pagination)
+curl --silent --header "Private-Token: ********************" "https://gitlab.example.com/api/v4/users?per_page=100&active" | jq --compact-output '.[] | [.id,.name,.username]'
+```
+
+### Block or Delete Users that have no projects or groups
+
+```ruby
+users = User.where('id NOT IN (select distinct(user_id) from project_authorizations)')
+
+# How many users will be removed?
+users.count
+
+# If that count looks sane:
+
+# You can either block the users:
+users.each { |user| user.block! }
+
+# Or you can delete them:
+ # need 'current user' (your user) for auditing purposes
+current_user = User.find_by(username: '<your username>')
+
+users.each do |user|
+ DeleteUserWorker.perform_async(current_user.id, user.id)
+end
+```
+
+### Block Users that have no recent activity
+
+```ruby
+days_inactive = 60
+inactive_users = User.active.where("last_activity_on <= ?", days_inactive.days.ago)
+
+inactive_users.each do |user|
+ puts "user '#{user.username}': #{user.last_activity_on}"
+ user.block!
+end
+```
+
+### Find Max permissions for project/group
+
+```ruby
+user = User.find_by_username 'username'
+project = Project.find_by_full_path 'group/project'
+user.max_member_access_for_project project.id
+```
+
+```ruby
+user = User.find_by_username 'username'
+group = Group.find_by_full_path 'group'
+user.max_member_access_for_group group.id
+```
+
+## Groups
+
+### Count unique users in a group and sub-groups
+
+```ruby
+group = Group.find_by_path_or_name("groupname")
+members = []
+for member in group.members_with_descendants
+ members.push(member.user_name)
+end
+
+members.uniq.length
+```
+
+```ruby
+group = Group.find_by_path_or_name("groupname")
+
+# Count users from subgroup and up (inherited)
+group.members_with_parents.count
+
+# Count users from parent group and down (specific grants)
+parent.members_with_descendants.count
+```
+
+### Delete a group
+
+```ruby
+GroupDestroyWorker.perform_async(group_id, user_id)
+```
+
+## LDAP
+
+### LDAP commands in the rails console
+
+TIP: **TIP:**
+Use the rails runner to avoid entering the rails console in the first place.
+This is great when only a single command (such as a UserSync or GroupSync)
+is needed.
+
+```ruby
+# Get debug output
+Rails.logger.level = Logger::DEBUG
+
+# Run a UserSync (normally performed once a day)
+LdapSyncWorker.new.perform
+
+# Run a GroupSync for all groups (9.3-)
+LdapGroupSyncWorker.new.perform
+
+# Run a GroupSync for all groups (9.3+)
+LdapAllGroupsSyncWorker.new.perform
+
+# Run a GroupSync for a single group (10.6-)
+group = Group.find_by(name: 'my_gitlab_group')
+EE::Gitlab::LDAP::Sync::Group.execute_all_providers(group)
+
+# Run a GroupSync for a single group (10.6+)
+group = Group.find_by(name: 'my_gitlab_group')
+EE::Gitlab::Auth::LDAP::Sync::Group.execute_all_providers(group)
+
+# Query an LDAP group directly (10.6-)
+adapter = Gitlab::LDAP::Adapter.new('ldapmain') # If `main` is the LDAP provider
+ldap_group = EE::Gitlab::LDAP::Group.find_by_cn('group_cn_here', adapter)
+ldap_group.member_dns
+ldap_group.member_uids
+
+# Query an LDAP group directly (10.6+)
+adapter = Gitlab::Auth::LDAP::Adapter.new('ldapmain') # If `main` is the LDAP provider
+ldap_group = EE::Gitlab::Auth::LDAP::Group.find_by_cn('group_cn_here', adapter)
+ldap_group.member_dns
+ldap_group.member_uids
+
+# Lookup a particular user (10.6+)
+# This could expose potential errors connecting to and/or querying LDAP that may seem to
+# fail silently in the GitLab UI
+adapter = Gitlab::Auth::LDAP::Adapter.new('ldapmain') # If `main` is the LDAP provider
+user = Gitlab::Auth::LDAP::Person.find_by_uid('<username>',adapter)
+
+# Query the LDAP server directly (10.6+)
+## For an example, see https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/ee/gitlab/auth/ldap/adapter.rb
+adapter = Gitlab::Auth::LDAP::Adapter.new('ldapmain')
+options = {
+ # the :base is required
+ # use adapter.config.base for the base or .group_base for the group_base
+ base: adapter.config.group_base,
+
+ # :filter is optional
+ # 'cn' looks for all "cn"s under :base
+ # '*' is the search string - here, it's a wildcard
+ filter: Net::LDAP::Filter.eq('cn', '*'),
+
+ # :attributes is optional
+ # the attributes we want to get returned
+ attributes: %w(dn cn memberuid member submember uniquemember memberof)
+}
+adapter.ldap_search(options)
+```
+
+### Update user accounts when the `dn` and email change
+
+The following will require that any accounts with the new email address are removed.
+Emails have to be unique in GitLab. This is expected to work but unverified as of yet:
+
+```ruby
+# Here's an example with a couple users.
+# Each entry will have to include the old username and the new email
+emails = {
+ 'ORIGINAL_USERNAME' => 'NEW_EMAIL_ADDRESS',
+ ...
+}
+
+emails.each do |username, email|
+ user = User.find_by_username(username)
+ user.email = email
+ user.skip_reconfirmation!
+ user.save!
+end
+
+# Run the UserSync to update the above users' data
+LdapSyncWorker.new.perform
+```
+
+## Routes
+
+### Remove redirecting routes
+
+See <https://gitlab.com/gitlab-org/gitlab-ce/issues/41758#note_54828133>.
+
+```ruby
+path = 'foo'
+conflicting_permanent_redirects = RedirectRoute.matching_path_and_descendants(path)
+
+# Check that conflicting_permanent_redirects is as expected
+conflicting_permanent_redirects.destroy_all
+```
+
+## Merge Requests
+
+### Find Merge Request
+
+```ruby
+m = project.merge_requests.find_by(iid: <IID>)
+m = MergeRequest.find_by_title('NEEDS UNIQUE TITLE!!!')
+```
+
+### Close a merge request properly (if merged but still marked as open)
+
+```ruby
+p = Project.find_by_full_path('')
+m = project.merge_requests.find_by(iid: )
+u = User.find_by_username('')
+MergeRequests::PostMergeService.new(p, u).execute(m)
+```
+
+### Rebase manually
+
+```ruby
+p = Project.find_by_full_path('')
+m = project.merge_requests.find_by(iid: )
+u = User.find_by_username('')
+MergeRequests::RebaseService.new(m.target_project, u).execute(m)
+```
+
+## CI
+
+### Cancel stuck pending pipelines
+
+See <https://gitlab.com/gitlab-com/support-forum/issues/2449#note_41929707>.
+
+```ruby
+Ci::Pipeline.where(project_id: p.id).where(status: 'pending').count
+Ci::Pipeline.where(project_id: p.id).where(status: 'pending').each {|p| p.cancel}
+Ci::Pipeline.where(project_id: p.id).where(status: 'pending').count
+```
+
+### Manually modify runner minutes
+
+```ruby
+Namespace.find_by_full_path("user/proj").namespace_statistics.update(shared_runners_seconds: 27360)
+```
+
+### Remove artifacts more than a week old
+
+```ruby
+### SELECTING THE BUILDS TO CLEAR
+# For a single project:
+project = Project.find_by_full_path('')
+builds_with_artifacts = project.builds.with_artifacts_archive
+
+# Prior to 10.6 the above line would be:
+# builds_with_artifacts = project.builds.with_artifacts
+
+# Instance-wide:
+builds_with_artifacts = Ci::Build.with_artifacts
+
+### CLEAR THEM OUT
+builds_to_clear = builds_with_artifacts.where("finished_at < ?", 1.week.ago)
+builds_to_clear.each do |build|
+ build.artifacts_expire_at = Time.now
+ build.erase_erasable_artifacts!
+end
+```
+
+### Find reason failure (for when build trace is empty) (Introduced in 10.3.0)
+
+See <https://gitlab.com/gitlab-org/gitlab-ce/issues/41111>.
+
+```ruby
+build = Ci::Build.find(78420)
+
+build.failure_reason
+
+build.dependencies.each do |d| { puts "status: #{d.status}, finished at: #{d.finished_at},
+ completed: #{d.complete?}, artifacts_expired: #{d.artifacts_expired?}, erased: #{d.erased?}" }
+```
+
+### Disable strict artifact checking (Introduced in GitLab 10.3.0)
+
+See <https://docs.gitlab.com/ee/administration/job_artifacts.html#validation-for-dependencies>.
+
+```ruby
+Feature.enable('ci_disable_validates_dependencies')
+```
+
+### Remove CI traces older than 6 months
+
+```ruby
+current_user = User.find_by_email('cindy@gitlap.com')
+Ci::Build.where("finished_at < ?", 6.months.ago.to_date).each {|b| puts b.id; b.erase(erased_by: current_user) if b.erasable?};nil
+```
+
+### Try CI service
+
+```ruby
+p = Project.find_by_full_path('')
+m = project.merge_requests.find_by(iid: )
+m.project.try(:ci_service)
+```
+
+### Disable AutoDevOps on Existing Projects
+
+```ruby
+Project.all.each do |p|
+ p.auto_devops_attributes={"enabled"=>"0"}
+ p.save
+end
+```
+
+## License
+
+### See license plan name (since v9.3.0-ee)
+
+```ruby
+License.current.plan
+```
+
+### Check if a project feature is available on the instance
+
+Features listed in <https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/app/models/license.rb>.
+
+```ruby
+License.current.feature_available?(:jira_dev_panel_integration)
+```
+
+### Check if a project feature is available in a project
+
+Features listed in <https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/app/models/license.rb>.
+
+```ruby
+p = Project.find_by_full_path('<group>/<project>')
+p.feature_available?(:jira_dev_panel_integration)
+```
+
+### Add a license through the console
+
+```ruby
+key = "<key>"
+license = License.new(data: key)
+license.save
+License.current # check to make sure it applied
+```
+
+## Unicorn
+
+From [Zendesk ticket #91083](https://gitlab.zendesk.com/agent/tickets/91083) (internal)
+
+### Poll unicorn requests by seconds
+
+```ruby
+require 'rubygems'
+require 'unicorn'
+
+# Usage for this program
+def usage
+ puts "ruby unicorn_status.rb <path to unix socket> <poll interval in seconds>"
+ puts "Polls the given Unix socket every interval in seconds. Will not allow you to drop below 3 second poll intervals."
+ puts "Example: /opt/gitlab/embedded/bin/ruby poll_unicorn.rb /var/opt/gitlab/gitlab-rails/sockets/gitlab.socket 10"
+end
+
+# Look for required args. Throw usage and exit if they don't exist.
+if ARGV.count < 2
+ usage
+ exit 1
+end
+
+# Get the socket and threshold values.
+socket = ARGV[0]
+threshold = (ARGV[1]).to_i
+
+# Check threshold - is it less than 3? If so, set to 3 seconds. Safety first!
+if threshold.to_i < 3
+ threshold = 3
+end
+
+# Check - does that socket exist?
+unless File.exist?(socket)
+ puts "Socket file not found: #{socket}"
+ exit 1
+end
+
+# Poll the given socket every THRESHOLD seconds as specified above.
+puts "Running infinite loop. Use CTRL+C to exit."
+puts "------------------------------------------"
+loop do
+ Raindrops::Linux.unix_listener_stats([socket]).each do |addr, stats|
+ puts DateTime.now.to_s + " Active: " + stats.active.to_s + " Queued: " + stats.queued.to_s
+ end
+ sleep threshold
+end
+```
+
+## Sidekiq
+
+### Size of a queue
+
+```ruby
+Sidekiq::Queue.new('background_migration').size
+```
+
+### Kill a worker's Sidekiq jobs
+
+```ruby
+queue = Sidekiq::Queue.new('repository_import')
+queue.each { |job| job.delete if <condition>}
+```
+
+### Enable debug logging of Sidekiq
+
+```ruby
+gitlab_rails['env'] = {
+ 'SIDEKIQ_LOG_ARGUMENTS' => "1"
+}
+```
+
+Then `gitlab-ctl reconfigure; gitlab-ctl restart sidekiq`. The Sidekiq logs will now include additional data for troubleshooting.
+
+### Sidekiq kill signals
+
+See <https://github.com/mperham/sidekiq/wiki/Signals#ttin>.
+
+## Redis
+
+### Connect to redis (omnibus)
+
+```sh
+/opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket
+```
+
+### Connect to redis (HA)
+
+```sh
+/opt/gitlab/embedded/bin/redis-cli -h <host ip> -a <password>
+```
+
+## LFS
+
+### Get info about LFS objects and associated project
+
+```ruby
+o=LfsObject.find_by(oid: "<oid>")
+p=Project.find(LfsObjectsProject.find_by_lfs_object_id(o.id).project_id)
+```
+
+You can then delete these records from the database with:
+
+```ruby
+LfsObjectsProject.find_by_lfs_object_id(o.id).destroy
+o.destroy
+```
+
+You would also want to combine this with deleting the LFS file in the LFS storage
+area on disk. It remains to be seen exactly how or whether the deletion is useful, however.
+
+## Decryption Problems
+
+### Bad Decrypt Script (for encrypted variables)
+
+See <https://gitlab.com/snippets/1730735/raw>.
+
+This script will go through all the encrypted variables and count how many are not able
+to be decrypted. Might be helpful to run on multiple nodes to see which `gitlab-secrets.json`
+file is most up to date:
+
+```bash
+wget -O /tmp/bad-decrypt.rb https://gitlab.com/snippets/1730735/raw
+gitlab-rails runner /tmp/bad-decrypt.rb
+```
+
+If `ProjectImportData Bad count:` is detected and the decision is made to delete the
+encrypted credentials to allow manual reentry:
+
+```ruby
+ # Find the ids of the corrupt ProjectImportData objects
+ total = 0
+ bad = []
+ ProjectImportData.find_each do |data|
+ begin
+ total += 1
+ data.credentials
+ rescue => e
+ bad << data.id
+ end
+ end
+
+ puts "Bad count: #{bad.count} / #{total}"
+
+ # See the bad ProjectImportData ids
+ bad
+
+ # Remove the corrupted credentials
+ import_data = ProjectImportData.where(id: bad)
+ import_data.each do |data|
+ data.update_columns({ encrypted_credentials: nil, encrypted_credentials_iv: nil, encrypted_credentials_salt: nil})
+ end
+```
+
+If `User OTP Secret Bad count:` is detected. For each user listed disable/enable
+two-factor authentication.
+
+### Decrypt Script for encrypted tokens
+
+This script will search for all encrypted tokens that are causing decryption errors,
+and update or reset as needed:
+
+```bash
+wget -O /tmp/encrypted-tokens.rb https://gitlab.com/snippets/1876342/raw
+gitlab-rails runner /tmp/encrypted-tokens.rb
+```
+
+## Geo
+
+### Artifacts
+
+#### Find failed artifacts
+
+```ruby
+Geo::JobArtifactRegistry.failed
+```
+
+#### Download artifact
+
+```ruby
+Gitlab::Geo::JobArtifactDownloader.new(:job_artifact, <artifact_id>).execute
+```
+
+#### Get a count of the synced artifacts
+
+```ruby
+Geo::JobArtifactRegistry.synced.count
+```
+
+#### Find `ID` of synced artifacts that are missing on primary
+
+```ruby
+Geo::JobArtifactRegistry.synced.missing_on_primary.pluck(:artifact_id)
+```
+
+### Repository verification failures
+
+#### Get the number of verification failed repositories
+
+```ruby
+Geo::ProjectRegistryFinder.new.count_verification_failed_repositories
+```
+
+#### Find the verification failed repositories
+
+```ruby
+Geo::ProjectRegistry.verification_failed_repos
+```
+
+### Find repositories that failed to sync
+
+```ruby
+Geo::ProjectRegistryFinder.new.find_failed_project_registries('repository')
+```
+
+### Resync repositories
+
+#### Queue up all repositories for resync. Sidekiq will handle each sync
+
+```ruby
+Geo::ProjectRegistry.update_all(resync_repository: true, resync_wiki: true)
+```
+
+#### Sync individual repository now
+
+```ruby
+project = Project.find_by_full_path('<group/project>')
+
+Geo::RepositorySyncService.new(project).execute
+```
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/administration/troubleshooting/linux_cheat_sheet.md b/doc/administration/troubleshooting/linux_cheat_sheet.md
new file mode 100644
index 00000000000..2bbb498f020
--- /dev/null
+++ b/doc/administration/troubleshooting/linux_cheat_sheet.md
@@ -0,0 +1,339 @@
+---
+type: reference
+---
+
+# Linux Cheat Sheet
+
+This is the GitLab Support Team's collection of information regarding Linux, that they
+sometimes use while troubleshooting. It is listed here for transparency,
+and it may be useful for users with experience with Linux. If you are currently
+having an issue with GitLab, you may want to check your [support options](https://about.gitlab.com/support/)
+first, before attempting to use this information.
+
+CAUTION: **CAUTION:**
+If you are administering GitLab you are expected to know these commands for your distribution
+of choice. If you are a GitLab Support Engineer, consider this a cross-reference to
+translate `yum` -> `apt-get` and the like.
+
+Note: **Note:**
+Most of the commands below have not been labeled as to which distribution they work
+on. Contributions are welcome to help add them.
+
+## System Commands
+
+### Distro Information
+
+```bash
+# Debian/Ubuntu
+uname -a
+lsb_release -a
+
+# CentOS/RedHat
+cat /etc/centos-release
+cat /etc/redhat-release
+
+# This will provide a lot more information
+cat /etc/os-release
+```
+
+### Shut down or Reboot
+
+```bash
+shutdown -h now
+reboot
+```
+
+### Permissions
+
+```bash
+# change the user:group ownership of a file/dir
+chown root:git <file_or_dir>
+
+# make a file executable
+chmod u+x <file>
+```
+
+### Files & Dirs
+
+```bash
+# create a new directory and all subdirectories
+mkdir -p dir/dir2/dir3
+
+# Send a command's output to file.txt, no STDOUT
+ls > file.txt
+
+# Send a command's output to file.txt AND see it in STDOUT
+ls | tee /tmp/file.txt
+
+# Search and Replace within a file
+sed -i 's/original-text/new-text/g' <filename>
+```
+
+### See all set environment variables
+
+```bash
+env
+```
+
+## Searching
+
+### File names
+
+```bash
+# search for a file in a filesystem
+find . -name 'filename.rb' -print
+
+# locate a file
+locate <filename>
+
+# see command history
+history
+
+# search CLI history
+<ctrl>-R
+```
+
+### File contents
+
+```bash
+# -B/A = show 2 lines before/after search_term
+grep -B 2 -A 2 search_term <filename>
+
+# -<number> shows both before and after
+grep -2 search_term <filename>
+
+# Search on all files in directory (recursively)
+grep -r search_term <directory>
+
+# search through *.gz files is the same except with zgrep
+zgrep search_term <filename>
+
+# Fast grep printing lines containing a string pattern
+fgrep -R string_pattern <filename or directory>
+```
+
+### CLI
+
+```bash
+# View command history
+history
+
+# Run last command that started with 'his' (3 letters min)
+!his
+
+# Search through command history
+<ctrl>-R
+
+# Execute last command with sudo
+sudo !!
+```
+
+## Managing resources
+
+### Memory, Disk, & CPU usage
+
+```bash
+# disk space info. The '-h' gives the data in human-readable values
+df -h
+
+# size of each file/dir and its contents in the current dir
+du -hd 1
+
+# or alternative
+du -h --max-depth=1
+
+# find files greater than certain size(k, M, G) and list them in order
+# get rid of the + for exact, - for less than
+find / -type f -size +100M -print0 | xargs -0 du -hs | sort -h
+
+# Find free memory on a system
+free -m
+
+# Find what processes are using memory/CPU and organize by it
+# Load average is 1/CPU for 1, 5, and 15 minutes
+top -o %MEM
+top -o %CPU
+```
+
+### Strace
+
+```bash
+# strace a process
+strace -tt -T -f -y -s 1024 -p <pid>
+
+# -tt print timestamps with microsecond accuracy
+
+# -T print the time spent in each syscall
+
+# -f also trace any child processes that forked
+
+# -y print the path associated with file handles
+
+# -s max string length to print for an event
+
+# -o output file
+
+# run strace on all unicorn processes
+ps auwx | grep unicorn | awk '{ print " -p " $2}' | xargs strace -tt -T -f -y -s 1024 -o /tmp/unicorn.txt
+```
+
+See the [strace zine](https://wizardzines.com/zines/strace/) for a quick walkthrough.
+
+Brendan Gregg has a more detailed explanation of [how to use strace](http://www.brendangregg.com/blog/2014-05-11/strace-wow-much-syscall.html).
+
+Be aware that strace can have major impacts to system performance when it is running.
+
+### The Strace Parser tool
+
+Our [strace-parser tool](https://gitlab.com/wchandler/strace-parser) can be used to
+provide a high level summary of the `strace` output. It is similar to `strace -C`,
+but provides much more detailed statistics.
+
+MacOS and Linux binaries [are available](https://gitlab.com/gitlab-com/support/toolbox/strace-parser/-/tags),
+or you can build it from source if you have the Rust compiler.
+
+#### How to use the tool
+
+First run the tool with no arguments other than the strace output file name to get
+a summary of the top processes sorted by time spent actively performing tasks. You
+can also sort based on total time, # of syscalls made, PID #, and # of child processes
+using the `-S` or `--sort` flag. The number of results defaults to 25 processes, but
+can be changed using the `-c`/`--count` option. See `--help` for full details.
+
+```sh
+$ ./strace-parser strace.txt
+
+Top 25 PIDs
+-----------
+
+ pid active (ms) wait (ms) total (ms) % active syscalls
+ ---------- ---------- --------- --------- --------- ---------
+ 8795 689.072 45773.832 46462.902 16.89% 23018
+ 13408 679.432 55910.891 56590.320 16.65% 28593
+ 6423 554.822 13175.485 13730.308 13.60% 13735
+...
+```
+
+Based on the summary, you can then view the details of syscalls made by one or more
+procsses using the `-p`/`--pid` for a specific process, or `-s`/`--stats` flags for
+a sorted list. `--stats` takes the same sorting and count options as summary.
+
+```sh
+$ ./strace-parse strace.text -p 6423
+
+PID 6423
+13735 syscalls, active time: 554.822ms, total time: 13730.308ms
+
+ syscall count total max avg min errors
+ (ms) (ms) (ms) (ms)
+ --------------- -------- ---------- ---------- ---------- ---------- --------
+ epoll_wait 628 13175.485 21.259 20.980 0.020
+ clock_gettime 7326 199.500 0.249 0.027 0.013
+ stat 2101 110.768 19.056 0.053 0.017 ENOENT: 2076
+ ...
+ ---------------
+
+ Parent PID: 495
+ Child PIDs: 8383, 8418, 8419, 8420, 8421
+
+ Slowest file access times for PID 6423:
+
+ open (ms) timestamp error file name
+ ----------- --------------- --------------- ----------
+ 29.818 10:53:11.528954 /srv/gitlab-data/builds/2018_08/6174/954448.log
+ 12.309 10:53:46.708274 /srv/gitlab-data/builds/2018_08/5342/954186.log
+ 0.039 10:53:49.222110 /opt/gitlab/embedded/service/gitlab-rails/app/views/events/event/_note.html.haml
+ 0.035 10:53:49.125115 /opt/gitlab/embedded/service/gitlab-rails/app/views/events/event/_push.html.haml
+ ...
+```
+
+In the example above, we can see that file opening times on `/srv/gitlab-data` are
+extremely slow, about 100X slower than `/opt/gitlab`.
+
+When nothing stands out in the results, a good way to get more context is to run `strace`
+on your own GitLab instance while performing the action performed by the customer,
+then compare summaries of both results and dive into the differences.
+
+#### Stats for the open syscall
+
+Rough numbers for calls to `open` and `openat` (used to access files) on various configurations.
+Slow storage can cause the dreaded `DeadlineExceeded` error in Gitaly.
+
+Also [see this entry](https://docs.gitlab.com/ee/administration/operations/filesystem_benchmarking.html)
+in the handbook for quick tests customers can perform to check their filesystem performance.
+
+Keep in mind that timing information from `strace` is often somewhat inaccurate, so
+small differences should not be considered significant.
+
+|Setup | access times |
+|:--------------|:--------------|
+| EFS | 10 - 30ms |
+| Local Storage | 0.01 - 1ms |
+
+## Networking
+
+### Ports
+
+```bash
+# Find the programs that are listening on ports
+netstat -plnt
+ss -plnt
+lsof -i -P | grep <port>
+```
+
+### Internet/DNS
+
+```bash
+# Show domain IP address
+dig +short example.com
+nslookup example.com
+
+# Check DNS using specific nameserver
+# 8.8.8.8 = google, 1.1.1.1 = cloudflare, 208.67.222.222 = opendns
+dig @8.8.8.8 example.com
+nslookup example.com 1.1.1.1
+
+# Find host provider
+whois <ip_address> | grep -i "orgname\|netname"
+
+# Curl headers with redirect
+curl --head --location https://example.com
+```
+
+## Package Management
+
+```bash
+# Debian/Ubuntu
+
+# List packages
+dpkg -l
+apt list --installed
+
+# Find an installed package
+dpkg -l | grep <package>
+apt list --installed | grep <package>
+
+# Install a package
+dpkg -i <package_name>.deb
+apt-get install <package>
+apt install <package>
+
+# CentOS/RedHat
+
+# Install a package
+yum install <package>
+dnf install <package> # RHEL/CentOS 8+
+
+rpm -ivh <package_name>.rpm
+
+# Find an installed package
+rpm -qa | grep <package>
+```
+
+## Logs
+
+```bash
+# Print last lines in log file where 'n'
+# is the number of lines to print
+tail -n /path/to/log/file
+```
diff --git a/doc/administration/troubleshooting/sidekiq.md b/doc/administration/troubleshooting/sidekiq.md
index 9b016c64e29..c41edb5dbfc 100644
--- a/doc/administration/troubleshooting/sidekiq.md
+++ b/doc/administration/troubleshooting/sidekiq.md
@@ -96,8 +96,9 @@ corresponding Ruby code where this is happening.
`gdb` can be another effective tool for debugging Sidekiq. It gives you a little
more interactive way to look at each thread and see what's causing problems.
-> **Note:** Attaching to a process with `gdb` will suspends the normal operation
- of the process (Sidekiq will not process jobs while `gdb` is attached).
+NOTE: **Note:**
+Attaching to a process with `gdb` will suspends the normal operation
+of the process (Sidekiq will not process jobs while `gdb` is attached).
Start by attaching to the Sidekiq PID:
@@ -280,10 +281,10 @@ has number of drawbacks, as mentioned in [Why Ruby’s Timeout is dangerous (and
> This is where the implications get interesting, and terrifying. This means that an exception can get raised:
>
-> * during a network request (ok, as long as the surrounding code is prepared to catch Timeout::Error)
-> * during the cleanup for the network request
-> * during a rescue block
-> * while creating an object to save to the database afterwards
-> * in any of your code, regardless of whether it could have possibly raised an exception before
+> - during a network request (ok, as long as the surrounding code is prepared to catch Timeout::Error)
+> - during the cleanup for the network request
+> - during a rescue block
+> - while creating an object to save to the database afterwards
+> - in any of your code, regardless of whether it could have possibly raised an exception before
>
> Nobody writes code to defend against an exception being raised on literally any line. That’s not even possible. So Thread.raise is basically like a sneak attack on your code that could result in almost anything. It would probably be okay if it were pure-functional code that did not modify any state. But this is Ruby, so that’s unlikely :)
diff --git a/doc/administration/troubleshooting/test_environments.md b/doc/administration/troubleshooting/test_environments.md
new file mode 100644
index 00000000000..075effc5dc3
--- /dev/null
+++ b/doc/administration/troubleshooting/test_environments.md
@@ -0,0 +1,126 @@
+---
+type: reference
+---
+
+# Apps for a Testing Environment
+
+This is the GitLab Support Team's collection of information regarding testing environments,
+for use while troubleshooting. It is listed here for transparency, and it may be useful
+for users with experience with these tools. If you are currently having an issue with
+GitLab, you may want to check your [support options](https://about.gitlab.com/support/)
+first, before attempting to use this information.
+
+NOTE: **Note:**
+This page was initially written for Support Engineers, so some of the links
+are only available internally at GitLab.
+
+## Docker
+
+The following were tested on docker containers running in the cloud. Support Engineers,
+please see [these docs](https://gitlab.com/gitlab-com/dev-resources/tree/master/dev-resources#running-docker-containers)
+on how to run Docker containers on `dev-resources`. Other setups haven't been tested,
+but contributions are welcome.
+
+### GitLab
+
+Please see [our Docker test environment docs](https://docs.gitlab.com/ee/install/digitaloceandocker.html#create-new-gitlab-container)
+for how to run GitLab on Docker. When spinning this up with `docker-machine`, ensure
+you change a few things:
+
+1. Update the name of the `docker-machine` host. You can see a list of hosts
+ with `docker-machine ls`.
+1. Expose the necessary ports using the `-p` flag. Docker normally doesn't
+ allow access to any ports it uses outside of the container, so they must be
+ explicitly exposed.
+1. Add any necessary `gitlab.rb` configuration to the
+ `GITLAB_OMNIBUS_CONFIG` variable.
+
+For example, when the `docker-machine` host we want to use is `do-docker`:
+
+```sh
+docker run --detach --name gitlab \
+--env GITLAB_OMNIBUS_CONFIG="external_url 'http://$(docker-machine ip do-docker)'; gitlab_rails['gitlab_shell_ssh_port'] = 2222;" \
+--hostname $(docker-machine ip do-docker) \
+-p 80:80 -p 2222:22 \
+gitlab/gitlab-ee:11.5.3-ee.0
+```
+
+### SAML
+
+#### SAML for Authentication
+
+We can use the [test-saml-idp Docker image](https://hub.docker.com/r/jamedjo/test-saml-idp)
+to do the work for us:
+
+```sh
+docker run --name gitlab_saml -p 8080:8080 -p 8443:8443 \
+-e SIMPLESAMLPHP_SP_ENTITY_ID=<GITLAB_IP_OR_DOMAIN> \
+-e SIMPLESAMLPHP_SP_ASSERTION_CONSUMER_SERVICE=<GITLAB_IP_OR_DOMAIN>/users/auth/saml/callback \
+-d jamedjo/test-saml-idp
+```
+
+The following will also need to go in your `/etc/gitlab/gitlab.rb`. See [our SAML docs](https://docs.gitlab.com/ee/integration/saml.html)
+for more, as well as the list of [default usernames, passwords, and emails](https://hub.docker.com/r/jamedjo/test-saml-idp/#usage).
+
+```ruby
+gitlab_rails['omniauth_enabled'] = true
+gitlab_rails['omniauth_allow_single_sign_on'] = ['saml']
+gitlab_rails['omniauth_sync_email_from_provider'] = 'saml'
+gitlab_rails['omniauth_sync_profile_from_provider'] = ['saml']
+gitlab_rails['omniauth_sync_profile_attributes'] = ['email']
+gitlab_rails['omniauth_auto_sign_in_with_provider'] = 'saml'
+gitlab_rails['omniauth_block_auto_created_users'] = false
+gitlab_rails['omniauth_auto_link_ldap_user'] = false
+gitlab_rails['omniauth_auto_link_saml_user'] = true
+gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "saml",
+ "label" => "SAML",
+ "args" => {
+ assertion_consumer_service_url: '<GITLAB_IP_OR_DOMAIN>/users/auth/saml/callback',
+ idp_cert_fingerprint: '119b9e027959cdb7c662cfd075d9e2ef384e445f',
+ idp_sso_target_url: '<SAML_IP_OR_DOMAIN>:8080/simplesaml/saml2/idp/SSOService.php',
+ issuer: '<GITLAB_IP_OR_DOMAIN>',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
+ }
+ }
+]
+```
+
+#### GroupSAML for GitLab.com
+
+See [the GDK SAML documentation](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/saml.md).
+
+### ElasticSearch
+
+```sh
+docker run -d --name elasticsearch \
+-p 9200:9200 -p 9300:9300 \
+-e "discovery.type=single-node" \
+docker.elastic.co/elasticsearch/elasticsearch:5.5.1
+```
+
+Then confirm it works in the browser at `curl http://<IP_ADDRESS>:9200/_cat/health`.
+ElasticSearch's default username is `elastic` and password is `changeme`.
+
+### PlantUML
+
+See [our PlantUML docs](../integration/plantuml.md#docker)
+on running PlantUML in Docker.
+
+### Jira
+
+```sh
+docker run -d -p 8081:8080 cptactionhank/atlassian-jira:latest
+```
+
+Then go to `<IP_ADDRESS>:8081` in the browser to set it up. This requires a
+Jira license.
+
+### Grafana
+
+```sh
+docker run -d --name grafana -e "GF_SECURITY_ADMIN_PASSWORD=gitlab" -p 3000:3000 grafana/grafana
+```
+
+Access it at `<IP_ADDRESS>:3000`.
diff --git a/doc/api/README.md b/doc/api/README.md
index 33394d46a2d..036b46da6e5 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -396,7 +396,7 @@ GET /api/v4/projects/diaspora%2Fdiaspora
NOTE: **Note:**
A project's **path** is not necessarily the same as its **name**. A
-project's path can found in the project's URL or in the project's settings
+project's path can be found in the project's URL or in the project's settings
under **General > Advanced > Change path**.
## Branches and tags name encoding
@@ -422,7 +422,7 @@ We can call the API with `array` and `hash` types parameters as shown below:
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
-d "import_sources[]=github" \
-d "import_sources[]=bitbucket" \
-"https://gitlab.example.com/api/v4/some_endpoint
+https://gitlab.example.com/api/v4/some_endpoint
```
### `hash`
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/dependencies.md b/doc/api/dependencies.md
index 5767d3572dd..5296d4e316f 100644
--- a/doc/api/dependencies.md
+++ b/doc/api/dependencies.md
@@ -5,7 +5,8 @@ This API is in an alpha stage and considered unstable.
The response payload may be subject to change or breakage
across GitLab releases.
-Every call to this endpoint requires authentication. To perform this call, user should be authorized to read
+Every call to this endpoint requires authentication. To perform this call, user should be authorized to read repository.
+To see vulnerabilities in response, user should be authorized to read
[Project Security Dashboard](../user/application_security/security_dashboard/index.md#project-security-dashboard).
## List project dependencies
@@ -17,8 +18,8 @@ supported by Gemnasium.
```
GET /projects/:id/dependencies
-GET /projects/:id/vulnerabilities?package_manager=maven
-GET /projects/:id/vulnerabilities?package_manager=yarn,bundler
+GET /projects/:id/dependencies?package_manager=maven
+GET /projects/:id/dependencies?package_manager=yarn,bundler
```
| Attribute | Type | Required | Description |
@@ -38,13 +39,18 @@ Example response:
"name": "rails",
"version": "5.0.1",
"package_manager": "bundler",
- "dependency_file_path": "Gemfile.lock"
+ "dependency_file_path": "Gemfile.lock",
+ "vulnerabilities": [{
+ "name": "DDoS",
+ "severity": "unknown"
+ }]
},
{
"name": "hanami",
"version": "1.3.1",
"package_manager": "bundler",
- "dependency_file_path": "Gemfile.lock"
+ "dependency_file_path": "Gemfile.lock",
+ "vulnerabilities": []
}
]
```
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/events.md b/doc/api/events.md
index 6dca8e52f69..1cd7047b867 100644
--- a/doc/api/events.md
+++ b/doc/api/events.md
@@ -70,7 +70,7 @@ Parameters:
Example request:
-```
+```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/events?target_type=issue&action=created&after=2017-01-31&before=2017-03-01
```
@@ -275,7 +275,7 @@ Parameters:
Example request:
-```
+```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/:project_id/events?target_type=issue&action=created&after=2017-01-31&before=2017-03-01
```
@@ -343,8 +343,8 @@ Example response:
"username": "root",
"id": 1,
"state": "active",
- "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
- "web_url": "http://localhost:3000/root"
+ "avatar_url": "https://gitlab.example.com/uploads/user/avatar/1/fox_avatar.png",
+ "web_url": "https://gitlab.example.com/root"
},
"created_at": "2015-12-04T10:33:56.698Z",
"system": false,
@@ -357,8 +357,8 @@ Example response:
"username": "root",
"id": 1,
"state": "active",
- "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png",
- "web_url": "http://localhost:3000/root"
+ "avatar_url": "https://gitlab.example.com/uploads/user/avatar/1/fox_avatar.png",
+ "web_url": "https://gitlab.example.com/root"
},
"author_username": "root"
}
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index d99a4c37d72..e87270f884a 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -516,4 +516,3 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `username` | String! | |
| `avatarUrl` | String! | |
| `webUrl` | String! | |
-
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 0d500f783aa..8b13462b887 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -156,8 +156,10 @@ Parameters:
| `with_issues_enabled` | boolean | no | Limit by projects with issues feature enabled. Default is `false` |
| `with_merge_requests_enabled` | boolean | no | Limit by projects with merge requests feature enabled. Default is `false` |
| `with_shared` | boolean | no | Include projects shared to this group. Default is `true` |
-| `include_subgroups` | boolean | no | Include projects in subgroups of this group. Default is `false` |
+| `include_subgroups` | boolean | no | Include projects in subgroups of this group. Default is `false` |
+| `min_access_level` | integer | no | Limit to projects where current user has at least this [access level](members.md) |
| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) |
+| `with_security_reports` | boolean | no | **(ULTIMATE)** Return only projects that have security reports artifacts present in any of their builds. This means "projects with security reports enabled". Default is `false` |
Example response:
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 4f2b4a966c9..a89a6e7c5cc 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -49,7 +49,7 @@ GET /issues?confidential=true
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `weight` **(STARTER)** | integer | no | Return issues with the specified `weight`. `None` returns issues with no weight assigned. `Any` returns issues with a weight assigned. |
| `iids[]` | integer array | no | Return only the issues having the given `iid` |
-| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `order_by` | string | no | Return issues ordered by `created_at`, `updated_at`, `priority`, `due_date`, `relative_position`, `label_priority`, `milestone_due`, `popularity`, `weight` fields. Default is `created_at` |
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search issues against their `title` and `description` |
| `in` | string | no | Modify the scope of the `search` attribute. `title`, `description`, or a string joining them with comma. Default is `title,description` |
@@ -198,7 +198,7 @@ GET /groups/:id/issues?confidential=true
| `assignee_username` | string array | no | Return issues assigned to the given `username`. Similar to `assignee_id` and mutually exclusive with `assignee_id`. In CE version `assignee_username` array should only contain a single value or an invalid param error will be returned otherwise. |
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `weight` **(STARTER)** | integer | no | Return issues with the specified `weight`. `None` returns issues with no weight assigned. `Any` returns issues with a weight assigned. |
-| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `order_by` | string | no | Return issues ordered by `created_at`, `updated_at`, `priority`, `due_date`, `relative_position`, `label_priority`, `milestone_due`, `popularity`, `weight` fields. Default is `created_at` |
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search group issues against their `title` and `description` |
| `created_after` | datetime | no | Return issues created on or after the given time |
@@ -284,7 +284,6 @@ Example response:
"award_emoji":"http://example.com/api/v4/projects/4/issues/41/award_emoji",
"project":"http://example.com/api/v4/projects/4"
},
- "subscribed": false,
"task_completion_status":{
"count":0,
"completed_count":0
@@ -347,7 +346,7 @@ GET /projects/:id/issues?confidential=true
| `assignee_username` | string array | no | Return issues assigned to the given `username`. Similar to `assignee_id` and mutually exclusive with `assignee_id`. In CE version `assignee_username` array should only contain a single value or an invalid param error will be returned otherwise. |
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `weight` **(STARTER)** | integer | no | Return issues with the specified `weight`. `None` returns issues with no weight assigned. `Any` returns issues with a weight assigned. |
-| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `order_by` | string | no | Return issues ordered by `created_at`, `updated_at`, `priority`, `due_date`, `relative_position`, `label_priority`, `milestone_due`, `popularity`, `weight` fields. Default is `created_at` |
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search project issues against their `title` and `description` |
| `created_after` | datetime | no | Return issues created on or after the given time |
@@ -574,6 +573,18 @@ the `weight` parameter:
}
```
+Users on GitLab [Ultimate](https://about.gitlab.com/pricing/) will additionally see
+the `epic_iid` property:
+
+```json
+{
+ "project_id" : 4,
+ "description" : "Omnis vero earum sunt corporis dolor et placeat.",
+ "epic_iid" : 42,
+ ...
+}
+```
+
**Note**: `assignee` column is deprecated, now we show it as a single-sized array `assignees` to conform to the GitLab EE API.
**Note**: The `closed_by` attribute was [introduced in GitLab 10.6][ce-17042]. This value will only be present for issues which were closed after GitLab 10.6 and when the user account that closed the issue still exists.
@@ -591,7 +602,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 |
@@ -692,7 +703,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/labels.md b/doc/api/labels.md
index 5db0edcf14d..9692cc8b710 100644
--- a/doc/api/labels.md
+++ b/doc/api/labels.md
@@ -137,8 +137,9 @@ DELETE /projects/:id/labels
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `name` | string | yes | The name of the label |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `label_id` | integer | yes (or `name`) | The id of the existing label |
+| `name` | string | yes (or `label_id`) | The name of the existing label |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/labels?name=bug"
@@ -156,7 +157,8 @@ PUT /projects/:id/labels
| Attribute | Type | Required | Description |
| --------------- | ------- | --------------------------------- | ------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `name` | string | yes | The name of the existing label |
+| `label_id` | integer | yes (or `name`) | The id of the existing label |
+| `name` | string | yes (or `label_id`) | The name of the existing label |
| `new_name` | string | yes if `color` is not provided | The new name of the label |
| `color` | string | yes if `new_name` is not provided | The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the [CSS color names](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords) |
| `description` | string | no | The new description of the label |
@@ -184,6 +186,40 @@ Example response:
}
```
+## Promote a project label to a group label
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/25218) in GitLab 12.3.
+
+Promotes a project label to a group label.
+
+```
+PUT /projects/:id/labels/promote
+```
+
+| Attribute | Type | Required | Description |
+| --------------- | ------- | --------------------------------- | ------------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `name` | string | yes | The name of the existing label |
+
+```bash
+curl --request PUT --data "name=documentation" --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/labels/promote"
+```
+
+Example response:
+
+```json
+{
+ "id" : 8,
+ "name" : "documentation",
+ "color" : "#8E44AD",
+ "description": "Documentation",
+ "open_issues_count": 1,
+ "closed_issues_count": 0,
+ "open_merge_requests_count": 2,
+ "subscribed": false
+}
+```
+
## Subscribe to a label
Subscribes the authenticated user to a label to receive notifications.
diff --git a/doc/api/merge_request_approvals.md b/doc/api/merge_request_approvals.md
index bf83bfd7e6a..d19f11ba1d4 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,341 @@ PUT /projects/:id/merge_requests/:merge_request_iid/approvers
}
```
+### Get the approval state of merge requests
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/13712) in [GitLab Starter](https://about.gitlab.com/pricing/) 12.3.
+
+You can request information about a merge request's approval state by using the following endpoint:
+
+```
+GET /projects/:id/merge_requests/:merge_request_iid/approval_state
+```
+
+The `approval_rules_overwritten` will be `true` if the merge request level rules
+are created for the merge request. If there's none, it'll be `false`.
+
+This includes additional information about the users who have already approved
+(`approved_by`) and whether a rule is already approved (`approved`).
+
+**Parameters:**
+
+| Attribute | Type | Required | Description |
+|----------------------|---------|----------|---------------------|
+| `id` | integer | yes | The ID of a project |
+| `merge_request_iid` | integer | yes | The IID of MR |
+
+```json
+{
+ "approval_rules_overwritten": true,
+ "rules": [
+ {
+ "id": 1,
+ "name": "Ruby",
+ "rule_type": "regular",
+ "eligible_approvers": [
+ {
+ "id": 4,
+ "name": "John Doe",
+ "username": "jdoe",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/jdoe"
+ }
+ ],
+ "approvals_required": 2,
+ "users": [
+ {
+ "id": 4,
+ "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": [],
+ "contains_hidden_groups": false,
+ "approved_by": [
+ {
+ "id": 4,
+ "name": "John Doe",
+ "username": "jdoe",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/jdoe"
+ }
+ ],
+ "source_rule": null,
+ "approved": true
+ }
+ ]
+}
+```
+
+### 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/project_snippets.md b/doc/api/project_snippets.md
index c1588f2292a..58d9d1cd4d8 100644
--- a/doc/api/project_snippets.md
+++ b/doc/api/project_snippets.md
@@ -81,6 +81,27 @@ Parameters:
- `code` (required) - The content of a snippet
- `visibility` (required) - The snippet's visibility
+Example request:
+
+```bash
+curl --request POST https://gitlab.com/api/v4/projects/:id/snippets \
+ --header "PRIVATE-TOKEN: <your access token>" \
+ --header "Content-Type: application/json" \
+ -d @snippet.json
+```
+
+`snippet.json` used in the above example request:
+
+```json
+{
+ "title" : "Example Snippet Title",
+ "description" : "More verbose snippet description",
+ "file_name" : "example.txt",
+ "code" : "source code \n with multiple lines\n",
+ "visibility" : "private"
+}
+```
+
## Update snippet
Updates an existing project snippet. The user must have permission to change an existing snippet.
@@ -99,6 +120,27 @@ Parameters:
- `code` (optional) - The content of a snippet
- `visibility` (optional) - The snippet's visibility
+Example request:
+
+```bash
+curl --request PUT https://gitlab.com/api/v4/projects/:id/snippets \
+ --header "PRIVATE-TOKEN: <your_access_token>" \
+ --header "Content-Type: application/json" \
+ -d @snippet.json
+```
+
+`snippet.json` used in the above example request:
+
+```json
+{
+ "title" : "Updated Snippet Title",
+ "description" : "More verbose snippet description",
+ "file_name" : "new_filename.txt",
+ "code" : "updated source code \n with multiple lines\n",
+ "visibility" : "private"
+}
+```
+
## Delete snippet
Deletes an existing project snippet. This returns a `204 No Content` status code if the operation was successfully or `404` if the resource was not found.
@@ -112,6 +154,13 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `snippet_id` (required) - The ID of a project's snippet
+Example request:
+
+```bash
+curl --request DELETE https://gitlab.com/api/v4/projects/:id/snippets \
+ --header "PRIVATE-TOKEN: <your_access_token>"
+```
+
## Snippet content
Returns the raw project snippet as plain text.
@@ -125,6 +174,13 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `snippet_id` (required) - The ID of a project's snippet
+Example request:
+
+```bash
+curl --request GET https://gitlab.com/api/v4/projects/:id/snippets/:snippet_id/raw \
+ --header "PRIVATE-TOKEN: <your_access_token>"
+```
+
## Get user agent details
> [Introduced][ce-29508] in GitLab 9.4.
@@ -140,6 +196,8 @@ GET /projects/:id/snippets/:snippet_id/user_agent_detail
| `id` | Integer | yes | The ID of a project |
| `snippet_id` | Integer | yes | The ID of a snippet |
+Example request:
+
```bash
curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/snippets/2/user_agent_detail
```
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 9f392418153..cf28ea84704 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -852,10 +852,10 @@ Get the users list of a project.
GET /projects/:id/users
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `search` | string | no | Search for specific users |
-| `skip_users` | array[int] | no | Filter out users with the specified IDs |
+| Attribute | Type | Required | Description |
+| ------------ | ------------- | -------- | ----------- |
+| `search` | string | no | Search for specific users |
+| `skip_users` | integer array | no | Filter out users with the specified IDs |
```json
[
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/api/templates/dockerfiles.md b/doc/api/templates/dockerfiles.md
index a08b8d33693..ee271c31b49 100644
--- a/doc/api/templates/dockerfiles.md
+++ b/doc/api/templates/dockerfiles.md
@@ -1,5 +1,13 @@
+---
+type: reference
+---
+
# Dockerfiles API
+In GitLab, there is an API endpoint available for Dockerfiles. For more
+information on Dockerfiles, see the
+[Docker documentation](https://docs.docker.com/engine/reference/builder/).
+
## List Dockerfile templates
Get all Dockerfile templates.
@@ -111,3 +119,15 @@ Example response:
"content": "# This file is a template, and might need editing before it works on your project.\n# This Dockerfile installs a compiled binary into a bare system.\n# You must either commit your compiled binary into source control (not recommended)\n# or build the binary first as part of a CI/CD pipeline.\n\nFROM buildpack-deps:jessie\n\nWORKDIR /usr/local/bin\n\n# Change `app` to whatever your binary is called\nAdd app .\nCMD [\"./app\"]\n"
}
```
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/api/templates/gitignores.md b/doc/api/templates/gitignores.md
index bf6a914e120..0b6d944cb62 100644
--- a/doc/api/templates/gitignores.md
+++ b/doc/api/templates/gitignores.md
@@ -1,4 +1,12 @@
-# `.gitignore` API
+---
+type: reference
+---
+
+# .gitignore API
+
+In GitLab, there is an API endpoint available for `.gitignore`. For more
+information on `gitignore`, see the
+[Git documentation](https://git-scm.com/docs/gitignore).
## List `.gitignore` templates
@@ -123,3 +131,15 @@ Example response:
"content": "*.gem\n*.rbc\n/.config\n/coverage/\n/InstalledFiles\n/pkg/\n/spec/reports/\n/spec/examples.txt\n/test/tmp/\n/test/version_tmp/\n/tmp/\n\n# Used by dotenv library to load environment variables.\n# .env\n\n## Specific to RubyMotion:\n.dat*\n.repl_history\nbuild/\n*.bridgesupport\nbuild-iPhoneOS/\nbuild-iPhoneSimulator/\n\n## Specific to RubyMotion (use of CocoaPods):\n#\n# We recommend against adding the Pods directory to your .gitignore. However\n# you should judge for yourself, the pros and cons are mentioned at:\n# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control\n#\n# vendor/Pods/\n\n## Documentation cache and generated files:\n/.yardoc/\n/_yardoc/\n/doc/\n/rdoc/\n\n## Environment normalization:\n/.bundle/\n/vendor/bundle\n/lib/bundler/man/\n\n# for a library or gem, you might want to ignore these files since the code is\n# intended to run in multiple environments; otherwise, check them in:\n# Gemfile.lock\n# .ruby-version\n# .ruby-gemset\n\n# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:\n.rvmrc\n"
}
```
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/api/templates/gitlab_ci_ymls.md b/doc/api/templates/gitlab_ci_ymls.md
index 11ec7360e06..80f94b953e3 100644
--- a/doc/api/templates/gitlab_ci_ymls.md
+++ b/doc/api/templates/gitlab_ci_ymls.md
@@ -1,5 +1,13 @@
+---
+type: reference
+---
+
# GitLab CI YMLs API
+In GitLab, there is an API endpoint available to work with CI YMLs. For more
+information on CI/CD pipeline configuration in GitLab, see the
+[configuration reference documentation](../../ci/yaml/README.md).
+
## List GitLab CI YML templates
Get all GitLab CI YML templates.
@@ -123,3 +131,15 @@ Example response:
"content": "# This file is a template, and might need editing before it works on your project.\n# Official language image. Look for the different tagged releases at:\n# https://hub.docker.com/r/library/ruby/tags/\nimage: \"ruby:2.5\"\n\n# Pick zero or more services to be used on all builds.\n# Only needed when using a docker container to run your tests in.\n# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-a-service\nservices:\n - mysql:latest\n - redis:latest\n - postgres:latest\n\nvariables:\n POSTGRES_DB: database_name\n\n# Cache gems in between builds\ncache:\n paths:\n - vendor/ruby\n\n# This is a basic example for a gem or script which doesn't use\n# services such as redis or postgres\nbefore_script:\n - ruby -v # Print out ruby version for debugging\n # Uncomment next line if your rails app needs a JS runtime:\n # - apt-get update -q && apt-get install nodejs -yqq\n - bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby\n\n# Optional - Delete if not using `rubocop`\nrubocop:\n script:\n - rubocop\n\nrspec:\n script:\n - rspec spec\n\nrails:\n variables:\n DATABASE_URL: \"postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB\"\n script:\n - rails db:migrate\n - rails db:seed\n - rails test\n\n# This deploy job uses a simple deploy flow to Heroku, other providers, e.g. AWS Elastic Beanstalk\n# are supported too: https://github.com/travis-ci/dpl\ndeploy:\n type: deploy\n environment: production\n script:\n - gem install dpl\n - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_PRODUCTION_KEY\n"
}
```
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/api/templates/licenses.md b/doc/api/templates/licenses.md
index 5feb1e498bd..92466c73bd3 100644
--- a/doc/api/templates/licenses.md
+++ b/doc/api/templates/licenses.md
@@ -1,5 +1,14 @@
+---
+type: reference
+---
+
# Licenses API
+In GitLab, there is an API endpoint available for working with various open
+source license templates. For more information on the terms of various
+licenses, see [this site](https://choosealicense.com/) or any of the many other
+resources available online.
+
## List license templates
Get all license templates.
@@ -145,3 +154,15 @@ Example response:
"content": "The MIT License (MIT)\n\nCopyright (c) 2016 John Doe\n [...]"
}
```
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 4be13204227..90d0e6a7dc6 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -131,7 +131,7 @@ Its feature set is listed on the table below according to DevOps stages.
| **Secure** ||
| [Container Scanning](../user/application_security/container_scanning/index.md) **(ULTIMATE)** | Check your Docker containers for known vulnerabilities.|
| [Dependency Scanning](../user/application_security/dependency_scanning/index.md) **(ULTIMATE)** | Analyze your dependencies for known vulnerabilities. |
-| [License Compliance](../user/application_security/license_management/index.md) **(ULTIMATE)** | Search your project dependencies for their licenses. |
+| [License Compliance](../user/application_security/license_compliance/index.md) **(ULTIMATE)** | Search your project dependencies for their licenses. |
| [Security Test reports](../user/project/merge_requests/index.md#security-reports-ultimate) **(ULTIMATE)** | Check for app vulnerabilities. |
## Examples
diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md
index f8151e3e18c..ab9fa517e23 100644
--- a/doc/ci/caching/index.md
+++ b/doc/ci/caching/index.md
@@ -172,6 +172,29 @@ job:
cache: {}
```
+### 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
+`policy` for one job:
+
+```yaml
+cache: &global_cache
+ key: ${CI_COMMIT_REF_SLUG}
+ paths:
+ - node_modules/
+ - public/
+ - vendor/
+ policy: pull-push
+
+job:
+ cache:
+ # inherit all global cache settings
+ <<: *global_cache
+ # override the policy
+ policy: pull
+```
+
For more fine tuning, read also about the
[`cache: policy`](../yaml/README.md#cachepolicy).
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/directed_acyclic_graph/index.md b/doc/ci/directed_acyclic_graph/index.md
index 1c38c08b7cb..60e3120ba33 100644
--- a/doc/ci/directed_acyclic_graph/index.md
+++ b/doc/ci/directed_acyclic_graph/index.md
@@ -36,7 +36,7 @@ It has a pipeline that looks like the following:
| ----- | ---- | ------ |
| build_a | test_a | deploy_a |
| build_b | test_b | deploy_b |
-| build_c | test_c | deploy_c |
+| build_c | test_c | deploy_c |
| build_d | test_d | deploy_d |
Using a DAG, you can relate the `_a` jobs to each other separately from the `_b` jobs,
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/examples/README.md b/doc/ci/examples/README.md
index 00995f881da..f9612d0c53d 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -22,7 +22,7 @@ The following table lists examples with step-by-step tutorials that are containe
| Use case | Resource |
|:----------------------------|:---------------------------------------------------------------------------------------------------------------------------|
-| Browser performance testing | [Browser Performance Testing with the Sitespeed.io container](browser_performance.md). |
+| Browser performance testing | [Browser Performance Testing with the Sitespeed.io container](../../user/project/merge_requests/browser_performance_testing.md). |
| Clojure | [Test a Clojure application with GitLab CI/CD](test-clojure-application.md). |
| Deployment with Dpl | [Using `dpl` as deployment tool](deployment/README.md). |
| Elixir | [Testing a Phoenix application with GitLab CI/CD](test_phoenix_app_with_gitlab_ci_cd/index.md). |
diff --git a/doc/ci/examples/browser_performance.md b/doc/ci/examples/browser_performance.md
index 3266e5dc62e..4a73fe2e62c 100644
--- a/doc/ci/examples/browser_performance.md
+++ b/doc/ci/examples/browser_performance.md
@@ -1,158 +1,5 @@
---
-type: howto
+redirect_to: '../../user/project/merge_requests/browser_performance_testing.md#configuring-browser-performance-testing'
---
-# Browser Performance Testing with the sitespeed.io container
-
-NOTE: **Note:**
-The job definition shown below is supported on GitLab 11.5 and later versions.
-It also requires the GitLab Runner 11.5 or later.
-For earlier versions, use the [previous job definitions](#previous-job-definitions).
-
-This example shows how to run the
-[sitespeed.io container](https://hub.docker.com/r/sitespeedio/sitespeed.io/) on
-your code by using GitLab CI/CD and [sitespeed.io](https://www.sitespeed.io)
-using Docker-in-Docker.
-
-First, you need GitLab Runner with
-[docker-in-docker build](../docker/using_docker_build.md#use-docker-in-docker-workflow-with-docker-executor).
-
-Once you set up the Runner, add a new job to `.gitlab-ci.yml` that
-generates the expected report:
-
-```yaml
-performance:
- stage: performance
- image: docker:git
- variables:
- URL: https://example.com
- services:
- - docker:stable-dind
- script:
- - mkdir gitlab-exporter
- - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
- - mkdir sitespeed-results
- - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results $URL
- - mv sitespeed-results/data/performance.json performance.json
- artifacts:
- paths:
- - sitespeed-results/
- reports:
- performance: performance.json
-```
-
-The above example will create a `performance` job in your CI/CD pipeline and will run
-sitespeed.io against the webpage you defined in `URL` to gather key metrics.
-The [GitLab plugin](https://gitlab.com/gitlab-org/gl-performance) for
-sitespeed.io is downloaded in order to save the report as a
-[Performance report artifact](../yaml/README.md#artifactsreportsperformance-premium)
-that you can later download and analyze.
-Due to implementation limitations we always take the latest Performance artifact available.
-
-The full HTML sitespeed.io report will also be saved as an artifact, and if you have
-[GitLab Pages](../../user/project/pages/index.md) enabled, it can be viewed
-directly in your browser.
-
-For further customization options for sitespeed.io, including the ability to
-provide a list of URLs to test, please see the
-[Sitespeed.io Configuration](https://www.sitespeed.io/documentation/sitespeed.io/configuration/) documentation.
-
-TIP: **Tip:**
-For [GitLab Premium](https://about.gitlab.com/pricing/) users, key metrics are automatically
-extracted and shown right in the merge request widget.
-[Learn more on Browser Performance Testing in merge requests](../../user/project/merge_requests/browser_performance_testing.md).
-
-## Performance testing on Review Apps
-
-The above CI YML is great for testing against static environments, and it can
-be extended for dynamic environments. There are a few extra steps to take to
-set this up:
-
-1. The `performance` job should run after the dynamic environment has started.
-1. In the `review` job, persist the hostname and upload it as an artifact so
- it's available to the `performance` job (the same can be done for static
- environments like staging and production to unify the code path). Saving it
- as an artifact is as simple as `echo $CI_ENVIRONMENT_URL > environment_url.txt`
- in your job's `script`.
-1. In the `performance` job, read the previous artifact into an environment
- variable, like `$CI_ENVIRONMENT_URL`, and use it to parameterize the test
- URLs.
-1. You can now run the sitespeed.io container against the desired hostname and
- paths.
-
-Your `.gitlab-ci.yml` file would look like:
-
-```yaml
-stages:
- - deploy
- - performance
-
-review:
- stage: deploy
- environment:
- name: review/$CI_COMMIT_REF_SLUG
- url: http://$CI_COMMIT_REF_SLUG.$APPS_DOMAIN
- script:
- - run_deploy_script
- - echo $CI_ENVIRONMENT_URL > environment_url.txt
- artifacts:
- paths:
- - environment_url.txt
- only:
- - branches
- except:
- - master
-
-performance:
- stage: performance
- image: docker:git
- services:
- - docker:stable-dind
- dependencies:
- - review
- script:
- - export CI_ENVIRONMENT_URL=$(cat environment_url.txt)
- - mkdir gitlab-exporter
- - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
- - mkdir sitespeed-results
- - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "$CI_ENVIRONMENT_URL"
- - mv sitespeed-results/data/performance.json performance.json
- artifacts:
- paths:
- - sitespeed-results/
- reports:
- performance: performance.json
-```
-
-A complete example can be found in our [Auto DevOps CI YML](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml).
-
-## Previous job definitions
-
-CAUTION: **Caution:**
-Before GitLab 11.5, Performance job and artifact had to be named specifically
-to automatically extract report data and show it in the merge request widget.
-While these old job definitions are still maintained they have been deprecated
-and may be removed in next major release, GitLab 12.0.
-You are advised to update your current `.gitlab-ci.yml` configuration to reflect that change.
-
-For GitLab 11.4 and earlier, the job should look like:
-
-```yaml
-performance:
- stage: performance
- image: docker:git
- variables:
- URL: https://example.com
- services:
- - docker:stable-dind
- script:
- - mkdir gitlab-exporter
- - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
- - mkdir sitespeed-results
- - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results $URL
- - mv sitespeed-results/data/performance.json performance.json
- artifacts:
- paths:
- - performance.json
- - sitespeed-results/
-```
+This document was moved to [another location](../../user/project/merge_requests/browser_performance_testing.md#configuring-browser-performance-testing).
diff --git a/doc/ci/examples/license_management.md b/doc/ci/examples/license_management.md
index 53e38111bf3..0d12c9a20f2 100644
--- a/doc/ci/examples/license_management.md
+++ b/doc/ci/examples/license_management.md
@@ -1,5 +1,5 @@
---
-redirect_to: '../../user/application_security/license_management/index.md'
+redirect_to: '../../user/application_security/license_compliance/index.md'
---
-This document was moved to [another location](../../user/application_security/license_management/index.md).
+This document was moved to [another location](../../user/application_security/license_compliance/index.md).
diff --git a/doc/ci/jenkins/index.md b/doc/ci/jenkins/index.md
index f8a3fab88e3..ace1204511e 100644
--- a/doc/ci/jenkins/index.md
+++ b/doc/ci/jenkins/index.md
@@ -32,7 +32,7 @@ There are some high level differences between the products worth mentioning:
## Groovy vs. YAML
-Jenkins Pipelines are based on [Groovy](https://groovy-lang.org/), so the pipeline specification is written as code.
+Jenkins Pipelines are based on [Groovy](https://groovy-lang.org/), so the pipeline specification is written as code.
GitLab works a bit differently, we use the more highly structured [YAML](https://yaml.org/) format, which
places scripting elements inside of `script:` blocks separate from the pipeline specification itself.
@@ -56,7 +56,7 @@ rspec:
- .in-docker
script:
- rake rspec
-```
+```
## Artifact publishing
@@ -143,7 +143,7 @@ default:
GitLab CI also lets you define stages, but is a little bit more free-form to configure. The GitLab [`stages` keyword](../yaml/README.md#stages)
is a top level setting that enumerates the list of stages, but you are not required to nest individual jobs underneath
-the `stages` section. Any job defined in the `.gitlab-ci.yml` can be made a part of any stage through use of the
+the `stages` section. Any job defined in the `.gitlab-ci.yml` can be made a part of any stage through use of the
[`stage:` keyword](../yaml/README.md#stage).
Note that, unless otherwise specified, every pipeline is instantiated with a `build`, `test`, and `deploy` stage
@@ -229,4 +229,4 @@ our very powerful [`only/except` rules system](../yaml/README.md#onlyexcept-basi
```yaml
my_job:
only: branches
-``` \ No newline at end of file
+```
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/merge_request_pipelines/pipelines_for_merged_results/index.md b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md
index 126e12e460f..d68fba82f4b 100644
--- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md
@@ -61,6 +61,20 @@ CAUTION: **Warning:**
Make sure your `gitlab-ci.yml` file is [configured properly for pipelines for merge requests](../index.md#configuring-pipelines-for-merge-requests),
otherwise pipelines for merged results won't run and your merge requests will be stuck in an unresolved state.
+## Automatic pipeline cancelation
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/12996) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.3.
+
+GitLab CI can detect the presence of redundant pipelines,
+and will cancel them automatically in order to conserve CI resources.
+
+When a user merges a merge request immediately within an ongoing merge
+train, the train will be reconstructed, as it will recreate the expected
+post-merge commit and pipeline. In this case, the merge train may already
+have pipelines running against the previous expected post-merge commit.
+These pipelines are considered redundant and will be automatically
+canceled.
+
## Troubleshooting
### Pipelines for merged results not created even with new change pushed to merge request
diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md
index 80a1c264bc4..7998b0452be 100644
--- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md
@@ -141,4 +141,4 @@ please ask administrator to execute the following commands:
> sudo gitlab-rails console # Login to Rails console of GitLab instance.
> Feature.enabled?(:merge_trains_enabled) # Check if it's enabled or not.
> Feature.disable(:merge_trains_enabled) # Disable the feature flag.
-``` \ No newline at end of file
+```
diff --git a/doc/ci/multi_project_pipelines.md b/doc/ci/multi_project_pipelines.md
index 61f260cb70d..9e18d1d883c 100644
--- a/doc/ci/multi_project_pipelines.md
+++ b/doc/ci/multi_project_pipelines.md
@@ -176,6 +176,20 @@ 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
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/11238) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.3.
+
+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/runners/README.md b/doc/ci/runners/README.md
index 269bd5c3428..abb503c6516 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -156,8 +156,7 @@ An admin can enable/disable a specific Runner for projects:
## Protected Runners
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13194)
-> in GitLab 10.0.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13194) in GitLab 10.0.
You can protect Runners from revealing sensitive information.
Whenever a Runner is protected, the Runner picks only jobs created on
diff --git a/doc/ci/services/mysql.md b/doc/ci/services/mysql.md
index 9ea113969c8..ce69a7df885 100644
--- a/doc/ci/services/mysql.md
+++ b/doc/ci/services/mysql.md
@@ -27,8 +27,8 @@ variables:
NOTE: **Note:**
The `MYSQL_DATABASE` and `MYSQL_ROOT_PASSWORD` variables can't be set in the GitLab UI.
-To set them, assign them to a variable [in the UI](../variables/README.md#via-the-ui),
-and then assign that variable to the
+To set them, assign them to a variable [in the UI](../variables/README.md#via-the-ui),
+and then assign that variable to the
`MYSQL_DATABASE` and `MYSQL_ROOT_PASSWORD` variables in your `.gitlab-ci.yml`.
And then configure your application to use the database, for example:
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index 2a382f18038..f62a4660713 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -58,8 +58,7 @@ Read more about the [pipelines trigger API][trigapi].
#### When a pipeline depends on the artifacts of another pipeline **(PREMIUM)**
-> The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced][ee-2346]
- in [GitLab Premium][ee] 9.5.
+> The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced][ee-2346] in [GitLab Premium][ee] 9.5.
With the introduction of dependencies between different projects, one of
them may need to access artifacts created by a previous one. This process
@@ -271,7 +270,7 @@ Old triggers, created before GitLab 9.0 will be marked as legacy.
Triggers with the legacy label do not have an associated user and only have
access to the current project. They are considered deprecated and will be
-removed with one of the future versions of GitLab.
+removed with one of the future versions of GitLab.
[ee-2017]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2017
[ee-2346]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2346
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 89a61b2a9e3..8f2e95dbb10 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -100,6 +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 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`. |
@@ -112,9 +113,10 @@ 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. |
+| [interruptible](#interruptible) | Defines if a job can be canceled when made redundant by a newer run |
NOTE: **Note:**
Parameters `types` and `type` are [deprecated](#deprecated-parameters).
@@ -386,7 +388,7 @@ In addition, `only` and `except` allow the use of special keywords:
| `triggers` | For pipelines created using a trigger token. |
| `web` | For pipelines created using **Run pipeline** button in GitLab UI (under your project's **Pipelines**). |
| `merge_requests` | When a merge request is created or updated (See [pipelines for merge requests](../merge_request_pipelines/index.md)). |
-| `chats` | For jobs created using a [GitLab ChatOps](../chatops/README.md) command. |
+| `chat` | For jobs created using a [GitLab ChatOps](../chatops/README.md) command. |
In the example below, `job` will run only for refs that start with `issue-`,
whereas all branches will be skipped:
@@ -690,6 +692,151 @@ In the scenario above, if a merge request is created or updated that changes
either files in `service-one` directory or the `Dockerfile`, GitLab creates
and triggers the `docker build service one` job.
+### `rules`
+
+> [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`](#rulesif)
+ (similar to [`only:variables`](#onlyvariablesexceptvariables)).
+- [`changes`](#ruleschanges)
+ (same as [`only:changes`](#onlychangesexceptchanges)).
+
+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"'
+ when: always
+ - if: '$VAR =~ /pattern/'
+ when: manual
+ - when: on_success
+```
+
+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`
+
+`rules:if` differs slightly from `only:variables` by accepting only a single
+expression string, rather than an array of them. Any set of expressions to be
+evaluated should be conjoined into a single expression using `&&` or `||`. For example:
+
+```yaml
+job:
+ script: "echo Hello, Rules!"
+ rules:
+ - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH =~ /^feature/ && $CI_MERGE_REQUEST_TARGET_BRANCH == "master"' # This rule will be evaluated
+ when: always
+ - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH =~ /^feature/' # This rule will only be evaluated if the target branch is not "master"
+ when: manual
+ - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH' # If neither of the first two match but the simple presence does, we set to "on_success" by default
+```
+
+If none of the provided rules match, the job will be set to `when:never`, and
+not included in the pipeline. If `rules:when` is not included in the configuration
+at all, the behavior defaults to `job:when`, which continues to default to
+`on_success`.
+
+#### `rules:changes`
+
+`changes` works exactly the same way as [`only`/`except`](#onlychangesexceptchanges),
+accepting an array of paths.
+
+For example:
+
+```yaml
+docker build:
+ script: docker build -t my-image:$CI_COMMIT_REF_SLUG .
+ rules:
+ - changes: # Will include the job and set to when:manual if any of the follow paths match a modified file.
+ - Dockerfile
+ when: manual
+ - if: '$VAR == "string value"'
+ when: manual # Will include the job and set to when:manual if the expression evaluates to true, after the `changes:` rule fails to match.
+ - when: on_success # If neither of the first rules match, set to on_success
+
+```
+
+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.
+
+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:
+ script: docker build -t my-image:$CI_COMMIT_REF_SLUG .
+ rules:
+ - if: '$VAR == "string value"'
+ changes: # Will include the job and set to when:manual if any of the follow paths match a modified file.
+ - Dockerfile
+ - docker/scripts/*
+ when: manual
+ # - when: never would be redundant here, this is implied any time rules are listed.
+```
+
+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`.
+- `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:
+
+```yaml
+docker build:
+ script: docker build -t my-image:$CI_COMMIT_REF_SLUG .
+ rules:
+ - changes: # Will include the job and delay 3 hours when the Dockerfile has changed
+ - Dockerfile
+ when: delayed
+ start_in: '3 hours'
+ - when: on_success # Otherwise include the job and set to run normally
+
+```
+
+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`
`tags` is used to select specific Runners from the list of all Runners that are
@@ -1579,7 +1726,7 @@ dashboards.
> Introduced in GitLab 11.5. Requires GitLab Runner 11.5 and above.
-The `license_management` report collects [Licenses](../../user/project/merge_requests/license_management.md)
+The `license_management` report collects [Licenses](../../user/application_security/license_compliance/index.md)
as artifacts.
The collected License Compliance report will be uploaded to GitLab as an artifact and will
@@ -1681,7 +1828,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`.
@@ -1700,19 +1848,19 @@ mac:build:
linux:rspec:
stage: test
- needs: [linux:build]
+ needs: ["linux:build"]
linux:rubocop:
stage: test
- needs: [linux:build]
+ needs: ["linux:build"]
mac:rspec:
stage: test
- needs: [mac:build]
+ needs: ["mac:build"]
mac:rubocop:
stage: test
- needs: [mac:build]
+ needs: ["mac:build"]
production:
stage: deploy
@@ -1732,30 +1880,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`
@@ -1779,9 +1923,8 @@ job1:
### `retry`
-> [Introduced][ce-12909] in GitLab 9.5.
-> [Behaviour expanded](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21758)
-> in GitLab 11.5 to control on which failures to retry.
+> - [Introduced][ce-12909] in GitLab 9.5.
+> - [Behaviour expanded](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21758) in GitLab 11.5 to control on which failures to retry.
`retry` allows you to configure how many times a job is going to be retried in
case of a failure.
@@ -1870,6 +2013,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.
@@ -1940,6 +2084,46 @@ staging:
branch: stable
```
+### `interruptible`
+
+`interruptible` is used to indicate that a job should be canceled if made redundant by a newer run of the same job. Defaults to `false` if there is an environment defined and `true` otherwise.
+This value will only be used if the [automatic cancellation of redundant pipelines feature](https://docs.gitlab.com/ee/user/project/pipelines/settings.html#auto-cancel-pending-pipelines)
+is enabled.
+
+When enabled, a pipeline on the same branch will be canceled when:
+
+- It is made redundant by a newer pipeline run.
+- Either all jobs are set as interruptible, or any uninterruptible jobs are not yet pending.
+
+Pending jobs are always considered interruptible.
+
+TIP: **Tip:**
+Set jobs as uninterruptible that should behave atomically and should never be canceled once started.
+
+Here is a simple example:
+
+```yaml
+stages:
+ - stage1
+ - stage2
+
+step-1:
+ stage: stage1
+ script:
+ - echo "Can be canceled"
+
+step-2:
+ stage: stage2
+ script:
+ - echo "Can not be canceled"
+ interruptible: false
+```
+
+In the example above, a new pipeline run will cause an existing running pipeline to be:
+
+- Canceled, if only `step-1` is running or pending.
+- Not canceled, once `step-2` becomes pending.
+
### `include`
> - Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 10.5.
@@ -2051,6 +2235,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 5e35d4c7437..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
@@ -65,6 +66,7 @@ description: 'Learn how to contribute to GitLab.'
- [Repository mirroring](repository_mirroring.md)
- [Git LFS](lfs.md)
- [Developing against interacting components or features](interacting_components.md)
+- [File uploads](uploads.md)
## Performance guides
diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md
index 7569ccc04c1..c3165dc2e21 100644
--- a/doc/development/api_graphql_styleguide.md
+++ b/doc/development/api_graphql_styleguide.md
@@ -191,6 +191,51 @@ end
policies at once. The fields for these will all have be non-nullable
booleans with a default description.
+## Enums
+
+GitLab GraphQL enums are defined in `app/graphql/types`. When defining new enums, the
+following rules apply:
+
+- Values must be uppercase.
+- Class names must end with the string `Enum`.
+- The `graphql_name` must not contain the string `Enum`.
+
+For example:
+
+```ruby
+module Types
+ class TrafficLightStateEnum < BaseEnum
+ graphql_name 'TrafficLightState'
+ description 'State of a traffic light'
+
+ value 'RED', description: 'Drivers must stop'
+ value 'YELLOW', description: 'Drivers must stop when it is safe to'
+ value 'GREEN', description: 'Drivers can start or keep driving'
+ end
+end
+```
+
+If the enum will be used for a class property in Ruby that is not an uppercase string,
+you can provide a `value:` option that will adapt the uppercase value.
+
+In the following example:
+
+- GraphQL inputs of `OPENED` will be converted to `'opened'`.
+- Ruby values of `'opened'` will be converted to `"OPENED"` in GraphQL responses.
+
+```ruby
+module Types
+ class EpicStateEnum < BaseEnum
+ graphql_name 'EpicState'
+ description 'State of a GitLab epic'
+
+ value 'OPENED', value: 'opened', description: 'An open Epic'
+ value 'CLOSED', value: 'closed', description: 'An closed Epic'
+ end
+end
+
+```
+
## Authorization
Authorizations can be applied to both types and fields using the same
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index 5cb2ddf6e52..147bd21e6c7 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -12,21 +12,21 @@ New versions of GitLab are released in stable branches and the master branch is
For information, see the [GitLab Release Process](https://gitlab.com/gitlab-org/release/docs/tree/master#gitlab-release-process).
-Both EE and CE require some add-on components called gitlab-shell and Gitaly. These components are available from the [gitlab-shell](https://gitlab.com/gitlab-org/gitlab-shell/tree/master) and [gitaly](https://gitlab.com/gitlab-org/gitaly/tree/master) repositories respectively. New versions are usually tags but staying on the master branch will give you the latest stable version. New releases are generally around the same time as GitLab CE releases with exception for informal security updates deemed critical.
+Both EE and CE require some add-on components called GitLab Shell and Gitaly. These components are available from the [GitLab Shell](https://gitlab.com/gitlab-org/gitlab-shell/tree/master) and [Gitaly](https://gitlab.com/gitlab-org/gitaly/tree/master) repositories respectively. New versions are usually tags but staying on the master branch will give you the latest stable version. New releases are generally around the same time as GitLab CE releases with exception for informal security updates deemed critical.
## Components
A typical install of GitLab will be on GNU/Linux. It uses Nginx or Apache as a web front end to proxypass the Unicorn web server. By default, communication between Unicorn and the front end is via a Unix domain socket but forwarding requests via TCP is also supported. The web front end accesses `/home/git/gitlab/public` bypassing the Unicorn server to serve static pages, uploads (e.g. avatar images or attachments), and precompiled assets. GitLab serves web pages and a [GitLab API](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/api) using the Unicorn web server. It uses Sidekiq as a job queue which, in turn, uses redis as a non-persistent database backend for job information, meta data, and incoming jobs.
-We also support deploying GitLab on Kubernetes using our [gitlab Helm chart](https://docs.gitlab.com/charts/).
+We also support deploying GitLab on Kubernetes using our [GitLab Helm chart](https://docs.gitlab.com/charts/).
-The GitLab web app uses PostgreSQL for persistent database information (e.g. users, permissions, issues, other meta data). GitLab stores the bare git repositories it serves in `/home/git/repositories` by default. It also keeps default branch and hook information with the bare repository.
+The GitLab web app uses PostgreSQL for persistent database information (e.g. users, permissions, issues, other meta data). GitLab stores the bare Git repositories it serves in `/home/git/repositories` by default. It also keeps default branch and hook information with the bare repository.
-When serving repositories over HTTP/HTTPS GitLab utilizes the GitLab API to resolve authorization and access as well as serving git objects.
+When serving repositories over HTTP/HTTPS GitLab utilizes the GitLab API to resolve authorization and access as well as serving Git objects.
-The add-on component gitlab-shell serves repositories over SSH. It manages the SSH keys within `/home/git/.ssh/authorized_keys` which should not be manually edited. gitlab-shell accesses the bare repositories through Gitaly to serve git objects and communicates with redis to submit jobs to Sidekiq for GitLab to process. gitlab-shell queries the GitLab API to determine authorization and access.
+The add-on component GitLab Shell serves repositories over SSH. It manages the SSH keys within `/home/git/.ssh/authorized_keys` which should not be manually edited. GitLab Shell accesses the bare repositories through Gitaly to serve Git objects and communicates with redis to submit jobs to Sidekiq for GitLab to process. GitLab Shell queries the GitLab API to determine authorization and access.
-Gitaly executes git operations from gitlab-shell and the GitLab web app, and provides an API to the GitLab web app to get attributes from git (e.g. title, branches, tags, other meta data), and to get blobs (e.g. diffs, commits, files).
+Gitaly executes Git operations from GitLab Shell and the GitLab web app, and provides an API to the GitLab web app to get attributes from Git (e.g. title, branches, tags, other meta data), and to get blobs (e.g. diffs, commits, files).
You may also be interested in the [production architecture of GitLab.com](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/).
@@ -61,8 +61,8 @@ graph TB
Unicorn --> PgBouncer[PgBouncer]
Unicorn --> Redis
Unicorn --> Gitaly
- Redis --> Sidekiq
- Sidekiq["Sidekiq (GitLab Rails, ES Indexer)"] --> PgBouncer
+ Sidekiq --> Redis
+ Sidekiq --> PgBouncer
GitLabWorkhorse[GitLab Workhorse] --> Unicorn
GitLabWorkhorse --> Redis
GitLabWorkhorse --> Gitaly
@@ -130,7 +130,7 @@ Component statuses are linked to configuration documentation for each component.
| [NGINX](#nginx) | Routes requests to appropriate components, terminates SSL | [✅][nginx-omnibus] | [✅][nginx-charts] | [⚙][nginx-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | [⤓][nginx-source] | ❌ | CE & EE |
| [Unicorn (GitLab Rails)](#unicorn) | Handles requests for the web interface and API | [✅][unicorn-omnibus] | [✅][unicorn-charts] | [✅][unicorn-charts] | [✅](../user/gitlab_com/index.md#unicorn) | [⚙][unicorn-source] | [✅][gitlab-yml] | CE & EE |
| [Sidekiq](#sidekiq) | Background jobs processor | [✅][sidekiq-omnibus] | [✅][sidekiq-charts] | [✅](https://docs.gitlab.com/charts/charts/gitlab/sidekiq/index.html) | [✅](../user/gitlab_com/index.md#sidekiq) | [✅][gitlab-yml] | [✅][gitlab-yml] | CE & EE |
-| [Gitaly](#gitaly) | Git RPC service for handling all git calls made by GitLab | [✅][gitaly-omnibus] | [✅][gitaly-charts] | [✅][gitaly-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | [⚙][gitaly-source] | ✅ | CE & EE |
+| [Gitaly](#gitaly) | Git RPC service for handling all Git calls made by GitLab | [✅][gitaly-omnibus] | [✅][gitaly-charts] | [✅][gitaly-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | [⚙][gitaly-source] | ✅ | CE & EE |
| [GitLab Workhorse](#gitlab-workhorse) | Smart reverse proxy, handles large HTTP requests | [✅][workhorse-omnibus] | [✅][workhorse-charts] | [✅][workhorse-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | [⚙][workhorse-source] | ✅ | CE & EE |
| [GitLab Shell](#gitlab-shell) | Handles `git` over SSH sessions | [✅][shell-omnibus] | [✅][shell-charts] | [✅][shell-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#service-architecture) | [⚙][shell-source] | [✅][gitlab-yml] | CE & EE |
| [GitLab Pages](#gitlab-pages) | Hosts static websites | [⚙][pages-omnibus] | [❌][pages-charts] | [❌][pages-charts] | [✅](../user/gitlab_com/index.md#gitlab-pages) | [⚙][pages-source] | [⚙][pages-gdk] | CE & EE |
@@ -185,7 +185,7 @@ GitLab can be considered to have two layers from a process perspective:
- Layer: Monitoring
- Process: `alertmanager`
-[Alert manager](https://prometheus.io/docs/alerting/alertmanager/) is a tool provided by Prometheus that _"handles alerts sent by client applications such as the Prometheus server. It takes care of deduplicating, grouping, and routing them to the correct receiver integration such as email, PagerDuty, or OpsGenie. It also takes care of silencing and inhibition of alerts."_ You can read more in [issue gitlab-ce#45740](https://gitlab.com/gitlab-org/gitlab-ce/issues/45740) about what we will be alerting on.
+[Alert manager](https://prometheus.io/docs/alerting/alertmanager/) is a tool provided by Prometheus that _"handles alerts sent by client applications such as the Prometheus server. It takes care of deduplicating, grouping, and routing them to the correct receiver integration such as email, PagerDuty, or OpsGenie. It also takes care of silencing and inhibition of alerts."_ You can read more in [issue #45740](https://gitlab.com/gitlab-org/gitlab-ce/issues/45740) about what we will be alerting on.
#### Certificate management
@@ -223,12 +223,12 @@ Elasticsearch is a distributed RESTful search engine built for the cloud.
Gitaly is a service designed by GitLab to remove our need for NFS for Git storage in distributed deployments of GitLab (think GitLab.com or High Availability Deployments). As of 11.3.0, this service handles all Git level access in GitLab. You can read more about the project [in the project's readme](https://gitlab.com/gitlab-org/gitaly).
-#### Gitlab Geo
+#### GitLab Geo
- Configuration: [Omnibus][geo-omnibus], [Charts][geo-charts], [GDK][geo-gdk]
- Layer: Core Service (Processor)
-#### Gitlab Monitor
+#### GitLab Monitor
- [Project page](https://gitlab.com/gitlab-org/gitlab-monitor)
- Configuration: [Omnibus][gitlab-monitor-omnibus], [Charts][gitlab-monitor-charts]
@@ -237,7 +237,7 @@ Gitaly is a service designed by GitLab to remove our need for NFS for Git storag
GitLab Monitor is a process designed in house that allows us to export metrics about GitLab application internals to Prometheus. You can read more [in the project's readme](https://gitlab.com/gitlab-org/gitlab-monitor).
-#### Gitlab Pages
+#### GitLab Pages
- Configuration: [Omnibus][pages-omnibus], [Charts][pages-charts], [Source][pages-source], [GDK][pages-gdk]
- Layer: Core Service (Processor)
@@ -246,7 +246,7 @@ GitLab Pages is a feature that allows you to publish static websites directly fr
You can use it either for personal or business websites, such as portfolios, documentation, manifestos, and business presentations. You can also attribute any license to your content.
-#### Gitlab Runner
+#### GitLab Runner
- [Project page](https://gitlab.com/gitlab-org/gitlab-runner/blob/master/README.md)
- Configuration: [Omnibus][runner-omnibus], [Charts][runner-charts], [Source][runner-source], [GDK][runner-gdk]
@@ -256,7 +256,7 @@ GitLab Runner runs tests and sends the results to GitLab.
GitLab CI is the open-source continuous integration service included with GitLab that coordinates the testing. The old name of this project was GitLab CI Multi Runner but please use "GitLab Runner" (without CI) from now on.
-#### Gitlab Shell
+#### GitLab Shell
- [Project page](https://gitlab.com/gitlab-org/gitlab-shell/blob/master/README.md)
- Configuration: [Omnibus][shell-omnibus], [Charts][shell-charts], [Source][shell-source], [GDK][gitlab-yml]
@@ -264,7 +264,7 @@ GitLab CI is the open-source continuous integration service included with GitLab
[GitLab Shell](https://gitlab.com/gitlab-org/gitlab-shell) is a program designed at GitLab to handle ssh-based `git` sessions, and modifies the list of authorized keys. GitLab Shell is not a Unix shell nor a replacement for Bash or Zsh.
-#### Gitlab Workhorse
+#### GitLab Workhorse
- [Project page](https://gitlab.com/gitlab-org/gitlab-workhorse/blob/master/README.md)
- Configuration: [Omnibus][workhorse-omnibus], [Charts][workhorse-charts], [Source][workhorse-source]
@@ -475,7 +475,7 @@ It's important to understand the distinction as some processes are used in both
When making a request to an HTTP Endpoint (think `/users/sign_in`) the request will take the following path through the GitLab Service:
- nginx - Acts as our first line reverse proxy.
-- gitlab-workhorse - This determines if it needs to go to the Rails application or somewhere else to reduce load on Unicorn.
+- GitLab Workhorse - This determines if it needs to go to the Rails application or somewhere else to reduce load on Unicorn.
- unicorn - Since this is a web request, and it needs to access the application it will go to Unicorn.
- Postgres/Gitaly/Redis - Depending on the type of request, it may hit these services to store or retrieve data.
@@ -493,13 +493,13 @@ TODO
## System Layout
-When referring to `~git` in the pictures it means the home directory of the git user which is typically `/home/git`.
+When referring to `~git` in the pictures it means the home directory of the Git user which is typically `/home/git`.
GitLab is primarily installed within the `/home/git` user home directory as `git` user. Within the home directory is where the gitlabhq server software resides as well as the repositories (though the repository location is configurable).
The bare repositories are located in `/home/git/repositories`. GitLab is a ruby on rails application so the particulars of the inner workings can be learned by studying how a ruby on rails application works.
-To serve repositories over SSH there's an add-on application called gitlab-shell which is installed in `/home/git/gitlab-shell`.
+To serve repositories over SSH there's an add-on application called GitLab Shell which is installed in `/home/git/gitlab-shell`.
### Installation Folder Summary
@@ -523,7 +523,7 @@ processes: `unicorn_rails master` (1 process), `unicorn_rails worker`
### Repository access
-Repositories get accessed via HTTP or SSH. HTTP cloning/push/pull utilizes the GitLab API and SSH cloning is handled by gitlab-shell (previously explained).
+Repositories get accessed via HTTP or SSH. HTTP cloning/push/pull utilizes the GitLab API and SSH cloning is handled by GitLab Shell (previously explained).
## Troubleshooting
@@ -531,28 +531,28 @@ See the README for more information.
### Init scripts of the services
-The GitLab init script starts and stops Unicorn and Sidekiq.
+The GitLab init script starts and stops Unicorn and Sidekiq:
```
/etc/init.d/gitlab
Usage: service gitlab {start|stop|restart|reload|status}
```
-Redis (key-value store/non-persistent database)
+Redis (key-value store/non-persistent database):
```
/etc/init.d/redis
Usage: /etc/init.d/redis {start|stop|status|restart|condrestart|try-restart}
```
-SSH daemon
+SSH daemon:
```
/etc/init.d/sshd
Usage: /etc/init.d/sshd {start|stop|restart|reload|force-reload|condrestart|try-restart|status}
```
-Web server (one of the following)
+Web server (one of the following):
```
/etc/init.d/httpd
@@ -562,7 +562,7 @@ $ /etc/init.d/nginx
Usage: nginx {start|stop|restart|reload|force-reload|status|configtest}
```
-Persistent database
+Persistent database:
```
$ /etc/init.d/postgresql
@@ -571,34 +571,34 @@ Usage: /etc/init.d/postgresql {start|stop|restart|reload|force-reload|status} [v
### Log locations of the services
-gitlabhq (includes Unicorn and Sidekiq logs)
+gitlabhq (includes Unicorn and Sidekiq logs):
- `/home/git/gitlab/log/` contains `application.log`, `production.log`, `sidekiq.log`, `unicorn.stdout.log`, `git_json.log` and `unicorn.stderr.log` normally.
-gitlab-shell
+GitLab Shell:
- `/home/git/gitlab-shell/gitlab-shell.log`
-ssh
+SSH:
- `/var/log/auth.log` auth log (on Ubuntu).
- `/var/log/secure` auth log (on RHEL).
-nginx
+nginx:
- `/var/log/nginx/` contains error and access logs.
-Apache httpd
+Apache httpd:
- [Explanation of Apache logs](https://httpd.apache.org/docs/2.2/logs.html).
- `/var/log/apache2/` contains error and output logs (on Ubuntu).
- `/var/log/httpd/` contains error and output logs (on RHEL).
-redis
+Redis:
- `/var/log/redis/redis.log` there are also log-rotated logs there.
-PostgreSQL
+PostgreSQL:
- `/var/log/postgresql/*`
@@ -610,7 +610,7 @@ GitLab has configuration files located in `/home/git/gitlab/config/*`. Commonly
- `unicorn.rb` - Unicorn web server settings.
- `database.yml` - Database connection settings.
-gitlab-shell has a configuration file at `/home/git/gitlab-shell/config.yml`.
+GitLab Shell has a configuration file at `/home/git/gitlab-shell/config.yml`.
### Maintenance Tasks
@@ -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/automatic_ce_ee_merge.md b/doc/development/automatic_ce_ee_merge.md
index 158606aa6a2..c2700461467 100644
--- a/doc/development/automatic_ce_ee_merge.md
+++ b/doc/development/automatic_ce_ee_merge.md
@@ -174,9 +174,9 @@ Now, every time you create an MR for CE and EE:
## How we run the Automatic CE->EE merge at GitLab
At GitLab, we use the [Merge Train](https://gitlab.com/gitlab-org/merge-train)
-project to keep our [gitlab-ee](https://gitlab.com/gitlab-org/gitlab-ee)
+project to keep our [GitLab EE](https://gitlab.com/gitlab-org/gitlab-ee)
repository updated with commits from
-[gitlab-ce](https://gitlab.com/gitlab-org/gitlab-ce).
+[GitLab CE](https://gitlab.com/gitlab-org/gitlab-ce).
We have a mirror of the [Merge Train](https://gitlab.com/gitlab-org/merge-train)
project [configured](https://ops.gitlab.net/gitlab-org/merge-train) to run an
diff --git a/doc/development/background_migrations.md b/doc/development/background_migrations.md
index 642dac614c7..a456bbc781f 100644
--- a/doc/development/background_migrations.md
+++ b/doc/development/background_migrations.md
@@ -61,8 +61,8 @@ migration classes must be defined in the namespace
## Scheduling
-Scheduling a migration can be done in either a regular migration or a
-post-deployment migration. To do so, simply use the following code while
+Scheduling a background migration should be done in a post-deployment migration.
+To do so, simply use the following code while
replacing the class name and arguments with whatever values are necessary for
your migration:
@@ -283,10 +283,13 @@ the `services.properties` column.
## Testing
-It is required to write tests for background migrations' scheduling migration
-(either a regular migration or a post deployment migration), background
-migration itself and a cleanup migration. You can use the `:migration` RSpec
-tag when testing a regular / post deployment migration.
+It is required to write tests for:
+
+- The background migrations' scheduling migration.
+- The background migration itself.
+- A cleanup migration.
+
+You can use the `:migration` RSpec tag when testing the migrations.
See [README][migrations-readme].
When you do that, keep in mind that `before` and `after` RSpec hooks are going
@@ -294,7 +297,7 @@ to migrate you database down and up, which can result in other background
migrations being called. That means that using `spy` test doubles with
`have_received` is encouraged, instead of using regular test doubles, because
your expectations defined in a `it` block can conflict with what is being
-called in RSpec hooks. See [gitlab-org/gitlab-ce#35351][issue-rspec-hooks]
+called in RSpec hooks. See [issue #35351][issue-rspec-hooks]
for more details.
## Best practices
diff --git a/doc/development/build_test_package.md b/doc/development/build_test_package.md
index c5f6adfeaeb..21891f70d73 100644
--- a/doc/development/build_test_package.md
+++ b/doc/development/build_test_package.md
@@ -3,7 +3,7 @@
While developing a new feature or modifying an existing one, it is helpful if an
installable package (or a docker image) containing those changes is available
for testing. For this very purpose, a manual job is provided in the GitLab CI/CD
-pipeline that can be used to trigger a pipeline in the omnibus-gitlab repository
+pipeline that can be used to trigger a pipeline in the Omnibus GitLab repository
that will create:
- A deb package for Ubuntu 16.04, available as a build artifact, and
@@ -12,7 +12,7 @@ that will create:
(images titled `gitlab-ce` and `gitlab-ee` respectively and image tag is the
commit which triggered the pipeline).
-When you push a commit to either the gitlab-ce or gitlab-ee project, the
+When you push a commit to either the GitLab CE or GitLab EE project, the
pipeline for that commit will have a `build-package` manual action you can
trigger.
@@ -30,9 +30,9 @@ branch `0-1-stable`, modify the content of `GITALY_SERVER_VERSION` to
`0-1-stable` and push the commit. This will create a manual job that can be
used to trigger the build.
-## Specifying the branch in omnibus-gitlab repository
+## Specifying the branch in Omnibus GitLab repository
-In scenarios where a configuration change is to be introduced and omnibus-gitlab
+In scenarios where a configuration change is to be introduced and Omnibus GitLab
repository already has the necessary changes in a specific branch, you can build
a package against that branch through an environment variable named
`OMNIBUS_BRANCH`. To do this, specify that environment variable with the name of
diff --git a/doc/development/changelog.md b/doc/development/changelog.md
index bd07a01e782..814624c7586 100644
--- a/doc/development/changelog.md
+++ b/doc/development/changelog.md
@@ -35,6 +35,7 @@ the `author` field. GitLab team members **should not**.
- Any user-facing change **should** have a changelog entry. Example: "GitLab now
uses system fonts for all text."
+- Any docs-only changes **should not** have a changelog entry.
- Any change behind a feature flag **should not** have a changelog entry. The entry should be added [in the merge request removing the feature flags](feature_flags/development.md).
- A fix for a regression introduced and then fixed in the same release (i.e.,
fixing a bug introduced during a monthly release candidate) **should not**
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index b7d74b17eb3..bcfc0734c06 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!
@@ -116,8 +116,13 @@ warrant a comment could be:
- Any benchmarking performed to complement the change
- Potentially insecure code
-Do not add these comments directly to the source code, unless the
-reviewer requires you to do so.
+Avoid:
+
+- Adding comments (referenced above, or TODO items) directly to the source code unless the reviewer requires you to do so. If the comments are added due to an actionable task,
+a link to an issue must be included.
+- Assigning merge requests with failed tests to maintainers. If the tests are failing and you have to assign, ensure you leave a comment with an explanation.
+- Excessively mentioning maintainers through email or Slack (if the maintainer is reachable
+through Slack). If you can't assign a merge request, `@` mentioning a maintainer in a comment is acceptable and in all other cases assigning the merge request is sufficient.
This
[saves reviewers time and helps authors catch mistakes earlier](https://www.ibm.com/developerworks/rational/library/11-proven-practices-for-peer-review/index.html#__RefHeading__97_174136755).
diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md
index 853882e8642..887f17b05b8 100644
--- a/doc/development/contributing/index.md
+++ b/doc/development/contributing/index.md
@@ -65,7 +65,7 @@ Sign up for the mailing list, answer GitLab questions on StackOverflow or
respond in the IRC channel. You can also sign up on [CodeTriage][codetriage] to help with
the remaining issues on the GitHub issue tracker.
-## I want to contribute!
+## I want to contribute
If you want to contribute to GitLab,
[issues with the `Accepting merge requests` label](issue_workflow.md#label-for-community-contributors)
@@ -93,26 +93,20 @@ When submitting code to GitLab, you may feel that your contribution requires the
When your code contains more than 500 changes, any major breaking changes, or an external library, `@mention` a maintainer in the merge request. If you are not sure who to mention, the reviewer will add one early in the merge request process.
-## Issues
+## Issues workflow
-This [documentation](issue_workflow.md) outlines the current issue process.
+This [documentation](issue_workflow.md) outlines the current issue workflow:
-- [Type labels](issue_workflow.md#type-labels)
-- [Subject labels](issue_workflow.md#subject-labels)
-- [Team labels](issue_workflow.md#team-labels)
-- [Release Scoping labels](issue_workflow.md#release-scoping-labels)
-- [Priority labels](issue_workflow.md#priority-labels)
-- [Severity labels](issue_workflow.md#severity-labels)
-- [Label for community contributors](issue_workflow.md#label-for-community-contributors)
+- [Issue tracker guidelines](issue_workflow.md#issue-tracker-guidelines)
- [Issue triaging](issue_workflow.md#issue-triaging)
+- [Labels](issue_workflow.md#labels)
- [Feature proposals](issue_workflow.md#feature-proposals)
-- [Issue tracker guidelines](issue_workflow.md#issue-tracker-guidelines)
- [Issue weight](issue_workflow.md#issue-weight)
- [Regression issues](issue_workflow.md#regression-issues)
- [Technical and UX debt](issue_workflow.md#technical-and-ux-debt)
-- [Stewardship](issue_workflow.md#stewardship)
+- [Technical debt in follow-up issues](issue_workflow.md#technical-debt-in-follow-up-issues)
-## Merge requests
+## Merge requests workflow
This [documentation](merge_request_workflow.md) outlines the current merge request process.
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index b2e3ef7bf63..8b5d380ad9e 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -1,17 +1,51 @@
-# Workflow labels
+# Issues workflow
-To allow for asynchronous issue handling, we use [milestones][milestones-page]
+## Issue tracker guidelines
+
+**[Search the issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues)** for similar entries before
+submitting your own, there's a good chance somebody else had the same issue or
+feature proposal. Show your support with an award emoji and/or join the
+discussion.
+
+Please submit bugs using the ['Bug' issue template](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/issue_templates/Bug.md) provided on the issue tracker.
+The text in the parenthesis is there to help you with what to include. Omit it
+when submitting the actual issue. You can copy-paste it and then edit as you
+see fit.
+
+## Issue triaging
+
+Our issue triage policies are [described in our handbook](https://about.gitlab.com/handbook/engineering/issue-triage/).
+You are very welcome to help the GitLab team triage issues.
+We also organize [issue bash events](https://gitlab.com/gitlab-org/gitlab-ce/issues/17815)
+once every quarter.
+
+The most important thing is making sure valid issues receive feedback from the
+development team. Therefore the priority is mentioning developers that can help
+on those issues. Please select someone with relevant experience from the
+[GitLab team](https://about.gitlab.com/team/).
+If there is nobody mentioned with that expertise look in the commit history for
+the affected files to find someone.
+
+We also use [GitLab Triage](https://gitlab.com/gitlab-org/gitlab-triage) to
+automate some triaging policies. This is currently set up as a
+[scheduled pipeline](https://gitlab.com/gitlab-org/quality/triage-ops/pipeline_schedules/10512/edit)
+running on [quality/triage-ops](https://gitlab.com/gitlab-org/quality/triage-ops) project.
+
+## Labels
+
+To allow for asynchronous issue handling, we use [milestones](https://gitlab.com/groups/gitlab-org/-/milestones)
and [labels](https://gitlab.com/gitlab-org/gitlab-ce/-/labels). Leads and product managers handle most of the
scheduling into milestones. Labelling is a task for everyone.
Most issues will have labels for at least one of the following:
- Type: ~feature, ~bug, ~backstage, etc.
-- Subject: ~wiki, ~"Container Registry", ~ldap, ~api, etc.
-- Team: ~Documentation, ~Delivery, etc.
- Stage: ~"devops::plan", ~"devops::create", etc.
-- Group: ~"group::source code" ~"group::knowledge" ~"group::editor", etc.
+- Group: ~"group::source code", ~"group::knowledge", ~"group::editor", etc.
+- Category: ~"Category:Code Analytics", ~"Category:DevOps Score", ~"Category:Templates", etc.
+- Feature: ~wiki, ~ldap, ~api, ~issues, ~"merge requests", etc.
- Department: ~UX, ~Quality
+- Team: ~Documentation, ~Delivery
- Specialization: ~frontend, ~backend
- Release Scoping: ~Deliverable, ~Stretch, ~"Next Patch Release"
- Priority: ~P1, ~P2, ~P3, ~P4
@@ -23,14 +57,18 @@ All labels, their meaning and priority are defined on the
If you come across an issue that has none of these, and you're allowed to set
labels, you can _always_ add the team and type, and often also the subject.
-[milestones-page]: https://gitlab.com/groups/gitlab-org/-/milestones
-
-## Type labels
+### Type labels
Type labels are very important. They define what kind of issue this is. Every
-issue should have one or more.
+issue should have one and only one.
+
+The current type labels are:
-Examples of type labels are ~feature, ~bug, ~backstage and ~security
+- ~feature
+- ~bug
+- ~backstage
+- ~"support request"
+- ~meta
A number of type labels have a priority assigned to them, which automatically
makes them float to the top, depending on their importance.
@@ -38,68 +76,74 @@ makes them float to the top, depending on their importance.
Type labels are always lowercase, and can have any color, besides blue (which is
already reserved for subject labels).
-The descriptions on the [labels page](https://gitlab.com/gitlab-org/gitlab-ce/-/labels) explain what falls under each type label.
+The descriptions on the [labels page](https://gitlab.com/groups/gitlab-org/-/labels)
+explain what falls under each type label.
-## Subject labels
+### Facet labels
-Subject labels are labels that define what area or feature of GitLab this issue
-hits. They are not always necessary, but very convenient.
+Sometimes it's useful to refine the type of an issue. In those cases, you can
+add facet labels.
-Subject labels are now used to infer and apply relevant group and devops stage
-labels. Please apply them whenever possible to facilitate accurate matching.
-Please refer to [this merge request][inferred-labels] for more information.
+Following is a non-exhaustive list of facet labels:
-Examples of subject labels are ~wiki, ~ldap, ~api,
-~issues, ~"merge requests", ~labels, and ~"Container Registry".
+- ~enhancement: This label can refine an issue that has the ~feature label.
+- ~"master:broken": This label can refine an issue that has the ~bug label.
+- ~"master:flaky": This label can refine an issue that has the ~bug label.
+- ~"technical debt": This label can refine an issue that has the ~backstage label.
+- ~"static analysis": This label can refine an issue that has the ~backstage label.
+- ~"ci-build": This label can refine an issue that has the ~backstage label.
+- ~performance: A performance issue could describe a ~bug or a ~feature.
+- ~security: A security issue could describe a ~bug or a ~feature.
+- ~database: A database issue could describe a ~bug or a ~feature.
+- ~customer: This relates to an issue that was created by a customer, or that is of interest for a customer.
-If you are an expert in a particular area, it makes it easier to find issues to
-work on. You can also subscribe to those labels to receive an email each time an
-issue is labeled with a subject label corresponding to your expertise.
+### Stage labels
-Subject labels are always all-lowercase.
+Stage labels specify which [stage](https://about.gitlab.com/handbook/product/categories/#hierarchy) the issue belongs to.
-## Team labels
-
-**Important**: Most of the historical team labels (e.g. Manage, Plan etc.) are
-now deprecated in favor of [Group labels](#group-labels) and [Stage labels](#stage-labels).
-
-Team labels specify what team is responsible for this issue.
-Assigning a team label makes sure issues get the attention of the appropriate
-people.
+#### Naming and color convention
-The current team labels are:
+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 a space.
-- ~Delivery
-- ~Documentation
-- ~Quality
+For instance, the "Manage" stage is represented by the ~"devops::manage" label in
+the `gitlab-org` group since its key under `stages` is `manage`.
-Team labels are always capitalized so that they show up as the first label for
-any issue.
+The current stage labels can be found by [searching the labels list for `devops::`](https://gitlab.com/groups/gitlab-org/-/labels?search=devops::).
-## Stage labels
+These labels are [scoped labels](../../user/project/labels.md#scoped-labels-premium)
+and thus are mutually exclusive.
-Stage labels specify which [DevOps stage][devops-stages] the issue belongs to.
+The Stage labels are used to generate the [direction pages](https://about.gitlab.com/direction/) automatically.
-The current stage labels can be found by [searching the labels list for `devops::`](https://gitlab.com/groups/gitlab-org/-/labels?search=devops%3A%3A).
+### Group labels
-These labels are [scoped labels](../../user/project/labels.md#scoped-labels-premium)
-and thus are mutually exclusive.
+Group labels specify which [groups](https://about.gitlab.com/company/team/structure/#product-groups) the issue belongs to.
-The Stage labels are used to generate the [direction pages][direction-pages] automatically.
+It's highly recommended to add a group label, as it's used by our triage
+automation to
+[infer the correct stage label](https://about.gitlab.com/handbook/engineering/quality/triage-operations/#auto-labelling-of-issues).
-[devops-stages]: https://about.gitlab.com/direction/#devops-stages
-[direction-pages]: https://about.gitlab.com/direction/
+#### Naming and color convention
-## Group labels
+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 a space.
-Group labels specify which [groups][structure-groups] the issue belongs to.
+For instance, the "Continuous Integration" group is represented by the
+~"group::continuous integration" label in the `gitlab-org` group since its key
+under `stages.manage.groups` is `continuous_integration`.
-The current group labels can be found by [searching the labels list for `group::`](https://gitlab.com/groups/gitlab-org/-/labels?search=group%3A%3A).
+The current group labels can be found by [searching the labels list for `group::`](https://gitlab.com/groups/gitlab-org/-/labels?search=group::).
These labels are [scoped labels](../../user/project/labels.md#scoped-labels-premium)
and thus are mutually exclusive.
-You can find the groups listed in the [Product Stages, Groups, and Categories][product-categories] page.
+You can find the groups listed in the [Product Stages, Groups, and Categories](https://about.gitlab.com/handbook/product/categories/) page.
We use the term group to map down product requirements from our product stages.
As a team needs some way to collect the work their members are planning to be assigned to, we use the `~group::` labels to do so.
@@ -110,24 +154,95 @@ any issue can be picked up by any group, depending on current priorities. For ex
We also use stage and group labels to help quantify our [throughput](https://about.gitlab.com/handbook/engineering/management/throughput).
Please read [Stage and Group labels in Throughtput](https://about.gitlab.com/handbook/engineering/management/throughput/#stage-and-group-labels-in-throughput) for more information on how the labels are used in this context.
-[structure-groups]: https://about.gitlab.com/company/team/structure/#groups
-[product-categories]: https://about.gitlab.com/handbook/product/categories/
+### Category labels
+
+From the handbook's
+[Product stages, groups, and categories](https://about.gitlab.com/handbook/product/categories/#hierarchy)
+page:
+
+> Categories are high-level capabilities that may be a standalone product at
+another company. e.g. Portfolio Management.
+
+It's highly recommended to add a category label, as it's used by our triage
+automation to
+[infer the correct group and stage labels](https://about.gitlab.com/handbook/engineering/quality/triage-operations/#auto-labelling-of-issues).
+
+If you are an expert in a particular area, it makes it easier to find issues to
+work on. You can also subscribe to those labels to receive an email each time an
+issue is labeled with a category label corresponding to your expertise.
+
+#### Naming and color convention
+
+Category labels respects the `Category:<Category Name>` naming convention and
+their color is `#428BCA`.
+`<Category Name>` is the category name as it is in the single source of truth for categories at
+<https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/categories.yml>.
+
+For instance, the "Code Analytics" category is represented by the
+~"Category:Code Analytics" label in the `gitlab-org` group since its
+`code_analytics.name` value is "Code Analytics".
+
+If a category's label doesn't respect this naming convention, it should be specified
+with [the `label` attribute](https://about.gitlab.com/handbook/marketing/website/#category-attributes)
+in <https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/categories.yml>.
+
+### Feature labels
+
+From the handbook's
+[Product stages, groups, and categories](https://about.gitlab.com/handbook/product/categories/#hierarchy)
+page:
+
+> Features: Small, discrete functionalities. e.g. Issue weights. Some common
+features are listed within parentheses to facilitate finding responsible PMs by keyword.
+
+It's highly recommended to add a feature label if no category label applies, as
+it's used by our triage automation to
+[infer the correct group and stage labels](https://about.gitlab.com/handbook/engineering/quality/triage-operations/#auto-labelling-of-issues).
+
+If you are an expert in a particular area, it makes it easier to find issues to
+work on. You can also subscribe to those labels to receive an email each time an
+issue is labeled with a feature label corresponding to your expertise.
+
+Examples of feature labels are ~wiki, ~ldap, ~api, ~issues, ~"merge requests" etc.
+
+#### Naming and color convention
-## Department labels
+Feature labels are all-lowercase.
+
+### Department labels
The current department labels are:
- ~UX
- ~Quality
-## Specialization labels
+### Team labels
+
+**Important**: Most of the historical team labels (e.g. Manage, Plan etc.) are
+now deprecated in favor of [Group labels](#group-labels) and [Stage labels](#stage-labels).
+
+Team labels specify what team is responsible for this issue.
+Assigning a team label makes sure issues get the attention of the appropriate
+people.
+
+The current team labels are:
+
+- ~Delivery
+- ~Documentation
+
+#### Naming and color convention
+
+Team labels are always capitalized so that they show up as the first label for
+any issue.
+
+### Specialization labels
These labels narrow the [specialization](https://about.gitlab.com/company/team/structure/#specialist) on a unit of work.
- ~frontend
- ~backend
-## Release Scoping labels
+### Release scoping labels
Release Scoping labels help us clearly communicate expectations of the work for the
release. There are three levels of Release Scoping labels:
@@ -145,7 +260,7 @@ Each issue scheduled for the current milestone should be labeled ~Deliverable
or ~"Stretch". Any open issue for a previous milestone should be labeled
~"Next Patch Release", or otherwise rescheduled to a different milestone.
-### Priority labels
+#### Priority labels
Priority labels help us define the time a ~bug fix should be completed. Priority determines how quickly the defect turnaround time must be.
If there are multiple defects, the priority decides which defect has to be fixed immediately versus later.
@@ -158,7 +273,7 @@ This label documents the planned timeline & urgency which is used to measure aga
| ~P3 | Medium Priority | Within the next 3 releases (approx one quarter or 90 days) |
| ~P4 | Low Priority | Anything outside the next 3 releases (more than one quarter or 120 days) |
-## Severity labels
+### Severity labels
Severity labels help us clearly communicate the impact of a ~bug on users.
There can be multiple facets of the impact. The below is a guideline.
@@ -187,7 +302,7 @@ If a bug seems to fall between two severity labels, assign it to the higher-seve
- Label colors are incorrect.
- UI elements are not fully aligned.
-## Label for community contributors
+### Label for community contributors
Issues that are beneficial to our users, 'nice to haves', that we currently do
not have the capacity for or want to give the priority to, are labeled as
@@ -217,11 +332,11 @@ After adding the ~"Accepting merge requests" label, we try to estimate the
[weight](#issue-weight) of the issue. We use issue weight to let contributors
know how difficult the issue is. Additionally:
-- We advertise [`Accepting merge requests` issues with weight < 5][up-for-grabs]
+- We advertise [`Accepting merge requests` issues with weight < 5](https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=None&sort=weight)
as suitable for people that have never contributed to GitLab before on the
[Up For Grabs campaign](http://up-for-grabs.net)
- We encourage people that have never contributed to any open source project to
- look for [`Accepting merge requests` issues with a weight of 1][first-timers]
+ look for [`Accepting merge requests` issues with a weight of 1](https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=None&sort=weight&weight=1)
If you've decided that you would like to work on an issue, please @-mention
the [appropriate product manager](https://about.gitlab.com/handbook/product/#who-to-talk-to-for-what)
@@ -234,42 +349,29 @@ GitLab team members who apply the ~"Accepting merge requests" label to an issue
should update the issue description with a responsible product manager, inviting
any potential community contributor to @-mention per above.
-[up-for-grabs]: https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=None&sort=weight
-[first-timers]: https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=None&sort=weight&weight=1
-
-## Issue triaging
-
-Our issue triage policies are [described in our handbook]. You are very welcome
-to help the GitLab team triage issues. We also organize [issue bash events] once
-every quarter.
+### Stewardship label
-The most important thing is making sure valid issues receive feedback from the
-development team. Therefore the priority is mentioning developers that can help
-on those issues. Please select someone with relevant experience from the
-[GitLab team][team]. If there is nobody mentioned with that expertise look in
-the commit history for the affected files to find someone.
+For issues related to the open source stewardship of GitLab,
+there is the ~"stewardship" label.
-We also use [GitLab Triage] to automate some triaging policies. This is
-currently set up as a [scheduled pipeline] running on [quality/triage-ops]
-project.
+This label is to be used for issues in which the stewardship of GitLab
+is a topic of discussion. For instance if GitLab Inc. is planning to add
+features from GitLab EE to GitLab CE, related issues would be labelled with
+~"stewardship".
-[described in our handbook]: https://about.gitlab.com/handbook/engineering/issue-triage/
-[issue bash events]: https://gitlab.com/gitlab-org/gitlab-ce/issues/17815
-[GitLab Triage]: https://gitlab.com/gitlab-org/gitlab-triage
-[scheduled pipeline]: https://gitlab.com/gitlab-org/quality/triage-ops/pipeline_schedules/10512/edit
-[quality/triage-ops]: https://gitlab.com/gitlab-org/quality/triage-ops
-[team]: https://about.gitlab.com/team/
+A recent example of this was the issue for
+[bringing the time tracking API to GitLab CE](https://gitlab.com/gitlab-org/gitlab-ce/issues/25517#note_20019084).
## Feature proposals
To create a feature proposal for CE, open an issue on the
-[issue tracker of CE][ce-tracker].
+[issue tracker of CE](https://gitlab.com/gitlab-org/gitlab-ce/issues).
For feature proposals for EE, open an issue on the
-[issue tracker of EE][ee-tracker].
+[issue tracker of EE](https://gitlab.com/gitlab-org/gitlab-ee/issues).
In order to help track the feature proposals, we have created a
-[`feature`][fl] label. For the time being, users that are not members
+[`feature`](https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=feature) label. For the time being, users that are not members
of the project cannot add labels. You can instead ask one of the [core team](https://about.gitlab.com/community/core-team/)
members to add the label ~feature to the issue or add the following
code snippet right after your description in a new line: `~feature`.
@@ -286,20 +388,6 @@ need to ask one of the [core team](https://about.gitlab.com/community/core-team/
If you want to create something yourself, consider opening an issue first to
discuss whether it is interesting to include this in GitLab.
-[fl]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=feature
-
-## Issue tracker guidelines
-
-**[Search the issue tracker][ce-tracker]** for similar entries before
-submitting your own, there's a good chance somebody else had the same issue or
-feature proposal. Show your support with an award emoji and/or join the
-discussion.
-
-Please submit bugs using the ['Bug' issue template](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/issue_templates/Bug.md) provided on the issue tracker.
-The text in the parenthesis is there to help you with what to include. Omit it
-when submitting the actual issue. You can copy-paste it and then edit as you
-see fit.
-
## Issue weight
Issue weight allows us to get an idea of the amount of work required to solve
@@ -345,7 +433,7 @@ addressed.
## Technical and UX debt
In order to track things that can be improved in GitLab's codebase,
-we use the ~"technical debt" label in [GitLab's issue tracker][ce-tracker].
+we use the ~"technical debt" label in [GitLab's issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues).
For missed user experience requirements, we use the ~"UX debt" label.
These labels should be added to issues that describe things that can be improved,
@@ -406,25 +494,6 @@ should be of the same quality as those created
**must not** begin with `Follow-up`! The creating maintainer should also expect
to be involved in some capacity when work begins on the follow-up issue.
-## Stewardship
-
-For issues related to the open source stewardship of GitLab,
-there is the ~"stewardship" label.
-
-This label is to be used for issues in which the stewardship of GitLab
-is a topic of discussion. For instance if GitLab Inc. is planning to add
-features from GitLab EE to GitLab CE, related issues would be labelled with
-~"stewardship".
-
-A recent example of this was the issue for
-[bringing the time tracking API to GitLab CE][time-tracking-issue].
-
-[time-tracking-issue]: https://gitlab.com/gitlab-org/gitlab-ce/issues/25517#note_20019084
-
---
[Return to Contributing documentation](index.md)
-
-[ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues
-[ee-tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues
-[inferred-labels]: https://gitlab.com/gitlab-org/quality/triage-ops/merge_requests/155
diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md
index 4e9c5c81379..bdb026d498d 100644
--- a/doc/development/contributing/merge_request_workflow.md
+++ b/doc/development/contributing/merge_request_workflow.md
@@ -1,4 +1,4 @@
-# Merge requests
+# Merge requests workflow
We welcome merge requests from everyone, with fixes and improvements
to GitLab code, tests, and documentation. The issues that are specifically suitable
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_debugging.md b/doc/development/database_debugging.md
index eb3b227473b..6c9fa983c96 100644
--- a/doc/development/database_debugging.md
+++ b/doc/development/database_debugging.md
@@ -9,7 +9,7 @@ An easy first step is to search for your error in Slack or google "GitLab (my er
Available `RAILS_ENV`
-- `production` (generally not for your main GDK db, but you may need this for e.g. omnibus)
+- `production` (generally not for your main GDK db, but you may need this for e.g. Omnibus)
- `development` (this is your main GDK db)
- `test` (used for tests like rspec)
diff --git a/doc/development/database_review.md b/doc/development/database_review.md
index 3f1b359cb0b..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:
@@ -91,7 +92,7 @@ and details for a database reviewer:
concurrent index/foreign key helpers (with transactions disabled)
- Check consistency with `db/schema.rb` and that migrations are [reversible](migration_style_guide.md#reversibility)
- Check queries timing (If any): Queries executed in a migration
- need to fit comfortable within `15s` - preferably much less than that - on GitLab.com.
+ need to fit comfortably within `15s` - preferably much less than that - on GitLab.com.
- Check [background migrations](background_migrations.md):
- For data migrations, establish a time estimate for execution
- They should only be used when migrating data in larger tables.
diff --git a/doc/development/documentation/feature-change-workflow.md b/doc/development/documentation/feature-change-workflow.md
index ac93ada5a4b..00c76fe0f1b 100644
--- a/doc/development/documentation/feature-change-workflow.md
+++ b/doc/development/documentation/feature-change-workflow.md
@@ -69,7 +69,7 @@ To follow a consistent workflow every month, documentation changes
involve the Product Managers, the developer who shipped the feature,
and the technical writer for the DevOps stage. Each role is described below.
-The Documentation items in the GitLab CE/EE [Feature Proposal issue template](https://gitlab.com/gitlab-org/gitlab-ce/raw/template-improvements-for-documentation/.gitlab/issue_templates/Feature%20proposal.md)
+The Documentation items in the GitLab CE/EE [Feature Proposal issue template](https://gitlab.com/gitlab-org/gitlab-ce/raw/master/.gitlab/issue_templates/Feature%20proposal.md)
and default merge request template will assist you with following this process.
### Product Manager role
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index c9ae00d148a..c267669aa79 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,15 +455,15 @@ 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
to [check locally](#previewing-the-changes-live) before pushing to GitLab by executing the command
`bundle exec nanoc check internal_links` on your local
[`gitlab-docs`](https://gitlab.com/gitlab-org/gitlab-docs) directory. In addition,
- `docs-lint` also runs [markdownlint](styleguide.md#markdown-rules) to ensure the
+ `docs-lint` also runs [`markdownlint`](#markdownlint) to ensure the
markdown is consistent across all documentation.
1. [`ee_compat_check`](../automatic_ce_ee_merge.md#avoiding-ce-ee-merge-conflicts-beforehand) (runs on CE only):
When you submit a merge request to GitLab Community Edition (CE),
@@ -484,14 +481,14 @@ 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.
The following are some suggested linters you can install locally and sample configuration:
- [`proselint`](#proselint)
-- [`markdownlint`](#markdownlint)
+- [`markdownlint`](#markdownlint), which is the same as the test run in [`docs-lint`](#testing)
NOTE: **Note:**
This list does not limit what other linters you can add to your local documentation writing toolchain.
@@ -537,76 +534,56 @@ A file with `proselint` configuration must be placed in a
#### `markdownlint`
-`markdownlint` checks that certain rules ([example](https://github.com/DavidAnson/markdownlint/blob/master/README.md#rules--aliases))
-are followed for Markdown syntax. Our [Documentation Style Guide](styleguide.md) and
-[Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/)
+[`markdownlint`](https://github.com/DavidAnson/markdownlint) checks that markdown
+syntax follows [certain rules](https://github.com/DavidAnson/markdownlint/blob/master/doc/Rules.md#rules),
+and is used by the [`docs-lint` test](#testing) with a [configuration file](#markdownlint-configuration).
+Our [Documentation Style Guide](styleguide.md#markdown) and [Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/)
elaborate on which choices must be made when selecting Markdown syntax for GitLab
-documentation. This tool helps catch deviations from those guidelines, and matches the
-tests run on the documentation by [`docs-lint`](#testing).
+documentation. This tool helps catch deviations from those guidelines.
`markdownlint` can be used [on the command line](https://github.com/igorshubovych/markdownlint-cli#markdownlint-cli--),
either on a single Markdown file or on all Markdown files in a project. For example, to run
`markdownlint` on all documentation in the [`gitlab-ce` project](https://gitlab.com/gitlab-org/gitlab-ce),
-run the following commands from within the `gitlab-ce` project:
+run the following commands from within your `gitlab-ce` project root directory, which will
+automatically detect the [`.markdownlint.json`](#markdownlint-configuration) config
+file in the root of the project, and test all files in `/doc` and its subdirectories:
```sh
-cd doc
-markdownlint **/*.md
+markdownlint 'doc/**/*.md'
```
-`markdownlint` can also be run from within editors using plugins. For example, the following plugins
- are available:
+If you wish to use a different config file, use the `-c` flag:
+
+```sh
+markdownlint -c <config-file-name> 'doc/**/*.md'
+```
+
+`markdownlint` can also be run from within text editors using [plugins/extensions](https://github.com/DavidAnson/markdownlint#related),
+such as:
- [Sublime Text](https://packagecontrol.io/packages/SublimeLinter-contrib-markdownlint)
- [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint)
-- [Others](https://github.com/DavidAnson/markdownlint#related)
+- [Atom](https://atom.io/packages/linter-node-markdownlint)
-##### Sample `markdownlint` configuration
+It is best to use the [same configuration file](#markdownlint-configuration) as what
+is in use in the four repos that are the sources for <https://docs.gitlab.com>. Each
+plugin/extension has different requirements regarding the configuration file, which
+is explained in each editor's docs.
-The following sample `markdownlint` configuration modifies the available default rules to:
+##### `markdownlint` configuration
-- Adhere to the [Documentation Style Guide](styleguide.md).
-- Apply conventions found in the GitLab documentation.
-- Allow the flexibility of using some inline HTML.
+Each formatting issue that `markdownlint` checks has an associated
+[rule](https://github.com/DavidAnson/markdownlint/blob/master/doc/Rules.md#rules).
+These rules are configured in the `.markdownlint.json` files located in the root of
+four repos that are the sources for <https://docs.gitlab.com>:
-```json
-{
- "default": true,
- "header-style": { "style": "atx" },
- "ul-style": { "style": "dash" },
- "line-length": false,
- "no-trailing-punctuation": false,
- "ol-prefix": { "style": "one" },
- "blanks-around-fences": true,
- "no-inline-html": {
- "allowed_elements": [
- "table",
- "tbody",
- "tr",
- "td",
- "ul",
- "ol",
- "li",
- "br",
- "img",
- "a",
- "strong",
- "i",
- "div",
- "b"
- ]
- },
- "hr-style": { "style": "---" },
- "code-block-style": { "style": "fenced" },
- "fenced-code-language": false,
- "no-duplicate-header": { "allow_different_nesting": true },
- "commands-show-output": false
-}
-```
+- <https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.markdownlint.json>
+- <https://gitlab.com/gitlab-org/gitlab-runner/blob/master/.markdownlint.json>
+- <https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/.markdownlint.json>
+- <https://gitlab.com/charts/gitlab/blob/master/.markdownlint.json>
-For [`markdownlint`](https://github.com/DavidAnson/markdownlint/), this configuration must be
-placed in a [valid location](https://github.com/igorshubovych/markdownlint-cli#configuration). For
-example, `~/.markdownlintrc`.
+By default all rules are enabled, so the configuration file is used to disable unwanted
+rules, and also to configure optional parameters for enabled rules as needed.
## Danger Bot
diff --git a/doc/development/documentation/site_architecture/global_nav.md b/doc/development/documentation/site_architecture/global_nav.md
index 753a636a779..be4d5b5353f 100644
--- a/doc/development/documentation/site_architecture/global_nav.md
+++ b/doc/development/documentation/site_architecture/global_nav.md
@@ -4,30 +4,74 @@ description: "Learn how GitLab docs' global navigation works and how to add new
# Global navigation
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab-docs/merge_requests/362)
-in GitLab 11.6.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-docs/merge_requests/362) in GitLab 11.6.
> - [Updated](https://gitlab.com/gitlab-org/gitlab-docs/merge_requests/482) in GitLab 12.1.
+> - [Per-project](https://gitlab.com/gitlab-org/gitlab-docs/merge_requests/498) navigation added in GitLab 12.2.
-The global nav adds to the left sidebar the ability to
-navigate and explore the contents of GitLab's documentation.
+Global navigation (the left-most pane in our three pane documentation) provides:
-The global nav should be maintained consistent through time to allow the
-users to locate their most-visited links easily to facilitate navigation.
-Therefore, any updates must be carefully considered by the technical writers.
+- A high-level grouped view of product features.
+- The ability to discover new features by browsing the menu structure.
+- A way to allow the reader to focus on product areas.
+- The ability to refine landing pages, so they don't have to do all the work of surfacing
+ every page contained within the documentation.
-## Adding new items to the global nav
+## Adding new items
-To add a new doc to the nav, first and foremost, check with the technical writing team:
+All new pages need a new navigation item. Without a navigation, the page becomes "orphaned". That
+is:
-- If it's applicable
-- What's the exact position the doc will be added to the nav
+- The navigation shuts when the page is opened, and the reader loses their place.
+- The page doesn't belong in a group with other pages.
-Once you get their approval and their guidance in regards to the position on the nav,
-read trhough this page to understand how it works, and submit a merge request to the
-docs site, adding the doc you wish to include in the nav into the
-[global nav data file](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/content/_data/global-nav.yaml).
+This means the decision to create a new page is a decision to create new navigation item and vice
+versa.
-Don't forget to ask a technical writer to review your changes before merging.
+### Where to add
+
+Documentation pages can be said to belong in the following groups:
+
+- GitLab users. This is documentation for day-to-day use of GitLab for users with any level
+ of permissions, from Reporter to Owner.
+- GitLab administrators. This tends to be documentation for self-managed instances that requires
+ access to the underlying infrastructure hosting GitLab.
+- Other documentation. This includes documentation for customers outside their day-to-day use of
+ GitLab and for contributors. Documentation that doesn't fit in the other groups belongs here.
+
+With these groups in mind, the following are general rules for where new items should be added.
+
+- User documentation for:
+ - Group-level features belongs under **Groups**.
+ - Project-level features belongs under **Projects**.
+ - Features outside a group or project level (sometimes called "instance-level") can be placed at
+ the top-level, but care must be taken not to overwhelm that top-level space. If possible, such
+ features could be grouped in some way.
+ - Outside the above, most other miscellaneous user documentation belongs under **User**.
+- Administration documentation belongs under **Administrator**.
+- Other documentation belongs at the top-level, but care must be taken to not create an enormously
+ long top-level navigation, which defeats the purpose of it.
+
+NOTE: **Note:**
+Making all documentation and navigation items adhere to these principles is being progressively
+rolled out.
+
+### What to add
+
+Having decided where to add a navigation element, the next step is deciding what to add. The
+mechanics of what is required is [documented below](#data-file) but, in principle:
+
+- Navigation item text (that which the reader sees) should:
+ - Be as short as possible.
+ - Be contextual. It's rare to need to repeat text from a parent item.
+ - Avoid jargon or terms of art, unless ubiquitous. For example, **CI** is an acceptable
+ substitution for **Continuous Integration**.
+- Navigation links must follow the rules documented in the [data file](#data-file).
+- EE badging is subject to the following:
+ - Required when linking to an EE-only page.
+ - Not required when linking to a page that is a mix of CE and EE-only content.
+ - Required when all sub-items are EE-only. In this case, no sub-items are EE badged.
+ - Not required when sub-items are a mix of CE and EE-only items. In this case, each item is
+ badged appropriately.
## How it works
@@ -55,7 +99,7 @@ To see the improvements planned, check the
[global nav epic](https://gitlab.com/groups/gitlab-com/-/epics/21).
CAUTION: **Attention!**
-**Do not** [add items](#adding-new-items-to-the-global-nav) to the global nav without
+**Do not** [add items](#adding-new-items) to the global nav without
the consent of one of the technical writers.
## Composition
@@ -70,8 +114,13 @@ the data among the nav in containers properly [styled](#css-classes).
### Data file
-The [data file](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/content/_data/global-nav.yaml)
-is structured in three components: sections, categories, and docs.
+The data file describes the structure of the navigation for the applicable project. All data files
+are stored at <https://gitlab.com/gitlab-org/gitlab-docs/blob/master/content/_data/> and comprise
+three components:
+
+- Sections
+- Categories
+- Docs
#### Sections
diff --git a/doc/development/documentation/site_architecture/index.md b/doc/development/documentation/site_architecture/index.md
index 1aef0ed855c..bb598906967 100644
--- a/doc/development/documentation/site_architecture/index.md
+++ b/doc/development/documentation/site_architecture/index.md
@@ -66,10 +66,12 @@ the GitLab Documentation website.
- [Google Analytics](https://marketingplatform.google.com/about/analytics/)
- [Google Tag Manager](https://developers.google.com/tag-manager/)
-## Global nav
+## Global navigation
-To understand how the global nav (left sidebar) is built, please
-read through the [global navigation](global_nav.md) doc.
+Read through the global navigation](global_nav.md) documentation to understand:
+
+- How the global navigation is built.
+- How to add new navigation items.
## Deployment
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/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index c1e3eb9680b..09dd31e2aee 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -110,18 +110,11 @@ Hard-coded HTML is valid, although it's discouraged to be used while we have `/h
### Markdown Rules
GitLab ensures that the Markdown used across all documentation is consistent, as
-well as easy to review and maintain, by testing documentation changes with
-[Markdownlint (mdl)](https://github.com/markdownlint/markdownlint). This lint test
-checks many common problems with Markdown, and fails when any document has an issue
+well as easy to review and maintain, by [testing documentation changes](index.md#testing) with
+[`markdownlint`](index.md#markdownlint). This lint test fails when any document has an issue
with Markdown formatting that may cause the page to render incorrectly within GitLab.
It will also fail when a document is using non-standard Markdown (which may render
-correctly, but is not the current standard in GitLab documentation).
-
-Each formatting issue that mdl checks has an associated [rule](https://github.com/markdownlint/markdownlint/blob/master/docs/RULES.md),
-and the rules that are currently enabled for GitLab documentation are listed in the
-[`.mdlrc.style`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.mdlrc.style) file.
-Configuration options are set in the [`.mdlrc`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.mdlrc.style)
-file.
+correctly, but is not the current standard for GitLab documentation).
## Structure
@@ -460,7 +453,7 @@ For other punctuation rules, please refer to the
This is to ensure that no document with wrong heading is going
live without an audit, thus preventing dead links and redirection issues when
corrected.
-- Leave exactly one new line after a heading.
+- Leave exactly one blank line before and after a heading.
- Do not use links in headings.
- Add the corresponding [product badge](#product-badges) according to the tier the feature belongs.
@@ -506,15 +499,6 @@ Example:
For more information, see the [confidential issue](../../user/project/issues/confidential_issues.md) `https://gitlab.com/gitlab-org/gitlab-ce/issues/<issue_number>`.
```
-### Unlinking emails
-
-By default, all email addresses will render in an email tag on docs.gitlab.com.
-To escape the code block and unlink email addresses, use two backticks:
-
-```md
-`` example@email.com ``
-```
-
## Navigation
To indicate the steps of navigation through the UI:
@@ -783,8 +767,6 @@ For multiple paragraphs, use the symbol `>` before every line:
>
> - This is a list item
> - Second item in the list
->
-> ### This is an `h3`
```
Which renders to:
@@ -795,9 +777,6 @@ Which renders to:
>
> - This is a list item
> - Second item in the list
->
-> ### This is an `h3`
->{:.no_toc}
## Terms
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index a732a94b7c4..391361a4b8f 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -910,7 +910,7 @@ import bundle from 'ee_else_ce/protected_branches/protected_branches_bundle.js';
```
See the frontend guide [performance section](fe_guide/performance.md) for
-information on managing page-specific javascript within EE.
+information on managing page-specific JavaScript within EE.
## Vue code in `assets/javascript`
@@ -1047,8 +1047,6 @@ code base. Examples of backports include the following:
Here is a workflow to make sure those changes end up backported safely into CE too.
-(This approach does not refer to changes introduced via [csslab](https://gitlab.com/gitlab-org/csslab/).)
-
1. **Make your changes in the EE branch.** If possible, keep a separated commit (to be squashed) to help backporting and review.
1. **Open merge request to EE project.**
1. **Apply the changes you made to CE files in a branch of the CE project.** (Tip: Use `patch` with the diff from your commit in EE branch)
@@ -1057,7 +1055,7 @@ Here is a workflow to make sure those changes end up backported safely into CE t
**Note:** regarding SCSS, make sure the files living outside `/ee/` don't diverge between CE and EE projects.
-## gitlab-svgs
+## GitLab-svgs
Conflicts in `app/assets/images/icons.json` or `app/assets/images/icons.svg` can
be resolved simply by regenerating those assets with
diff --git a/doc/development/elasticsearch.md b/doc/development/elasticsearch.md
index 090e5235619..f2412c249c1 100644
--- a/doc/development/elasticsearch.md
+++ b/doc/development/elasticsearch.md
@@ -40,9 +40,11 @@ There is no need to install any plugins
If you're interested on working with the new beta repo indexer, all you need to do is:
-- git clone git@gitlab.com:gitlab-org/gitlab-elasticsearch-indexer.git
-- make
-- make install
+```sh
+git clone git@gitlab.com:gitlab-org/gitlab-elasticsearch-indexer.git
+make
+make install
+```
this adds `gitlab-elasticsearch-indexer` to `$GOPATH/bin`, please make sure that is in your `$PATH`. After that GitLab will find it and you'll be able to enable it in the admin settings area.
@@ -188,7 +190,7 @@ The global configurations per version are now in the `Elastic::(Version)::Config
NOTE: **Note:** this is not applicable yet as multiple indices functionality is not fully implemented.
-Folders like `ee/lib/elastic/v12p1` contain snapshots of search logic from different versions. To keep a continuous git history, the latest version lives under `ee/lib/elastic/latest`, but its classes are aliased under an actual version (e.g. `ee/lib/elastic/v12p3`). When referencing these classes, never use the `Latest` namespace directly, but use the actual version (e.g. `V12p3`).
+Folders like `ee/lib/elastic/v12p1` contain snapshots of search logic from different versions. To keep a continuous Git history, the latest version lives under `ee/lib/elastic/latest`, but its classes are aliased under an actual version (e.g. `ee/lib/elastic/v12p3`). When referencing these classes, never use the `Latest` namespace directly, but use the actual version (e.g. `V12p3`).
The version name basically follows GitLab's release version. If setting is changed in 12.3, we will create a new namespace called `V12p3` (p stands for "point"). Raise an issue if there is a need to name a version differently.
diff --git a/doc/development/emails.md b/doc/development/emails.md
index edec0f86989..5676c3b32f4 100644
--- a/doc/development/emails.md
+++ b/doc/development/emails.md
@@ -6,7 +6,7 @@ To view rendered emails "sent" in your development instance, visit
[`/rails/letter_opener`](http://localhost:3000/rails/letter_opener).
Please note that [S/MIME signed](../administration/smime_signing_email.md) emails
-[cannot be currently previewed](https://github.com/fgrehm/letter_opener_web/issues/96) with
+[cannot be currently previewed](https://github.com/fgrehm/letter_opener_web/issues/96) with
`letter_opener`.
## Mailer previews
diff --git a/doc/development/fe_guide/development_process.md b/doc/development/fe_guide/development_process.md
index ae0e2361840..9224a2548ab 100644
--- a/doc/development/fe_guide/development_process.md
+++ b/doc/development/fe_guide/development_process.md
@@ -58,6 +58,24 @@ Please use your best judgement when to use it and please contribute new points t
- [ ] Follow up on issues that came out of the review. Create issues for discovered edge cases that should be covered in future iterations.
```
+### Merge Request Review
+
+With the purpose of being [respectful of others' time](https://about.gitlab.com/handbook/values/#be-respectful-of-others-time) please follow these guidelines when asking for a review:
+
+- Make sure your Merge Request:
+ - milestone is set
+ - at least the labels suggested by danger-bot are set
+ - has a clear description
+ - includes before/after screenshots if there is a UI change
+ - pipeline is green
+ - includes tests
+ - includes a changelog entry (when necessary)
+- Before assigning to a maintainer, assign to a reviewer.
+- If you assigned a merge request, or pinged someone directly, keep in mind that we work in different timezones and asynchronously, so be patient. Unless the merge request is urgent (like fixing a broken master), please don't DM or reassign the merge request before waiting for a 24-hour window.
+- If you have a question regarding your merge request/issue, make it on the merge request/issue. When we DM each other, we no longer have a SSOT and [no one else is able to contribute](https://about.gitlab.com/handbook/values/#public-by-default).
+- When you have a big WIP merge request with many changes, you're adivsed to get the review started before adding/removing significant code. Make sure it is assigned well before the release cut-off, as the reviewer(s)/maintainer(s) would always prioritize reviewing finished MRs before WIP ones.
+- Make sure to remove the WIP title before the last round of review.
+
### Share your work early
1. Before writing code, ensure your vision of the architecture is aligned with
diff --git a/doc/development/fe_guide/performance.md b/doc/development/fe_guide/performance.md
index 676bce32998..3a8ea04407f 100644
--- a/doc/development/fe_guide/performance.md
+++ b/doc/development/fe_guide/performance.md
@@ -81,7 +81,7 @@ bundle and included on the page.
> can find this out by inspecting `document.body.dataset.page` within your
> browser's developer console while on any page within gitlab.
-#### Important Considerations:
+#### Important Considerations
- **Keep Entry Points Lite:**
Page-specific javascript entry points should be as lite as possible. These
diff --git a/doc/development/feature_flags/index.md b/doc/development/feature_flags/index.md
index 56872f8c075..f1374b9e280 100644
--- a/doc/development/feature_flags/index.md
+++ b/doc/development/feature_flags/index.md
@@ -8,5 +8,5 @@ disable those changes, without having to revert an entire release.
Before using feature flags for GitLab's development, read through the following:
- [Process for using features flags](process.md).
-- [Developing with feature flags documentation](development.md).
-- [Controlling feature flags documentation](controls.md).
+- [Developing with feature flags](development.md).
+- [Controlling feature flags](controls.md).
diff --git a/doc/development/file_storage.md b/doc/development/file_storage.md
index 475d1c1611e..44af2b020a4 100644
--- a/doc/development/file_storage.md
+++ b/doc/development/file_storage.md
@@ -2,6 +2,8 @@
We use the [CarrierWave] gem to handle file upload, store and retrieval.
+File uploads should be accelerated by workhorse, for details please refer to [uploads development documentation](uploads.md).
+
There are many places where file uploading is used, according to contexts:
- System
diff --git a/doc/development/filtering_by_label.md b/doc/development/filtering_by_label.md
index 5e7376db725..dd8944ff1c8 100644
--- a/doc/development/filtering_by_label.md
+++ b/doc/development/filtering_by_label.md
@@ -40,16 +40,14 @@ In particular, note that:
This is more complicated than is ideal. It makes the query construction more
prone to errors (such as
-[gitlab-org/gitlab-ce#15557](https://gitlab.com/gitlab-org/gitlab-ce/issues/15557)).
+[issue #15557](https://gitlab.com/gitlab-org/gitlab-ce/issues/15557)).
## Attempt A: WHERE EXISTS
### Attempt A1: use multiple subqueries with WHERE EXISTS
-In
-[gitlab-org/gitlab-ce#37137](https://gitlab.com/gitlab-org/gitlab-ce/issues/37137)
-and its associated merge request
-[gitlab-org/gitlab-ce!14022](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14022),
+In [issue #37137](https://gitlab.com/gitlab-org/gitlab-ce/issues/37137)
+and its associated [merge request](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14022),
we tried to replace the `GROUP BY` with multiple uses of `WHERE EXISTS`. For the
example above, this would give:
@@ -81,12 +79,11 @@ it did not improve query performance.
## Attempt B: Denormalize using an array column
-Having [removed MySQL support in GitLab
-12.1](https://about.gitlab.com/2019/06/27/removing-mysql-support/), using
-[Postgres's arrays](https://www.postgresql.org/docs/9.6/arrays.html) became more
+Having [removed MySQL support in GitLab 12.1](https://about.gitlab.com/2019/06/27/removing-mysql-support/),
+using [Postgres's arrays](https://www.postgresql.org/docs/9.6/arrays.html) became more
tractable as we didn't have to support two databases. We discussed denormalizing
the `label_links` table for querying in
-[gitlab-org/gitlab-ce#49651](https://gitlab.com/gitlab-org/gitlab-ce/issues/49651),
+[issue #49651](https://gitlab.com/gitlab-org/gitlab-ce/issues/49651),
with two options: label IDs and titles.
We can think of both of those as array columns on `issues`, `merge_requests`,
@@ -150,8 +147,7 @@ WHERE
label_titles @> ARRAY['Plan', 'backend']
```
-And our [tests in
-gitlab-org/gitlab-ce#49651](https://gitlab.com/gitlab-org/gitlab-ce/issues/49651#note_188777346)
+And our [tests in issue #49651](https://gitlab.com/gitlab-org/gitlab-ce/issues/49651#note_188777346)
showed that this could be fast.
However, at present, the disadvantages outweigh the advantages.
diff --git a/doc/development/gemfile.md b/doc/development/gemfile.md
index ec9718cea71..8d93c52e7bc 100644
--- a/doc/development/gemfile.md
+++ b/doc/development/gemfile.md
@@ -3,9 +3,9 @@
When adding a new entry to `Gemfile` or upgrading an existing dependency pay
attention to the following rules.
-## No gems fetched from git repositories
+## No gems fetched from Git repositories
-We do not allow gems that are fetched from git repositories. All gems have
+We do not allow gems that are fetched from Git repositories. All gems have
to be available in the RubyGems index. We want to minimize external build
dependencies and build times.
diff --git a/doc/development/geo.md b/doc/development/geo.md
index 24f16eae9fa..cc3e2d1ccc5 100644
--- a/doc/development/geo.md
+++ b/doc/development/geo.md
@@ -170,7 +170,7 @@ while `pull` requests will continue to be served by the **secondary** node for m
HTTPS and SSH requests are handled differently:
- With HTTPS, we will give the user a `HTTP 302 Redirect` pointing to the project on the **primary** node.
- The git client is wise enough to understand that status code and process the redirection.
+ The Git client is wise enough to understand that status code and process the redirection.
- With SSH, because there is no equivalent way to perform a redirect, we have to proxy the request.
This is done inside [`gitlab-shell`](https://gitlab.com/gitlab-org/gitlab-shell), by first translating the request
to the HTTP protocol, and then proxying it to the **primary** node.
diff --git a/doc/development/git_object_deduplication.md b/doc/development/git_object_deduplication.md
index 5ce59891afa..e8af6346524 100644
--- a/doc/development/git_object_deduplication.md
+++ b/doc/development/git_object_deduplication.md
@@ -8,30 +8,6 @@ storage disk use. To counteract this problem, we are adding Git object
deduplication for forks to GitLab. In this document, we will describe how
GitLab implements Git object deduplication.
-## Enabling Git object deduplication via feature flags
-
-As of GitLab 12.0, Git object deduplication in GitLab is still behind a
-feature flag. In this document, you can read about the effects of
-enabling the feature. Also, note that Git object deduplication is
-limited to forks of public projects on hashed repository storage.
-
-You can enable deduplication globally by setting the `object_pools`
-feature flag to `true`:
-
-``` {.ruby}
-Feature.enable(:object_pools)
-```
-
-Or just for forks of a specific project:
-
-``` {.ruby}
-fork_parent = Project.find(MY_PROJECT_ID)
-Feature.enable(:object_pools, fork_parent)
-```
-
-To check if a project uses Git object deduplication, look in a Rails
-console if `project.pool_repository` is present.
-
## Pool repositories
### Understanding Git alternates
@@ -193,7 +169,7 @@ There are three different things that can go wrong here.
In this case, we miss out on disk space savings but all RPC's on A
itself will function fine. The next time garbage collection runs on A,
the alternates connection gets established in Gitaly. This is done by
-`Projects::GitDeduplicationService` in gitlab-rails.
+`Projects::GitDeduplicationService` in GitLab Rails.
#### 2. SQL says repo A belongs to pool P1 but Gitaly says A has alternate objects in pool P2
@@ -210,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/gitaly.md b/doc/development/gitaly.md
index 2ade59b76ed..592fc13873b 100644
--- a/doc/development/gitaly.md
+++ b/doc/development/gitaly.md
@@ -45,13 +45,13 @@ The process for adding new Gitaly features is:
- release a new version of gitaly-proto
- write implementation and tests for the RPC [in Gitaly](https://gitlab.com/gitlab-org/gitaly), in Go or Ruby
- release a new version of Gitaly
-- write client code in gitlab-ce/ee, gitlab-workhorse or gitlab-shell that calls the new Gitaly RPC
+- write client code in GitLab CE/EE, GitLab Workhorse or GitLab Shell that calls the new Gitaly RPC
These steps often overlap. It is possible to use an unreleased version
of Gitaly and gitaly-proto during testing and development.
- See the [Gitaly repo](https://gitlab.com/gitlab-org/gitaly/blob/master/CONTRIBUTING.md#development-and-testing-with-a-custom-gitaly-proto) for instructions on writing server side code with an unreleased protocol.
-- See [below](#running-tests-with-a-locally-modified-version-of-gitaly) for instructions on running gitlab-ce tests with a modified version of Gitaly.
+- See [below](#running-tests-with-a-locally-modified-version-of-gitaly) for instructions on running GitLab CE tests with a modified version of Gitaly.
- In GDK run `gdk install` and restart `gdk run` (or `gdk run app`) to use a locally modified Gitaly version for development
### Gitaly-ruby
@@ -146,7 +146,7 @@ Once the code is wrapped in this block, this code-path will be excluded from n+1
## Request counts
-Commits and other git data, is now fetched through Gitaly. These fetches can,
+Commits and other Git data, is now fetched through Gitaly. These fetches can,
much like with a database, be batched. This improves performance for the client
and for Gitaly itself and therefore for the users too. To keep performance stable
and guard performance regressions, Gitaly calls can be counted and the call count
@@ -164,10 +164,10 @@ end
## Running tests with a locally modified version of Gitaly
-Normally, gitlab-ce/ee tests use a local clone of Gitaly in
+Normally, GitLab CE/EE tests use a local clone of Gitaly in
`tmp/tests/gitaly` pinned at the version specified in
`GITALY_SERVER_VERSION`. The `GITALY_SERVER_VERSION` file supports
-`=my-branch` syntax to use a custom branch in gitlab-org/gitaly. If
+`=my-branch` syntax to use a custom branch in <https://gitlab.com/gitlab-org/gitaly>. If
you want to run tests locally against a modified version of Gitaly you
can replace `tmp/tests/gitaly` with a symlink. This is much faster
because the `=my-branch` syntax forces a Gitaly re-install each time
@@ -276,9 +276,9 @@ Here are the steps to gate a new feature in Gitaly behind a feature flag.
require.NoError(t, err)
```
-### Gitlab-Rails
+### GitLab Rails
-1. Add feature flag to `lib/gitlab/gitaly_client.rb` (in gitlab-rails):
+1. Add feature flag to `lib/gitlab/gitaly_client.rb` (in GitLab Rails):
```ruby
SERVER_FEATURE_FLAGS = %w[go-find-all-tags].freeze
diff --git a/doc/development/go_guide/index.md b/doc/development/go_guide/index.md
index 83444093f9c..2df0e846671 100644
--- a/doc/development/go_guide/index.md
+++ b/doc/development/go_guide/index.md
@@ -94,7 +94,7 @@ become available, you will be able to share job templates like this
Dependencies should be kept to the minimum. The introduction of a new
dependency should be argued in the merge request, as per our [Approval
Guidelines](../code_review.md#approval-guidelines). Both [License
-Management](../../user/project/merge_requests/license_management.md)
+Management](../../user/application_security/license_compliance/index.md)
**(ULTIMATE)** and [Dependency
Scanning](../../user/application_security/dependency_scanning/index.md)
**(ULTIMATE)** should be activated on all projects to ensure new dependencies
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/i18n/translation.md b/doc/development/i18n/translation.md
index 62be3786549..15b1af1aa8f 100644
--- a/doc/development/i18n/translation.md
+++ b/doc/development/i18n/translation.md
@@ -92,4 +92,3 @@ To propose additions to the glossary please
In French, the "écriture inclusive" is now over (see on [Legifrance](https://www.legifrance.gouv.fr/affichTexte.do?cidTexte=JORFTEXT000036068906&categorieLien=id)).
So, to include both genders, write “Utilisateurs et utilisatrices” instead of “Utilisateur·rice·s”.
When space is missing, the male gender should be used alone.
-
diff --git a/doc/development/integrations/jira_connect.md b/doc/development/integrations/jira_connect.md
index e1350b02262..9ac87a17232 100644
--- a/doc/development/integrations/jira_connect.md
+++ b/doc/development/integrations/jira_connect.md
@@ -10,8 +10,10 @@ The following are required to install and test the app:
For the app to work, Jira Cloud should be able to connect to the GitLab instance through the internet.
- To easily expose your local development environment, you can use tools like [serveo](https://serveo.net) or [ngrok](https://ngrok.com).
- These also take care of SSL for you because Jira requires all connections to the app host to be over SSL.
+ To easily expose your local development environment, you can use tools like
+ [serveo](https://medium.com/@osanda.deshan/how-to-forward-my-local-port-to-public-using-serveo-4979f352a3bf)
+ or [ngrok](https://ngrok.com). These also take care of SSL for you because Jira
+ requires all connections to the app host to be over SSL.
> This feature is currently behind the `:jira_connect_app` feature flag
diff --git a/doc/development/kubernetes.md b/doc/development/kubernetes.md
index 4b2d48903ac..f4528667814 100644
--- a/doc/development/kubernetes.md
+++ b/doc/development/kubernetes.md
@@ -107,7 +107,7 @@ Mitigation strategies include:
## Debugging
Logs related to the Kubernetes integration can be found in
-[kubernetes.log](../administration/logs.md#kuberneteslog). On a local
+[`kubernetes.log`](../administration/logs.md#kuberneteslog). On a local
GDK install, this will be present in `log/kubernetes.log`.
Some services such as
diff --git a/doc/development/logging.md b/doc/development/logging.md
index 4f63c84fc0e..b43f1029cc6 100644
--- a/doc/development/logging.md
+++ b/doc/development/logging.md
@@ -133,7 +133,7 @@ importer progresses. Here's what to do:
logs in `/var/log/gitlab/gitlab-rails/*.log` every hour and [keep at
most 30 compressed files](https://docs.gitlab.com/omnibus/settings/logs.html#logrotate).
On GitLab.com, that setting is only 6 compressed files. These settings should suffice
- for most users, but you may need to tweak them in [omnibus-gitlab](https://gitlab.com/gitlab-org/omnibus-gitlab).
+ for most users, but you may need to tweak them in [Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab).
1. If you add a new file, submit an issue to the [production
tracker](https://gitlab.com/gitlab-com/gl-infra/production/issues) or
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 3181b3a88cc..4740cf4de7b 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -1,12 +1,12 @@
# Migration Style Guide
When writing migrations for GitLab, you have to take into account that
-these will be ran by hundreds of thousands of organizations of all sizes, some with
+these will be run by hundreds of thousands of organizations of all sizes, some with
many years of data in their database.
In addition, having to take a server offline for an upgrade small or big is a
-big burden for most organizations. For this reason it is important that your
-migrations are written carefully, can be applied online and adhere to the style
+big burden for most organizations. For this reason, it is important that your
+migrations are written carefully, can be applied online, and adhere to the style
guide below.
Migrations are **not** allowed to require GitLab installations to be taken
@@ -85,7 +85,38 @@ be possible to downgrade in case of a vulnerability or bugs.
In your migration, add a comment describing how the reversibility of the
migration was tested.
-## Multi Threading
+## Atomicity
+
+By default, migrations are single transaction. That is, a transaction is opened
+at the beginning of the migration, and committed after all steps are processed.
+
+Running migrations in a single transaction makes sure that if one of the steps fails,
+none of the steps will be executed, leaving the database in valid state.
+Therefore, either:
+
+- Put all migrations in one single-transaction migration.
+- If necessary, put most actions in one migration and create a separate migration
+ for the steps that cannot be done in a single transaction.
+
+For example, if you create an empty table and need to build an index for it,
+it is recommended to use a regular single-transaction migration and the default
+rails schema statement: [`add_index`](https://api.rubyonrails.org/v5.2/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_index).
+This is a blocking operation, but it won't cause problems because the table is not yet used,
+and therefore it does not have any records yet.
+
+## Heavy operations in a single transaction
+
+When using a single-transaction migration, a transaction will hold on a database connection
+for the duration of the migration, so you must make sure the actions in the migration
+do not take too much time: In general, queries executed in a migration need to fit comfortably
+within `15s` on GitLab.com.
+
+In case you need to insert, update, or delete a significant amount of data, you:
+
+- Must disable the single transaction with `disable_ddl_transaction!`.
+- Should consider doing it in a [Background Migration](background_migrations.md).
+
+## Multi-Threading
Sometimes a migration might need to use multiple Ruby threads to speed up a
migration. For this to work your migration needs to include the module
@@ -122,16 +153,16 @@ pool. This ensures each thread has its own connection object, and won't time
out when trying to obtain one.
**NOTE:** PostgreSQL has a maximum amount of connections that it allows. This
-limit can vary from installation to installation. As a result it's recommended
-you do not use more than 32 threads in a single migration. Usually 4-8 threads
+limit can vary from installation to installation. As a result, it's recommended
+you do not use more than 32 threads in a single migration. Usually, 4-8 threads
should be more than enough.
## Removing indexes
-When removing an index make sure to use the method `remove_concurrent_index` instead
-of the regular `remove_index` method. The `remove_concurrent_index` method
-automatically drops concurrent indexes when using PostgreSQL, removing the
-need for downtime. To use this method you must disable single-transaction mode
+If the table is not empty when removing an index, make sure to use the method
+`remove_concurrent_index` instead of the regular `remove_index` method.
+The `remove_concurrent_index` method drops indexes concurrently, so no locking is required,
+and there is no need for downtime. To use this method, you must disable single-transaction mode
by calling the method `disable_ddl_transaction!` in the body of your migration
class like so:
@@ -149,19 +180,25 @@ end
Note that it is not necessary to check if the index exists prior to
removing it.
+For a small table (such as an empty one or one with less than `1,000` records),
+it is recommended to use `remove_index` in a single-transaction migration,
+combining it with other operations that don't require `disable_ddl_transaction!`.
+
## Adding indexes
-If you need to add a unique index please keep in mind there is the possibility
+If you need to add a unique index, please keep in mind there is the possibility
of existing duplicates being present in the database. This means that should
always _first_ add a migration that removes any duplicates, before adding the
unique index.
-When adding an index make sure to use the method `add_concurrent_index` instead
-of the regular `add_index` method. The `add_concurrent_index` method
-automatically creates concurrent indexes when using PostgreSQL, removing the
-need for downtime. To use this method you must disable transactions by calling
-the method `disable_ddl_transaction!` in the body of your migration class like
-so:
+When adding an index to a non-empty table make sure to use the method
+`add_concurrent_index` instead of the regular `add_index` method.
+The `add_concurrent_index` method automatically creates concurrent indexes
+when using PostgreSQL, removing the need for downtime.
+
+To use this method, you must disable single-transactions mode
+by calling the method `disable_ddl_transaction!` in the body of your migration
+class like so:
```ruby
class MyMigration < ActiveRecord::Migration[4.2]
@@ -179,16 +216,20 @@ class MyMigration < ActiveRecord::Migration[4.2]
end
```
+For a small table (such as an empty one or one with less than `1,000` records),
+it is recommended to use `add_index` in a single-transaction migration, combining it with other
+operations that don't require `disable_ddl_transaction!`.
+
## Adding foreign-key constraints
-When adding a foreign-key constraint to either an existing or new
-column remember to also add a index on the column.
+When adding a foreign-key constraint to either an existing or a new column also
+remember to add an index on the column.
This is **required** for all foreign-keys, e.g., to support efficient cascading
deleting: when a lot of rows in a table get deleted, the referenced records need
to be deleted too. The database has to look for corresponding records in the
referenced table. Without an index, this will result in a sequential scan on the
-table which can take a long time.
+table, which can take a long time.
Here's an example where we add a new column with a foreign key
constraint. Note it includes `index: true` to create an index for it.
@@ -202,13 +243,17 @@ class Migration < ActiveRecord::Migration[4.2]
end
```
-When adding a foreign-key constraint to an existing column, we
-have to employ `add_concurrent_foreign_key` and `add_concurrent_index`
+When adding a foreign-key constraint to an existing column in a non-empty table,
+we have to employ `add_concurrent_foreign_key` and `add_concurrent_index`
instead of `add_reference`.
+For an empty table (such as a fresh one), it is recommended to use
+`add_reference` in a single-transaction migration, combining it with other
+operations that don't require `disable_ddl_transaction!`.
+
## Adding Columns With Default Values
-When adding columns with default values you must use the method
+When adding columns with default values to non-empty tables, you must use
`add_column_with_default`. This method ensures the table is updated without
requiring downtime. This method is not reversible so you must manually define
the `up` and `down` methods in your migration class.
@@ -232,10 +277,14 @@ end
```
Keep in mind that this operation can easily take 10-15 minutes to complete on
-larger installations (e.g. GitLab.com). As a result you should only add default
-values if absolutely necessary. There is a RuboCop cop that will fail if this
-method is used on some tables that are very large on GitLab.com, which would
-cause other issues.
+larger installations (e.g. GitLab.com). As a result, you should only add
+default values if absolutely necessary. There is a RuboCop cop that will fail if
+this method is used on some tables that are very large on GitLab.com, which
+would cause other issues.
+
+For a small table (such as an empty one or one with less than `1,000` records),
+use `add_column` and `change_column_default` in a single-transaction migration,
+combining it with other operations that don't require `disable_ddl_transaction!`.
## Updating an existing column
@@ -253,8 +302,10 @@ update_column_in_batches(:projects, :foo, 10) do |table, query|
end
```
-To perform a computed update, the value can be wrapped in `Arel.sql`, so Arel
-treats it as an SQL literal. The below example is the same as the one above, but
+If a computed update is needed, the value can be wrapped in `Arel.sql`, so Arel
+treats it as an SQL literal. It's also a required deprecation for [Rails 6](https://gitlab.com/gitlab-org/gitlab-ce/issues/61451).
+
+The below example is the same as the one above, but
the value is set to the product of the `bar` and `baz` columns:
```ruby
@@ -275,12 +326,12 @@ staging environment - or asking someone else to do so for you - beforehand.
By default, an integer column can hold up to a 4-byte (32-bit) number. That is
a max value of 2,147,483,647. Be aware of this when creating a column that will
-hold file sizes in byte units. If you are tracking file size in bytes this
+hold file sizes in byte units. If you are tracking file size in bytes, this
restricts the maximum file size to just over 2GB.
To allow an integer column to hold up to an 8-byte (64-bit) number, explicitly
set the limit to 8-bytes. This will allow the column to hold a value up to
-9,223,372,036,854,775,807.
+`9,223,372,036,854,775,807`.
Rails migration example:
@@ -294,9 +345,11 @@ add_column(:projects, :foo, :integer, default: 10, limit: 8)
## Timestamp column type
-By default, Rails uses the `timestamp` data type that stores timestamp data without timezone information.
-The `timestamp` data type is used by calling either the `add_timestamps` or the `timestamps` method.
-Also Rails converts the `:datetime` data type to the `timestamp` one.
+By default, Rails uses the `timestamp` data type that stores timestamp data
+without timezone information. The `timestamp` data type is used by calling
+either the `add_timestamps` or the `timestamps` method.
+
+Also, Rails converts the `:datetime` data type to the `timestamp` one.
Example:
@@ -317,14 +370,16 @@ def up
end
```
-Instead of using these methods one should use the following methods to store timestamps with timezones:
+Instead of using these methods, one should use the following methods to store
+timestamps with timezones:
- `add_timestamps_with_timezone`
- `timestamps_with_timezone`
-This ensures all timestamps have a time zone specified. This in turn means existing timestamps won't
-suddenly use a different timezone when the system's timezone changes. It also makes it very clear which
-timezone was used in the first place.
+This ensures all timestamps have a time zone specified. This, in turn, means
+existing timestamps won't suddenly use a different timezone when the system's
+timezone changes. It also makes it very clear which timezone was used in the
+first place.
## Storing JSON in database
@@ -359,7 +414,7 @@ Make sure your migration can be reversed.
## Data migration
Please prefer Arel and plain SQL over usual ActiveRecord syntax. In case of
-using plain SQL you need to quote all input manually with `quote_string` helper.
+using plain SQL, you need to quote all input manually with `quote_string` helper.
Example with Arel:
@@ -384,7 +439,7 @@ select_all("SELECT name, COUNT(id) as cnt FROM tags GROUP BY name HAVING COUNT(i
end
```
-If you need more complex logic you can define and use models local to a
+If you need more complex logic, you can define and use models local to a
migration. For example:
```ruby
@@ -395,13 +450,13 @@ class MyMigration < ActiveRecord::Migration[4.2]
end
```
-When doing so be sure to explicitly set the model's table name so it's not
+When doing so be sure to explicitly set the model's table name, so it's not
derived from the class name or namespace.
### Renaming reserved paths
-When a new route for projects is introduced that could conflict with any
-existing records. The path for this records should be renamed, and the
+When a new route for projects is introduced, it could conflict with any
+existing records. The path for these records should be renamed, and the
related data should be moved on disk.
Since we had to do this a few times already, there are now some helpers to help
diff --git a/doc/development/namespaces_storage_statistics.md b/doc/development/namespaces_storage_statistics.md
index b3285de4705..2c7e5935435 100644
--- a/doc/development/namespaces_storage_statistics.md
+++ b/doc/development/namespaces_storage_statistics.md
@@ -20,8 +20,8 @@ every time the project is saved.
The summary of those statistics per namespace is then retrieved
by [`Namespaces#with_statistics`](https://gitlab.com/gitlab-org/gitlab-ce/blob/v12.2.0.pre/app/models/namespace.rb#L70) scope. Analyzing this query we noticed that:
-* It takes up to `1.2` seconds for namespaces with over `15k` projects.
-* It can't be analyzed with [ChatOps](chatops_on_gitlabcom.md), as it times out.
+- It takes up to `1.2` seconds for namespaces with over `15k` projects.
+- It can't be analyzed with [ChatOps](chatops_on_gitlabcom.md), as it times out.
Additionally, the pattern that is currently used to update the project statistics
(the callback) doesn't scale adequately. It is currently one of the largest
@@ -62,8 +62,8 @@ REFRESH MATERIALIZED VIEW root_namespace_storage_statistics;
While this implied a single query update (and probably a fast one), it has some downsides:
-* Materialized views syntax varies from PostgreSQL and MySQL. While this feature was worked on, MySQL was still supported by GitLab.
-* Rails does not have native support for materialized views. We'd need to use a specialized gem to take care of the management of the database views, which implies additional work.
+- Materialized views syntax varies from PostgreSQL and MySQL. While this feature was worked on, MySQL was still supported by GitLab.
+- Rails does not have native support for materialized views. We'd need to use a specialized gem to take care of the management of the database views, which implies additional work.
### Attempt B: An update through a CTE
@@ -131,8 +131,8 @@ WHERE namespace_id IN (
Even though this approach would make aggregating much easier, it has some major downsides:
-* We'd have to migrate **all namespaces** by adding and filling a new column. Because of the size of the table, dealing with time/cost will not be great. The background migration will take approximately `153h`, see <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29772>.
-* Background migration has to be shipped one release before, delaying the functionality by another milestone.
+- We'd have to migrate **all namespaces** by adding and filling a new column. Because of the size of the table, dealing with time/cost will not be great. The background migration will take approximately `153h`, see <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29772>.
+- Background migration has to be shipped one release before, delaying the functionality by another milestone.
### Attempt E (final): Update the namespace storage statistics in async way
@@ -155,10 +155,10 @@ but we refresh them through Sidekiq jobs and in different transactions:
This implementation has the following benefits:
-* All the updates are done async, so we're not increasing the length of the transactions for `project_statistics`.
-* We're doing the update in a single SQL query.
-* It is compatible with PostgreSQL and MySQL.
-* No background migration required.
+- All the updates are done async, so we're not increasing the length of the transactions for `project_statistics`.
+- We're doing the update in a single SQL query.
+- It is compatible with PostgreSQL and MySQL.
+- No background migration required.
The only downside of this approach is that namespaces' statistics are updated up to `1.5` hours after the change is done,
which means there's a time window in which the statistics are inaccurate. Because we're still not
@@ -171,8 +171,8 @@ performant approach of aggregating the root namespaces.
All the details regarding this use case can be found on:
-* <https://gitlab.com/gitlab-org/gitlab-ce/issues/62214>
-* Merge Request with the implementation: <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/28996>
+- <https://gitlab.com/gitlab-org/gitlab-ce/issues/62214>
+- Merge Request with the implementation: <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/28996>
Performance of the namespace storage statistics were measured in staging and production (GitLab.com). All results were posted
on <https://gitlab.com/gitlab-org/gitlab-ce/issues/64092>: No problem has been reported so far.
diff --git a/doc/development/new_fe_guide/dependencies.md b/doc/development/new_fe_guide/dependencies.md
index 8a6930acd37..161ffb1fb57 100644
--- a/doc/development/new_fe_guide/dependencies.md
+++ b/doc/development/new_fe_guide/dependencies.md
@@ -1,6 +1,6 @@
# Dependencies
-## Adding Dependencies.
+## Adding Dependencies
GitLab uses `yarn` to manage dependencies. These dependencies are defined in
two groups within `package.json`, `dependencies` and `devDependencies`. For
diff --git a/doc/development/new_fe_guide/development/testing.md b/doc/development/new_fe_guide/development/testing.md
index e0d413b748b..b990425ca3c 100644
--- a/doc/development/new_fe_guide/development/testing.md
+++ b/doc/development/new_fe_guide/development/testing.md
@@ -3,4 +3,3 @@ redirect_to: '../../testing_guide/frontend_testing.md'
---
This document was moved to [another location](../../testing_guide/frontend_testing.md).
-
diff --git a/doc/development/omnibus.md b/doc/development/omnibus.md
index 0ba354d28a2..ea5c18f1a8c 100644
--- a/doc/development/omnibus.md
+++ b/doc/development/omnibus.md
@@ -1,32 +1,32 @@
-# What you should know about omnibus packages
+# What you should know about Omnibus packages
-Most users install GitLab using our omnibus packages. As a developer it can be
-good to know how the omnibus packages differ from what you have on your laptop
+Most users install GitLab using our Omnibus packages. As a developer it can be
+good to know how the Omnibus packages differ from what you have on your laptop
when you are coding.
## Files are owned by root by default
-All the files in the Rails tree (`app/`, `config/` etc.) are owned by 'root' in
-omnibus installations. This makes the installation simpler and it provides
-extra security. The omnibus reconfigure script contains commands that give
-write access to the 'git' user only where needed.
+All the files in the Rails tree (`app/`, `config/` etc.) are owned by `root` in
+Omnibus installations. This makes the installation simpler and it provides
+extra security. The Omnibus reconfigure script contains commands that give
+write access to the `git` user only where needed.
-For example, the 'git' user is allowed to write in the `log/` directory, in
+For example, the `git` user is allowed to write in the `log/` directory, in
`public/uploads`, and they are allowed to rewrite the `db/schema.rb` file.
In other cases, the reconfigure script tricks GitLab into not trying to write a
file. For instance, GitLab will generate a `.secret` file if it cannot find one
-and write it to the Rails root. In the omnibus packages, reconfigure writes the
+and write it to the Rails root. In the Omnibus packages, reconfigure writes the
`.secret` file first, so that GitLab never tries to write it.
## Code, data and logs are in separate directories
-The omnibus design separates code (read-only, under `/opt/gitlab`) from data
+The Omnibus design separates code (read-only, under `/opt/gitlab`) from data
(read/write, under `/var/opt/gitlab`) and logs (read/write, under
`/var/log/gitlab`). To make this happen the reconfigure script sets custom
paths where it can in GitLab config files, and where there are no path
settings, it uses symlinks.
For example, `config/gitlab.yml` is treated as data so that file is a symlink.
-The same goes for `public/uploads`. The `log/` directory is replaced by omnibus
+The same goes for `public/uploads`. The `log/` directory is replaced by Omnibus
with a symlink to `/var/log/gitlab/gitlab-rails`.
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/session.md b/doc/development/session.md
index 9edce3dbda0..971795d8816 100644
--- a/doc/development/session.md
+++ b/doc/development/session.md
@@ -17,7 +17,7 @@ When storing values in a session it is best to:
- Use simple primitives and avoid storing objects to avoid marshaling complications.
- Clean up after unneeded variables to keep memory usage in Redis down.
-## Gitlab::Session
+## GitLab::Session
Sometimes you might want to persist data in the session instead of another store like the database. `Gitlab::Session` lets you access this without passing the session around extensively. For example, you could access it from within a policy without having to pass the session through to each place permissions are checked from.
diff --git a/doc/development/shell_commands.md b/doc/development/shell_commands.md
index 7bdf676be58..1300c99622e 100644
--- a/doc/development/shell_commands.md
+++ b/doc/development/shell_commands.md
@@ -35,7 +35,7 @@ Gitlab::Popen.popen(%W(find /some/path -not -path /some/path -mmin +120 -delete)
This coding style could have prevented CVE-2013-4490.
-## Always use the configurable git binary path for git commands
+## Always use the configurable Git binary path for Git commands
```ruby
# Wrong
@@ -114,7 +114,7 @@ user = `whoami`
user, exit_status = Gitlab::Popen.popen(%W(whoami))
```
-In other repositories, such as gitlab-shell you can also use `IO.popen`.
+In other repositories, such as GitLab Shell you can also use `IO.popen`.
```ruby
# Safe IO.popen example
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/best_practices.md b/doc/development/testing_guide/end_to_end/best_practices.md
index 89500ef9a90..2200069ecfd 100644
--- a/doc/development/testing_guide/end_to_end/best_practices.md
+++ b/doc/development/testing_guide/end_to_end/best_practices.md
@@ -1,5 +1,7 @@
# Best practices when writing end-to-end tests
+## Avoid using a GUI when it's not required
+
The majority of the end-to-end tests require some state to be built in the application for the tests to happen.
A good example is a user being logged in as a pre-condition for testing the feature.
@@ -36,3 +38,18 @@ Finally, interacting with the application only by its GUI generates a higher rat
- When depending only on the GUI to create the application's state and tests fail due to front-end issues, we can't rely on the test failures rate, and we generate a higher rate of test flakiness.
Now that we are aware of all of it, [let's go create some tests](quick_start_guide.md).
+
+## Prefer to split tests across multiple files
+
+Our framework includes a couple of parallelization mechanisms that work by executing spec files in parallel.
+
+However, because tests are parallelized by spec *file* and not by test/example, we can't achieve greater parallelization if a new test is added to an existing file.
+
+Nonetheless, there could be other reasons to add a new test to an existing file.
+
+For example, if tests share state that is expensive to set up it might be more efficient to perform that setup once even if it means the tests that use the setup can't be parallelized.
+
+In summary:
+
+- **Do**: Split tests across separate files, unless the tests share expensive setup.
+- **Don't**: Put new tests in an existing file without considering the impact on parallelization.
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/end_to_end/page_objects.md b/doc/development/testing_guide/end_to_end/page_objects.md
index 850ea6b60ac..8820b54fa87 100644
--- a/doc/development/testing_guide/end_to_end/page_objects.md
+++ b/doc/development/testing_guide/end_to_end/page_objects.md
@@ -167,6 +167,18 @@ There are two supported methods of defining elements within a view.
Any existing `.qa-selector` class should be considered deprecated
and we should prefer the `data-qa-selector` method of definition.
+### Exceptions
+
+In some cases it might not be possible or worthwhile to add a selector.
+
+Some UI components use external libraries, including some maintained by third parties.
+Even if a library is maintained by GitLab, the selector sanity test only runs
+on code within the GitLab project, so it's not possible to specify the path for
+the view for code in a library.
+
+In such rare cases it's reasonable to use CSS selectors in page object methods,
+with a comment explaining why an `element` can't be added.
+
## Running the test locally
During development, you can run the `qa:selectors` test by running
diff --git a/doc/development/testing_guide/end_to_end/quick_start_guide.md b/doc/development/testing_guide/end_to_end/quick_start_guide.md
index e1df8be8b6f..d52d6db38b9 100644
--- a/doc/development/testing_guide/end_to_end/quick_start_guide.md
+++ b/doc/development/testing_guide/end_to_end/quick_start_guide.md
@@ -10,7 +10,7 @@ It's important to understand that end-to-end tests of isolated features, such as
If you don't exactly understand what we mean by **not everything needs to happen through the GUI,** please make sure you've read the [best practices](best_practices.md) before moving on.
-## This document covers the following items:
+## This document covers the following items
- [0.](#0-are-end-to-end-tests-needed) Identifying if end-to-end tests are really needed
- [1.](#1-identifying-the-devops-stage) Identifying the [DevOps stage](https://about.gitlab.com/stages-devops-lifecycle/) of the feature that you are going to cover with end-to-end tests
diff --git a/doc/development/testing_guide/end_to_end/style_guide.md b/doc/development/testing_guide/end_to_end/style_guide.md
index 97560e616a1..54ed3f34c89 100644
--- a/doc/development/testing_guide/end_to_end/style_guide.md
+++ b/doc/development/testing_guide/end_to_end/style_guide.md
@@ -141,4 +141,4 @@ Resource::MergeRequest.fabricate! do |merge_request_page|
end
```
-> Besides the advantage of having a standard in place, by following this standard we also write shorter lines of code. \ No newline at end of file
+> Besides the advantage of having a standard in place, by following this standard we also write shorter lines of code.
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 11449712a04..13772cbe015 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -132,7 +132,7 @@ to prevent other pods from being scheduled on this node pool.
This is to ensure Tiller isn't affected by "noisy" neighbors that could put
their node under pressure.
-## How to:
+## How to
### Log into my Review App
@@ -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/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md
index 0090c84cbf0..1aee306f492 100644
--- a/doc/development/testing_guide/testing_levels.md
+++ b/doc/development/testing_guide/testing_levels.md
@@ -126,7 +126,7 @@ possible).
| ---------- | -------------- | ----- |
| `spec/features/` | [Capybara] + [RSpec] | If your test has the `:js` metadata, the browser driver will be [Poltergeist], otherwise it's using [RackTest]. |
-### Consider **not** writing a system test!
+### Consider **not** writing a system test
If we're confident that the low-level components work well (and we should be if
we have enough Unit & Integration tests), we shouldn't need to duplicate their
diff --git a/doc/development/uploads.md b/doc/development/uploads.md
new file mode 100644
index 00000000000..681ce9d9fe8
--- /dev/null
+++ b/doc/development/uploads.md
@@ -0,0 +1,270 @@
+# Uploads development documentation
+
+[GitLab Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse) has special rules for handling uploads.
+To prevent occupying a ruby process on I/O operations, we process the upload in workhorse, where is cheaper.
+This process can also directly upload to object storage.
+
+## The problem description
+
+The following graph explains machine boundaries in a scalable GitLab installation. Without any workhorse optimization in place, we can expect incoming requests to follow the numbers on the arrows.
+
+```mermaid
+graph TB
+ subgraph "load balancers"
+ LB(HA Proxy)
+ end
+
+ subgraph "Shared storage"
+ nfs(NFS)
+ end
+
+ subgraph "redis cluster"
+ r(persisted redis)
+ end
+ LB-- 1 -->workhorse
+
+ subgraph "web or API fleet"
+ workhorse-- 2 -->rails
+ end
+ rails-- "3 (write files)" -->nfs
+ rails-- "4 (schedule a job)" -->r
+
+ subgraph sidekiq
+ s(sidekiq)
+ end
+ s-- "5 (fetch a job)" -->r
+ s-- "6 (read files)" -->nfs
+```
+
+We have three challenges here: performance, availability, and scalability.
+
+### Performance
+
+Rails process are expensive in terms of both CPU and memory. Ruby [global interpreter lock](https://en.wikipedia.org/wiki/Global_interpreter_lock) adds to cost too because the ruby process will spend time on I/O operations on step 3 causing incoming requests to pile up.
+
+In order to improve this, [workhorse disk acceleration](#workhorse-disk-acceleration) was implemented. With this, Rails no longer deals with writing uploaded files to disk.
+
+```mermaid
+graph TB
+ subgraph "load balancers"
+ LB(HA Proxy)
+ end
+
+ subgraph "Shared storage"
+ nfs(NFS)
+ end
+
+ subgraph "redis cluster"
+ r(persisted redis)
+ end
+ LB-- 1 -->workhorse
+
+ subgraph "web or API fleet"
+ workhorse-- "3 (without files)" -->rails
+ end
+ workhorse -- "2 (write files)" -->nfs
+ rails-- "4 (schedule a job)" -->r
+
+ subgraph sidekiq
+ s(sidekiq)
+ end
+ s-- "5 (fetch a job)" -->r
+ s-- "6 (read files)" -->nfs
+```
+
+### Availability
+
+There's also an availability problem in this setup, NFS is a [single point of failure](https://en.wikipedia.org/wiki/Single_point_of_failure).
+
+To address this problem an HA object storage can be used and it's supported by [workhorse object storage acceleration](#workhorse-object-storage-acceleration)
+
+### Scalability
+
+Scaling NFS is outside of our support scope, and NFS is not a part of cloud native installations.
+
+All features that require sidekiq and do not use object storage acceleration won't work without NFS. In Kubernetes, machine boundaries translate to PODs, and in this case the uploaded file will be written into the POD private disk. Since sidekiq POD cannot reach into other pods, the operation will fail to read it.
+
+## How to select the proper level of acceleration?
+
+Selecting the proper acceleration is a tradeoff between speed of development and operational costs.
+
+We can identify three major use-cases for an upload:
+
+1. **storage:** if we are uploading for storing a file (i.e. artifacts, packages, discussion attachments). In this case [object storage acceleration](#workhorse-object-storage-acceleration) is the proper level as it's the less resource-intensive operation. Additional information can be found on [File Storage in GitLab](file_storage.md).
+1. **in-controller/synchronous processing:** if we allow processing **small files** synchronously, using [disk acceleration](#workhorse-disk-acceleration) may speed up development.
+1. **sidekiq/asynchronous processing:** Async processing must implement [object storage acceleration](#workhorse-object-storage-acceleration), the reason being that it's the only way to support Cloud Native deployments without a shared NFS.
+
+For more details about currently broken feature see [epic &1802](https://gitlab.com/groups/gitlab-org/-/epics/1802).
+
+### Handling repository uploads
+
+Some features involves git repository uploads without using a regular git client.
+Some examples are uploading a repository file from the web interface and [design management](../user/project/issues/design_management.md).
+
+Those uploads requires the rails controller to act as a git client in lieu of the user.
+Those operation falls into _in-controller/synchronous processing_ category, but we have no warranties on the file size.
+
+In case of a LFS upload, the file pointer is committed synchronously, but file upload to object storage is performed asynchronously with sidekiq.
+
+## Upload encodings
+
+By upload encoding we mean how the file is included within the incoming request.
+
+We have three kinds of file encoding in our uploads:
+
+1. <i class="fa fa-check-circle"></i> **multipart**: `multipart/form-data` is the most common, a file is encoded as a part of a multipart encoded request.
+1. <i class="fa fa-check-circle"></i> **body**: some APIs uploads files as the whole request body.
+1. <i class="fa fa-times-circle"></i> **JSON**: some JSON API uploads files as base64 encoded strings. This requires [gitlab-workhorse#226](https://gitlab.com/gitlab-org/gitlab-workhorse/issues/226) to be implemented.
+
+## Uploading technologies
+
+By uploading technologies we mean how all the involved services interact with each other.
+
+GitLab supports 3 kinds of uploading technologies, here follows a brief description with a sequence diagram for each one. Diagrams are not meant to be exhaustive.
+
+### Regular rails upload
+
+This is the default kind of upload, and it's most expensive in terms of resources.
+
+In this case, workhorse is unaware of files being uploaded and acts as a regular proxy.
+
+When a multipart request reaches the rails application, `Rack::Multipart` leaves behind tempfiles in `/tmp` and uses valuable Ruby process time to copy files around.
+
+```mermaid
+sequenceDiagram
+ participant c as Client
+ participant w as Workhorse
+ participant r as Rails
+
+ activate c
+ c ->>+w: POST /some/url/upload
+ w->>+r: POST /some/url/upload
+
+ r->>r: save the incoming file on /tmp
+ r->>r: read the file for processing
+
+ r-->>-c: request result
+ deactivate c
+ deactivate w
+```
+
+### Workhorse disk acceleration
+
+This kind of upload avoids wasting resources caused by handling upload writes to `/tmp` in rails.
+
+This optimization is not active by default on REST API requests.
+
+When enabled, Workhorse looks for files in multipart MIME requests, uploading
+any it finds to a temporary file on shared storage. The MIME data in the request
+is replaced with the path to the corresponding file before it is forwarded to
+Rails.
+
+To prevent abuse of this feature, Workhorse signs the modified request with a
+special header, stating which entries it modified. Rails will ignore any
+unsigned path entries.
+
+```mermaid
+sequenceDiagram
+ participant c as Client
+ participant w as Workhorse
+ participant r as Rails
+ participant s as NFS
+
+ activate c
+ c ->>+w: POST /some/url/upload
+
+ w->>+s: save the incoming file on a temporary location
+ s-->>-w:
+
+ w->>+r: POST /some/url/upload
+ Note over w,r: file was replaced with its location<br>and other metadata
+
+ opt requires async processing
+ r->>+redis: schedule a job
+ redis-->>-r:
+ end
+
+ r-->>-c: request result
+ deactivate c
+ w->>-w: cleanup
+
+ opt requires async processing
+ activate sidekiq
+ sidekiq->>+redis: fetch a job
+ redis-->>-sidekiq: job
+
+ sidekiq->>+s: read file
+ s-->>-sidekiq: file
+
+ sidekiq->>sidekiq: process file
+
+ deactivate sidekiq
+ end
+```
+
+### Workhorse object storage acceleration
+
+This is the more advanced acceleration technique we have in place.
+
+Workhorse asks rails for temporary pre-signed object storage URLs and directly uploads to object storage.
+
+In this setup an extra rails route needs to be implemented in order to handle authorization,
+you can see an example of this in [`Projects::LfsStorageController`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/controllers/projects/lfs_storage_controller.rb)
+and [its routes](https://gitlab.com/gitlab-org/gitlab-ce/blob/v12.2.0/config/routes/git_http.rb#L31-32).
+
+**note:** this will fallback to _Workhorse disk acceleration_ when object storage is not enabled in the gitlab instance. The answer to the `/authorize` call will only contain a file system path.
+
+```mermaid
+sequenceDiagram
+ participant c as Client
+ participant w as Workhorse
+ participant r as Rails
+ participant os as Object Storage
+
+ activate c
+ c ->>+w: POST /some/url/upload
+
+ w ->>+r: POST /some/url/upload/authorize
+ Note over w,r: this request has an empty body
+ r-->>-w: presigned OS URL
+
+ w->>+os: PUT file
+ Note over w,os: file is stored on a temporary location. Rails select the destination
+ os-->>-w:
+
+ w->>+r: POST /some/url/upload
+ Note over w,r: file was replaced with its location<br>and other metadata
+
+ r->>+os: move object to final destination
+ os-->>-r:
+
+ opt requires async processing
+ r->>+redis: schedule a job
+ redis-->>-r:
+ end
+
+ r-->>-c: request result
+ deactivate c
+ w->>-w: cleanup
+
+ opt requires async processing
+ activate sidekiq
+ sidekiq->>+redis: fetch a job
+ redis-->>-sidekiq: job
+
+ sidekiq->>+os: get object
+ os-->>-sidekiq: file
+
+ sidekiq->>sidekiq: process file
+
+ deactivate sidekiq
+ end
+```
+
+## What does the `direct_upload` setting mean?
+
+[Object storage setting](../administration/uploads.md#object-storage-settings) allows instance administators to enable `direct_upload`, this in an option that only affects the behavior of [workhorse object storage acceleration](#workhorse-object-storage-acceleration).
+
+This option affect the response to the `/authorize` call. When not enabled, the API response will not contain presigned URLs and workhorse will write the file the shared disk, on the path is provided by rails, acting like object storage was disabled.
+
+Once the request reachs rails, it will schedule an object storage upload as a sidekiq job.
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..00371057d3c 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
@@ -92,7 +88,7 @@ class RenameUsersUpdatedAtToUpdatedAtTimestamp < ActiveRecord::Migration[4.2]
end
def down
- cleanup_concurrent_column_rename :users, :updated_at_timestamp, :updated_at
+ undo_rename_column_concurrently :users, :updated_at, :updated_at_timestamp
end
end
```
@@ -122,7 +118,7 @@ class CleanupUsersUpdatedAtRename < ActiveRecord::Migration[4.2]
end
def down
- rename_column_concurrently :users, :updated_at_timestamp, :updated_at
+ undo_cleanup_concurrent_column_rename :users, :updated_at, :updated_at_timestamp
end
end
```
diff --git a/doc/gitlab-basics/add-file.md b/doc/gitlab-basics/add-file.md
index 41cc8bc4aeb..d547584bf80 100644
--- a/doc/gitlab-basics/add-file.md
+++ b/doc/gitlab-basics/add-file.md
@@ -70,7 +70,7 @@ git commit -m "DESCRIBE COMMIT IN A FEW WORDS"
```
Now you can push (send) your changes (in the branch `<branch-name>`) to GitLab
-(the git remote named 'origin'):
+(the Git remote named 'origin'):
```sh
git push origin <branch-name>
diff --git a/doc/gitlab-basics/add-merge-request.md b/doc/gitlab-basics/add-merge-request.md
index 1a6a26152fa..28f32fefb95 100644
--- a/doc/gitlab-basics/add-merge-request.md
+++ b/doc/gitlab-basics/add-merge-request.md
@@ -12,8 +12,6 @@ check the [merge requests documentation](../user/project/merge_requests/index.md
you can watch our [GitLab Flow video](https://www.youtube.com/watch?v=InKNIvky2KE) for
a quick overview of working with merge requests.
----
-
1. Before you start, you should have already [created a branch](create-branch.md)
and [pushed your changes](start-using-git.md#send-changes-to-gitlabcom) to GitLab.
1. Go to the project where you'd like to merge your changes and click on the
diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md
index ed70d3ce598..74539b33642 100644
--- a/doc/gitlab-basics/command-line-commands.md
+++ b/doc/gitlab-basics/command-line-commands.md
@@ -10,7 +10,7 @@ learn, in order to make full use of the command line.
## Start working on your project
-To work on a git project locally (from your own computer), with the command line,
+To work on a Git project locally (from your own computer), with the command line,
first you will need to [clone (copy) it](start-using-git.md#clone-a-repository) to
your computer.
diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md
index 8bbaf5d1927..18565daa900 100644
--- a/doc/gitlab-basics/create-project.md
+++ b/doc/gitlab-basics/create-project.md
@@ -2,7 +2,7 @@
type: howto
---
-# Creating projects
+# Create a project
Most work in GitLab is done within a [Project](../user/project/index.md). Files and
code are saved in projects, and most features are used within the scope of projects.
diff --git a/doc/gitlab-basics/create-your-ssh-keys.md b/doc/gitlab-basics/create-your-ssh-keys.md
index 338b96374aa..98f2679c9d6 100644
--- a/doc/gitlab-basics/create-your-ssh-keys.md
+++ b/doc/gitlab-basics/create-your-ssh-keys.md
@@ -5,7 +5,7 @@ type: howto
# Create and add your SSH public key
It is best practice to use [Git over SSH instead of Git over HTTP](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols).
-In order to use SSH, you will need to
+In order to use SSH, you will need to:
1. [Create an SSH key pair](#creating-your-ssh-key-pair) on your local computer.
1. [Add the key to GitLab](#adding-your-ssh-public-key-to-gitlab).
diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md
index 5de173abbff..a289b90b81b 100644
--- a/doc/gitlab-basics/start-using-git.md
+++ b/doc/gitlab-basics/start-using-git.md
@@ -102,7 +102,7 @@ files to your local computer, automatically preserving the Git connection with t
remote repository.
You can either clone it via HTTPS or [SSH](../ssh/README.md). If you chose to clone
-it via HTTPS, you'll have to enter your credentials every time you pull and push. You can read more about credential storage in the [Git Credentials documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage). With SSH, you enter your credentials only once.
+it via HTTPS, you'll have to enter your credentials every time you pull and push. You can read more about credential storage in the [Git Credentials documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage). With SSH, you enter your credentials only once.
You can find both paths (HTTPS and SSH) by navigating to your project's landing page
and clicking **Clone**. GitLab will prompt you with both paths, from which you can copy
@@ -157,7 +157,7 @@ git pull <REMOTE> <name-of-branch>
When you clone a repository, `REMOTE` is typically `origin`. This is where the
repository was cloned from, and it indicates the SSH or HTTPS URL of the repository
on the remote server. `<name-of-branch>` is usually `master`, but it may be any existing
-branch. You can create additional named remotes and branches as necessary.
+branch. You can create additional named remotes and branches as necessary.
You can learn more on how Git manages remote repositories in the [Git Remote documentation](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes).
@@ -169,7 +169,7 @@ To view your remote repositories, type:
git remote -v
```
-The `-v` flag stands for verbose.
+The `-v` flag stands for verbose.
### Add a remote repository
diff --git a/doc/install/README.md b/doc/install/README.md
index af98791c8e9..fd91527ed4c 100644
--- a/doc/install/README.md
+++ b/doc/install/README.md
@@ -13,7 +13,7 @@ and cost of hosting.
There are many ways you can install GitLab depending on your platform:
-1. **Omnibus Gitlab**: The official deb/rpm packages that contain a bundle of GitLab
+1. **Omnibus GitLab**: The official deb/rpm packages that contain a bundle of GitLab
and the various components it depends on like PostgreSQL, Redis, Sidekiq, etc.
1. **GitLab Helm chart**: The cloud native Helm chart for installing GitLab and all
its components on Kubernetes.
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/install/installation.md b/doc/install/installation.md
index 6d64b8a4936..6039ddc45ae 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -57,18 +57,18 @@ of this page:
```
- `/home/git/.ssh` - Contains OpenSSH settings. Specifically the `authorized_keys`
- file managed by gitlab-shell.
+ file managed by GitLab Shell.
- `/home/git/gitlab` - GitLab core software.
- `/home/git/gitlab-shell` - Core add-on component of GitLab. Maintains SSH
cloning and other functionality.
- `/home/git/repositories` - Bare repositories for all projects organized by
- namespace. This is where the git repositories which are pushed/pulled are
+ namespace. This is where the Git repositories which are pushed/pulled are
maintained for all projects. **This area contains critical data for projects.
[Keep a backup](../raketasks/backup_restore.md).**
NOTE: **Note:**
The default locations for repositories can be configured in `config/gitlab.yml`
-of GitLab and `config.yml` of gitlab-shell.
+of GitLab and `config.yml` of GitLab Shell.
For a more in-depth overview, see the [GitLab architecture doc](../development/architecture.md).
@@ -569,7 +569,7 @@ GitLab Shell application startup time can be greatly reduced by disabling RubyGe
- Compile Ruby with `configure --disable-rubygems` to disable RubyGems by default. Not recommended for system-wide Ruby.
- Omnibus GitLab [replaces the *shebang* line of the `gitlab-shell/bin/*` scripts](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1707).
-### Install gitlab-workhorse
+### Install GitLab Workhorse
GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/). The
following command-line will install GitLab-Workhorse in `/home/git/gitlab-workhorse`
@@ -611,7 +611,7 @@ You can specify a different Git repository by providing it as an extra parameter
sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories,https://example.com/gitaly.git]" RAILS_ENV=production
```
-Next, make sure gitaly configured:
+Next, make sure that Gitaly is configured:
```sh
# Restrict Gitaly socket access
@@ -833,7 +833,7 @@ To use GitLab with HTTPS:
1. In `gitlab.yml`:
1. Set the `port` option in section 1 to `443`.
1. Set the `https` option in section 1 to `true`.
-1. In the `config.yml` of gitlab-shell:
+1. In the `config.yml` of GitLab Shell:
1. Set `gitlab_url` option to the HTTPS endpoint of GitLab (e.g. `https://git.example.com`).
1. Set the certificates using either the `ca_file` or `ca_path` option.
1. Use the `gitlab-ssl` Nginx example config instead of the `gitlab` config.
@@ -852,7 +852,7 @@ Using a self-signed certificate is discouraged but if you must use it, follow th
sudo chmod o-r gitlab.key
```
-1. In the `config.yml` of gitlab-shell set `self_signed_cert` to `true`.
+1. In the `config.yml` of GitLab Shell set `self_signed_cert` to `true`.
### Enable Reply by email
@@ -950,8 +950,8 @@ To use GitLab with Puma:
If you see this message when attempting to clone a repository hosted by GitLab,
this is likely due to an outdated Nginx or Apache configuration, or a missing or
-misconfigured gitlab-workhorse instance. Double-check that you've
-[installed Go](#3-go), [installed gitlab-workhorse](#install-gitlab-workhorse),
+misconfigured GitLab Workhorse instance. Double-check that you've
+[installed Go](#3-go), [installed GitLab Workhorse](#install-gitlab-workhorse),
and correctly [configured Nginx](#site-configuration).
### google-protobuf "LoadError: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.14' not found"
diff --git a/doc/install/relative_url.md b/doc/install/relative_url.md
index bc6364f57f7..595304e27e2 100644
--- a/doc/install/relative_url.md
+++ b/doc/install/relative_url.md
@@ -110,7 +110,7 @@ Make sure to follow all steps below:
**Note:**
If you are using a custom init script, make sure to edit the above
- gitlab-workhorse setting as needed.
+ GitLab Workhorse setting as needed.
1. [Restart GitLab][] for the changes to take effect.
diff --git a/doc/integration/auth0.md b/doc/integration/auth0.md
index 5061b863e79..2dd6fa3d5a2 100644
--- a/doc/integration/auth0.md
+++ b/doc/integration/auth0.md
@@ -30,7 +30,7 @@ application.
1. On your GitLab server, open the configuration file.
- For omnibus package:
+ For Omnibus package:
```sh
sudo editor /etc/gitlab/gitlab.rb
@@ -48,7 +48,7 @@ application.
1. Add the provider configuration:
- For omnibus package:
+ For Omnibus package:
```ruby
gitlab_rails['omniauth_providers'] = [
diff --git a/doc/integration/azure.md b/doc/integration/azure.md
index a9468f201ef..c30d79e3dab 100644
--- a/doc/integration/azure.md
+++ b/doc/integration/azure.md
@@ -32,7 +32,7 @@ To enable the Microsoft Azure OAuth2 OmniAuth provider you must register your ap
1. On your GitLab server, open the configuration file.
- For omnibus package:
+ For Omnibus package:
```sh
sudo editor /etc/gitlab/gitlab.rb
@@ -50,7 +50,7 @@ To enable the Microsoft Azure OAuth2 OmniAuth provider you must register your ap
1. Add the provider configuration:
- For omnibus package:
+ For Omnibus package:
```ruby
gitlab_rails['omniauth_providers'] = [
diff --git a/doc/integration/cas.md b/doc/integration/cas.md
index f99337376a8..83b64773c9f 100644
--- a/doc/integration/cas.md
+++ b/doc/integration/cas.md
@@ -4,7 +4,7 @@ To enable the CAS OmniAuth provider you must register your application with your
1. On your GitLab server, open the configuration file.
- For omnibus package:
+ For Omnibus package:
```sh
sudo editor /etc/gitlab/gitlab.rb
@@ -22,7 +22,7 @@ To enable the CAS OmniAuth provider you must register your application with your
1. Add the provider configuration:
- For omnibus package:
+ For Omnibus package:
```ruby
gitlab_rails['omniauth_providers'] = [
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index eee05eaef02..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`.
@@ -123,13 +124,15 @@ production instances, they recommend considerably more resources.
Storage requirements also vary based on the installation side, but as a rule of
thumb, you should allocate the total size of your production database, **plus**
-two-thirds of the total size of your git repositories. Efforts to reduce this
-total are being tracked in this epic: [gitlab-org&153](https://gitlab.com/groups/gitlab-org/-/epics/153).
+two-thirds of the total size of your Git repositories. Efforts to reduce this
+total are being tracked in [epic &153](https://gitlab.com/groups/gitlab-org/-/epics/153).
## 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,197 +174,248 @@ 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
There are several rake tasks available to you via the command line:
-- [sudo gitlab-rake gitlab:elastic:index](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+- [`sudo gitlab-rake gitlab:elastic:index`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- This is a wrapper task. It does the following:
- `sudo gitlab-rake gitlab:elastic:create_empty_index`
- `sudo gitlab-rake gitlab:elastic:clear_index_status`
- `sudo gitlab-rake gitlab:elastic:index_projects`
- `sudo gitlab-rake gitlab:elastic:index_snippets`
-- [sudo gitlab-rake gitlab:elastic:index_projects](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+- [`sudo gitlab-rake gitlab:elastic:index_projects`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- This iterates over all projects and queues sidekiq jobs to index them in the background.
-- [sudo gitlab-rake gitlab:elastic:index_projects_status](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+- [`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.
-- [sudo gitlab-rake gitlab:elastic:clear_index_status](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+- [`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, 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)
+- [`sudo gitlab-rake gitlab:elastic:delete_index`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- This removes the GitLab index on the Elasticsearch instance.
-- [sudo gitlab-rake gitlab:elastic:recreate_index](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+- [`sudo gitlab-rake gitlab:elastic:recreate_index`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- Does the same thing as `sudo gitlab-rake gitlab:elastic:create_empty_index`
-- [sudo gitlab-rake gitlab:elastic:index_snippets](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+- [`sudo gitlab-rake gitlab:elastic:index_snippets`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- Performs an Elasticsearch import that indexes the snippets data.
-- [sudo gitlab-rake gitlab:elastic:projects_not_indexed](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+- [`sudo gitlab-rake gitlab:elastic:projects_not_indexed`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- Displays which projects are not indexed.
### Environment Variables
@@ -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/facebook.md b/doc/integration/facebook.md
index 49b3d194a01..d46486ad888 100644
--- a/doc/integration/facebook.md
+++ b/doc/integration/facebook.md
@@ -49,7 +49,7 @@ To enable the Facebook OmniAuth provider you must register your application with
1. On your GitLab server, open the configuration file.
- For omnibus package:
+ For Omnibus package:
```sh
sudo editor /etc/gitlab/gitlab.rb
@@ -67,7 +67,7 @@ To enable the Facebook OmniAuth provider you must register your application with
1. Add the provider configuration:
- For omnibus package:
+ For Omnibus package:
```ruby
gitlab_rails['omniauth_providers'] = [
diff --git a/doc/integration/github.md b/doc/integration/github.md
index c8dbae65465..f19b3109d15 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -36,7 +36,7 @@ To get the credentials (a pair of Client ID and Client Secret), you must registe
1. On your GitLab server, open the configuration file.
- For omnibus package:
+ For Omnibus package:
```sh
sudo editor /etc/gitlab/gitlab.rb
@@ -54,7 +54,7 @@ To get the credentials (a pair of Client ID and Client Secret), you must registe
1. Add the provider configuration:
- For omnibus package:
+ For Omnibus package:
For GitHub.com:
@@ -124,7 +124,7 @@ certificate and the imports are failing, you will need to disable SSL verificati
It should be disabled by adding `verify_ssl` to `false` in the provider configuration
and changing the global Git `sslVerify` option to `false` in the GitLab server.
-For omnibus package:
+For Omnibus package:
```ruby
gitlab_rails['omniauth_providers'] = [
diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md
index 46da3d88d90..74f2d5cb403 100644
--- a/doc/integration/gitlab.md
+++ b/doc/integration/gitlab.md
@@ -30,7 +30,7 @@ GitLab.com will generate an application ID and secret key for you to use.
1. On your GitLab server, open the configuration file.
- For omnibus package:
+ For Omnibus package:
```sh
sudo editor /etc/gitlab/gitlab.rb
@@ -48,7 +48,7 @@ GitLab.com will generate an application ID and secret key for you to use.
1. Add the provider configuration:
- For omnibus package:
+ For Omnibus package:
```ruby
gitlab_rails['omniauth_providers'] = [
diff --git a/doc/integration/jenkins_deprecated.md b/doc/integration/jenkins_deprecated.md
index bac89ae2dc6..3e437eb688a 100644
--- a/doc/integration/jenkins_deprecated.md
+++ b/doc/integration/jenkins_deprecated.md
@@ -14,12 +14,12 @@ Integration includes:
Requirements:
- [Jenkins GitLab Hook plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Hook+Plugin)
-- git clone access for Jenkins from GitLab repo (via ssh key)
+- Git clone access for Jenkins from GitLab repo (via ssh key)
## Jenkins
1. Install [GitLab Hook plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Hook+Plugin)
-1. Set up jenkins project
+1. Set up Jenkins project
![screen](img/jenkins_project.png)
diff --git a/doc/integration/jira.md b/doc/integration/jira.md
index c09fde08326..37eba25fb5a 100644
--- a/doc/integration/jira.md
+++ b/doc/integration/jira.md
@@ -2,4 +2,4 @@
redirect_to: '../user/project/integrations/jira.md'
---
-This document was moved to [integrations/jira](../user/project/integrations/jira.md).
+This document was moved to [another location](../user/project/integrations/jira.md).
diff --git a/doc/integration/jira_development_panel.md b/doc/integration/jira_development_panel.md
index 3e894371df9..c413e07ec93 100644
--- a/doc/integration/jira_development_panel.md
+++ b/doc/integration/jira_development_panel.md
@@ -23,7 +23,7 @@ or instance admin (in the case of self-hosted GitLab) set up the integration,
in order to simplify administration.
TIP: **Tip:**
-Create and use a single-purpose "jira" user in GitLab, so that removing
+Create and use a single-purpose `jira` user in GitLab, so that removing
regular users won't impact your integration.
## Requirements
diff --git a/doc/integration/kerberos.md b/doc/integration/kerberos.md
index b791cbd428d..81a1e9b0067 100644
--- a/doc/integration/kerberos.md
+++ b/doc/integration/kerberos.md
@@ -46,7 +46,7 @@ sudo chmod 0600 /etc/http.keytab
For source installations, make sure the `kerberos` gem group
[has been installed](../install/installation.md#install-gems).
-1. Edit the kerberos section of [gitlab.yml] to enable Kerberos ticket-based
+1. Edit the `kerberos` section of [`gitlab.yml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example) to enable Kerberos ticket-based
authentication. In most cases, you only need to enable Kerberos and specify
the location of the keytab:
@@ -153,7 +153,7 @@ keep offering only `basic` authentication.
listen [::]:8443 ipv6only=on ssl;
```
-1. Update the Kerberos section of [gitlab.yml]:
+1. Update the `kerberos` section of [`gitlab.yml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example):
```yaml
kerberos:
@@ -203,7 +203,7 @@ remove the OmniAuth provider named `kerberos` from your `gitlab.yml` /
**For installations from source**
-1. Edit [gitlab.yml] and remove the `- { name: 'kerberos' }` line under omniauth
+1. Edit [`gitlab.yml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example) and remove the `- { name: 'kerberos' }` line under omniauth
providers:
```yaml
@@ -295,7 +295,6 @@ See also: [Git v2.11 release notes](https://github.com/git/git/blob/master/Docum
- <http://blog.manula.org/2012/04/setting-up-kerberos-server-with-debian.html>
- <http://www.roguelynn.com/words/explain-like-im-5-kerberos/>
-[gitlab.yml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example
[restart gitlab]: ../administration/restart_gitlab.md#installations-from-source
[reconfigure gitlab]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
[nginx]: http://nginx.org/en/docs/http/ngx_http_core_module.html#large_client_header_buffers
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 7a92ed994c7..ef319f7f0ce 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -69,7 +69,7 @@ that are in common for all providers that we need to consider.
To change these settings:
-- **For omnibus package**
+- **For Omnibus package**
Open the configuration file:
@@ -277,3 +277,24 @@ omniauth:
sync_profile_from_provider: ['twitter', 'google_oauth2']
sync_profile_attributes: ['email', 'location']
```
+
+## Bypassing two factor authentication
+
+Starting with GitLab 12.3, this allows users to login with the specified
+providers without two factor authentication.
+
+Define the allowed providers using an array, e.g. `["twitter", 'google_oauth2']`, or as
+`true`/`false` to allow all providers or none. This option should only be configured
+for providers which already have two factor authentication (default: false).
+This configration dose not apply to SAML.
+
+```ruby
+gitlab_rails['omniauth_allow_bypass_two_factor'] = ['twitter', 'google_oauth2']
+```
+
+**For installations from source**
+
+```yaml
+omniauth:
+ allow_bypass_two_factor: ['twitter', 'google_oauth2']
+```
diff --git a/doc/integration/salesforce.md b/doc/integration/salesforce.md
index 176622e8050..10ab9d3c126 100644
--- a/doc/integration/salesforce.md
+++ b/doc/integration/salesforce.md
@@ -29,7 +29,7 @@ To get the credentials (a pair of Client ID and Client Secret), you must [create
1. On your GitLab server, open the configuration file.
- For omnibus package:
+ For Omnibus package:
```sh
sudo editor /etc/gitlab/gitlab.rb
@@ -46,7 +46,7 @@ To get the credentials (a pair of Client ID and Client Secret), you must [create
1. Add the provider configuration:
- For omnibus package:
+ For Omnibus package:
```ruby
gitlab_rails['omniauth_providers'] = [
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 22e07594d6f..de160e72dda 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -17,7 +17,7 @@ in your SAML IdP:
1. On your GitLab server, open the configuration file.
- For omnibus package:
+ For Omnibus package:
```sh
sudo editor /etc/gitlab/gitlab.rb
@@ -34,7 +34,7 @@ in your SAML IdP:
1. To allow your users to use SAML to sign up without having to manually create
an account first, don't forget to add the following values to your configuration:
- For omnibus package:
+ For Omnibus package:
```ruby
gitlab_rails['omniauth_enabled'] = true
@@ -54,7 +54,7 @@ in your SAML IdP:
1. You can also automatically link SAML users with existing GitLab users if their
email addresses match by adding the following setting:
- For omnibus package:
+ For Omnibus package:
```ruby
gitlab_rails['omniauth_auto_link_saml_user'] = true
@@ -68,7 +68,7 @@ in your SAML IdP:
1. Add the provider configuration:
- For omnibus package:
+ For Omnibus package:
```ruby
gitlab_rails['omniauth_providers'] = [
@@ -342,7 +342,7 @@ You can add this setting to your GitLab configuration to automatically redirect
to your SAML server for authentication, thus removing the need to click a button
before actually signing in.
-For omnibus package:
+For Omnibus package:
```ruby
gitlab_rails['omniauth_auto_sign_in_with_provider'] = 'saml'
diff --git a/doc/integration/shibboleth.md b/doc/integration/shibboleth.md
index 27355d25266..ca5a8077e73 100644
--- a/doc/integration/shibboleth.md
+++ b/doc/integration/shibboleth.md
@@ -1,18 +1,18 @@
# Shibboleth OmniAuth Provider
-This documentation is for enabling shibboleth with omnibus-gitlab package.
+This documentation is for enabling Shibboleth with the Omnibus GitLab package.
-In order to enable Shibboleth support in gitlab we need to use Apache instead of Nginx (It may be possible to use Nginx, however this is difficult to configure using the bundled Nginx provided in the omnibus-gitlab package). Apache uses mod_shib2 module for shibboleth authentication and can pass attributes as headers to omniauth-shibboleth provider.
+In order to enable Shibboleth support in GitLab we need to use Apache instead of Nginx (It may be possible to use Nginx, however this is difficult to configure using the bundled Nginx provided in the Omnibus GitLab package). Apache uses mod_shib2 module for Shibboleth authentication and can pass attributes as headers to Omniauth Shibboleth provider.
-To enable the Shibboleth OmniAuth provider you must configure Apache shibboleth module.
+To enable the Shibboleth OmniAuth provider you must configure Apache Shibboleth module.
The installation and configuration of the module itself is out of the scope of this document.
Check <https://wiki.shibboleth.net/confluence/display/SP3/Apache> for more info.
-You can find Apache config in gitlab-recipes (<https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache>).
+You can find Apache config in [GitLab Recipes](https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache).
The following changes are needed to enable Shibboleth:
-1. Protect omniauth-shibboleth callback URL:
+1. Protect Omniauth Shibboleth callback URL:
```
<Location /users/auth/shibboleth/callback>
@@ -32,7 +32,7 @@ The following changes are needed to enable Shibboleth:
</Location>
```
-1. Exclude shibboleth URLs from rewriting. Add `RewriteCond %{REQUEST_URI} !/Shibboleth.sso` and `RewriteCond %{REQUEST_URI} !/shibboleth-sp`. Config should look like this:
+1. Exclude Shibboleth URLs from rewriting. Add `RewriteCond %{REQUEST_URI} !/Shibboleth.sso` and `RewriteCond %{REQUEST_URI} !/shibboleth-sp`. Config should look like this:
```
# Apache equivalent of Nginx try files
@@ -50,7 +50,7 @@ The following changes are needed to enable Shibboleth:
attribute mapping. Therefore the values of the `args` hash
should be in the form of `"HTTP_ATTRIBUTE"`. The keys in the hash are arguments
to the [OmniAuth::Strategies::Shibboleth class](https://github.com/toyokazu/omniauth-shibboleth/blob/master/lib/omniauth/strategies/shibboleth.rb)
- and are documented by the [omniauth-shibboleth gem](https://github.com/toyokazu/omniauth-shibboleth)
+ and are documented by the [`omniauth-shibboleth` gem](https://github.com/toyokazu/omniauth-shibboleth)
(take care to note the version of the gem packaged with GitLab). If some of
your users appear to be authenticated by Shibboleth and Apache, but GitLab
rejects their account with a URI that contains "e-mail is invalid" then your
@@ -94,7 +94,7 @@ On the sign in page, there should now be a "Sign in with: Shibboleth" icon below
## Apache 2.4 / GitLab 8.6 update
The order of the first 2 Location directives is important. If they are reversed,
-you will not get a shibboleth session!
+you will not get a Shibboleth session!
```
<Location />
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/integration/twitter.md b/doc/integration/twitter.md
index d8096993885..b2bd1b57d0f 100644
--- a/doc/integration/twitter.md
+++ b/doc/integration/twitter.md
@@ -32,7 +32,7 @@ To enable the Twitter OmniAuth provider you must register your application with
1. On your GitLab server, open the configuration file.
- For omnibus package:
+ For Omnibus package:
```sh
sudo editor /etc/gitlab/gitlab.rb
@@ -50,7 +50,7 @@ To enable the Twitter OmniAuth provider you must register your application with
1. Add the provider configuration:
- For omnibus package:
+ For Omnibus package:
```ruby
gitlab_rails['omniauth_providers'] = [
diff --git a/doc/integration/ultra_auth.md b/doc/integration/ultra_auth.md
index 9ed1bdb4882..fb950ba989a 100644
--- a/doc/integration/ultra_auth.md
+++ b/doc/integration/ultra_auth.md
@@ -25,7 +25,7 @@ To get the credentials (a pair of Client ID and Client Secret), you must registe
1. Select **Register application**.
1. On your GitLab server, open the configuration file.
- For omnibus package:
+ For Omnibus package:
```sh
sudo editor /etc/gitlab/gitlab.rb
@@ -41,7 +41,7 @@ To get the credentials (a pair of Client ID and Client Secret), you must registe
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
1. Add the provider configuration:
- For omnibus package:
+ For Omnibus package:
```ruby
gitlab_rails['omniauth_providers'] = [
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/project_services/bamboo.md b/doc/project_services/bamboo.md
index a1ff8909f87..b1d37898516 100644
--- a/doc/project_services/bamboo.md
+++ b/doc/project_services/bamboo.md
@@ -2,4 +2,4 @@
redirect_to: '../user/project/integrations/bamboo.md'
---
-This document was moved to [user/project/integrations/bamboo.md](../user/project/integrations/bamboo.md).
+This document was moved to [another location](../user/project/integrations/bamboo.md).
diff --git a/doc/project_services/bugzilla.md b/doc/project_services/bugzilla.md
index 1abf1b28939..17dff538c0e 100644
--- a/doc/project_services/bugzilla.md
+++ b/doc/project_services/bugzilla.md
@@ -2,4 +2,4 @@
redirect_to: '../user/project/integrations/bugzilla.md'
---
-This document was moved to [user/project/integrations/bugzilla.md](../user/project/integrations/bugzilla.md).
+This document was moved to [another location](../user/project/integrations/bugzilla.md).
diff --git a/doc/project_services/emails_on_push.md b/doc/project_services/emails_on_push.md
index c5ab8aa5c70..a7d91934ce9 100644
--- a/doc/project_services/emails_on_push.md
+++ b/doc/project_services/emails_on_push.md
@@ -2,4 +2,4 @@
redirect_to: '../user/project/integrations/emails_on_push.md'
---
-This document was moved to [user/project/integrations/emails_on_push.md](../user/project/integrations/emails_on_push.md).
+This document was moved to [another location](../user/project/integrations/emails_on_push.md).
diff --git a/doc/project_services/hipchat.md b/doc/project_services/hipchat.md
index 4ae9f6c6b2e..a2fbbd5cce5 100644
--- a/doc/project_services/hipchat.md
+++ b/doc/project_services/hipchat.md
@@ -1 +1,5 @@
-This document was moved to [user/project/integrations/hipchat.md](../user/project/integrations/hipchat.md).
+---
+redirect_to: '../user/project/integrations/hipchat.md'
+---
+
+This document was moved to [another location](../user/project/integrations/hipchat.md).
diff --git a/doc/project_services/irker.md b/doc/project_services/irker.md
index af47abab117..70e46b1b364 100644
--- a/doc/project_services/irker.md
+++ b/doc/project_services/irker.md
@@ -2,4 +2,4 @@
redirect_to: '../user/project/integrations/irker.md'
---
-This document was moved to [user/project/integrations/irker.md](../user/project/integrations/irker.md).
+This document was moved to [another location](../user/project/integrations/irker.md).
diff --git a/doc/project_services/jira.md b/doc/project_services/jira.md
index 6a0108f400f..37eba25fb5a 100644
--- a/doc/project_services/jira.md
+++ b/doc/project_services/jira.md
@@ -2,4 +2,4 @@
redirect_to: '../user/project/integrations/jira.md'
---
-This document was moved to [user/project/integrations/jira.md](../user/project/integrations/jira.md).
+This document was moved to [another location](../user/project/integrations/jira.md).
diff --git a/doc/project_services/kubernetes.md b/doc/project_services/kubernetes.md
index cfe36fcd1b2..585c5ddb002 100644
--- a/doc/project_services/kubernetes.md
+++ b/doc/project_services/kubernetes.md
@@ -2,4 +2,4 @@
redirect_to: '../user/project/integrations/kubernetes.md'
---
-This document was moved to [user/project/integrations/kubernetes.md](../user/project/integrations/kubernetes.md).
+This document was moved to [another location](../user/project/integrations/kubernetes.md).
diff --git a/doc/project_services/mattermost.md b/doc/project_services/mattermost.md
index de9f4d14cf7..78888395031 100644
--- a/doc/project_services/mattermost.md
+++ b/doc/project_services/mattermost.md
@@ -2,4 +2,4 @@
redirect_to: '../user/project/integrations/mattermost.md'
---
-This document was moved to [user/project/integrations/mattermost.md](../user/project/integrations/mattermost.md).
+This document was moved to [another location](../user/project/integrations/mattermost.md).
diff --git a/doc/project_services/mattermost_slash_commands.md b/doc/project_services/mattermost_slash_commands.md
index 82ec34739c1..0c2774d95e0 100644
--- a/doc/project_services/mattermost_slash_commands.md
+++ b/doc/project_services/mattermost_slash_commands.md
@@ -2,4 +2,4 @@
redirect_to: '../user/project/integrations/mattermost_slash_commands.md'
---
-This document was moved to [user/project/integrations/mattermost_slash_commands.md](../user/project/integrations/mattermost_slash_commands.md).
+This document was moved to [another location](../user/project/integrations/mattermost_slash_commands.md).
diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md
index a355851a273..2ae7a0ce3ce 100644
--- a/doc/project_services/project_services.md
+++ b/doc/project_services/project_services.md
@@ -2,4 +2,4 @@
redirect_to: '../user/project/integrations/project_services.md'
---
-This document was moved to [user/project/integrations/project_services.md](../user/project/integrations/project_services.md).
+This document was moved to [another location](../user/project/integrations/project_services.md).
diff --git a/doc/project_services/redmine.md b/doc/project_services/redmine.md
index 05f28f00adc..141c72d6b6b 100644
--- a/doc/project_services/redmine.md
+++ b/doc/project_services/redmine.md
@@ -2,4 +2,4 @@
redirect_to: '../user/project/integrations/redmine.md'
---
-This document was moved to [user/project/integrations/redmine.md](../user/project/integrations/redmine.md).
+This document was moved to [another location](../user/project/integrations/redmine.md).
diff --git a/doc/project_services/services_templates.md b/doc/project_services/services_templates.md
index ac6b85cc801..8b2c85802de 100644
--- a/doc/project_services/services_templates.md
+++ b/doc/project_services/services_templates.md
@@ -2,4 +2,4 @@
redirect_to: '../user/project/integrations/services_templates.md'
---
-This document was moved to [user/project/integrations/services_templates.md](../user/project/integrations/services_templates.md).
+This document was moved to [another location](../user/project/integrations/services_templates.md).
diff --git a/doc/project_services/slack.md b/doc/project_services/slack.md
index 4c89ce92002..815032a08d5 100644
--- a/doc/project_services/slack.md
+++ b/doc/project_services/slack.md
@@ -2,4 +2,4 @@
redirect_to: '../user/project/integrations/slack.md'
---
-This document was moved to [user/project/integrations/slack.md](../user/project/integrations/slack.md).
+This document was moved to [another location](../user/project/integrations/slack.md).
diff --git a/doc/project_services/slack_slash_commands.md b/doc/project_services/slack_slash_commands.md
index ca0034256f1..caae4d2ba4b 100644
--- a/doc/project_services/slack_slash_commands.md
+++ b/doc/project_services/slack_slash_commands.md
@@ -2,4 +2,4 @@
redirect_to: '../user/project/integrations/slack_slash_commands.md'
---
-This document was moved to [user/project/integrations/slack_slash_commands.md](../user/project/integrations/slack_slash_commands.md).
+This document was moved to [another location](../user/project/integrations/slack_slash_commands.md).
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/README.md b/doc/raketasks/README.md
index dcc96507676..ad86555fc17 100644
--- a/doc/raketasks/README.md
+++ b/doc/raketasks/README.md
@@ -12,7 +12,7 @@ comments: false
- [General Maintenance](../administration/raketasks/maintenance.md) and self-checks
- [User management](user_management.md)
- [Webhooks](web_hooks.md)
-- [Import](import.md) of git repositories in bulk
+- [Import](import.md) of Git repositories in bulk
- [Rebuild authorized_keys file](../administration/raketasks/maintenance.md#rebuild-authorized_keys-file) task for administrators
- [Migrate Uploads](../administration/raketasks/uploads/migrate.md)
- [Sanitize Uploads](../administration/raketasks/uploads/sanitize.md)
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index b4b7d711d5a..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
@@ -458,7 +458,7 @@ You may also send backups to a mounted share (`NFS` / `CIFS` / `SMB` / etc.) by
using the Fog [`Local`](https://github.com/fog/fog-local#usage) storage provider.
The directory pointed to by the `local_root` key **must** be owned by the `git`
user **when mounted** (mounting with the `uid=` of the `git` user for `CIFS` and
-`SMB`) or the user that you are executing the backup tasks under (for omnibus
+`SMB`) or the user that you are executing the backup tasks under (for Omnibus
packages, this is the `git` user).
The `backup_upload_remote_directory` **must** be set in addition to the
@@ -507,7 +507,7 @@ For installations from source:
### Backup archive permissions
The backup archives created by GitLab (`1393513186_2014_02_27_gitlab_backup.tar`)
-will have owner/group git:git and 0600 permissions by default.
+will have owner/group `git`/`git` and 0600 permissions by default.
This is meant to avoid other system users reading GitLab's data.
If you need the backup archives to have different permissions you can use the 'archive_permissions' setting.
@@ -614,7 +614,7 @@ GitLab that you created it on, for example CE 9.1.0.
You need to have a working GitLab installation before you can perform
a restore. This is mainly because the system user performing the
-restore actions ('git') is usually not allowed to create or delete
+restore actions (`git`) is usually not allowed to create or delete
the SQL database it needs to import data into ('gitlabhq_production').
All existing data will be either erased (SQL) or moved to a separate
directory (repositories, uploads).
@@ -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
@@ -773,14 +773,14 @@ In this case you can consider using filesystem snapshots as part of your backup
Example: Amazon EBS
-> A GitLab server using omnibus-gitlab hosted on Amazon AWS.
+> A GitLab server using Omnibus GitLab hosted on Amazon AWS.
> An EBS drive containing an ext4 filesystem is mounted at `/var/opt/gitlab`.
> In this case you could make an application backup by taking an EBS snapshot.
> The backup includes all repositories, uploads and Postgres data.
Example: LVM snapshots + rsync
-> A GitLab server using omnibus-gitlab, with an LVM logical volume mounted at `/var/opt/gitlab`.
+> A GitLab server using Omnibus GitLab, with an LVM logical volume mounted at `/var/opt/gitlab`.
> Replicating the `/var/opt/gitlab` directory using rsync would not be reliable because too many files would change while rsync is running.
> Instead of rsync-ing `/var/opt/gitlab`, we create a temporary LVM snapshot, which we mount as a read-only filesystem at `/mnt/gitlab_backup`.
> Now we can have a longer running rsync job which will create a consistent replica on the remote server.
@@ -804,7 +804,7 @@ will have all your repositories, but not any other data.
## Troubleshooting
-### Restoring database backup using omnibus packages outputs warnings
+### Restoring database backup using Omnibus packages outputs warnings
If you are using backup restore procedures you might encounter the following warnings:
@@ -923,6 +923,29 @@ backup beforehand.
UPDATE ci_runners SET token = null, token_encrypted = null;
```
+#### Reset pending pipeline jobs
+
+1. Enter the DB console:
+
+ For Omnibus GitLab packages:
+
+ ```sh
+ sudo gitlab-rails dbconsole
+ ```
+
+ For installations from source:
+
+ ```sh
+ sudo -u git -H bundle exec rails dbconsole RAILS_ENV=production
+ ```
+
+1. Clear all the tokens for pending jobs:
+
+ ```sql
+ -- Clear build tokens
+ UPDATE ci_builds SET token = null, token_encrypted = null;
+ ```
+
A similar strategy can be employed for the remaining features - by removing the
data that cannot be decrypted, GitLab can be brought back into working order,
and the lost data can be manually replaced.
diff --git a/doc/raketasks/features.md b/doc/raketasks/features.md
index 57c16f110e4..c06800a2aa3 100644
--- a/doc/raketasks/features.md
+++ b/doc/raketasks/features.md
@@ -6,7 +6,7 @@ This command will enable the namespaces feature introduced in v4.0. It will move
Note:
-- Because the **repository location will change**, you will need to **update all your git URLs** to point to the new location.
+- Because the **repository location will change**, you will need to **update all your Git URLs** to point to the new location.
- Username can be changed at **Profile ➔ Account**.
**Example:**
diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md
index c76180a242e..d93e7241fda 100644
--- a/doc/raketasks/import.md
+++ b/doc/raketasks/import.md
@@ -13,7 +13,7 @@
### Create a new folder to import your Git repositories from
-The new folder needs to have git user ownership and read/write/execute access for git user and its group:
+The new folder needs to have Git user ownership and read/write/execute access for Git user and its group:
```
sudo -u git mkdir -p /var/opt/gitlab/git-data/repository-import-<date>/new_group
@@ -21,7 +21,7 @@ sudo -u git mkdir -p /var/opt/gitlab/git-data/repository-import-<date>/new_group
### Copy your bare repositories inside this newly created folder
-- Any .git repositories found on any of the subfolders will be imported as projects
+- Any `.git` repositories found on any of the subfolders will be imported as projects
- Groups will be created as needed, these could be nested folders. Example:
If we copy the repos to `/var/opt/gitlab/git-data/repository-import-<date>`, and repo A needs to be under the groups G1 and G2, it will
@@ -34,7 +34,7 @@ sudo cp -r /old/git/foo.git /var/opt/gitlab/git-data/repository-import-<date>/ne
sudo chown -R git:git /var/opt/gitlab/git-data/repository-import-<date>
```
-`foo.git` needs to be owned by the git user and git users group.
+`foo.git` needs to be owned by the `git` user and `git` users group.
If you are using an installation from source, replace `/var/opt/gitlab/` with `/home/git`.
@@ -96,7 +96,7 @@ Importing bare repositories from hashed storage is unsupported.
To support importing bare repositories from hashed storage, GitLab 10.4 and
later stores the full project path with each repository, in a special section of
-the git repository's config file. This section is formatted as follows:
+the Git repository's config file. This section is formatted as follows:
```
[gitlab]
diff --git a/doc/security/README.md b/doc/security/README.md
index 5d498ac7602..fe96f7f2846 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -5,6 +5,7 @@ type: index
# Security
+- [Password storage](password_storage.md)
- [Password length limits](password_length_limits.md)
- [Restrict SSH key technologies and minimum length](ssh_keys_restrictions.md)
- [Rate limits](rate_limits.md)
@@ -17,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/security/password_storage.md b/doc/security/password_storage.md
new file mode 100644
index 00000000000..f4e32f96f7b
--- /dev/null
+++ b/doc/security/password_storage.md
@@ -0,0 +1,13 @@
+---
+type: reference
+---
+
+# Password Storage
+
+GitLab stores user passwords in a hashed format, to prevent passwords from being visible.
+
+GitLab uses the [Devise](https://github.com/plataformatec/devise) authentication library, which handles the hashing of user passwords. Password hashes are created with the following attributes:
+
+- **Hashing**: the [bcrypt](https://en.wikipedia.org/wiki/Bcrypt) hashing function is used to generate the hash of the provided password. This is a strong, industry-standard cryptographic hashing function.
+- **Stretching**: Password hashes are [stretched](https://en.wikipedia.org/wiki/Key_stretching) to harden against brute-force attacks. GitLab uses a stretching factor of 10 by default.
+- **Salting**: A [cryptographic salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) is added to each password to harden against pre-computed hash and dictionary attacks. Each salt is randomly generated for each password, so that no two passwords share a salt, to further increase security.
diff --git a/doc/security/rack_attack.md b/doc/security/rack_attack.md
index b99bfb16829..09d29bf3446 100644
--- a/doc/security/rack_attack.md
+++ b/doc/security/rack_attack.md
@@ -77,9 +77,12 @@ authentication requests were received in a 3-minute period from a single IP addr
This applies only to Git requests and container registry (`/jwt/auth`) requests
(combined).
-This limit is reset by requests that authenticate successfully. For example, 29
-failed authentication requests followed by 1 successful request, followed by 29
-more failed authentication requests would not trigger a ban.
+This limit:
+
+- Is reset by requests that authenticate successfully. For example, 29
+ failed authentication requests followed by 1 successful request, followed by 29
+ more failed authentication requests would not trigger a ban.
+- Does not apply to JWT requests authenticated by `gitlab-ci-token`.
No response headers are provided.
diff --git a/doc/security/reset_root_password.md b/doc/security/reset_root_password.md
index ec360e2d338..00c9dc1407d 100644
--- a/doc/security/reset_root_password.md
+++ b/doc/security/reset_root_password.md
@@ -9,7 +9,7 @@ To reset your root password, first log into your server with root privileges.
Start a Ruby on Rails console with this command:
```bash
-gitlab-rails console production
+gitlab-rails console -e production
```
Wait until the console has loaded.
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 26d5221acc5..15fdb52ac00 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -95,34 +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 need a [Kubernetes cluster][kubernetes-clusters]
- for the project, or a Kubernetes [default service template](../../user/project/integrations/services_templates.md)
- for the entire GitLab installation.
- - **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)
@@ -415,7 +426,7 @@ report is created, it's uploaded as an artifact which you can later download and
check out.
Any licenses are also shown in the merge request widget. Read more how
-[License Compliance works](../../user/application_security/license_management/index.md).
+[License Compliance works](../../user/application_security/license_compliance/index.md).
### Auto Container Scanning **(ULTIMATE)**
@@ -695,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.
@@ -783,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/diff_limits.md b/doc/user/admin_area/diff_limits.md
index 9fe4b50a991..5117b5f476f 100644
--- a/doc/user/admin_area/diff_limits.md
+++ b/doc/user/admin_area/diff_limits.md
@@ -6,6 +6,8 @@ type: reference
You can set a maximum size for display of diff files (patches).
+For details about diff files, [View changes between files](../project/merge_requests/index.md#view-changes-between-file-versions).
+
## Maximum diff patch size
Diff files which exceed this value will be presented as 'too large' and won't
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/monitoring/health_check.md b/doc/user/admin_area/monitoring/health_check.md
index 333d2fb42b3..52f24c602df 100644
--- a/doc/user/admin_area/monitoring/health_check.md
+++ b/doc/user/admin_area/monitoring/health_check.md
@@ -21,28 +21,63 @@ traffic until the system is ready or restart the container as needed.
To access monitoring resources, the requesting client IP needs to be included in a whitelist.
For details, see [how to add IPs to a whitelist for the monitoring endpoints](../../../administration/monitoring/ip_whitelist.md).
-## Using the endpoints
+## Using the endpoints locally
With default whitelist settings, the probes can be accessed from localhost using the following URLs:
-- `http://localhost/-/health`
-- `http://localhost/-/readiness`
-- `http://localhost/-/liveness`
+```text
+GET http://localhost/-/health
+```
+
+```text
+GET http://localhost/-/readiness
+```
+
+```text
+GET http://localhost/-/liveness
+```
+
+## Health
-The first endpoint, `health`, only checks whether the application server is running. It does not verify the database or other services are running. A successful response will return a 200 status code with the following message:
+Checks whether the application server is running. It does not verify the database or other services are running.
+
+```text
+GET /-/health
+```
+
+Example request:
+
+```sh
+curl 'https://gitlab.example.com/-/health'
+```
+
+Example response:
```text
GitLab OK
```
-The readiness and liveness probes will provide a report of system health in JSON format.
+## Readiness
+
+The readiness probe checks whether the Gitlab instance is ready to use. It checks the dependent services (Database, Redis, Gitaly etc.) and gives a status for each.
+
+```text
+GET /-/readiness
+```
+
+Example request:
-`readiness` probe example output:
+```sh
+curl 'https://gitlab.example.com/-/readiness'
+```
+
+Example response:
```json
{
"db_check":{
- "status":"ok"
+ "status":"failed",
+ "message": "unexpected Db check result: 0"
},
"redis_check":{
"status":"ok"
@@ -65,7 +100,23 @@ The readiness and liveness probes will provide a report of system health in JSON
}
```
-`liveness` probe example output:
+## Liveness
+
+The liveness probe checks whether the application server is alive. Unlike the [`health`](#health) check, this check hits the database.
+
+```text
+GET /-/liveness
+```
+
+Example request:
+
+```sh
+curl 'https://gitlab.example.com/-/liveness'
+```
+
+Example response:
+
+On success, the endpoint will return a valid successful HTTP status code, and a response like below.
```json
{
@@ -90,10 +141,7 @@ The readiness and liveness probes will provide a report of system health in JSON
}
```
-## Status
-
-On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint
-will return a valid successful HTTP status code, and a `success` message.
+On failure, the endpoint will return a `500` HTTP status code.
## Access token (Deprecated)
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/admin_area/settings/terms.md b/doc/user/admin_area/settings/terms.md
index baf219ac9c7..0b5f9a13b03 100644
--- a/doc/user/admin_area/settings/terms.md
+++ b/doc/user/admin_area/settings/terms.md
@@ -4,8 +4,7 @@ type: reference
# Enforce accepting Terms of Service **(CORE ONLY)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18570)
-> in [GitLab Core](https://about.gitlab.com/pricing/) 10.8
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18570) in [GitLab Core](https://about.gitlab.com/pricing/) 10.8.
An admin can enforce acceptance of a terms of service and privacy policy. When this option is enabled, new and existing users must accept the terms.
diff --git a/doc/user/admin_area/settings/third_party_offers.md b/doc/user/admin_area/settings/third_party_offers.md
index ca26147b287..50dea8f50a2 100644
--- a/doc/user/admin_area/settings/third_party_offers.md
+++ b/doc/user/admin_area/settings/third_party_offers.md
@@ -4,8 +4,7 @@ type: reference
# Third party offers **(CORE ONLY)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20379)
-> in [GitLab Core](https://about.gitlab.com/pricing/) 11.1
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20379) in [GitLab Core](https://about.gitlab.com/pricing/) 11.1.
Within GitLab, we inform users of available third-party offers they might find valuable in order
to enhance the development of their projects. An example is the Google Cloud Platform free credit
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index 86491c7d74e..c3f80c6a0fd 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -94,10 +94,40 @@ If you want to whitelist some specific vulnerabilities, you can do so by definin
them in a YAML file named `clair-whitelist.yml`. Read more in the
[Clair documentation](https://github.com/arminc/clair-scanner/blob/master/README.md#example-whitelist-yaml-file).
+## 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.
+
+```yaml
+variables:
+ DOCKER_DRIVER: overlay2
+
+services:
+ - docker:stable-dind
+
+stages:
+ - build
+ - test
+
+include:
+ - template: Container-Scanning.gitlab-ci.yml
+
+build:
+ image: docker:stable
+ stage: build
+ variables:
+ IMAGE: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA
+ script:
+ - docker info
+ - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
+ - docker build -t $IMAGE .
+ - docker push $IMAGE
+```
+
## Security Dashboard
The Security Dashboard is a good place to get an overview of all the security
-vulnerabilities in your groups and projects. Read more about the
+vulnerabilities in your groups, projects and pipelines. Read more about the
[Security Dashboard](../security_dashboard/index.md).
## Interacting with the vulnerabilities
diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md
index fa84f995e58..2d9f522c4f0 100644
--- a/doc/user/application_security/dast/index.md
+++ b/doc/user/application_security/dast/index.md
@@ -84,6 +84,8 @@ There are two ways to define the URL to be scanned by DAST:
- Set the `DAST_WEBSITE` [variable](../../../ci/yaml/README.md#variables).
- Add it in an `environment_url.txt` file at the root of your project.
+If both values are set, the `DAST_WEBSITE` value will take precedence.
+
The included template will create a `dast` job in your CI/CD pipeline and scan
your project's source code for possible vulnerabilities.
@@ -134,7 +136,7 @@ variables:
The DAST settings can be changed through environment variables by using the
[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`.
-These variables are documented in the [DAST README](https://gitlab.com/gitlab-org/security-products/dast#settings).
+These variables are documented in [available variables](#available-variables).
For example:
@@ -196,7 +198,7 @@ variable value.
## Security Dashboard
The Security Dashboard is a good place to get an overview of all the security
-vulnerabilities in your groups and projects. Read more about the
+vulnerabilities in your groups, projects and pipelines. Read more about the
[Security Dashboard](../security_dashboard/index.md).
## Interacting with the vulnerabilities
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 3148ec63c79..fa2df667031 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
@@ -141,22 +141,22 @@ dependency_scanning:
Dependency Scanning can be [configured](#customizing-the-dependency-scanning-settings)
using environment variables.
-| Environment variable | Function |
-|-------------------------------- |----------|
-| `DS_ANALYZER_IMAGES` | Comma separated list of custom images. The official default images are still enabled. Read more about [customizing analyzers](analyzers.md). |
-| `DS_ANALYZER_IMAGE_PREFIX` | Override the name of the Docker registry providing the official default images (proxy). Read more about [customizing analyzers](analyzers.md). |
-| `DS_ANALYZER_IMAGE_TAG` | Override the Docker tag of the official default images. Read more about [customizing analyzers](analyzers.md). |
-| `DS_PYTHON_VERSION` | Version of Python. If set to 2, dependencies are installed using Python 2.7 instead of Python 3.6. ([Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/12296) in GitLab 12.1)|
-| `DS_PIP_DEPENDENCY_PATH` | Path to load Python pip dependencies from. ([Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/12412) in GitLab 12.2) |
-| `DS_DEFAULT_ANALYZERS` | Override the names of the official default images. Read more about [customizing analyzers](analyzers.md). |
-| `DS_DISABLE_REMOTE_CHECKS` | Do not send any data to GitLab. Used in the [Gemnasium analyzer](#remote-checks). |
-| `DS_PULL_ANALYZER_IMAGES` | Pull the images from the Docker registry (set to `0` to disable). |
-| `DS_EXCLUDED_PATHS` | Exclude vulnerabilities from output based on the paths. A comma-separated list of patterns. Patterns can be globs, file or folder paths. Parent directories will also match patterns. |
-| `DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT` | Time limit for Docker client negotiation. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. |
-| `DS_PULL_ANALYZER_IMAGE_TIMEOUT` | Time limit when pulling the image of an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. |
-| `DS_RUN_ANALYZER_TIMEOUT` | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. |
-| `PIP_INDEX_URL` | Base URL of Python Package Index (default `https://pypi.org/simple`). |
-| `PIP_EXTRA_INDEX_URL` | Array of [extra URLs](https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-extra-index-url) of package indexes to use in addition to `PIP_INDEX_URL`. Comma separated. |
+| Environment variable | Description | Example usage |
+|-------------------------------- |-------------| |
+| `DS_ANALYZER_IMAGES` | Comma separated list of custom images. The official default images are still enabled. Read more about [customizing analyzers](analyzers.md). | |
+| `DS_ANALYZER_IMAGE_PREFIX` | Override the name of the Docker registry providing the official default images (proxy). Read more about [customizing analyzers](analyzers.md). | |
+| `DS_ANALYZER_IMAGE_TAG` | Override the Docker tag of the official default images. Read more about [customizing analyzers](analyzers.md). | |
+| `DS_PYTHON_VERSION` | Version of Python. If set to 2, dependencies are installed using Python 2.7 instead of Python 3.6. ([Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/12296) in GitLab 12.1)| |
+| `DS_PIP_DEPENDENCY_PATH` | Path to load Python pip dependencies from. ([Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/12412) in GitLab 12.2) | |
+| `DS_DEFAULT_ANALYZERS` | Override the names of the official default images. Read more about [customizing analyzers](analyzers.md). | |
+| `DS_DISABLE_REMOTE_CHECKS` | Do not send any data to GitLab. Used in the [Gemnasium analyzer](#remote-checks). | |
+| `DS_PULL_ANALYZER_IMAGES` | Pull the images from the Docker registry (set to `0` to disable). | |
+| `DS_EXCLUDED_PATHS` | Exclude vulnerabilities from output based on the paths. A comma-separated list of patterns. Patterns can be globs, file or folder paths. Parent directories will also match patterns. | `DS_EXCLUDED_PATHS=doc,spec` |
+| `DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT` | Time limit for Docker client negotiation. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. | |
+| `DS_PULL_ANALYZER_IMAGE_TIMEOUT` | Time limit when pulling the image of an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. | |
+| `DS_RUN_ANALYZER_TIMEOUT` | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. | |
+| `PIP_INDEX_URL` | Base URL of Python Package Index (default `https://pypi.org/simple`). | |
+| `PIP_EXTRA_INDEX_URL` | Array of [extra URLs](https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-extra-index-url) of package indexes to use in addition to `PIP_INDEX_URL`. Comma separated. | |
## Reports JSON format
@@ -276,8 +276,8 @@ it highlighted:
Here is the description of the report file structure nodes and their meaning. All fields are mandatory to be present in
the report JSON unless stated otherwise. Presence of optional fields depends on the underlying analyzers being used.
-| Report JSON node | Function |
-|------------------------------------------------------|----------|
+| Report JSON node | Description |
+|------------------------------------------------------|-------------|
| `version` | Report syntax version used to generate this JSON. |
| `vulnerabilities` | Array of vulnerability objects. |
| `vulnerabilities[].category` | Where this vulnerability belongs (SAST, Dependency Scanning etc.). For Dependency Scanning, it will always be `dependency_scanning`. |
@@ -314,7 +314,7 @@ the report JSON unless stated otherwise. Presence of optional fields depends on
## Security Dashboard
The Security Dashboard is a good place to get an overview of all the security
-vulnerabilities in your groups and projects. Read more about the
+vulnerabilities in your groups, projects and pipelines. Read more about the
[Security Dashboard](../security_dashboard/index.md).
## Interacting with the vulnerabilities
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_management/img/license_management.png b/doc/user/application_security/license_compliance/img/license_compliance.png
index cdce6b5fe38..cdce6b5fe38 100644
--- a/doc/user/application_security/license_management/img/license_management.png
+++ b/doc/user/application_security/license_compliance/img/license_compliance.png
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_management/img/license_management_decision.png b/doc/user/application_security/license_compliance/img/license_compliance_decision.png
index fbf90bec7fd..fbf90bec7fd 100644
--- a/doc/user/application_security/license_management/img/license_management_decision.png
+++ b/doc/user/application_security/license_compliance/img/license_compliance_decision.png
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_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_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
new file mode 100644
index 00000000000..6de1db8650d
--- /dev/null
+++ b/doc/user/application_security/license_compliance/index.md
@@ -0,0 +1,243 @@
+---
+type: reference, howto
+---
+
+# License Compliance **(ULTIMATE)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5483) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.0.
+
+## Overview
+
+If you are using [GitLab CI/CD](../../../ci/README.md), you can search your project dependencies for their licenses
+using License Compliance.
+
+You can take advantage of License Compliance by either [including the job](#configuration)
+in your existing `.gitlab-ci.yml` file or by implicitly using
+[Auto License Compliance](../../../topics/autodevops/index.md#auto-license-compliance-ultimate)
+that is provided by [Auto DevOps](../../../topics/autodevops/index.md).
+
+GitLab checks the License Compliance report, compares the licenses between the
+source and target branches, and shows the information right on the merge request.
+Blacklisted licenses will be clearly visible with an `x` red icon next to them
+as well as new licenses which need a decision from you. In addition, you can
+[manually approve or blacklist](#project-policies-for-license-compliance)
+licenses in your project's settings.
+
+NOTE: **Note:**
+If the license compliance report doesn't have anything to compare to, no information
+will be displayed in the merge request area. That is the case when you add the
+`license_management` job in your `.gitlab-ci.yml` for the first time.
+Consecutive merge requests will have something to compare to and the license
+compliance report will be shown properly.
+
+![License Compliance Widget](img/license_compliance.png)
+
+If you are a project or group Maintainer, you can click on a license to be given
+the choice to approve it or blacklist it.
+
+![License approval decision](img/license_compliance_decision.png)
+
+## Use cases
+
+It helps you find what licenses your project uses in its dependencies, and decide for each of then
+whether to allow it or forbid it. For example, your application is using an external (open source)
+library whose license is incompatible with yours.
+
+## Supported languages and package managers
+
+The following languages and package managers are supported.
+
+| Language | Package managers | Scan Tool |
+|------------|-------------------------------------------------------------------|----------------------------------------------------------|
+| JavaScript | [Bower](https://bower.io/), [npm](https://www.npmjs.com/), [yarn](https://yarnpkg.com/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Go | [Godep](https://github.com/tools/godep), go get ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), gvt ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), glide ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), dep ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), trash ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) and govendor ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), [go mod](https://github.com/golang/go/wiki/Modules) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Java | [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
+| .NET | [Nuget](https://www.nuget.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Python | [pip](https://pip.pypa.io/en/stable/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Ruby | [gem](https://rubygems.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Erlang | [rebar](https://www.rebar3.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Objective-C, Swift | [Carthage](https://github.com/Carthage/Carthage) , [CocoaPods v0.39 and below](https://cocoapods.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Elixir | [mix](https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
+| C++/C | [conan](https://conan.io/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Scala | [sbt](https://www.scala-sbt.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
+| Rust | [cargo](https://crates.io/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
+| PHP | [composer](https://getcomposer.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
+
+## Requirements
+
+To run a License Compliance scanning job, you need GitLab Runner with the
+[`docker` executor](https://docs.gitlab.com/runner/executors/docker.html).
+
+## Configuration
+
+For GitLab 11.9 and later, to enable License Compliance, you must
+[include](../../../ci/yaml/README.md#includetemplate) the
+[`License-Management.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml)
+that's provided as a part of your GitLab installation.
+For GitLab versions earlier than 11.9, you can copy and use the job as defined
+that template.
+
+Add the following to your `.gitlab-ci.yml` file:
+
+```yaml
+include:
+ template: License-Management.gitlab-ci.yml
+```
+
+The included template will create a `license_management` job in your CI/CD pipeline
+and scan your dependencies to find their licenses.
+
+The results will be saved as a
+[License Compliance report artifact](../../../ci/yaml/README.md#artifactsreportslicense_management-ultimate)
+that you can later download and analyze. Due to implementation limitations, we
+always take the latest License Compliance artifact available. Behind the scenes, the
+[GitLab License Compliance Docker image](https://gitlab.com/gitlab-org/security-products/license-management)
+is used to detect the languages/frameworks and in turn analyzes the licenses.
+
+The License Compliance settings can be changed through environment variables by using the
+[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`. These variables are documented in the [License Compliance documentation](https://gitlab.com/gitlab-org/security-products/license-management#settings).
+
+### Installing custom dependencies
+
+> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.4.
+
+The `license_management` image already embeds many auto-detection scripts, languages,
+and packages. Nevertheless, it's almost impossible to cover all cases for all projects.
+That's why sometimes it's necessary to install extra packages, or to have extra steps
+in the project automated setup, like the download and installation of a certificate.
+For that, a `LICENSE_MANAGEMENT_SETUP_CMD` environment variable can be passed to the container,
+with the required commands to run before the license detection.
+
+If present, this variable will override the setup step necessary to install all the packages
+of your application (e.g.: for a project with a `Gemfile`, the setup step could be
+`bundle install`).
+
+For example:
+
+```yaml
+include:
+ template: License-Management.gitlab-ci.yml
+
+variables:
+ LICENSE_MANAGEMENT_SETUP_CMD: sh my-custom-install-script.sh
+```
+
+In this example, `my-custom-install-script.sh` is a shell script at the root
+directory of your project.
+
+### Overriding the template
+
+If you want to override the job definition (for example, change properties like
+`variables` or `dependencies`), you need to declare a `license_management` job
+after the template inclusion and specify any additional keys under it. For example:
+
+```yaml
+include:
+ template: License-Management.gitlab-ci.yml
+
+license_management:
+ variables:
+ CI_DEBUG_TRACE: "true"
+```
+
+### Configuring Maven projects
+
+The License Compliance tool provides a `MAVEN_CLI_OPTS` environment variable which can hold
+the command line arguments to pass to the `mvn install` command which is executed under the hood.
+Feel free to use it for the customization of Maven execution. For example:
+
+```yaml
+include:
+ template: License-Management.gitlab-ci.yml
+
+license_management:
+ variables:
+ MAVEN_CLI_OPTS: --debug
+```
+
+`mvn install` runs through all of the [build life cycle](http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html)
+stages prior to `install`, including `test`. Running unit tests is not directly
+necessary for the license scanning purposes and consumes time, so it's skipped
+by having the default value of `MAVEN_CLI_OPTS` as `-DskipTests`. If you want
+to supply custom `MAVEN_CLI_OPTS` and skip tests at the same time, don't forget
+to explicitly add `-DskipTests` to your options.
+If you still need to run tests during `mvn install`, add `-DskipTests=false` to
+`MAVEN_CLI_OPTS`.
+
+### Selecting the version of Python
+
+> - [Introduced](https://gitlab.com/gitlab-org/security-products/license-management/merge_requests/36) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
+> - In GitLab 12.2, Python 3.5 became the default.
+
+License Compliance uses Python 3.5 and pip 19.1 by default.
+If your project requires Python 2, you can switch to Python 2.7 and pip 10.0
+by setting the `LM_PYTHON_VERSION` environment variable to `2`.
+
+```yaml
+include:
+ template: License-Management.gitlab-ci.yml
+
+license_management:
+ variables:
+ LM_PYTHON_VERSION: 2
+```
+
+## Project policies for License Compliance
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5940) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.4.
+
+From the project's settings:
+
+- The list of licenses and their status can be managed.
+- Licenses can be manually approved or blacklisted.
+
+To approve or blacklist a license:
+
+1. Either use the **Manage licenses** button in the merge request widget, or
+ navigate to the project's **Settings > CI/CD** and expand the
+ **License Compliance** section.
+1. Click the **Add a license** button.
+
+ ![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
+ at the top of the list.
+ - Enter arbitrary text in the field at the top of the list. This will cause the text to be
+ added as a license name to the list.
+1. Select the **Approve** or **Blacklist** radio button to approve or blacklist respectively
+ the selected license.
+
+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_v12_3.png)
+
+Searching for Licenses:
+
+1. Use the **Search** box to search for a specific license.
+
+ ![License Compliance Search](img/license_compliance_search_v12_3.png)
+
+## License Compliance report under pipelines
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5491) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.2.
+
+From your project's left sidebar, navigate to **CI/CD > Pipelines** and click on the
+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_v12_3.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/application_security/license_management/img/license_management_add_license.png b/doc/user/application_security/license_management/img/license_management_add_license.png
deleted file mode 100644
index c9a5dc14c57..00000000000
--- a/doc/user/application_security/license_management/img/license_management_add_license.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/license_management/img/license_management_pipeline_tab.png b/doc/user/application_security/license_management/img/license_management_pipeline_tab.png
deleted file mode 100644
index 80ffca815b9..00000000000
--- a/doc/user/application_security/license_management/img/license_management_pipeline_tab.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/license_management/img/license_management_search.png b/doc/user/application_security/license_management/img/license_management_search.png
deleted file mode 100644
index b3ffd8d95a1..00000000000
--- a/doc/user/application_security/license_management/img/license_management_search.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/license_management/img/license_management_settings.png b/doc/user/application_security/license_management/img/license_management_settings.png
deleted file mode 100644
index 2e3e8888e93..00000000000
--- a/doc/user/application_security/license_management/img/license_management_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/license_management/index.md b/doc/user/application_security/license_management/index.md
index 912f2f0e209..319da2c3a6e 100644
--- a/doc/user/application_security/license_management/index.md
+++ b/doc/user/application_security/license_management/index.md
@@ -1,245 +1,5 @@
---
-type: reference, howto
+redirect_to: ../license_compliance/index.md
---
-# License Compliance **(ULTIMATE)**
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5483)
-in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.0.
-
-## Overview
-
-If you are using [GitLab CI/CD](../../../ci/README.md), you can search your project dependencies for their licenses
-using License Compliance.
-
-You can take advantage of License Compliance by either [including the job](#configuration)
-in your existing `.gitlab-ci.yml` file or by implicitly using
-[Auto License Compliance](../../../topics/autodevops/index.md#auto-license-compliance-ultimate)
-that is provided by [Auto DevOps](../../../topics/autodevops/index.md).
-
-GitLab checks the License Compliance report, compares the licenses between the
-source and target branches, and shows the information right on the merge request.
-Blacklisted licenses will be clearly visible with an `x` red icon next to them
-as well as new licenses which need a decision from you. In addition, you can
-[manually approve or blacklist](#project-policies-for-license-compliance)
-licenses in your project's settings.
-
-NOTE: **Note:**
-If the license management report doesn't have anything to compare to, no information
-will be displayed in the merge request area. That is the case when you add the
-`license_management` job in your `.gitlab-ci.yml` for the first time.
-Consecutive merge requests will have something to compare to and the license
-management report will be shown properly.
-
-![License Compliance Widget](img/license_management.png)
-
-If you are a project or group Maintainer, you can click on a license to be given
-the choice to approve it or blacklist it.
-
-![License approval decision](img/license_management_decision.png)
-
-## Use cases
-
-It helps you find what licenses your project uses in its dependencies, and decide for each of then
-whether to allow it or forbid it. For example, your application is using an external (open source)
-library whose license is incompatible with yours.
-
-## Supported languages and package managers
-
-The following languages and package managers are supported.
-
-| Language | Package managers | Scan Tool |
-|------------|-------------------------------------------------------------------|----------------------------------------------------------|
-| JavaScript | [Bower](https://bower.io/), [npm](https://www.npmjs.com/), [yarn](https://yarnpkg.com/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Go | [Godep](https://github.com/tools/godep), go get ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), gvt ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), glide ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), dep ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), trash ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) and govendor ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)), [go mod](https://github.com/golang/go/wiki/Modules) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Java | [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
-| .NET | [Nuget](https://www.nuget.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Python | [pip](https://pip.pypa.io/en/stable/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Ruby | [gem](https://rubygems.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Erlang | [rebar](https://www.rebar3.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Objective-C, Swift | [Carthage](https://github.com/Carthage/Carthage) , [CocoaPods v0.39 and below](https://cocoapods.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Elixir | [mix](https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types)) |[License Finder](https://github.com/pivotal/LicenseFinder)|
-| C++/C | [conan](https://conan.io/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Scala | [sbt](https://www.scala-sbt.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
-| Rust | [cargo](https://crates.io/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
-| PHP | [composer](https://getcomposer.org/) ([experimental support](https://github.com/pivotal/LicenseFinder#experimental-project-types))|[License Finder](https://github.com/pivotal/LicenseFinder)|
-
-## Requirements
-
-To run a License Compliance scanning job, you need GitLab Runner with the
-[`docker` executor](https://docs.gitlab.com/runner/executors/docker.html).
-
-## Configuration
-
-For GitLab 11.9 and later, to enable License Compliance, you must
-[include](../../../ci/yaml/README.md#includetemplate) the
-[`License-Management.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml)
-that's provided as a part of your GitLab installation.
-For GitLab versions earlier than 11.9, you can copy and use the job as defined
-that template.
-
-Add the following to your `.gitlab-ci.yml` file:
-
-```yaml
-include:
- template: License-Management.gitlab-ci.yml
-```
-
-The included template will create a `license_management` job in your CI/CD pipeline
-and scan your dependencies to find their licenses.
-
-The results will be saved as a
-[License Compliance report artifact](../../../ci/yaml/README.md#artifactsreportslicense_management-ultimate)
-that you can later download and analyze. Due to implementation limitations, we
-always take the latest License Compliance artifact available. Behind the scenes, the
-[GitLab License Compliance Docker image](https://gitlab.com/gitlab-org/security-products/license-management)
-is used to detect the languages/frameworks and in turn analyzes the licenses.
-
-The License Compliance settings can be changed through environment variables by using the
-[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`. These variables are documented in the [License Compliance documentation](https://gitlab.com/gitlab-org/security-products/license-management#settings).
-
-### Installing custom dependencies
-
-> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.4.
-
-The `license_management` image already embeds many auto-detection scripts, languages,
-and packages. Nevertheless, it's almost impossible to cover all cases for all projects.
-That's why sometimes it's necessary to install extra packages, or to have extra steps
-in the project automated setup, like the download and installation of a certificate.
-For that, a `LICENSE_MANAGEMENT_SETUP_CMD` environment variable can be passed to the container,
-with the required commands to run before the license detection.
-
-If present, this variable will override the setup step necessary to install all the packages
-of your application (e.g.: for a project with a `Gemfile`, the setup step could be
-`bundle install`).
-
-For example:
-
-```yaml
-include:
- template: License-Management.gitlab-ci.yml
-
-variables:
- LICENSE_MANAGEMENT_SETUP_CMD: sh my-custom-install-script.sh
-```
-
-In this example, `my-custom-install-script.sh` is a shell script at the root
-directory of your project.
-
-### Overriding the template
-
-If you want to override the job definition (for example, change properties like
-`variables` or `dependencies`), you need to declare a `license_management` job
-after the template inclusion and specify any additional keys under it. For example:
-
-```yaml
-include:
- template: License-Management.gitlab-ci.yml
-
-license_management:
- variables:
- CI_DEBUG_TRACE: "true"
-```
-
-### Configuring Maven projects
-
-The License Compliance tool provides a `MAVEN_CLI_OPTS` environment variable which can hold
-the command line arguments to pass to the `mvn install` command which is executed under the hood.
-Feel free to use it for the customization of Maven execution. For example:
-
-```yaml
-include:
- template: License-Management.gitlab-ci.yml
-
-license_management:
- variables:
- MAVEN_CLI_OPTS: --debug
-```
-
-`mvn install` runs through all of the [build life cycle](http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html)
-stages prior to `install`, including `test`. Running unit tests is not directly
-necessary for the license scanning purposes and consumes time, so it's skipped
-by having the default value of `MAVEN_CLI_OPTS` as `-DskipTests`. If you want
-to supply custom `MAVEN_CLI_OPTS` and skip tests at the same time, don't forget
-to explicitly add `-DskipTests` to your options.
-If you still need to run tests during `mvn install`, add `-DskipTests=false` to
-`MAVEN_CLI_OPTS`.
-
-### Selecting the version of Python
-
-> [Introduced](https://gitlab.com/gitlab-org/security-products/license-management/merge_requests/36) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
-
-License Compliance uses Python 2.7 and pip 10.0 by default.
-If your project requires Python 3, you can switch to Python 3.5 and pip 19.1
-by setting the `LM_PYTHON_VERSION` environment variable to `3`.
-
-```yaml
-include:
- template: License-Management.gitlab-ci.yml
-
-license_management:
- variables:
- LM_PYTHON_VERSION: 3
-```
-
-## Project policies for License Compliance
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5940)
-in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.4.
-
-From the project's settings:
-
-- The list of licenses and their status can be managed.
-- Licenses can be manually approved or blacklisted.
-
-To approve or blacklist a license:
-
-1. Either use the **Manage licenses** button in the merge request widget, or
- navigate to the project's **Settings > CI/CD** and expand the
- **License Compliance** section.
-1. Click the **Add a license** button.
-
- ![License Compliance Add License](img/license_management_add_license.png)
-
-1. In the **License name** dropdown, either:
- - Select one of the available licenses. You can search for licenses in the field
- at the top of the list.
- - Enter arbitrary text in the field at the top of the list. This will cause the text to be
- added as a license name to the list.
-1. Select the **Approve** or **Blacklist** radio button to approve or blacklist respectively
- the selected license.
-
-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_management_settings.png)
-
-Searching for Licenses:
-
-1. Use the **Search** box to search for a specific license.
-
- ![License Compliance Search](img/license_management_search.png)
-
-## License Compliance report under pipelines
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5491)
-in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.2.
-
-From your project's left sidebar, navigate to **CI/CD > Pipelines** and click on the
-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_management_pipeline_tab.png)
-
-<!-- ## Troubleshooting
-
-Include any troubleshooting steps that you can foresee. If you know beforehand what issues
-one might have when setting this up, or when something is changed, or on upgrading, it's
-important to describe those, too. Think of things that may go wrong and include them here.
-This is important to minimize requests for support, and to avoid doc comments with
-questions that you know someone might ask.
-
-Each scenario can be a third-level heading, e.g. `### Getting error message X`.
-If you have none to add when creating a doc, leave this section in place
-but commented out to help encourage others to add to it in the future. -->
+This document was moved to [another location](../license_compliance/index.md).
diff --git a/doc/user/application_security/sast/analyzers.md b/doc/user/application_security/sast/analyzers.md
index f730a25a9fc..a1bd00f34e3 100644
--- a/doc/user/application_security/sast/analyzers.md
+++ b/doc/user/application_security/sast/analyzers.md
@@ -128,7 +128,7 @@ custom analyzer can scan the source code.
| Start column | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | ✓ | ✓ | 𐄂 | ✓ | ✓ | ✓ | 𐄂 |
| End column | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ | 𐄂 |
| External id (e.g. CVE) | 𐄂 | 𐄂 | ⚠ | 𐄂 | ⚠ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
-| URLs | ✓ | 𐄂 | ✓ | 𐄂 | ⚠ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
+| URLs | ✓ | 𐄂 | ✓ | 𐄂 | ⚠ | 𐄂 | ⚠ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| Internal doc/explanation | ✓ | ⚠ | ✓ | 𐄂 | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ |
| Solution | ✓ | 𐄂 | 𐄂 | 𐄂 | ⚠ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| Confidence | 𐄂 | ✓ | ✓ | 𐄂 | ✓ | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ |
diff --git a/doc/user/application_security/sast/img/security_report.png b/doc/user/application_security/sast/img/security_report.png
deleted file mode 100644
index ba41b707238..00000000000
--- a/doc/user/application_security/sast/img/security_report.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md
index 2f15d997b5b..15a21bb82e0 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
@@ -160,17 +175,21 @@ The following are Docker image-related variables.
Some analyzers make it possible to filter out vulnerabilities under a given threshold.
-| `SAST_BANDIT_EXCLUDED_PATHS` | - | comma-separated list of paths to exclude from scan. Uses Python's [`fnmatch` syntax](https://docs.python.org/2/library/fnmatch.html) |
-| `SAST_BRAKEMAN_LEVEL` | 1 | Ignore Brakeman vulnerabilities under given confidence level. Integer, 1=Low 3=High. |
-| `SAST_FLAWFINDER_LEVEL` | 1 | Ignore Flawfinder vulnerabilities under given risk level. Integer, 0=No risk, 5=High risk. |
-| `SAST_GITLEAKS_ENTROPY_LEVEL` | 8.0 | Minimum entropy for secret detection. Float, 0.0 = low, 8.0 = high. |
-| `SAST_GOSEC_LEVEL` | 0 | Ignore gosec vulnerabilities under given confidence level. Integer, 0=Undefined, 1=Low, 2=Medium, 3=High. |
-| `SAST_EXCLUDED_PATHS` | - | Exclude vulnerabilities from output based on the paths. This is a comma-separated list of patterns. Patterns can be globs, file or folder paths. Parent directories will also match patterns. |
+| Environment variable | Default value | Description | Example usage |
+|----------------------|---------------|-------------|---|
+| `SAST_BANDIT_EXCLUDED_PATHS` | - | comma-separated list of paths to exclude from scan. Uses Python's [`fnmatch` syntax](https://docs.python.org/2/library/fnmatch.html) | |
+| `SAST_BRAKEMAN_LEVEL` | 1 | Ignore Brakeman vulnerabilities under given confidence level. Integer, 1=Low 3=High. | |
+| `SAST_FLAWFINDER_LEVEL` | 1 | Ignore Flawfinder vulnerabilities under given risk level. Integer, 0=No risk, 5=High risk. | |
+| `SAST_GITLEAKS_ENTROPY_LEVEL` | 8.0 | Minimum entropy for secret detection. Float, 0.0 = low, 8.0 = high. | |
+| `SAST_GOSEC_LEVEL` | 0 | Ignore gosec vulnerabilities under given confidence level. Integer, 0=Undefined, 1=Low, 2=Medium, 3=High. | |
+| `SAST_EXCLUDED_PATHS` | - | Exclude vulnerabilities from output based on the paths. This is a comma-separated list of patterns. Patterns can be globs, file or folder paths. Parent directories will also match patterns. | `SAST_EXCLUDED_PATHS=doc,spec` |
### Timeouts
The following variables configure timeouts.
+| Environment variable | Default value | Description |
+|----------------------|---------------|-------------|
| `SAST_DOCKER_CLIENT_NEGOTIATION_TIMEOUT` | 2m | Time limit for Docker client negotiation. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". For example, "300ms", "1.5h" or "2h45m". |
| `SAST_PULL_ANALYZER_IMAGE_TIMEOUT` | 5m | Time limit when pulling the image of an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". For example, "300ms", "1.5h" or "2h45m". |
| `SAST_RUN_ANALYZER_TIMEOUT` | 20m | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". For example, "300ms", "1.5h" or "2h45m".|
@@ -186,6 +205,7 @@ Some analyzers can be customized with environment variables.
| `GRADLE_PATH` | spotbugs | Path to the `gradle` executable. |
| `JAVA_OPTS` | spotbugs | Additional arguments for the `java` executable. |
| `JAVA_PATH` | spotbugs | Path to the `java` executable. |
+| `SAST_JAVA_VERSION` | spotbugs | Which Java version to use. Supported versions are `8` and `11`. Defaults to `8`. |
| `MAVEN_CLI_OPTS` | spotbugs | Additional arguments for the `mvn` or `mvnw` executable. |
| `MAVEN_PATH` | spotbugs | Path to the `mvn` executable. |
| `MAVEN_REPO_PATH` | spotbugs | Path to the Maven local repository (shortcut for the `maven.repo.local` property). |
@@ -314,20 +334,10 @@ CI/CD configuration file to turn it on. Results are available in the SAST report
GitLab currently includes [Gitleaks](https://github.com/zricethezav/gitleaks) and [TruffleHog](https://github.com/dxa4481/truffleHog) checks.
-## Security report under pipelines
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/3776)
-in [GitLab Ultimate](https://about.gitlab.com/pricing) 10.6.
-
-Visit any pipeline page which has a `sast` job and you will be able to see
-the security report tab with the listed vulnerabilities (if any).
-
-![Security Report](img/security_report.png)
-
## Security Dashboard
The Security Dashboard is a good place to get an overview of all the security
-vulnerabilities in your groups and projects. Read more about the
+vulnerabilities in your groups, projects and pipelines. Read more about the
[Security Dashboard](../security_dashboard/index.md).
## Interacting with the vulnerabilities
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 100755
index 00000000000..1fe76a9e08f
--- /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/img/pipeline_security_dashboard_v12_3.png b/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v12_3.png
new file mode 100755
index 00000000000..09979ba99b3
--- /dev/null
+++ b/doc/user/application_security/security_dashboard/img/pipeline_security_dashboard_v12_3.png
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/img/project_security_dashboard.png b/doc/user/application_security/security_dashboard/img/project_security_dashboard.png
deleted file mode 100644
index baa136fd885..00000000000
--- a/doc/user/application_security/security_dashboard/img/project_security_dashboard.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/img/project_security_dashboard_v12_3.png b/doc/user/application_security/security_dashboard/img/project_security_dashboard_v12_3.png
new file mode 100755
index 00000000000..51e80bdb50d
--- /dev/null
+++ b/doc/user/application_security/security_dashboard/img/project_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 ac8c1ac0354..ac539509e22 100644
--- a/doc/user/application_security/security_dashboard/index.md
+++ b/doc/user/application_security/security_dashboard/index.md
@@ -5,7 +5,7 @@ type: reference, howto
# GitLab Security Dashboard **(ULTIMATE)**
The Security Dashboard is a good place to get an overview of all the security
-vulnerabilities in your groups and projects.
+vulnerabilities in your groups, projects and pipelines.
You can also drill down into a vulnerability and get extra information, see which
project it comes from, the file it's in, and various metadata to help you analyze
@@ -26,7 +26,7 @@ The Security Dashboard supports the following reports:
## Requirements
-To use the project or group security dashboard:
+To use the group, project or pipeline security dashboard:
1. At least one project inside a group must be configured with at least one of
the [supported reports](#supported-reports).
@@ -34,6 +34,16 @@ To use the project or group security dashboard:
1. [GitLab Runner](https://docs.gitlab.com/runner/) 11.5 or newer must be used.
If you're using the shared Runners on GitLab.com, this is already the case.
+## Pipeline Security Dashboard
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/13496) in [GitLab Ultimate](https://about.gitlab.com/pricing) 12.3.
+
+At the pipeline level, the Security Dashboard displays the vulnerabilities present in the branch of the project the pipeline was run against.
+
+Visit the page for any pipeline which has run any of the [supported reports](#supported-reports). Click the **Security** tab to view the Security Dashboard.
+
+![Pipeline Security Dashboard](img/pipeline_security_dashboard_v12_3.png)
+
## Project Security Dashboard
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6165) in [GitLab Ultimate](https://about.gitlab.com/pricing) 11.1.
@@ -42,12 +52,11 @@ At the project level, the Security Dashboard displays the latest security report
for your project. Use it to find and fix vulnerabilities affecting the
[default branch](../../project/repository/branches/index.md#default-branch).
-![Project Security Dashboard](img/project_security_dashboard.png)
+![Project Security Dashboard](img/project_security_dashboard_v12_3.png)
## Group Security Dashboard
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6709) in
-> [GitLab Ultimate](https://about.gitlab.com/pricing) 11.5.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6709) in [GitLab Ultimate](https://about.gitlab.com/pricing) 11.5.
The group Security Dashboard gives an overview of the vulnerabilities of all the
projects in a group and its subgroups.
@@ -62,16 +71,17 @@ Once you're on the dashboard, at the top you should see a series of filters for:
- Report type
- Project
-![dashboard with action buttons and metrics](img/group_security_dashboard.png)
+To the right of the filters, you should see a **Hide dismissed** toggle button.
-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.
+NOTE: **Note:**
+The dashboard only shows projects with [security reports](#supported-reports) enabled in a group.
+
+![dashboard with action buttons and metrics](img/group_security_dashboard_v12_3.png)
-Finally, there is a list of all the vulnerabilities in the group, sorted by severity.
+Selecting one or more filters will filter the results in this page. Disabling the **Hide dismissed**
+toggle button will let you also see vulnerabilities that have been dismissed.
+
+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.
@@ -82,6 +92,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 096730f800c..f922b8911ae 100644
--- a/doc/user/clusters/applications.md
+++ b/doc/user/clusters/applications.md
@@ -82,10 +82,12 @@ certificates are valid and up-to-date.
NOTE: **Note:**
The
-[stable/cert-manager](https://github.com/helm/charts/tree/master/stable/cert-manager)
+[jetstack/cert-manager](https://github.com/jetstack/cert-manager)
chart is used to install this application with a
[`values.yaml`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/vendor/cert_manager/values.yaml)
-file.
+file. Prior to GitLab 12.3,
+the [stable/cert-manager](https://github.com/helm/charts/tree/master/stable/cert-manager)
+chart was used.
### GitLab Runner
@@ -103,7 +105,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.
@@ -186,7 +188,8 @@ You can clone repositories from the files tab in Jupyter:
### Knative
-> Available for project-level clusters since GitLab 11.5.
+> - Available for project-level clusters since GitLab 11.5.
+> - Available for group-level and instance-level clusters since GitLab 12.3.
[Knative](https://cloud.google.com/knative) provides a platform to
create, deploy, and manage serverless workloads from a Kubernetes
@@ -225,8 +228,7 @@ file.
## Upgrading applications
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24789)
-in GitLab 11.8.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24789) in GitLab 11.8.
The applications below can be upgraded.
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 af37cc896ad..2f2955f5a1c 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -314,27 +314,34 @@ Source:
#### Git and container registry failed authentication ban
-GitLab.com responds with HTTP status code 403 for 1 hour, if 30 failed
+GitLab.com responds with HTTP status code `403` for 1 hour, if 30 failed
authentication requests were received in a 3-minute period from a single IP address.
This applies only to Git requests and container registry (`/jwt/auth`) requests
(combined).
-This limit is reset by requests that authenticate successfully. For example, 29
-failed authentication requests followed by 1 successful request, followed by 29
-more failed authentication requests would not trigger a ban.
+This limit:
+
+- Is reset by requests that authenticate successfully. For example, 29
+ failed authentication requests followed by 1 successful request, followed by 29
+ more failed authentication requests would not trigger a ban.
+- Does not apply to JWT requests authenticated by `gitlab-ci-token`.
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..c09acd36e31 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**.
@@ -440,6 +443,12 @@ A group owner can check the aggregated storage usage for all the project in a gr
![Group storage usage quota](img/group_storage_usage_quota.png)
+The total usage of the storage is updated if any relevant event that
+will affect its value is triggered (e.g., a commit push).
+For performance reasons, we may delay the update up to 1 hour and 30 minutes.
+
+If your namespace shows `N/A` as the total storage usage, you can trigger a recalculation by pushing a commit to any project in that namespace.
+
## User contribution analysis **(STARTER)**
With [GitLab Contribution Analytics](contribution_analytics/index.md),
diff --git a/doc/user/index.md b/doc/user/index.md
index c93f64cd528..27e75189fc3 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -53,7 +53,7 @@ With GitLab Enterprise Edition, you can also:
- Improve collaboration with
[Merge Request Approvals](project/merge_requests/index.md#merge-request-approvals-starter),
[Multiple Assignees for Issues](project/issues/multiple_assignees_for_issues.md),
- and [Multiple Issue Boards](project/issue_board.md#multiple-issue-boards-starter).
+ and [Multiple Issue Boards](project/issue_board.md#multiple-issue-boards).
- Create formal relationships between issues with [Related Issues](project/issues/related_issues.md).
- Use [Burndown Charts](project/milestones/burndown_charts.md) to track progress during a sprint or while working on a new version of their software.
- Leverage [Elasticsearch](../integration/elasticsearch.md) with [Advanced Global Search](search/advanced_global_search.md) and [Advanced Syntax Search](search/advanced_search_syntax.md) for faster, more advanced code search across your entire GitLab instance.
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 17bbed2945d..edf2fedab3c 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -181,9 +181,6 @@ graph TD;
#### Subgraphs
-NOTE: **Note:** GitLab 12.1 and up now [requires quotes around subgraph
-titles that contain multiple words](https://github.com/knsv/mermaid/pull/845).
-
Subgraphs can also be included:
~~~
@@ -1151,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
```
@@ -1159,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/permissions.md b/doc/user/permissions.md
index 80d1bf992ec..02b5f7437ee 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -37,6 +37,8 @@ usernames. A GitLab administrator can configure the GitLab instance to
NOTE: **Note:**
In GitLab 11.0, the Master role was renamed to Maintainer.
+While Maintainer is the highest project-level role, some actions can only be performed by a personal namespace or group owner.
+
The following table depicts the various user permission levels in a project.
| Action | Guest | Reporter | Developer |Maintainer| Owner |
@@ -74,7 +76,7 @@ The following table depicts the various user permission levels in a project.
| View project statistics | | ✓ | ✓ | ✓ | ✓ |
| View Error Tracking list | | ✓ | ✓ | ✓ | ✓ |
| Pull from [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **(PREMIUM)** | | ✓ | ✓ | ✓ | ✓ |
-| Publish to [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **(PREMIUM)** | | | ✓ | ✓ | ✓ ||
+| Publish to [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **(PREMIUM)** | | | ✓ | ✓ | ✓ |
| Upload [Design Management](project/issues/design_management.md) files **(PREMIUM)** | | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ |
| Push to non-protected branches | | | ✓ | ✓ | ✓ |
@@ -204,27 +206,29 @@ Any user can remove themselves from a group, unless they are the last Owner of
the group. The following table depicts the various user permission levels in a
group.
-| Action | Guest | Reporter | Developer | Maintainer | Owner |
-|-------------------------------------------------|-------|----------|-----------|------------|-------|
-| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ |
-| View Insights charts **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ |
-| View group epic **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Create/edit group epic **(ULTIMATE)** | | ✓ | ✓ | ✓ | ✓ |
-| Manage group labels | | ✓ | ✓ | ✓ | ✓ |
-| Create project in group | | | ✓ | ✓ | ✓ |
-| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ |
-| Enable/disable a dependency proxy **(PREMIUM)** | | | ✓ | ✓ | ✓ |
-| Use security dashboard **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
-| Create subgroup | | | | ✓ (1) | ✓ |
-| Edit group | | | | | ✓ |
-| Manage group members | | | | | ✓ |
-| Remove group | | | | | ✓ |
-| Delete group epic **(ULTIMATE)** | | | | | ✓ |
-| View group Audit Events | | | | | ✓ |
-| Disable notification emails | | | | | ✓ |
+| Action | Guest | Reporter | Developer | Maintainer | Owner |
+|--------------------------------------------------------|-------|----------|-----------|------------|-------|
+| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ |
+| View Insights charts **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ |
+| View group epic **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Create/edit group epic **(ULTIMATE)** | | ✓ | ✓ | ✓ | ✓ |
+| Manage group labels | | ✓ | ✓ | ✓ | ✓ |
+| Create project in group | | | ✓ | ✓ | ✓ |
+| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ |
+| Enable/disable a dependency proxy **(PREMIUM)** | | | ✓ | ✓ | ✓ |
+| Use security dashboard **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
+| Create subgroup | | | | ✓ (1) | ✓ |
+| Edit group | | | | | ✓ |
+| Manage group members | | | | | ✓ |
+| Remove group | | | | | ✓ |
+| Delete group epic **(ULTIMATE)** | | | | | ✓ |
+| Edit epic comments (posted by any user) **(ULTIMATE)** | | | | ✓ (2) | ✓ (2) |
+| View group Audit Events | | | | | ✓ |
+| Disable notification emails | | | | | ✓ |
- (1): Groups can be set to [allow either Owners or Owners and
Maintainers to create subgroups](group/subgroups/index.md#creating-a-subgroup)
+- (2): Introduced in GitLab 12.2.
### Subgroup permissions
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index cf3a3fef79f..3bde0a375c6 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -199,7 +199,7 @@ To add an existing Kubernetes cluster to your project:
kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}'
```
- - **CA certificate** (required) - A valid Kubernetes certificate is needed to authenticate to the EKS cluster. We will use the certificate created by default.
+ - **CA certificate** (required) - A valid Kubernetes certificate is needed to authenticate to the cluster. We will use the certificate created by default.
- List the secrets with `kubectl get secrets`, and one should named similar to
`default-token-xxxxx`. Copy that token name for use below.
- Get the certificate by running this command:
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/img/protected_branches_devs_can_push.png b/doc/user/project/img/protected_branches_devs_can_push.png
deleted file mode 100644
index b537839c00b..00000000000
--- a/doc/user/project/img/protected_branches_devs_can_push.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/protected_branches_devs_can_push_v12_3.png b/doc/user/project/img/protected_branches_devs_can_push_v12_3.png
new file mode 100644
index 00000000000..adc03a41abb
--- /dev/null
+++ b/doc/user/project/img/protected_branches_devs_can_push_v12_3.png
Binary files differ
diff --git a/doc/user/project/img/protected_branches_list.png b/doc/user/project/img/protected_branches_list.png
deleted file mode 100644
index 495ce4d7b6f..00000000000
--- a/doc/user/project/img/protected_branches_list.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/protected_branches_list_v12_3.png b/doc/user/project/img/protected_branches_list_v12_3.png
new file mode 100644
index 00000000000..365d8d99e5a
--- /dev/null
+++ b/doc/user/project/img/protected_branches_list_v12_3.png
Binary files differ
diff --git a/doc/user/project/img/protected_branches_page.png b/doc/user/project/img/protected_branches_page.png
deleted file mode 100644
index 9b10991f62e..00000000000
--- a/doc/user/project/img/protected_branches_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/protected_branches_page_v12_3.png b/doc/user/project/img/protected_branches_page_v12_3.png
new file mode 100644
index 00000000000..17f19642552
--- /dev/null
+++ b/doc/user/project/img/protected_branches_page_v12_3.png
Binary files differ
diff --git a/doc/user/project/img/protected_tags_list.png b/doc/user/project/img/protected_tags_list.png
deleted file mode 100644
index 6c5295e0f4b..00000000000
--- a/doc/user/project/img/protected_tags_list.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/protected_tags_list_v12_3.png b/doc/user/project/img/protected_tags_list_v12_3.png
new file mode 100644
index 00000000000..6a30f615f2f
--- /dev/null
+++ b/doc/user/project/img/protected_tags_list_v12_3.png
Binary files differ
diff --git a/doc/user/project/img/protected_tags_page.png b/doc/user/project/img/protected_tags_page.png
deleted file mode 100644
index 5f8a2106cd1..00000000000
--- a/doc/user/project/img/protected_tags_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/protected_tags_page_v12_3.png b/doc/user/project/img/protected_tags_page_v12_3.png
new file mode 100644
index 00000000000..841e19af8a7
--- /dev/null
+++ b/doc/user/project/img/protected_tags_page_v12_3.png
Binary files differ
diff --git a/doc/user/project/img/protected_tags_permissions_dropdown.png b/doc/user/project/img/protected_tags_permissions_dropdown.png
deleted file mode 100644
index 77098eeb591..00000000000
--- a/doc/user/project/img/protected_tags_permissions_dropdown.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/protected_tags_permissions_dropdown_v12_3.png b/doc/user/project/img/protected_tags_permissions_dropdown_v12_3.png
new file mode 100644
index 00000000000..913d4725d53
--- /dev/null
+++ b/doc/user/project/img/protected_tags_permissions_dropdown_v12_3.png
Binary files differ
diff --git a/doc/user/project/import/gemnasium.md b/doc/user/project/import/gemnasium.md
index cf48189fa6e..3217bbc4772 100644
--- a/doc/user/project/import/gemnasium.md
+++ b/doc/user/project/import/gemnasium.md
@@ -98,7 +98,7 @@ back to both GitLab and GitHub when completed.
1. The result of the job will be visible directly from the pipeline view:
- ![security report](img/gemnasium/report.png)
+ ![Security Dashboard](../../application_security/security_dashboard/img/pipeline_security_dashboard_v12_3.png)
NOTE: **Note:**
If you don't commit very often to your project, you may want to use
diff --git a/doc/user/project/import/img/gemnasium/report.png b/doc/user/project/import/img/gemnasium/report.png
deleted file mode 100644
index 5c4d58662c0..00000000000
--- a/doc/user/project/import/img/gemnasium/report.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/import/img/import_projects_from_repo_url.png b/doc/user/project/import/img/import_projects_from_repo_url.png
index c453c7e558a..90bcff5d31b 100644
--- a/doc/user/project/import/img/import_projects_from_repo_url.png
+++ b/doc/user/project/import/img/import_projects_from_repo_url.png
Binary files differ
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/index.md b/doc/user/project/index.md
index 932e7fd10b2..c63d5308536 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -17,7 +17,7 @@ When you create a project in GitLab, you'll have access to a large number of
- [Issue tracker](issues/index.md): Discuss implementations with your team within issues
- [Issue Boards](issue_board.md): Organize and prioritize your workflow
- - [Multiple Issue Boards](issue_board.md#multiple-issue-boards-starter): Allow your teams to create their own workflows (Issue Boards) for the same project **(STARTER)**
+ - [Multiple Issue Boards](issue_board.md#multiple-issue-boards): Allow your teams to create their own workflows (Issue Boards) for the same project
- [Repositories](repository/index.md): Host your code in a fully
integrated platform
- [Branches](repository/branches/index.md): use Git branching strategies to
@@ -34,7 +34,7 @@ When you create a project in GitLab, you'll have access to a large number of
- [Issue tracker](issues/index.md): Discuss implementations with your team within issues
- [Issue Boards](issue_board.md): Organize and prioritize your workflow
- - [Multiple Issue Boards](issue_board.md#multiple-issue-boards-starter): Allow your teams to create their own workflows (Issue Boards) for the same project **(STARTER)**
+ - [Multiple Issue Boards](issue_board.md#multiple-issue-boards): Allow your teams to create their own workflows (Issue Boards) for the same project
- [Merge Requests](merge_requests/index.md): Apply your branching
strategy and get reviewed by your team
- [Merge Request Approvals](merge_requests/merge_request_approvals.md): Ask for approval before
@@ -98,7 +98,7 @@ When you create a project in GitLab, you'll have access to a large number of
- [Maven packages](packages/maven_repository.md): your private Maven repository in GitLab. **(PREMIUM)**
- [NPM packages](packages/npm_registry.md): your private NPM package registry in GitLab. **(PREMIUM)**
- [Code owners](code_owners.md): specify code owners for certain files **(STARTER)**
-- [License Compliance](../application_security/license_management/index.md): approve and blacklist licenses for projects. **(ULTIMATE)**
+- [License Compliance](../application_security/license_compliance/index.md): approve and blacklist licenses for projects. **(ULTIMATE)**
- [Dependency List](../application_security/dependency_list/index.md): view project dependencies. **(ULTIMATE)**
### Project integrations
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/mattermost.md b/doc/user/project/integrations/mattermost.md
index 6e0f39956d3..c240b6cb6b4 100644
--- a/doc/user/project/integrations/mattermost.md
+++ b/doc/user/project/integrations/mattermost.md
@@ -13,8 +13,13 @@ To enable Mattermost integration you must create an incoming webhook integration
1. Choose a display name, description and channel, those can be overridden on GitLab.
1. Save it, copy the **Webhook URL**, we'll need this later for GitLab.
-There might be some cases that Incoming Webhooks are blocked by admin, ask your mattermost admin to enable
-it on **Mattermost System Console > Integrations > Integration Management**, or on **Mattermost System Console > Integrations > Custom Integrations** in Mattermost versions 5.11 and earlier.
+Incoming Webhooks might be blocked on your Mattermost instance. Ask your Mattermost admin
+to enable it on:
+
+- **Mattermost System Console > Integrations > Integration Management** in Mattermost
+ versions 5.12 and later.
+- **Mattermost System Console > Integrations > Custom Integrations** in Mattermost
+ versions 5.11 and earlier.
Display name override is not enabled by default, you need to ask your admin to enable it on that same section.
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 9fc0ade809e..3583c0554ee 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -19,7 +19,7 @@ Once enabled, GitLab will automatically detect metrics from known services in th
### Managed Prometheus on Kubernetes
-> **Note**: [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/28916) in GitLab 10.5
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/28916) in GitLab 10.5.
GitLab can seamlessly deploy and manage Prometheus on a [connected Kubernetes cluster](../clusters/index.md), making monitoring of your apps easy.
@@ -194,7 +194,7 @@ The following tables outline the details of expected properties.
| Property | Type | Required | Description |
| ------ | ------ | ------ | ------- |
-| `type` | enum | no, defaults to `area-chart` | Specifies the chart type to use. |
+| `type` | enum | no, defaults to `area-chart` | Specifies the chart type to use, can be `area-chart` or `line-chart` |
| `title` | string | yes | Heading for the panel. |
| `y_label` | string | no, but highly encouraged | Y-Axis label for the panel. |
| `weight` | number | no, defaults to order in file | Order to appear within the grouping. Lower number means higher priority, which will be higher on the page. Numbers do not need to be consecutive. |
@@ -271,7 +271,7 @@ Note the following properties:
### Downloading data as CSV
-Data from Prometheus charts on the metrics dashboard can be downloaded as CSV.
+Data from Prometheus charts on the metrics dashboard can be downloaded as CSV.
![Downloading as CSV](img/download_as_csv.png)
@@ -342,15 +342,21 @@ If the metric exceeds the threshold of the alert for over 5 minutes, an email wi
## Determining the performance impact of a merge
-> [Introduced][ce-10408] in GitLab 9.2.
-> GitLab 9.3 added the [numeric comparison](https://gitlab.com/gitlab-org/gitlab-ce/issues/27439) of the 30 minute averages.
-> Requires [Kubernetes](prometheus_library/kubernetes.md) metrics
+> - [Introduced][ce-10408] in GitLab 9.2.
+> - GitLab 9.3 added the [numeric comparison](https://gitlab.com/gitlab-org/gitlab-ce/issues/27439) of the 30 minute averages.
Developers can view the performance impact of their changes within the merge
-request workflow. When a source branch has been deployed to an environment, a sparkline and numeric comparison of the average memory consumption will appear. On the sparkline, a dot
-indicates when the current changes were deployed, with up to 30 minutes of
-performance data displayed before and after. The comparison shows the difference between the 30 minute average before and after the deployment. This information is updated after
-each commit has been deployed.
+request workflow.
+
+NOTE: **Note:**
+Requires [Kubernetes](prometheus_library/kubernetes.md) metrics.
+
+When a source branch has been deployed to an environment, a sparkline and
+numeric comparison of the average memory consumption will appear. On the
+sparkline, a dot indicates when the current changes were deployed, with up to 30 minutes of
+performance data displayed before and after. The comparison shows the difference
+between the 30 minute average before and after the deployment. This information
+is updated after each commit has been deployed.
Once merged and the target branch has been redeployed, the metrics will switch
to show the new environments this revision has been deployed to.
@@ -363,13 +369,15 @@ Prometheus server.
## Embedding metric charts within GitLab Flavored Markdown
> [Introduced][ce-29691] in GitLab 12.2.
-> Requires [Kubernetes](prometheus_library/kubernetes.md) metrics.
It is possible to display metrics charts within [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm).
+NOTE: **Note:**
+Requires [Kubernetes](prometheus_library/kubernetes.md) metrics.
+
To display a metric chart, include a link of the form `https://<root_url>/<project>/environments/<environment_id>/metrics`.
-A single chart may also be embedded. You can generate a link to the chart via the dropdown located on the right side of the chart:
+A single chart may also be embedded. You can generate a link to the chart via the dropdown located on the right side of the chart:
![Generate Link To Chart](img/generate_link_to_chart.png)
@@ -385,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/issue_board.md b/doc/user/project/issue_board.md
index eaca5f8cfb8..519c02cf0ad 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -13,7 +13,7 @@ keeping everything in the same place, so that you don't need to jump
between different platforms to organize your workflow.
With GitLab Issue Boards, you organize your issues in lists that correspond to
-their assigned labels, visualizing issues designed as cards throughout that lists.
+their assigned labels, visualizing issues designed as cards throughout those lists.
You define your process and GitLab organizes it for you. You add your labels
then create the corresponding list to pull in your existing issues. When
@@ -27,12 +27,9 @@ Issue Boards** (version introduced in GitLab 8.11 - August 2016).
### Advanced features of Issue Boards
-With [GitLab Starter](https://about.gitlab.com/pricing/), you can create
-[multiple issue boards](#multiple-issue-boards-starter) for a given project. **(STARTER)**
-
-With [GitLab Premium](https://about.gitlab.com/pricing/), you can also create multiple
-issue boards for your groups, and add lists for [assignees](#assignee-lists-premium) and
-[milestones](#milestone-lists-premium). **(PREMIUM)**
+- Create multiple issue boards per project.
+- Create multiple issue boards per group. **(PREMIUM)**
+- Add lists for [assignees](#assignee-lists-premium) and [milestones](#milestone-lists-premium). **(PREMIUM)**
Check all the [advanced features of Issue Boards](#gitlab-enterprise-features-for-issue-boards)
below.
@@ -58,8 +55,7 @@ You create issues, host code, perform reviews, build, test,
and deploy from one single platform. Issue Boards help you to visualize
and manage the entire process _in_ GitLab.
-With [Multiple Issue Boards](#use-cases-for-multiple-issue-boards), available
-only in [different tiers of GitLab Enterprise Edition](#gitlab-enterprise-features-for-issue-boards),
+With [Multiple Issue Boards](#use-cases-for-multiple-issue-boards),
you go even further, as you can not only keep yourself and your project
organized from a broader perspective with one Issue Board per project,
but also allow your team members to organize their own workflow by creating
@@ -88,7 +84,7 @@ If we have the labels "**backend**", "**frontend**", "**staging**", and
"**production**", and an Issue Board with a list for each, we can:
- Visualize the entire flow of implementations since the
- beginning of the development lifecycle until deployed to production
+ beginning of the development life cycle until deployed to production
- Prioritize the issues in a list by moving them vertically
- Move issues between lists to organize them according to the labels you've set
- Add multiple issues to lists in the board by selecting one or more existing issues
@@ -97,8 +93,7 @@ If we have the labels "**backend**", "**frontend**", "**staging**", and
### Use cases for Multiple Issue Boards
-With [Multiple Issue Boards](#multiple-issue-boards-starter), available only in
-[GitLab Enterprise Edition](https://about.gitlab.com/pricing/),
+With [Multiple Issue Boards](#multiple-issue-boards),
each team can have their own board to organize their workflow individually.
#### Scrum team
@@ -159,13 +154,14 @@ Issue Board, that is, create or delete lists and drag issues from one list to an
GitLab Issue Boards are available on GitLab Core and GitLab.com Free, but some
advanced functionalities are only present in higher tiers: GitLab.com Bronze,
Silver, or Gold, or GitLab self-managed Starter, Premium, and Ultimate, as described
-on the following sections.
+in the following sections.
For a collection of [features per tier](#summary-of-features-per-tier), check the summary below.
-### Multiple Issue Boards **(STARTER)**
+### Multiple Issue Boards
-> Introduced in [GitLab Enterprise Edition 8.13](https://about.gitlab.com/2016/10/22/gitlab-8-13-released/#multiple-issue-boards-ee).
+> - Multiple Issue Boards per project [moved](https://gitlab.com/gitlab-org/gitlab-ce/issues/53811) to [GitLab Core](https://about.gitlab.com/pricing/) in GitLab 12.1.
+> - Multiple Issue Boards per group is available in [GitLab Premium Edition](https://about.gitlab.com/pricing/).
Multiple Issue Boards, as the name suggests, allow for more than one Issue Board
for a given project or group. This is great for large projects with more than one team
@@ -184,10 +180,6 @@ These are shortcuts to your last 4 visited boards.
When you're revisiting an issue board in a project or group with multiple boards,
GitLab will automatically load the last board you visited.
-NOTE: **Note:**
-The Multiple Issue Boards feature is available for
-**projects in GitLab Starter Edition** and for **groups in GitLab Premium Edition**.
-
### Configurable Issue Boards **(STARTER)**
> Introduced in [GitLab Starter Edition 10.2](https://about.gitlab.com/2017/11/22/gitlab-10-2-released/#issue-boards-configuration).
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/browser_performance_testing.md b/doc/user/project/merge_requests/browser_performance_testing.md
index 49b9826a52a..2339cfa0db8 100644
--- a/doc/user/project/merge_requests/browser_performance_testing.md
+++ b/doc/user/project/merge_requests/browser_performance_testing.md
@@ -4,8 +4,7 @@ type: reference, howto
# Browser Performance Testing **(PREMIUM)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3507)
-in [GitLab Premium](https://about.gitlab.com/pricing/) 10.3.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3507) in [GitLab Premium](https://about.gitlab.com/pricing/) 10.3.
If your application offers a web interface and you are using
[GitLab CI/CD](../../../ci/README.md), you can quickly determine the performance
@@ -25,18 +24,20 @@ for [additional metrics](https://gitlab.com/gitlab-org/gitlab-ee/issues/4370)
in a future release.
Going a step further, GitLab can show the Performance report right
-in the merge request widget area:
+in the merge request widget area (see below).
## Use cases
For instance, consider the following workflow:
-1. A member of the marketing team is attempting to track engagement by adding a new tool
-1. With browser performance metrics, they see how their changes are impacting the usability of the page for end users
-1. The metrics show that after their changes the performance score of the page has gone down
-1. When looking at the detailed report, they see that the new Javascript library was included in `<head>` which affects loading page speed
-1. They ask a front end developer to help them, who sets the library to load asynchronously
-1. The frontend developer approves the merge request and authorizes its deployment to production
+1. A member of the marketing team is attempting to track engagement by adding a new tool.
+1. With browser performance metrics, they see how their changes are impacting the usability
+ of the page for end users.
+1. The metrics show that after their changes the performance score of the page has gone down.
+1. When looking at the detailed report, they see that the new JavaScript library was
+ included in `<head>` which affects loading page speed.
+1. They ask a front end developer to help them, who sets the library to load asynchronously.
+1. The frontend developer approves the merge request and authorizes its deployment to production.
## How it works
@@ -48,15 +49,165 @@ example on [Testing Browser Performance](../../../ci/examples/browser_performanc
GitLab then checks this report, compares key performance metrics for each page
between the source and target branches, and shows the information right on the merge request.
->**Note:**
+NOTE: **Note:**
If the Performance report doesn't have anything to compare to, no information
will be displayed in the merge request area. That is the case when you add the
Performance job in your `.gitlab-ci.yml` for the very first time.
-Consecutive merge requests will have something to compare to and the Performance
+Consecutive merge requests will have something to compare to, and the Performance
report will be shown properly.
![Performance Widget](img/browser_performance_testing.png)
+## Configuring Browser Performance Testing
+
+NOTE: **Note:**
+The job definition shown below is supported in GitLab 11.5 and later versions.
+It also requires GitLab Runner 11.5 or later. For earlier versions, use the
+[previous job definitions](#previous-job-definitions).
+
+This example shows how to run the [sitespeed.io container](https://hub.docker.com/r/sitespeedio/sitespeed.io/)
+on your code by using GitLab CI/CD and [sitespeed.io](https://www.sitespeed.io)
+using Docker-in-Docker.
+
+First, you need GitLab Runner with
+[docker-in-docker build](../../../ci/docker/using_docker_build.md#use-docker-in-docker-workflow-with-docker-executor).
+
+Once you set up the Runner, add a new job to `.gitlab-ci.yml` that generates the
+expected report:
+
+```yaml
+performance:
+ stage: performance
+ image: docker:git
+ variables:
+ URL: https://example.com
+ services:
+ - docker:stable-dind
+ script:
+ - mkdir gitlab-exporter
+ - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
+ - mkdir sitespeed-results
+ - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results $URL
+ - mv sitespeed-results/data/performance.json performance.json
+ artifacts:
+ paths:
+ - sitespeed-results/
+ reports:
+ performance: performance.json
+```
+
+The above example will create a `performance` job in your CI/CD pipeline and will run
+sitespeed.io against the webpage you defined in `URL` to gather key metrics.
+The [GitLab plugin for sitespeed.io](https://gitlab.com/gitlab-org/gl-performance)
+is downloaded in order to save the report as a [Performance report artifact](../../../ci/yaml/README.md#artifactsreportsperformance-premium)
+that you can later download and analyze. Due to implementation limitations we always
+take the latest Performance artifact available.
+
+The full HTML sitespeed.io report will also be saved as an artifact, and if you have
+[GitLab Pages](../pages/index.md) enabled, it can be viewed directly in your browser.
+
+For further customization options for sitespeed.io, including the ability to provide a
+list of URLs to test, please see the [Sitespeed.io Configuration](https://www.sitespeed.io/documentation/sitespeed.io/configuration/)
+documentation.
+
+TIP: **Tip:**
+Key metrics are automatically extracted and shown in the merge request widget.
+
+### Performance testing on Review Apps
+
+The above CI YML is great for testing against static environments, and it can
+be extended for dynamic environments. There are a few extra steps to take to
+set this up:
+
+1. The `performance` job should run after the dynamic environment has started.
+1. In the `review` job, persist the hostname and upload it as an artifact so
+ it's available to the `performance` job (the same can be done for static
+ environments like staging and production to unify the code path). Saving it
+ as an artifact is as simple as `echo $CI_ENVIRONMENT_URL > environment_url.txt`
+ in your job's `script`.
+1. In the `performance` job, read the previous artifact into an environment
+ variable, like `$CI_ENVIRONMENT_URL`, and use it to parameterize the test
+ URLs.
+1. You can now run the sitespeed.io container against the desired hostname and
+ paths.
+
+Your `.gitlab-ci.yml` file would look like:
+
+```yaml
+stages:
+ - deploy
+ - performance
+
+review:
+ stage: deploy
+ environment:
+ name: review/$CI_COMMIT_REF_SLUG
+ url: http://$CI_COMMIT_REF_SLUG.$APPS_DOMAIN
+ script:
+ - run_deploy_script
+ - echo $CI_ENVIRONMENT_URL > environment_url.txt
+ artifacts:
+ paths:
+ - environment_url.txt
+ only:
+ - branches
+ except:
+ - master
+
+performance:
+ stage: performance
+ image: docker:git
+ services:
+ - docker:stable-dind
+ dependencies:
+ - review
+ script:
+ - export CI_ENVIRONMENT_URL=$(cat environment_url.txt)
+ - mkdir gitlab-exporter
+ - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
+ - mkdir sitespeed-results
+ - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "$CI_ENVIRONMENT_URL"
+ - mv sitespeed-results/data/performance.json performance.json
+ artifacts:
+ paths:
+ - sitespeed-results/
+ reports:
+ performance: performance.json
+```
+
+A complete example can be found in our [Auto DevOps CI YML](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml).
+
+### Previous job definitions
+
+CAUTION: **Caution:**
+Before GitLab 11.5, Performance job and artifact had to be named specifically
+to automatically extract report data and show it in the merge request widget.
+While these old job definitions are still maintained they have been deprecated
+and may be removed in next major release, GitLab 12.0.
+You are advised to update your current `.gitlab-ci.yml` configuration to reflect that change.
+
+For GitLab 11.4 and earlier, the job should look like:
+
+```yaml
+performance:
+ stage: performance
+ image: docker:git
+ variables:
+ URL: https://example.com
+ services:
+ - docker:stable-dind
+ script:
+ - mkdir gitlab-exporter
+ - wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
+ - mkdir sitespeed-results
+ - docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results $URL
+ - mv sitespeed-results/data/performance.json performance.json
+ artifacts:
+ paths:
+ - performance.json
+ - sitespeed-results/
+```
+
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
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/img/merge_request_diff_v12_2.png b/doc/user/project/merge_requests/img/merge_request_diff_v12_2.png
new file mode 100644
index 00000000000..e56fbb9750f
--- /dev/null
+++ b/doc/user/project/merge_requests/img/merge_request_diff_v12_2.png
Binary files differ
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 04db54872d3..aa58e971cc3 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -41,7 +41,7 @@ With **[GitLab Enterprise Edition][ee]**, you can also:
- View the deployment process across projects with [Multi-Project Pipelines](../../../ci/multi_project_pipelines.md) **(PREMIUM)**
- Request [approvals](merge_request_approvals.md) from your managers **(STARTER)**
- Analyze the impact of your changes with [Code Quality reports](code_quality.md) **(STARTER)**
-- Manage the licenses of your dependencies with [License Compliance](../../application_security/license_management/index.md) **(ULTIMATE)**
+- Manage the licenses of your dependencies with [License Compliance](../../application_security/license_compliance/index.md) **(ULTIMATE)**
- Analyze your source code for vulnerabilities with [Static Application Security Testing](../../application_security/sast/index.md) **(ULTIMATE)**
- Analyze your running web applications for vulnerabilities with [Dynamic Application Security Testing](../../application_security/dast/index.md) **(ULTIMATE)**
- Analyze your dependencies for vulnerabilities with [Dependency Scanning](../../application_security/dependency_scanning/index.md) **(ULTIMATE)**
@@ -57,7 +57,7 @@ A. Consider you are a software developer working in a team:
1. You gather feedback from your team
1. You work on the implementation optimizing code with [Code Quality reports](code_quality.md) **(STARTER)**
1. You verify your changes with [JUnit test reports](../../../ci/junit_test_reports.md) in GitLab CI/CD
-1. You avoid using dependencies whose license is not compatible with your project with [License Compliance reports](license_management.md) **(ULTIMATE)**
+1. You avoid using dependencies whose license is not compatible with your project with [License Compliance reports](../../application_security/license_compliance/index.md) **(ULTIMATE)**
1. You request the [approval](#merge-request-approvals-starter) from your manager
1. Your manager pushes a commit with their final review, [approves the merge request](merge_request_approvals.md), and set it to [merge when pipeline succeeds](#merge-when-pipeline-succeeds) (Merge Request Approvals are available in GitLab Starter)
1. Your changes get deployed to production with [manual actions](../../../ci/yaml/README.md#whenmanual) for GitLab CI/CD
@@ -164,6 +164,26 @@ you hide threads that are no longer relevant.
[Read more about resolving threads in merge requests reviews.](../../discussions/index.md)
+## View changes between file versions
+
+The **Changes** tab of a merge request shows the changes to files between branches or
+commits. This view of changes to a file is also known as a **diff**. By default, the diff view
+compares the file in the merge request branch and the file in the target branch.
+
+The diff view includes the following:
+
+- The file's name and path.
+- The number of lines added and deleted.
+- Buttons for the following options:
+ - Toggle comments for this file; useful for inline reviews.
+ - Edit the file in the merge request's branch.
+ - Show full file, in case you want to look at the changes in context with the rest of the file.
+ - View file at the current commit.
+ - Preview the changes with [Review Apps](../../../ci/review_apps/index.md).
+- The changed lines, with the specific changes highlighted.
+
+![Example screenshot of a source code diff](img/merge_request_diff_v12_2.png)
+
## Commenting on any file line in merge requests
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/13950) in GitLab 11.5.
@@ -218,7 +238,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
@@ -289,6 +309,7 @@ as pushing changes:
- Set the merge request to remove the source branch when it's merged.
- Set the title of the merge request to a particular title.
- Set the description of the merge request to a particular description.
+- Add or remove labels from the merge request.
### Create a new merge request using git push options
@@ -375,6 +396,35 @@ git push -o merge_request.description="The description I want"
You can also use this push option in addition to the
`merge_request.create` push option.
+### Add or remove labels using git push options
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/31831) in GitLab 12.3.
+
+You can add or remove labels from merge requests using push options.
+
+For example, to add two labels to an existing merge request, use the
+`merge_request.label` push option:
+
+```sh
+git push -o merge_request.label="label1" -o merge_request.label="label2"
+```
+
+To remove two labels from an existing merge request, use
+the `merge_request.unlabel` push option:
+
+```sh
+git push -o merge_request.unlabel="label1" -o merge_request.unlabel="label2"
+```
+
+You can also use these push options in addition to the
+`merge_request.create` push option.
+
+To create a merge request and add two labels to it, use:
+
+```sh
+git push -o merge_request.create -o merge_request.label="label1" -o merge_request.label="label2"
+```
+
## Find the merge request that introduced a change
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/2383) in GitLab 10.5.
diff --git a/doc/user/project/merge_requests/license_management.md b/doc/user/project/merge_requests/license_management.md
index 93116ebd7c6..df5bd073ade 100644
--- a/doc/user/project/merge_requests/license_management.md
+++ b/doc/user/project/merge_requests/license_management.md
@@ -1,5 +1,5 @@
---
-redirect_to: '../../application_security/license_management/index.md'
+redirect_to: '../../application_security/license_compliance/index.md'
---
-This document was moved to [another location](../../application_security/license_management/index.md).
+This document was moved to [another location](../../application_security/license_compliance/index.md).
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 75b0623e6b0..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**.
@@ -85,7 +81,7 @@ NOTE: **NOTE**
We'd highly recommend you to use the [Environment](../../../ci/environments.md)
feature in order to quickly assess which flag is enabled per environment.
-## Rollout Strategy
+## Rollout strategy
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/8240) in GitLab 12.2.
@@ -97,33 +93,38 @@ However, a feature will be enabled for 50% of logged-in users if the matching en
### All users
-Enables the feature for all users.
-
-**All users** is implemented using the Unleash [default](https://unleash.github.io/docs/activation_strategy#default) activation strategy.
+Enables the feature for all users. It is implemented using the Unleash
+[`default`](https://unleash.github.io/docs/activation_strategy#default)
+activation strategy.
### Percent rollout (logged in users)
-**Percent rollout (logged in users)** enables the feature for a percentage of authenticated users. Set a value of 15%, for example, to enable the feature for 15% of authenticated users.
+Enables the feature for a percentage of authenticated users. It is
+implemented using the Unleash
+[`gradualRolloutUserId`](https://unleash.github.io/docs/activation_strategy#gradualrolloutuserid)
+activation strategy.
+
+Set a value of 15%, for example, to enable the feature for 15% of authenticated users.
A rollout percentage may be between 0% and 100%.
CAUTION: **Caution:**
-If this strategy is selected, then the Unleash client **must** be given a user id for the feature to be enabled. See the [Ruby example](#ruby-application-example) below.
+If this strategy is selected, then the Unleash client **must** be given a user
+ID for the feature to be enabled. See the [Ruby example](#ruby-application-example) below.
-**Percent rollout (logged in users)** is implemented using the Unleash [gradualRolloutUserId](https://unleash.github.io/docs/activation_strategy#gradualrolloutuserid) activation strategy.
-
-## Target Users
+## Target users
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/8240) in GitLab 12.2.
-A feature flag may be enabled for a list of target users.
+A feature flag may be enabled for a list of target users. It is implemented
+using the Unleash [`userWithId`](https://unleash.github.io/docs/activation_strategy#userwithid)
+activation strategy.
![Feature flag target users](img/target_users_v12_2.png)
CAUTION: **Caution:**
-The Unleash client **must** be given a user id for the feature to be enabled for target users. See the [Ruby example](#ruby-application-example) below.
-
-**Target users** is implemented using the Unleash [userWithId](https://unleash.github.io/docs/activation_strategy#userwithid) activation strategy.
+The Unleash client **must** be given a user ID for the feature to be enabled for
+target users. See the [Ruby example](#ruby-application-example) below.
## Integrating with your application
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/protected_branches.md b/doc/user/project/protected_branches.md
index 7a79cdbcaee..895c8ac88e7 100644
--- a/doc/user/project/protected_branches.md
+++ b/doc/user/project/protected_branches.md
@@ -15,7 +15,7 @@ By default, a protected branch does four simple things:
- It prevents its creation, if not already created, from everybody except users
with Maintainer permission.
-- It prevents pushes from everybody except users with Maintainer permission.
+- It prevents pushes from everybody except users with **Allowed** permission.
- It prevents **anyone** from force pushing to the branch.
- It prevents **anyone** from deleting the branch.
@@ -34,11 +34,11 @@ that the `master` branch is protected by default.
1. From the **Branch** dropdown menu, select the branch you want to protect and
click **Protect**. In the screenshot below, we chose the `develop` branch.
- ![Protected branches page](img/protected_branches_page.png)
+ ![Protected branches page](img/protected_branches_page_v12_3.png)
1. Once done, the protected branch will appear in the "Protected branches" list.
- ![Protected branches list](img/protected_branches_list.png)
+ ![Protected branches list](img/protected_branches_list_v12_3.png)
## Using the Allowed to merge and Allowed to push settings
@@ -65,7 +65,7 @@ You can set the "Allowed to push" and "Allowed to merge" options while creating
a protected branch or afterwards by selecting the option you want from the
dropdown list in the "Already protected" area.
-![Developers can push](img/protected_branches_devs_can_push.png)
+![Developers can push](img/protected_branches_devs_can_push_v12_3.png)
If you don't choose any of those options while creating a protected branch,
they are set to "Maintainers" by default.
diff --git a/doc/user/project/protected_tags.md b/doc/user/project/protected_tags.md
index 5cc3b8a7fc3..9651c8824ab 100644
--- a/doc/user/project/protected_tags.md
+++ b/doc/user/project/protected_tags.md
@@ -24,15 +24,15 @@ To protect a tag, you need to have at least Maintainer permission level.
1. From the **Tag** dropdown menu, select the tag you want to protect or type and click **Create wildcard**. In the screenshot below, we chose to protect all tags matching `v*`:
- ![Protected tags page](img/protected_tags_page.png)
+ ![Protected tags page](img/protected_tags_page_v12_3.png)
1. From the **Allowed to create** dropdown, select who will have permission to create matching tags and then click **Protect**:
- ![Allowed to create tags dropdown](img/protected_tags_permissions_dropdown.png)
+ ![Allowed to create tags dropdown](img/protected_tags_permissions_dropdown_v12_3.png)
1. Once done, the protected tag will appear in the **Protected tags** list:
- ![Protected tags list](img/protected_tags_list.png)
+ ![Protected tags list](img/protected_tags_list_v12_3.png)
## Wildcard protected tags
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/repository/index.md b/doc/user/project/repository/index.md
index bd966185c94..a838f06b2fd 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -73,7 +73,7 @@ according to the markup language.
| [Markdown](../../markdown.md) | `mdown`, `mkd`, `mkdn`, `md`, `markdown` |
| [reStructuredText](http://docutils.sourceforge.net/rst.html) | `rst` |
| [AsciiDoc](../../asciidoc.md) | `adoc`, `ad`, `asciidoc` |
-| [Textile](https://txstyle.org/) | `textile` |
+| [Textile](https://textile-lang.com/) | `textile` |
| [rdoc](http://rdoc.sourceforge.net/doc/index.html) | `rdoc` |
| [Orgmode](https://orgmode.org/) | `org` |
| [creole](http://www.wikicreole.org/) | `creole` |
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/forking/branch_select.png b/doc/workflow/forking/branch_select.png
index 77236137190..0ea5410f832 100644
--- a/doc/workflow/forking/branch_select.png
+++ b/doc/workflow/forking/branch_select.png
Binary files differ
diff --git a/doc/workflow/forking/merge_request.png b/doc/workflow/forking/merge_request.png
index 407ddfb4799..43851203f3f 100644
--- a/doc/workflow/forking/merge_request.png
+++ b/doc/workflow/forking/merge_request.png
Binary files differ
diff --git a/doc/workflow/forking_workflow.md b/doc/workflow/forking_workflow.md
index 869a0a621c3..82c2709143d 100644
--- a/doc/workflow/forking_workflow.md
+++ b/doc/workflow/forking_workflow.md
@@ -10,8 +10,7 @@ document more information about using branches to work together.
Forking a project is in most cases a two-step process.
-1. Click on the fork button located in the middle of the page or a project's
- home page right next to the stars button.
+1. Click on the fork button located located in between the star and clone buttons on the project's home page.
![Fork button](img/forking_workflow_fork_button.png)
@@ -25,7 +24,7 @@ Forking a project is in most cases a two-step process.
**Note:**
If the namespace you chose to fork the project to has another project with
the same path name, you will be presented with a warning that the forking
- could not be completed. Try to resolve the error and repeat the forking
+ could not be completed. Try to resolve the error before repeating the forking
process.
![Path taken error](img/forking_workflow_path_taken_error.png)
@@ -44,7 +43,7 @@ create the [merge request](merge_requests.md).
![Selecting branches](forking/branch_select.png)
You can then assign the merge request to someone to have them review
-your changes. Upon pressing the 'Accept Merge Request' button, your
+your changes. Upon pressing the 'Submit Merge Request' button, your
changes will be added to the repository and branch you're merging into.
![New merge request](forking/merge_request.png)
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/img/forking_workflow_choose_namespace.png b/doc/workflow/img/forking_workflow_choose_namespace.png
index b34b12090a1..eb023ca85f2 100644
--- a/doc/workflow/img/forking_workflow_choose_namespace.png
+++ b/doc/workflow/img/forking_workflow_choose_namespace.png
Binary files differ
diff --git a/doc/workflow/img/forking_workflow_fork_button.png b/doc/workflow/img/forking_workflow_fork_button.png
index 941d5363c35..7fb07529b6d 100644
--- a/doc/workflow/img/forking_workflow_fork_button.png
+++ b/doc/workflow/img/forking_workflow_fork_button.png
Binary files differ
diff --git a/doc/workflow/img/forking_workflow_path_taken_error.png b/doc/workflow/img/forking_workflow_path_taken_error.png
index df938da5677..ef62d0ab6a9 100644
--- a/doc/workflow/img/forking_workflow_path_taken_error.png
+++ b/doc/workflow/img/forking_workflow_path_taken_error.png
Binary files differ
diff --git a/doc/workflow/img/repository_mirroring_push_settings.png b/doc/workflow/img/repository_mirroring_push_settings.png
index 21a6aca4526..3c0eacaa2df 100644
--- a/doc/workflow/img/repository_mirroring_push_settings.png
+++ b/doc/workflow/img/repository_mirroring_push_settings.png
Binary files differ
diff --git a/doc/workflow/issue_weight.md b/doc/workflow/issue_weight.md
index a80519f0748..291646a430e 100644
--- a/doc/workflow/issue_weight.md
+++ b/doc/workflow/issue_weight.md
@@ -1,7 +1,6 @@
-# Issue Weight **(STARTER)**
+# Issue weight **(STARTER)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/76)
-> in [GitLab Starter](https://about.gitlab.com/pricing/) 8.3.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/76) in [GitLab Starter](https://about.gitlab.com/pricing/) 8.3.
When you have a lot of issues, it can be hard to get an overview.
By adding a weight to each issue, you can get a better idea of how much time,
diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
index f5593927cc2..36db4f73885 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -62,9 +62,8 @@ git commit -am "Added Debian iso" # commit the file meta data
git push origin master # sync the git repo and large file to the GitLab server
```
-NOTE: **Note:**
**Make sure** that `.gitattributes` is tracked by Git. Otherwise Git
-LFS will not be working properly for people cloning the project.
+LFS will not be working properly for people cloning the project:
```bash
git add .gitattributes
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/repository_mirroring.md b/doc/workflow/repository_mirroring.md
index 753518d0424..5bf1f484106 100644
--- a/doc/workflow/repository_mirroring.md
+++ b/doc/workflow/repository_mirroring.md
@@ -37,7 +37,8 @@ The following are some possible use cases for repository mirroring:
## Pushing to a remote repository **(CORE)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/249) in GitLab Enterprise Edition 8.7. [Moved to GitLab Core](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18715) in 10.8.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/249) in GitLab Enterprise Edition 8.7.
+> - [Moved to GitLab Core](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18715) in 10.8.
For an existing project, you can set up push mirroring as follows:
@@ -66,7 +67,8 @@ section.
### Push only protected branches **(CORE)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3350) in [GitLab Starter](https://about.gitlab.com/pricing/) 10.3. [Moved to GitLab Core](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18715) in 10.8.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3350) in [GitLab Starter](https://about.gitlab.com/pricing/) 10.3.
+> - [Moved to GitLab Core](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18715) in 10.8.
You can choose to only push your protected branches from GitLab to your remote repository.
@@ -96,7 +98,8 @@ The repository will push soon. To force a push, click the appropriate button.
## Pulling from a remote repository **(STARTER)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/51) in GitLab Enterprise Edition 8.2. [Added Git LFS support](https://gitlab.com/gitlab-org/gitlab-ee/issues/10871) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.11.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/51) in GitLab Enterprise Edition 8.2.
+> - [Added Git LFS support](https://gitlab.com/gitlab-org/gitlab-ee/issues/10871) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.11.
NOTE: **Note:** This feature [is available for free](https://gitlab.com/gitlab-org/gitlab-ee/issues/10361) to
GitLab.com users until September 22nd, 2019.
@@ -154,7 +157,8 @@ Repository mirrors are updated as Sidekiq becomes available to process them. If
### SSH authentication
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2551) for Pull mirroring in [GitLab Starter](https://about.gitlab.com/pricing/) 9.5. [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22982) for Push mirroring in [GitLab Core](https://about.gitlab.com/pricing/) 11.6
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2551) for Pull mirroring in [GitLab Starter](https://about.gitlab.com/pricing/) 9.5.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22982) for Push mirroring in [GitLab Core](https://about.gitlab.com/pricing/) 11.6
SSH authentication is mutual:
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/api.rb b/lib/api/api.rb
index 219ed45eff6..aa6a67d817a 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -118,7 +118,7 @@ module API
mount ::API::GroupContainerRepositories
mount ::API::GroupVariables
mount ::API::ImportGithub
- mount ::API::Internal
+ mount ::API::Internal::Base
mount ::API::Issues
mount ::API::JobArtifacts
mount ::API::Jobs
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 5e66b4e76a5..f7cd6d35854 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -978,7 +978,9 @@ module API
expose :created_at
def todo_target_class(target_type)
- ::API::Entities.const_get(target_type)
+ # false as second argument prevents looking up in module hierarchy
+ # see also https://gitlab.com/gitlab-org/gitlab-ce/issues/59719
+ ::API::Entities.const_get(target_type, false)
end
end
@@ -1174,6 +1176,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 +1231,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,5 +1711,36 @@ 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
+
+# rubocop: disable Cop/InjectEnterpriseEditionModule
+API::Entities.prepend_if_ee('EE::API::Entities::Entities')
+::API::Entities::ApplicationSetting.prepend_if_ee('EE::API::Entities::ApplicationSetting')
+::API::Entities::Board.prepend_if_ee('EE::API::Entities::Board')
+::API::Entities::Group.prepend_if_ee('EE::API::Entities::Group', with_descendants: true)
+::API::Entities::GroupDetail.prepend_if_ee('EE::API::Entities::GroupDetail')
+::API::Entities::IssueBasic.prepend_if_ee('EE::API::Entities::IssueBasic', with_descendants: true)
+::API::Entities::Issue.prepend_if_ee('EE::API::Entities::Issue')
+::API::Entities::List.prepend_if_ee('EE::API::Entities::List')
+::API::Entities::MergeRequestBasic.prepend_if_ee('EE::API::Entities::MergeRequestBasic', with_descendants: true)
+::API::Entities::Namespace.prepend_if_ee('EE::API::Entities::Namespace')
+::API::Entities::Project.prepend_if_ee('EE::API::Entities::Project', with_descendants: true)
+::API::Entities::ProtectedRefAccess.prepend_if_ee('EE::API::Entities::ProtectedRefAccess')
+::API::Entities::UserPublic.prepend_if_ee('EE::API::Entities::UserPublic', with_descendants: true)
+::API::Entities::Todo.prepend_if_ee('EE::API::Entities::Todo')
+::API::Entities::ProtectedBranch.prepend_if_ee('EE::API::Entities::ProtectedBranch')
+::API::Entities::Identity.prepend_if_ee('EE::API::Entities::Identity')
+::API::Entities::UserWithAdmin.prepend_if_ee('EE::API::Entities::UserWithAdmin', with_descendants: true)
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index f545f33c06b..0b086f2e36d 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -75,6 +75,7 @@ module API
).execute
projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled]
projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
+ projects = projects.visible_to_user_and_access_level(current_user, params[:min_access_level]) if params[:min_access_level]
projects = reorder_projects(projects)
paginate(projects)
end
@@ -213,9 +214,11 @@ module API
optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature'
optional :with_shared, type: Boolean, default: true, desc: 'Include projects shared to this group'
optional :include_subgroups, type: Boolean, default: false, desc: 'Includes projects in subgroups of this group'
+ optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user on projects'
use :pagination
use :with_custom_attributes
+ use :optional_projects_params
end
get ":id/projects" do
projects = find_group_projects(params)
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 1aa6dc44bf7..5755f4b8d74 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -416,17 +416,7 @@ module API
# rubocop: enable CodeReuse/ActiveRecord
def project_finder_params
- finder_params = { without_deleted: true }
- finder_params[:owned] = true if params[:owned].present?
- finder_params[:non_public] = true if params[:membership].present?
- finder_params[:starred] = true if params[:starred].present?
- finder_params[:visibility_level] = Gitlab::VisibilityLevel.level_value(params[:visibility]) if params[:visibility]
- finder_params[:archived] = archived_param unless params[:archived].nil?
- finder_params[:search] = params[:search] if params[:search]
- finder_params[:user] = params.delete(:user) if params[:user]
- finder_params[:custom_attributes] = params[:custom_attributes] if params[:custom_attributes]
- finder_params[:min_access_level] = params[:min_access_level] if params[:min_access_level]
- finder_params
+ project_finder_params_ce.merge(project_finder_params_ee)
end
# file helpers
@@ -461,6 +451,27 @@ module API
end
end
+ protected
+
+ def project_finder_params_ce
+ finder_params = { without_deleted: true }
+ finder_params[:owned] = true if params[:owned].present?
+ finder_params[:non_public] = true if params[:membership].present?
+ finder_params[:starred] = true if params[:starred].present?
+ finder_params[:visibility_level] = Gitlab::VisibilityLevel.level_value(params[:visibility]) if params[:visibility]
+ finder_params[:archived] = archived_param unless params[:archived].nil?
+ finder_params[:search] = params[:search] if params[:search]
+ finder_params[:user] = params.delete(:user) if params[:user]
+ finder_params[:custom_attributes] = params[:custom_attributes] if params[:custom_attributes]
+ finder_params[:min_access_level] = params[:min_access_level] if params[:min_access_level]
+ finder_params
+ end
+
+ # Overridden in EE
+ def project_finder_params_ee
+ {}
+ end
+
private
# rubocop:disable Gitlab/ModuleWithInstanceVariables
diff --git a/lib/api/helpers/groups_helpers.rb b/lib/api/helpers/groups_helpers.rb
index 2c33d79f6c8..6af12828ca5 100644
--- a/lib/api/helpers/groups_helpers.rb
+++ b/lib/api/helpers/groups_helpers.rb
@@ -28,6 +28,13 @@ module API
use :optional_params_ce
use :optional_params_ee
end
+
+ params :optional_projects_params_ee do
+ end
+
+ params :optional_projects_params do
+ use :optional_projects_params_ee
+ end
end
end
end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index 9afe6c5b027..9a80671f996 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -17,6 +17,18 @@ module API
@project # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
+ def access_checker_for(actor, protocol)
+ access_checker_klass.new(actor.key_or_user, project, protocol,
+ authentication_abilities: ssh_authentication_abilities,
+ namespace_path: namespace_path,
+ project_path: project_path,
+ redirected_path: redirected_path)
+ end
+
+ def access_checker_klass
+ repo_type.access_checker_class
+ end
+
def ssh_authentication_abilities
[
:read_project,
@@ -44,8 +56,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 +66,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/issues_helpers.rb b/lib/api/helpers/issues_helpers.rb
index 5b7199fddb0..a8480bb9339 100644
--- a/lib/api/helpers/issues_helpers.rb
+++ b/lib/api/helpers/issues_helpers.rb
@@ -27,6 +27,10 @@ module API
]
end
+ def self.sort_options
+ %w[created_at updated_at priority due_date relative_position label_priority milestone_due popularity]
+ end
+
def issue_finder(args = {})
args = declared_params.merge(args)
@@ -34,15 +38,14 @@ module API
args[:milestone_title] ||= args.delete(:milestone)
args[:label_name] ||= args.delete(:labels)
args[:scope] = args[:scope].underscore if args[:scope]
+ args[:sort] = "#{args[:order_by]}_#{args[:sort]}"
IssuesFinder.new(current_user, args)
end
def find_issues(args = {})
finder = issue_finder(args)
- issues = finder.execute.with_api_entity_associations
-
- issues.reorder(order_options_with_tie_breaker) # rubocop: disable CodeReuse/ActiveRecord
+ finder.execute.with_api_entity_associations
end
def issues_statistics(args = {})
diff --git a/lib/api/helpers/label_helpers.rb b/lib/api/helpers/label_helpers.rb
index 896b0aba52b..ec5b688dd1c 100644
--- a/lib/api/helpers/label_helpers.rb
+++ b/lib/api/helpers/label_helpers.rb
@@ -11,9 +11,9 @@ module API
optional :description, type: String, desc: 'The description of label to be created'
end
- def find_label(parent, id, include_ancestor_groups: true)
+ def find_label(parent, id_or_title, include_ancestor_groups: true)
labels = available_labels_for(parent, include_ancestor_groups: include_ancestor_groups)
- label = labels.find_by_id(id) || labels.find_by_title(id)
+ label = labels.find_by_id(id_or_title) || labels.find_by_title(id_or_title)
label || not_found!('Label')
end
@@ -35,12 +35,7 @@ module API
priority = params.delete(:priority)
label_params = declared_params(include_missing: false)
- label =
- if parent.is_a?(Project)
- ::Labels::CreateService.new(label_params).execute(project: parent)
- else
- ::Labels::CreateService.new(label_params).execute(group: parent)
- end
+ label = ::Labels::CreateService.new(label_params).execute(create_service_params(parent))
if label.persisted?
if parent.is_a?(Project)
@@ -56,10 +51,13 @@ module API
def update_label(parent, entity)
authorize! :admin_label, parent
- label = find_label(parent, params[:name], include_ancestor_groups: false)
+ label = find_label(parent, params_id_or_title, include_ancestor_groups: false)
update_priority = params.key?(:priority)
priority = params.delete(:priority)
+ # params is used to update the label so we need to remove this field here
+ params.delete(:label_id)
+
label = ::Labels::UpdateService.new(declared_params(include_missing: false)).execute(label)
render_validation_error!(label) unless label.valid?
@@ -77,10 +75,24 @@ module API
def delete_label(parent)
authorize! :admin_label, parent
- label = find_label(parent, params[:name], include_ancestor_groups: false)
+ label = find_label(parent, params_id_or_title, include_ancestor_groups: false)
destroy_conditionally!(label)
end
+
+ def params_id_or_title
+ @params_id_or_title ||= params[:label_id] || params[:name]
+ end
+
+ def create_service_params(parent)
+ if parent.is_a?(Project)
+ { project: parent }
+ elsif parent.is_a?(Group)
+ { group: parent }
+ else
+ raise TypeError, 'Parent type is not supported'
+ end
+ end
end
end
end
diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb
index 6bf9057fad7..f445834323d 100644
--- a/lib/api/helpers/notes_helpers.rb
+++ b/lib/api/helpers/notes_helpers.rb
@@ -3,6 +3,8 @@
module API
module Helpers
module NotesHelpers
+ include ::RendersNotes
+
def self.noteable_types
# This is a method instead of a constant, allowing EE to more easily
# extend it.
@@ -10,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
@@ -59,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
deleted file mode 100644
index 224aaaaf006..00000000000
--- a/lib/api/internal.rb
+++ /dev/null
@@ -1,293 +0,0 @@
-# frozen_string_literal: true
-
-module API
- # Internal access API
- class Internal < Grape::API
- before { authenticate_by_gitlab_shell_token! }
-
- helpers ::API::Helpers::InternalHelpers
- helpers ::Gitlab::Identifier
-
- UNKNOWN_CHECK_RESULT_ERROR = 'Unknown check result'.freeze
-
- helpers do
- def response_with_status(code: 200, success: true, message: nil, **extra_options)
- status code
- { status: success, message: message }.merge(extra_options).compact
- end
-
- def lfs_authentication_url(project)
- # This is a separate method so that EE can alter its behaviour more
- # easily.
- project.http_url_to_repo
- end
- end
-
- namespace 'internal' do
- # Check if git command is allowed for project
- #
- # Params:
- # key_id - ssh key id for Git over SSH
- # user_id - user id for Git over HTTP or over SSH in keyless SSH CERT mode
- # username - user name for Git over SSH in keyless SSH cert mode
- # protocol - Git access protocol being used, e.g. HTTP or SSH
- # project - project full_path (not path on disk)
- # action - git action (git-upload-pack or git-receive-pack)
- # changes - changes as "oldrev newrev ref", see Gitlab::ChangesList
- # rubocop: disable CodeReuse/ActiveRecord
- post "/allowed" do
- # Stores some Git-specific env thread-safely
- env = parse_env
- Gitlab::Git::HookEnv.set(gl_repository, env) if project
-
- actor =
- if params[:key_id]
- Key.find_by(id: params[:key_id])
- elsif params[:user_id]
- User.find_by(id: params[:user_id])
- elsif params[:username]
- UserFinder.new(params[:username]).find_by_username
- end
-
- protocol = params[:protocol]
-
- actor.update_last_used_at if actor.is_a?(Key)
- user =
- if actor.is_a?(Key)
- actor.user
- else
- actor
- end
-
- access_checker_klass = repo_type.access_checker_class
- access_checker = access_checker_klass.new(actor, project,
- protocol, authentication_abilities: ssh_authentication_abilities,
- namespace_path: namespace_path, project_path: project_path,
- redirected_path: redirected_path)
-
- check_result = begin
- result = access_checker.check(params[:action], params[:changes])
- @project ||= access_checker.project
- result
- rescue Gitlab::GitAccess::UnauthorizedError => e
- break response_with_status(code: 401, success: false, message: e.message)
- rescue Gitlab::GitAccess::TimeoutError => e
- break response_with_status(code: 503, success: false, message: e.message)
- rescue Gitlab::GitAccess::NotFoundError => e
- break response_with_status(code: 404, success: false, message: e.message)
- end
-
- log_user_activity(actor)
-
- case check_result
- when ::Gitlab::GitAccessResult::Success
- payload = {
- gl_repository: gl_repository,
- gl_project_path: gl_project_path,
- gl_id: Gitlab::GlId.gl_id(user),
- gl_username: user&.username,
- git_config_options: [],
- gitaly: gitaly_payload(params[:action]),
- gl_console_messages: check_result.console_messages
- }
-
- # Custom option for git-receive-pack command
- receive_max_input_size = Gitlab::CurrentSettings.receive_max_input_size.to_i
- if receive_max_input_size > 0
- payload[:git_config_options] << "receive.maxInputSize=#{receive_max_input_size.megabytes}"
- end
-
- response_with_status(**payload)
- when ::Gitlab::GitAccessResult::CustomAction
- response_with_status(code: 300, message: check_result.message, payload: check_result.payload)
- else
- response_with_status(code: 500, success: false, message: UNKNOWN_CHECK_RESULT_ERROR)
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- # rubocop: disable CodeReuse/ActiveRecord
- post "/lfs_authenticate" do
- status 200
-
- if params[:key_id]
- actor = Key.find(params[:key_id])
- actor.update_last_used_at
- elsif params[:user_id]
- actor = User.find_by(id: params[:user_id])
- raise ActiveRecord::RecordNotFound.new("No such user id!") unless actor
- else
- raise ActiveRecord::RecordNotFound.new("No key_id or user_id passed!")
- end
-
- Gitlab::LfsToken
- .new(actor)
- .authentication_payload(lfs_authentication_url(project))
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- get "/merge_request_urls" do
- merge_request_urls
- end
-
- #
- # Get a ssh key using the fingerprint
- #
- # rubocop: disable CodeReuse/ActiveRecord
- get "/authorized_keys" do
- fingerprint = params.fetch(:fingerprint) do
- Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint
- end
- key = Key.find_by(fingerprint: fingerprint)
- not_found!("Key") if key.nil?
- present key, with: Entities::SSHKey
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- #
- # Discover user by ssh key, user id or username
- #
- # rubocop: disable CodeReuse/ActiveRecord
- get "/discover" do
- if params[:key_id]
- key = Key.find(params[:key_id])
- user = key.user
- elsif params[:user_id]
- user = User.find_by(id: params[:user_id])
- elsif params[:username]
- user = UserFinder.new(params[:username]).find_by_username
- end
-
- present user, with: Entities::UserSafe
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- get "/check" do
- {
- api_version: API.version,
- gitlab_version: Gitlab::VERSION,
- gitlab_rev: Gitlab.revision,
- redis: redis_ping
- }
- end
-
- get "/broadcast_messages" do
- if messages = BroadcastMessage.current
- present messages, with: Entities::BroadcastMessage
- else
- []
- end
- end
-
- get "/broadcast_message" do
- if message = BroadcastMessage.current&.last
- present message, with: Entities::BroadcastMessage
- else
- {}
- end
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- post '/two_factor_recovery_codes' do
- status 200
-
- if params[:key_id]
- key = Key.find_by(id: params[:key_id])
-
- if key
- key.update_last_used_at
- else
- break { 'success' => false, 'message' => 'Could not find the given key' }
- end
-
- if key.is_a?(DeployKey)
- break { success: false, message: 'Deploy keys cannot be used to retrieve recovery codes' }
- end
-
- user = key.user
-
- unless user
- break { success: false, message: 'Could not find a user for the given key' }
- end
- elsif params[:user_id]
- user = User.find_by(id: params[:user_id])
-
- unless user
- break { success: false, message: 'Could not find the given user' }
- end
- end
-
- unless user.two_factor_enabled?
- break { success: false, message: 'Two-factor authentication is not enabled for this user' }
- end
-
- codes = nil
-
- ::Users::UpdateService.new(current_user, user: user).execute! do |user|
- codes = user.generate_otp_backup_codes!
- end
-
- { success: true, recovery_codes: codes }
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- post '/pre_receive' do
- status 200
-
- reference_counter_increased = Gitlab::ReferenceCounter.new(params[:gl_repository]).increase
-
- { reference_counter_increased: reference_counter_increased }
- end
-
- post "/notify_post_receive" do
- status 200
-
- # TODO: Re-enable when Gitaly is processing the post-receive notification
- # return unless Gitlab::GitalyClient.enabled?
- #
- # begin
- # repository = wiki? ? project.wiki.repository : project.repository
- # Gitlab::GitalyClient::NotificationService.new(repository.raw_repository).post_receive
- # rescue GRPC::Unavailable => e
- # render_api_error!(e, 500)
- # end
- end
-
- post '/post_receive' do
- status 200
-
- output = {} # Messages to gitlab-shell
- user = identify(params[:identifier])
- project = Gitlab::GlRepository.parse(params[:gl_repository]).first
- push_options = Gitlab::PushOptions.new(params[:push_options])
-
- 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?
-
- broadcast_message = BroadcastMessage.current&.last&.message
- reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease
-
- output.merge!(
- broadcast_message: broadcast_message,
- reference_counter_decreased: reference_counter_decreased,
- merge_request_urls: merge_request_urls
- )
-
- # A user is not guaranteed to be returned; an orphaned write deploy
- # key could be used
- if user
- 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
- end
-
- output
- end
- end
- end
-end
diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb
new file mode 100644
index 00000000000..087985d34b0
--- /dev/null
+++ b/lib/api/internal/base.rb
@@ -0,0 +1,273 @@
+# frozen_string_literal: true
+
+module API
+ # Internal access API
+ module Internal
+ class Base < Grape::API
+ before { authenticate_by_gitlab_shell_token! }
+
+ helpers ::API::Helpers::InternalHelpers
+ helpers ::Gitlab::Identifier
+
+ UNKNOWN_CHECK_RESULT_ERROR = 'Unknown check result'.freeze
+
+ helpers do
+ def response_with_status(code: 200, success: true, message: nil, **extra_options)
+ status code
+ { status: success, message: message }.merge(extra_options).compact
+ end
+
+ def lfs_authentication_url(project)
+ # This is a separate method so that EE can alter its behaviour more
+ # easily.
+ project.http_url_to_repo
+ end
+ end
+
+ namespace 'internal' do
+ # Check if git command is allowed for project
+ #
+ # Params:
+ # key_id - ssh key id for Git over SSH
+ # user_id - user id for Git over HTTP or over SSH in keyless SSH CERT mode
+ # username - user name for Git over SSH in keyless SSH cert mode
+ # protocol - Git access protocol being used, e.g. HTTP or SSH
+ # project - project full_path (not path on disk)
+ # action - git action (git-upload-pack or git-receive-pack)
+ # changes - changes as "oldrev newrev ref", see Gitlab::ChangesList
+ post "/allowed" do
+ # Stores some Git-specific env thread-safely
+ env = parse_env
+ Gitlab::Git::HookEnv.set(gl_repository, env) if project
+
+ actor = Support::GitAccessActor.from_params(params)
+ actor.update_last_used_at!
+ access_checker = access_checker_for(actor, params[:protocol])
+
+ check_result = begin
+ result = access_checker.check(params[:action], params[:changes])
+ @project ||= access_checker.project
+ result
+ rescue Gitlab::GitAccess::UnauthorizedError => e
+ break response_with_status(code: 401, success: false, message: e.message)
+ rescue Gitlab::GitAccess::TimeoutError => e
+ break response_with_status(code: 503, success: false, message: e.message)
+ rescue Gitlab::GitAccess::NotFoundError => e
+ break response_with_status(code: 404, success: false, message: e.message)
+ end
+
+ log_user_activity(actor.user)
+
+ case check_result
+ when ::Gitlab::GitAccessResult::Success
+ payload = {
+ gl_repository: gl_repository,
+ gl_project_path: gl_project_path,
+ gl_id: Gitlab::GlId.gl_id(actor.user),
+ gl_username: actor.username,
+ git_config_options: [],
+ gitaly: gitaly_payload(params[:action]),
+ gl_console_messages: check_result.console_messages
+ }
+
+ # Custom option for git-receive-pack command
+ receive_max_input_size = Gitlab::CurrentSettings.receive_max_input_size.to_i
+ if receive_max_input_size > 0
+ payload[:git_config_options] << "receive.maxInputSize=#{receive_max_input_size.megabytes}"
+ end
+
+ response_with_status(**payload)
+ when ::Gitlab::GitAccessResult::CustomAction
+ response_with_status(code: 300, message: check_result.message, payload: check_result.payload)
+ else
+ response_with_status(code: 500, success: false, message: UNKNOWN_CHECK_RESULT_ERROR)
+ end
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ post "/lfs_authenticate" do
+ status 200
+
+ if params[:key_id]
+ actor = Key.find(params[:key_id])
+ actor.update_last_used_at
+ elsif params[:user_id]
+ actor = User.find_by(id: params[:user_id])
+ raise ActiveRecord::RecordNotFound.new("No such user id!") unless actor
+ else
+ raise ActiveRecord::RecordNotFound.new("No key_id or user_id passed!")
+ end
+
+ Gitlab::LfsToken
+ .new(actor)
+ .authentication_payload(lfs_authentication_url(project))
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ get "/merge_request_urls" do
+ merge_request_urls
+ end
+
+ #
+ # Get a ssh key using the fingerprint
+ #
+ # rubocop: disable CodeReuse/ActiveRecord
+ get "/authorized_keys" do
+ fingerprint = params.fetch(:fingerprint) do
+ Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint
+ end
+ key = Key.find_by(fingerprint: fingerprint)
+ not_found!("Key") if key.nil?
+ present key, with: Entities::SSHKey
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ #
+ # Discover user by ssh key, user id or username
+ #
+ # rubocop: disable CodeReuse/ActiveRecord
+ get "/discover" do
+ if params[:key_id]
+ key = Key.find(params[:key_id])
+ user = key.user
+ elsif params[:user_id]
+ user = User.find_by(id: params[:user_id])
+ elsif params[:username]
+ user = UserFinder.new(params[:username]).find_by_username
+ end
+
+ present user, with: Entities::UserSafe
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ get "/check" do
+ {
+ api_version: API.version,
+ gitlab_version: Gitlab::VERSION,
+ gitlab_rev: Gitlab.revision,
+ redis: redis_ping
+ }
+ end
+
+ get "/broadcast_messages" do
+ if messages = BroadcastMessage.current
+ present messages, with: Entities::BroadcastMessage
+ else
+ []
+ end
+ end
+
+ get "/broadcast_message" do
+ if message = BroadcastMessage.current&.last
+ present message, with: Entities::BroadcastMessage
+ else
+ {}
+ end
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ post '/two_factor_recovery_codes' do
+ status 200
+
+ if params[:key_id]
+ key = Key.find_by(id: params[:key_id])
+
+ if key
+ key.update_last_used_at
+ else
+ break { 'success' => false, 'message' => 'Could not find the given key' }
+ end
+
+ if key.is_a?(DeployKey)
+ break { success: false, message: 'Deploy keys cannot be used to retrieve recovery codes' }
+ end
+
+ user = key.user
+
+ unless user
+ break { success: false, message: 'Could not find a user for the given key' }
+ end
+ elsif params[:user_id]
+ user = User.find_by(id: params[:user_id])
+
+ unless user
+ break { success: false, message: 'Could not find the given user' }
+ end
+ end
+
+ unless user.two_factor_enabled?
+ break { success: false, message: 'Two-factor authentication is not enabled for this user' }
+ end
+
+ codes = nil
+
+ ::Users::UpdateService.new(current_user, user: user).execute! do |user|
+ codes = user.generate_otp_backup_codes!
+ end
+
+ { success: true, recovery_codes: codes }
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ post '/pre_receive' do
+ status 200
+
+ reference_counter_increased = Gitlab::ReferenceCounter.new(params[:gl_repository]).increase
+
+ { reference_counter_increased: reference_counter_increased }
+ end
+
+ post "/notify_post_receive" do
+ status 200
+
+ # TODO: Re-enable when Gitaly is processing the post-receive notification
+ # return unless Gitlab::GitalyClient.enabled?
+ #
+ # begin
+ # repository = wiki? ? project.wiki.repository : project.repository
+ # Gitlab::GitalyClient::NotificationService.new(repository.raw_repository).post_receive
+ # rescue GRPC::Unavailable => e
+ # render_api_error!(e, 500)
+ # end
+ end
+
+ post '/post_receive' do
+ status 200
+
+ 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)
+ 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
+ response.add_alert_message(broadcast_message)
+
+ response.add_merge_request_urls(merge_request_urls)
+
+ # A user is not guaranteed to be returned; an orphaned write deploy
+ # key could be used
+ if user
+ redirect_message = Gitlab::Checks::ProjectMoved.fetch_message(user.id, project.id)
+ project_created_message = Gitlab::Checks::ProjectCreated.fetch_message(user.id, project.id)
+
+ response.add_basic_message(redirect_message)
+ response.add_basic_message(project_created_message)
+ end
+
+ present response, with: Entities::InternalPostReceive::Response
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 7819c2de515..215178478d0 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -44,7 +44,7 @@ module API
optional :with_labels_details, type: Boolean, desc: 'Return more label data than just lable title', default: false
optional :state, type: String, values: %w[opened closed all], default: 'all',
desc: 'Return opened, closed, or all issues'
- optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at',
+ optional :order_by, type: String, values: Helpers::IssuesHelpers.sort_options, default: 'created_at',
desc: 'Return issues ordered by `created_at` or `updated_at` fields.'
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return issues sorted in `asc` or `desc` order.'
@@ -163,7 +163,8 @@ module API
with_labels_details: declared_params[:with_labels_details],
current_user: current_user,
project: user_project,
- issuable_metadata: issuable_meta_data(issues, 'Issue', current_user)
+ issuable_metadata: issuable_meta_data(issues, 'Issue', current_user),
+ include_subscribed: false
}
present issues, options
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index c183198d3c6..de89e94b0c0 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -38,11 +38,13 @@ module API
success Entities::ProjectLabel
end
params do
- requires :name, type: String, desc: 'The name of the label to be updated'
+ optional :label_id, type: Integer, desc: 'The id of the label to be updated'
+ optional :name, type: String, desc: 'The name of the label to be updated'
optional :new_name, type: String, desc: 'The new name of the label'
optional :color, type: String, desc: "The new color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the allowed CSS color names"
optional :description, type: String, desc: 'The new description of label'
optional :priority, type: Integer, desc: 'The priority of the label', allow_blank: true
+ exactly_one_of :label_id, :name
at_least_one_of :new_name, :color, :description, :priority
end
put ':id/labels' do
@@ -53,11 +55,38 @@ module API
success Entities::ProjectLabel
end
params do
- requires :name, type: String, desc: 'The name of the label to be deleted'
+ optional :label_id, type: Integer, desc: 'The id of the label to be deleted'
+ optional :name, type: String, desc: 'The name of the label to be deleted'
+ exactly_one_of :label_id, :name
end
delete ':id/labels' do
delete_label(user_project)
end
+
+ desc 'Promote a label to a group label' do
+ detail 'This feature was added in GitLab 12.3'
+ success Entities::GroupLabel
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the label to be promoted'
+ end
+ put ':id/labels/promote' do
+ authorize! :admin_label, user_project
+
+ label = find_label(user_project, params[:name], include_ancestor_groups: false)
+
+ begin
+ group_label = ::Labels::PromoteService.new(user_project, current_user).execute(label)
+
+ if group_label
+ present group_label, with: Entities::GroupLabel, current_user: current_user, parent: user_project.group
+ else
+ render_api_error!('Failed to promote project label to group label', 400)
+ end
+ rescue => error
+ render_api_error!(error.to_s, 400)
+ end
+ end
end
end
end
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 9381f045144..16fca9acccb 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -36,12 +36,13 @@ module API
# page can have less elements than :per_page even if
# there's more than one page.
raw_notes = noteable.notes.with_metadata.reorder(order_options_with_tie_breaker)
- notes =
- # paginate() only works with a relation. This could lead to a
- # mismatch between the pagination headers info and the actual notes
- # array returned, but this is really a edge-case.
- paginate(raw_notes)
- .reject { |n| n.cross_reference_not_visible_for?(current_user) }
+
+ # paginate() only works with a relation. This could lead to a
+ # mismatch between the pagination headers info and the actual notes
+ # array returned, but this is really a edge-case.
+ notes = paginate(raw_notes)
+ notes = prepare_notes_for_rendering(notes)
+ notes = notes.select { |note| note.visible_for?(current_user) }
present notes, with: Entities::Note
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index 667bf1ec801..9e888368e7b 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -4,7 +4,7 @@ module API
class Pipelines < Grape::API
include PaginationParams
- before { authenticate! }
+ before { authenticate_non_get! }
params do
requires :id, type: String, desc: 'The project ID'
@@ -32,6 +32,7 @@ module API
end
get ':id/pipelines' do
authorize! :read_pipeline, user_project
+ authorize! :read_build, user_project
pipelines = PipelinesFinder.new(user_project, current_user, params).execute
present paginate(pipelines), with: Entities::PipelineBasic
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/support/git_access_actor.rb b/lib/api/support/git_access_actor.rb
new file mode 100644
index 00000000000..2e0962c6295
--- /dev/null
+++ b/lib/api/support/git_access_actor.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module API
+ module Support
+ class GitAccessActor
+ attr_reader :user
+
+ def initialize(user: nil, key: nil)
+ @user = user
+ @key = key
+
+ @user = key.user if !user && key
+ end
+
+ def self.from_params(params)
+ if params[:key_id]
+ new(key: Key.find_by_id(params[:key_id]))
+ elsif params[:user_id]
+ new(user: UserFinder.new(params[:user_id]).find_by_id)
+ elsif params[:username]
+ new(user: UserFinder.new(params[:username]).find_by_username)
+ end
+ end
+
+ def key_or_user
+ key || user
+ end
+
+ def username
+ user&.username
+ end
+
+ def update_last_used_at!
+ key&.update_last_used_at
+ end
+
+ private
+
+ attr_reader :key
+ end
+ end
+end
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/api/validations/types/workhorse_file.rb b/lib/api/validations/types/workhorse_file.rb
new file mode 100644
index 00000000000..18d111f6556
--- /dev/null
+++ b/lib/api/validations/types/workhorse_file.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module API
+ module Validations
+ module Types
+ class WorkhorseFile < Virtus::Attribute
+ def coerce(input)
+ # Processing of multipart file objects
+ # is already taken care of by Gitlab::Middleware::Multipart.
+ # Nothing to do here.
+ input
+ end
+
+ def value_coerced?(value)
+ value.is_a?(::UploadedFile)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
index 5724adb2c40..c5a5488950d 100644
--- a/lib/api/wikis.rb
+++ b/lib/api/wikis.rb
@@ -4,11 +4,21 @@ module API
class Wikis < Grape::API
helpers do
def commit_params(attrs)
- {
- file_name: attrs[:file][:filename],
- file_content: attrs[:file][:tempfile].read,
- branch_name: attrs[:branch]
- }
+ # In order to avoid service disruption this can work with an old workhorse without the acceleration
+ # the first branch of this if must be removed when we drop support for non accelerated uploads
+ if attrs[:file].is_a?(Hash)
+ {
+ file_name: attrs[:file][:filename],
+ file_content: attrs[:file][:tempfile].read,
+ branch_name: attrs[:branch]
+ }
+ else
+ {
+ file_name: attrs[:file].original_filename,
+ file_content: attrs[:file].read,
+ branch_name: attrs[:branch]
+ }
+ end
end
params :common_wiki_page_params do
@@ -106,7 +116,7 @@ module API
success Entities::WikiAttachment
end
params do
- requires :file, type: ::API::Validations::Types::SafeFile, desc: 'The attachment file to be uploaded'
+ requires :file, types: [::API::Validations::Types::SafeFile, ::API::Validations::Types::WorkhorseFile], desc: 'The attachment file to be uploaded'
optional :branch, type: String, desc: 'The name of the branch'
end
post ":id/wikis/attachments" do
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..8acd3917d81
--- /dev/null
+++ b/lib/banzai/filter/asset_proxy_filter.rb
@@ -0,0 +1,66 @@
+# 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'] = determine_whitelist(application_settings)
+ 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
+
+ def self.determine_whitelist(application_settings)
+ application_settings.asset_proxy_whitelist.presence || [Gitlab.config.gitlab.host]
+ 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/feature/gitaly.rb b/lib/feature/gitaly.rb
index be397a478cd..656becbffd3 100644
--- a/lib/feature/gitaly.rb
+++ b/lib/feature/gitaly.rb
@@ -10,6 +10,7 @@ class Feature
get_commit_signatures
cache_invalidator
inforef_uploadpack_cache
+ get_all_lfs_pointers_go
].freeze
DEFAULT_ON_FLAGS = Set.new([]).freeze
diff --git a/lib/gitlab.rb b/lib/gitlab.rb
index d9d8dcf7900..e8b938e46b1 100644
--- a/lib/gitlab.rb
+++ b/lib/gitlab.rb
@@ -55,7 +55,7 @@ module Gitlab
SUBDOMAIN_REGEX === Gitlab.config.gitlab.url
end
- def self.dev_env_or_com?
+ def self.dev_env_org_or_com?
Rails.env.development? || org? || com?
end
diff --git a/lib/gitlab/analytics/cycle_analytics/default_stages.rb b/lib/gitlab/analytics/cycle_analytics/default_stages.rb
new file mode 100644
index 00000000000..286c393005f
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/default_stages.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+# This module represents the default Cycle Analytics stages that are currently provided by CE
+# Each method returns a hash that can be used to build a new stage object.
+#
+# Example:
+#
+# params = Gitlab::Analytics::CycleAnalytics::DefaultStages.params_for_issue_stage
+# Analytics::CycleAnalytics::ProjectStage.new(params)
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module DefaultStages
+ def self.all
+ [
+ params_for_issue_stage,
+ params_for_plan_stage,
+ params_for_code_stage,
+ params_for_test_stage,
+ params_for_review_stage,
+ params_for_staging_stage,
+ params_for_production_stage
+ ]
+ end
+
+ def self.params_for_issue_stage
+ {
+ name: 'issue',
+ custom: false, # this stage won't be customizable, we provide it as it is
+ relative_position: 1, # when opening the CycleAnalytics page in CE, this stage will be the first item
+ start_event_identifier: :issue_created, # IssueCreated class is used as start event
+ end_event_identifier: :issue_stage_end # IssueStageEnd class is used as end event
+ }
+ end
+
+ def self.params_for_plan_stage
+ {
+ name: 'plan',
+ custom: false,
+ relative_position: 2,
+ start_event_identifier: :plan_stage_start,
+ end_event_identifier: :issue_first_mentioned_in_commit
+ }
+ end
+
+ def self.params_for_code_stage
+ {
+ name: 'code',
+ custom: false,
+ relative_position: 3,
+ start_event_identifier: :code_stage_start,
+ end_event_identifier: :merge_request_created
+ }
+ end
+
+ def self.params_for_test_stage
+ {
+ name: 'test',
+ custom: false,
+ relative_position: 4,
+ start_event_identifier: :merge_request_last_build_started,
+ end_event_identifier: :merge_request_last_build_finished
+ }
+ end
+
+ def self.params_for_review_stage
+ {
+ name: 'review',
+ custom: false,
+ relative_position: 5,
+ start_event_identifier: :merge_request_created,
+ end_event_identifier: :merge_request_merged
+ }
+ end
+
+ def self.params_for_staging_stage
+ {
+ name: 'staging',
+ custom: false,
+ relative_position: 6,
+ start_event_identifier: :merge_request_merged,
+ end_event_identifier: :merge_request_first_deployed_to_production
+ }
+ end
+
+ def self.params_for_production_stage
+ {
+ name: 'production',
+ custom: false,
+ relative_position: 7,
+ start_event_identifier: :merge_request_merged,
+ end_event_identifier: :merge_request_first_deployed_to_production
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events.rb b/lib/gitlab/analytics/cycle_analytics/stage_events.rb
new file mode 100644
index 00000000000..d21f344f483
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ # Convention:
+ # Issue: < 100
+ # MergeRequest: >= 100 && < 1000
+ # Custom events for default stages: >= 1000 (legacy)
+ ENUM_MAPPING = {
+ StageEvents::IssueCreated => 1,
+ StageEvents::IssueFirstMentionedInCommit => 2,
+ StageEvents::MergeRequestCreated => 100,
+ StageEvents::MergeRequestFirstDeployedToProduction => 101,
+ StageEvents::MergeRequestLastBuildFinished => 102,
+ StageEvents::MergeRequestLastBuildStarted => 103,
+ StageEvents::MergeRequestMerged => 104,
+ StageEvents::CodeStageStart => 1_000,
+ StageEvents::IssueStageEnd => 1_001,
+ StageEvents::PlanStageStart => 1_002
+ }.freeze
+
+ EVENTS = ENUM_MAPPING.keys.freeze
+
+ # Defines which start_event and end_event pairs are allowed
+ PAIRING_RULES = {
+ StageEvents::PlanStageStart => [
+ StageEvents::IssueFirstMentionedInCommit
+ ],
+ StageEvents::CodeStageStart => [
+ StageEvents::MergeRequestCreated
+ ],
+ StageEvents::IssueCreated => [
+ StageEvents::IssueStageEnd
+ ],
+ StageEvents::MergeRequestCreated => [
+ StageEvents::MergeRequestMerged
+ ],
+ StageEvents::MergeRequestLastBuildStarted => [
+ StageEvents::MergeRequestLastBuildFinished
+ ],
+ StageEvents::MergeRequestMerged => [
+ StageEvents::MergeRequestFirstDeployedToProduction
+ ]
+ }.freeze
+
+ def [](identifier)
+ events.find { |e| e.identifier.to_s.eql?(identifier.to_s) } || raise(KeyError)
+ end
+
+ # hash for defining ActiveRecord enum: identifier => number
+ def to_enum
+ ENUM_MAPPING.each_with_object({}) { |(k, v), hash| hash[k.identifier] = v }
+ end
+
+ # will be overridden in EE with custom events
+ def pairing_rules
+ PAIRING_RULES
+ end
+
+ # will be overridden in EE with custom events
+ def events
+ EVENTS
+ end
+
+ module_function :[], :to_enum, :pairing_rules, :events
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start.rb
new file mode 100644
index 00000000000..ff9c8a79225
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class CodeStageStart < SimpleStageEvent
+ def self.name
+ s_("CycleAnalyticsEvent|Issue first mentioned in a commit")
+ end
+
+ def self.identifier
+ :code_stage_start
+ end
+
+ def object_type
+ MergeRequest
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created.rb
new file mode 100644
index 00000000000..a601c9797f8
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class IssueCreated < SimpleStageEvent
+ def self.name
+ s_("CycleAnalyticsEvent|Issue created")
+ end
+
+ def self.identifier
+ :issue_created
+ end
+
+ def object_type
+ Issue
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit.rb
new file mode 100644
index 00000000000..7424043ef7b
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class IssueFirstMentionedInCommit < SimpleStageEvent
+ def self.name
+ s_("CycleAnalyticsEvent|Issue first mentioned in a commit")
+ end
+
+ def self.identifier
+ :issue_first_mentioned_in_commit
+ end
+
+ def object_type
+ Issue
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end.rb
new file mode 100644
index 00000000000..ceb229c552f
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class IssueStageEnd < SimpleStageEvent
+ def self.name
+ PlanStageStart.name
+ end
+
+ def self.identifier
+ :issue_stage_end
+ end
+
+ def object_type
+ Issue
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created.rb
new file mode 100644
index 00000000000..8be00831b4f
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class MergeRequestCreated < SimpleStageEvent
+ def self.name
+ s_("CycleAnalyticsEvent|Merge request created")
+ end
+
+ def self.identifier
+ :merge_request_created
+ end
+
+ def object_type
+ MergeRequest
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production.rb
new file mode 100644
index 00000000000..6d7a2c023ff
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class MergeRequestFirstDeployedToProduction < SimpleStageEvent
+ def self.name
+ s_("CycleAnalyticsEvent|Merge request first deployed to production")
+ end
+
+ def self.identifier
+ :merge_request_first_deployed_to_production
+ end
+
+ def object_type
+ MergeRequest
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished.rb
new file mode 100644
index 00000000000..12d82fe2c62
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class MergeRequestLastBuildFinished < SimpleStageEvent
+ def self.name
+ s_("CycleAnalyticsEvent|Merge request last build finish time")
+ end
+
+ def self.identifier
+ :merge_request_last_build_finished
+ end
+
+ def object_type
+ MergeRequest
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started.rb
new file mode 100644
index 00000000000..9e749b0fdfa
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class MergeRequestLastBuildStarted < SimpleStageEvent
+ def self.name
+ s_("CycleAnalyticsEvent|Merge request last build start time")
+ end
+
+ def self.identifier
+ :merge_request_last_build_started
+ end
+
+ def object_type
+ MergeRequest
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged.rb
new file mode 100644
index 00000000000..bbfb5d12992
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class MergeRequestMerged < SimpleStageEvent
+ def self.name
+ s_("CycleAnalyticsEvent|Merge request merged")
+ end
+
+ def self.identifier
+ :merge_request_merged
+ end
+
+ def object_type
+ MergeRequest
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start.rb
new file mode 100644
index 00000000000..803317d8b55
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class PlanStageStart < SimpleStageEvent
+ def self.name
+ s_("CycleAnalyticsEvent|Issue first associated with a milestone or issue first added to a board")
+ end
+
+ def self.identifier
+ :plan_stage_start
+ end
+
+ def object_type
+ Issue
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/simple_stage_event.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/simple_stage_event.rb
new file mode 100644
index 00000000000..253c489d822
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/simple_stage_event.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ # Represents a simple event that usually refers to one database column and does not require additional user input
+ class SimpleStageEvent < StageEvent
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb
new file mode 100644
index 00000000000..a55eee048c2
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ # Base class for expressing an event that can be used for a stage.
+ class StageEvent
+ def initialize(params)
+ @params = params
+ end
+
+ def self.name
+ raise NotImplementedError
+ end
+
+ def self.identifier
+ raise NotImplementedError
+ end
+
+ def object_type
+ raise NotImplementedError
+ end
+ end
+ end
+ end
+ end
+end
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 e17a096ef19..bdc46abeb9f 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -198,12 +198,10 @@ module Gitlab
end.uniq
end
- # rubocop: disable CodeReuse/ActiveRecord
def deploy_token_check(login, password)
return unless password.present?
- token =
- DeployToken.active.find_by(token: password)
+ token = DeployToken.active.find_by_token(password)
return unless token && login
return if login != token.username
@@ -214,7 +212,6 @@ module Gitlab
Gitlab::Auth::Result.new(token, token.project, :deploy_token, scopes)
end
end
- # rubocop: enable CodeReuse/ActiveRecord
def lfs_token_check(login, encoded_token, project)
deploy_key_matches = login.match(/\Alfs\+deploy-key-(\d+)\z/)
@@ -268,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/auth/o_auth/user.rb b/lib/gitlab/auth/o_auth/user.rb
index 09d1d79fefc..f121dce4cbb 100644
--- a/lib/gitlab/auth/o_auth/user.rb
+++ b/lib/gitlab/auth/o_auth/user.rb
@@ -77,7 +77,12 @@ module Gitlab
end
def bypass_two_factor?
- false
+ providers = Gitlab.config.omniauth.allow_bypass_two_factor
+ if providers.is_a?(Array)
+ providers.include?(auth_hash.provider)
+ else
+ providers
+ end
end
protected
diff --git a/lib/gitlab/auth/unique_ips_limiter.rb b/lib/gitlab/auth/unique_ips_limiter.rb
index 31dd61ae6cf..97e78ecf094 100644
--- a/lib/gitlab/auth/unique_ips_limiter.rb
+++ b/lib/gitlab/auth/unique_ips_limiter.rb
@@ -3,7 +3,7 @@
module Gitlab
module Auth
class UniqueIpsLimiter
- USER_UNIQUE_IPS_PREFIX = 'user_unique_ips'.freeze
+ USER_UNIQUE_IPS_PREFIX = 'user_unique_ips'
class << self
def limit_user_id!(user_id)
diff --git a/lib/gitlab/auth/user_auth_finders.rb b/lib/gitlab/auth/user_auth_finders.rb
index bba7e2cbb3c..97755117edc 100644
--- a/lib/gitlab/auth/user_auth_finders.rb
+++ b/lib/gitlab/auth/user_auth_finders.rb
@@ -20,7 +20,7 @@ module Gitlab
module UserAuthFinders
include Gitlab::Utils::StrongMemoize
- PRIVATE_TOKEN_HEADER = 'HTTP_PRIVATE_TOKEN'.freeze
+ PRIVATE_TOKEN_HEADER = 'HTTP_PRIVATE_TOKEN'
PRIVATE_TOKEN_PARAM = :private_token
# Check the Rails session for valid authentication details
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/background_migration/merge_request_assignees_migration_progress_check.rb b/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check.rb
index e948cedaad5..de0c357ab1c 100644
--- a/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check.rb
+++ b/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check.rb
@@ -7,7 +7,7 @@ module Gitlab
include Gitlab::Utils::StrongMemoize
RESCHEDULE_DELAY = 3.hours
- WORKER = 'PopulateMergeRequestAssigneesTable'.freeze
+ WORKER = 'PopulateMergeRequestAssigneesTable'
DeadJobsError = Class.new(StandardError)
def perform
diff --git a/lib/gitlab/background_migration/prepare_untracked_uploads.rb b/lib/gitlab/background_migration/prepare_untracked_uploads.rb
index 2ac51dd7b55..3d943205783 100644
--- a/lib/gitlab/background_migration/prepare_untracked_uploads.rb
+++ b/lib/gitlab/background_migration/prepare_untracked_uploads.rb
@@ -10,15 +10,15 @@ module Gitlab
include ::Gitlab::Utils::StrongMemoize
FIND_BATCH_SIZE = 500
- RELATIVE_UPLOAD_DIR = "uploads".freeze
+ RELATIVE_UPLOAD_DIR = "uploads"
ABSOLUTE_UPLOAD_DIR = File.join(
Gitlab.config.uploads.storage_path,
RELATIVE_UPLOAD_DIR
)
- FOLLOW_UP_MIGRATION = 'PopulateUntrackedUploads'.freeze
+ FOLLOW_UP_MIGRATION = 'PopulateUntrackedUploads'
START_WITH_ROOT_REGEX = %r{\A#{Gitlab.config.uploads.storage_path}/}.freeze
- EXCLUDED_HASHED_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/@hashed/*".freeze
- EXCLUDED_TMP_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/tmp/*".freeze
+ EXCLUDED_HASHED_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/@hashed/*"
+ EXCLUDED_TMP_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/tmp/*"
# This class is used to iterate over batches of
# `untracked_files_for_uploads` rows.
diff --git a/lib/gitlab/bitbucket_server_import/importer.rb b/lib/gitlab/bitbucket_server_import/importer.rb
index ff2694abd5e..93c6fdcf69c 100644
--- a/lib/gitlab/bitbucket_server_import/importer.rb
+++ b/lib/gitlab/bitbucket_server_import/importer.rb
@@ -7,7 +7,7 @@ module Gitlab
attr_reader :project, :project_key, :repository_slug, :client, :errors, :users
attr_accessor :logger
- REMOTE_NAME = 'bitbucket_server'.freeze
+ REMOTE_NAME = 'bitbucket_server'
BATCH_SIZE = 100
TempBranch = Struct.new(:name, :sha)
diff --git a/lib/gitlab/checks/lfs_check.rb b/lib/gitlab/checks/lfs_check.rb
index 67a65d61441..7b013567a03 100644
--- a/lib/gitlab/checks/lfs_check.rb
+++ b/lib/gitlab/checks/lfs_check.rb
@@ -3,8 +3,8 @@
module Gitlab
module Checks
class LfsCheck < BaseChecker
- LOG_MESSAGE = "Scanning repository for blobs stored in LFS and verifying their files have been uploaded to GitLab...".freeze
- ERROR_MESSAGE = 'LFS objects are missing. Ensure LFS is properly set up or try a manual "git lfs push --all".'.freeze
+ LOG_MESSAGE = 'Scanning repository for blobs stored in LFS and verifying their files have been uploaded to GitLab...'
+ ERROR_MESSAGE = 'LFS objects are missing. Ensure LFS is properly set up or try a manual "git lfs push --all".'
def validate!
return unless Feature.enabled?(:lfs_check, default_enabled: true)
diff --git a/lib/gitlab/checks/project_created.rb b/lib/gitlab/checks/project_created.rb
index 0058a402a62..362562068e9 100644
--- a/lib/gitlab/checks/project_created.rb
+++ b/lib/gitlab/checks/project_created.rb
@@ -3,7 +3,7 @@
module Gitlab
module Checks
class ProjectCreated < PostPushMessage
- PROJECT_CREATED = "project_created".freeze
+ PROJECT_CREATED = "project_created"
def message
<<~MESSAGE
diff --git a/lib/gitlab/checks/project_moved.rb b/lib/gitlab/checks/project_moved.rb
index cb3b7acaaad..6f04fddc6c4 100644
--- a/lib/gitlab/checks/project_moved.rb
+++ b/lib/gitlab/checks/project_moved.rb
@@ -3,7 +3,7 @@
module Gitlab
module Checks
class ProjectMoved < PostPushMessage
- REDIRECT_NAMESPACE = "redirect_namespace".freeze
+ REDIRECT_NAMESPACE = "redirect_namespace"
def initialize(project, user, protocol, redirected_path)
@redirected_path = redirected_path
diff --git a/lib/gitlab/ci/build/port.rb b/lib/gitlab/ci/build/port.rb
index 6c4656ffea2..017b31b4f77 100644
--- a/lib/gitlab/ci/build/port.rb
+++ b/lib/gitlab/ci/build/port.rb
@@ -4,8 +4,8 @@ module Gitlab
module Ci
module Build
class Port
- DEFAULT_PORT_NAME = 'default_port'.freeze
- DEFAULT_PORT_PROTOCOL = 'http'.freeze
+ DEFAULT_PORT_NAME = 'default_port'
+ DEFAULT_PORT_PROTOCOL = 'http'
attr_reader :number, :protocol, :name
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/build/step.rb b/lib/gitlab/ci/build/step.rb
index 7fcabc035ac..48111ae5717 100644
--- a/lib/gitlab/ci/build/step.rb
+++ b/lib/gitlab/ci/build/step.rb
@@ -4,9 +4,9 @@ module Gitlab
module Ci
module Build
class Step
- WHEN_ON_FAILURE = 'on_failure'.freeze
- WHEN_ON_SUCCESS = 'on_success'.freeze
- WHEN_ALWAYS = 'always'.freeze
+ WHEN_ON_FAILURE = 'on_failure'
+ WHEN_ON_SUCCESS = 'on_success'
+ WHEN_ALWAYS = 'always'
attr_reader :name
attr_accessor :script, :timeout, :when, :allow_failure
diff --git a/lib/gitlab/ci/config/entry/cache.rb b/lib/gitlab/ci/config/entry/cache.rb
index 7b94af24c09..ef07c319ce4 100644
--- a/lib/gitlab/ci/config/entry/cache.rb
+++ b/lib/gitlab/ci/config/entry/cache.rb
@@ -12,7 +12,7 @@ module Gitlab
include ::Gitlab::Config::Entry::Attributable
ALLOWED_KEYS = %i[key untracked paths policy].freeze
- DEFAULT_POLICY = 'pull-push'.freeze
+ DEFAULT_POLICY = 'pull-push'
validations do
validates :config, allowed_keys: ALLOWED_KEYS
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index 6e11c582750..3009c7e8329 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -15,7 +15,7 @@ module Gitlab
ALLOWED_KEYS = %i[tags script only except rules type image services
allow_failure type stage when start_in artifacts cache
dependencies needs before_script after_script variables
- environment coverage retry parallel extends].freeze
+ environment coverage retry parallel extends interruptible].freeze
REQUIRED_BY_NEEDS = %i[stage].freeze
@@ -37,6 +37,7 @@ module Gitlab
with_options allow_nil: true do
validates :tags, array_of_strings: true
validates :allow_failure, boolean: true
+ validates :interruptible, boolean: true
validates :parallel, numericality: { only_integer: true,
greater_than_or_equal_to: 2,
less_than_or_equal_to: 50 }
@@ -122,10 +123,11 @@ module Gitlab
helpers :before_script, :script, :stage, :type, :after_script,
:cache, :image, :services, :only, :except, :variables,
:artifacts, :environment, :coverage, :retry,
- :parallel, :needs
+ :parallel, :needs, :interruptible
attributes :script, :tags, :allow_failure, :when, :dependencies,
- :needs, :retry, :parallel, :extends, :start_in, :rules
+ :needs, :retry, :parallel, :extends, :start_in, :rules,
+ :interruptible
def self.matching?(name, config)
!name.to_s.start_with?('.') &&
@@ -207,6 +209,7 @@ module Gitlab
coverage: coverage_defined? ? coverage_value : nil,
retry: retry_defined? ? retry_value : nil,
parallel: parallel_defined? ? parallel_value.to_i : nil,
+ interruptible: interruptible_defined? ? interruptible_value : nil,
artifacts: artifacts_value,
after_script: after_script_value,
ignore: ignored?,
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/config/external/file/template.rb b/lib/gitlab/ci/config/external/file/template.rb
index 54f4cf74c4d..db56f6a9b00 100644
--- a/lib/gitlab/ci/config/external/file/template.rb
+++ b/lib/gitlab/ci/config/external/file/template.rb
@@ -8,7 +8,7 @@ module Gitlab
class Template < Base
attr_reader :location, :project
- SUFFIX = '.gitlab-ci.yml'.freeze
+ SUFFIX = '.gitlab-ci.yml'
def initialize(params, context)
@location = params[:template]
diff --git a/lib/gitlab/ci/cron_parser.rb b/lib/gitlab/ci/cron_parser.rb
index 94f4a4e36c9..1d7e7ea0f9a 100644
--- a/lib/gitlab/ci/cron_parser.rb
+++ b/lib/gitlab/ci/cron_parser.rb
@@ -3,8 +3,8 @@
module Gitlab
module Ci
class CronParser
- VALID_SYNTAX_SAMPLE_TIME_ZONE = 'UTC'.freeze
- VALID_SYNTAX_SAMPLE_CRON = '* * * * *'.freeze
+ VALID_SYNTAX_SAMPLE_TIME_ZONE = 'UTC'
+ VALID_SYNTAX_SAMPLE_CRON = '* * * * *'
def initialize(cron, cron_timezone = 'UTC')
@cron = cron
diff --git a/lib/gitlab/ci/pipeline/chain/build.rb b/lib/gitlab/ci/pipeline/chain/build.rb
index 164a4634d84..899df81ea5c 100644
--- a/lib/gitlab/ci/pipeline/chain/build.rb
+++ b/lib/gitlab/ci/pipeline/chain/build.rb
@@ -19,6 +19,7 @@ module Gitlab
user: @command.current_user,
pipeline_schedule: @command.schedule,
merge_request: @command.merge_request,
+ external_pull_request: @command.external_pull_request,
variables_attributes: Array(@command.variables_attributes)
)
diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb
index afad391e8e0..58f89a6be5e 100644
--- a/lib/gitlab/ci/pipeline/chain/command.rb
+++ b/lib/gitlab/ci/pipeline/chain/command.rb
@@ -7,7 +7,7 @@ module Gitlab
Command = Struct.new(
:source, :project, :current_user,
:origin_ref, :checkout_sha, :after_sha, :before_sha, :source_sha, :target_sha,
- :trigger_request, :schedule, :merge_request,
+ :trigger_request, :schedule, :merge_request, :external_pull_request,
:ignore_skip_ci, :save_incompleted,
:seeds_block, :variables_attributes, :push_options,
:chat_data, :allow_mirror_update
diff --git a/lib/gitlab/ci/pipeline/chain/helpers.rb b/lib/gitlab/ci/pipeline/chain/helpers.rb
index 6bb3a75291b..8ccb1066575 100644
--- a/lib/gitlab/ci/pipeline/chain/helpers.rb
+++ b/lib/gitlab/ci/pipeline/chain/helpers.rb
@@ -5,7 +5,12 @@ module Gitlab
module Pipeline
module Chain
module Helpers
- def error(message)
+ def error(message, config_error: false)
+ if config_error && command.save_incompleted
+ pipeline.yaml_errors = message
+ pipeline.drop!(:config_error)
+ end
+
pipeline.errors.add(:base, message)
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/pipeline/chain/populate.rb b/lib/gitlab/ci/pipeline/chain/populate.rb
index 65029f5ce7f..13eca5a9d28 100644
--- a/lib/gitlab/ci/pipeline/chain/populate.rb
+++ b/lib/gitlab/ci/pipeline/chain/populate.rb
@@ -26,7 +26,7 @@ module Gitlab
# Gather all runtime build/stage errors
#
if seeds_errors = pipeline.stage_seeds.flat_map(&:errors).compact.presence
- return error(seeds_errors.join("\n"))
+ return error(seeds_errors.join("\n"), config_error: true)
end
##
diff --git a/lib/gitlab/ci/reports/test_case.rb b/lib/gitlab/ci/reports/test_case.rb
index 292e273a03a..fdeaad698b9 100644
--- a/lib/gitlab/ci/reports/test_case.rb
+++ b/lib/gitlab/ci/reports/test_case.rb
@@ -4,10 +4,10 @@ module Gitlab
module Ci
module Reports
class TestCase
- STATUS_SUCCESS = 'success'.freeze
- STATUS_FAILED = 'failed'.freeze
- STATUS_SKIPPED = 'skipped'.freeze
- STATUS_ERROR = 'error'.freeze
+ STATUS_SUCCESS = 'success'
+ STATUS_FAILED = 'failed'
+ STATUS_SKIPPED = 'skipped'
+ STATUS_ERROR = 'error'
STATUS_TYPES = [STATUS_SUCCESS, STATUS_FAILED, STATUS_SKIPPED, STATUS_ERROR].freeze
attr_reader :name, :classname, :execution_time, :status, :file, :system_output, :stack_trace, :key
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index 5c1c0c142e5..f704266b73d 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -54,7 +54,7 @@ variables:
ROLLOUT_RESOURCE_TYPE: deployment
- DOCKER_TLS_CERTDIR: "" # https://gitlab.com/gitlab-org/gitlab-runner/issues/4501
+ DOCKER_TLS_CERTDIR: "" # https://gitlab.com/gitlab-org/gitlab-runner/issues/4501
stages:
- build
@@ -73,16 +73,16 @@ stages:
- cleanup
include:
- - template: Jobs/Build.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
- - template: Jobs/Test.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Test.gitlab-ci.yml
- - template: Jobs/Code-Quality.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
- - template: Jobs/Deploy.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
- - template: Jobs/Browser-Performance-Testing.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml
- - template: Security/DAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
- - template: Security/Container-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
- - template: Security/Dependency-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
- - template: Security/License-Management.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml
- - template: Security/SAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
+ - template: Jobs/Build.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
+ - template: Jobs/Test.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Test.gitlab-ci.yml
+ - template: Jobs/Code-Quality.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
+ - template: Jobs/Deploy.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
+ - template: Jobs/Browser-Performance-Testing.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml
+ - template: Security/DAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
+ - template: Security/Container-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
+ - template: Security/Dependency-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
+ - template: Security/License-Management.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml
+ - template: Security/SAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
# Override DAST job to exclude master branch
dast:
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/ci/yaml_processor.rb b/lib/gitlab/ci/yaml_processor.rb
index 2e1eab270ff..501d91fa9ad 100644
--- a/lib/gitlab/ci/yaml_processor.rb
+++ b/lib/gitlab/ci/yaml_processor.rb
@@ -41,6 +41,7 @@ module Gitlab
coverage_regex: job[:coverage],
yaml_variables: yaml_variables(name),
needs_attributes: job[:needs]&.map { |need| { name: need } },
+ interruptible: job[:interruptible],
options: {
image: job[:image],
services: job[:services],
diff --git a/lib/gitlab/cleanup/project_upload_file_finder.rb b/lib/gitlab/cleanup/project_upload_file_finder.rb
index 5aace564c2d..3d35d474f5d 100644
--- a/lib/gitlab/cleanup/project_upload_file_finder.rb
+++ b/lib/gitlab/cleanup/project_upload_file_finder.rb
@@ -5,9 +5,9 @@ module Gitlab
class ProjectUploadFileFinder
FIND_BATCH_SIZE = 500
ABSOLUTE_UPLOAD_DIR = FileUploader.root.freeze
- EXCLUDED_SYSTEM_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/-/*".freeze
- EXCLUDED_HASHED_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/@hashed/*".freeze
- EXCLUDED_TMP_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/tmp/*".freeze
+ EXCLUDED_SYSTEM_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/-/*"
+ EXCLUDED_HASHED_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/@hashed/*"
+ EXCLUDED_TMP_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/tmp/*"
# Paths are relative to the upload directory
def each_file_batch(batch_size: FIND_BATCH_SIZE, &block)
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/danger/helper.rb b/lib/gitlab/danger/helper.rb
index 332ca8bf9b8..e2911b4e6c8 100644
--- a/lib/gitlab/danger/helper.rb
+++ b/lib/gitlab/danger/helper.rb
@@ -38,8 +38,17 @@ module Gitlab
ENV['CI_PROJECT_NAME'] == 'gitlab-ee' || File.exist?('../../CHANGELOG-EE.md')
end
+ def gitlab_helper
+ # Unfortunately the following does not work:
+ # - respond_to?(:gitlab)
+ # - respond_to?(:gitlab, true)
+ gitlab
+ rescue NoMethodError
+ nil
+ end
+
def release_automation?
- gitlab.mr_author == RELEASE_TOOLS_BOT
+ gitlab_helper&.mr_author == RELEASE_TOOLS_BOT
end
def project_name
@@ -83,7 +92,8 @@ module Gitlab
docs: "~Documentation", # Docs are reviewed along DevOps stages, so don't need roulette for now.
none: "",
qa: "~QA",
- test: "~test for `spec/features/*`"
+ test: "~test for `spec/features/*`",
+ engineering_productivity: "Engineering Productivity for CI config review"
}.freeze
CATEGORIES = {
%r{\Adoc/} => :none, # To reinstate roulette for documentation, set to `:docs`.
@@ -110,7 +120,8 @@ module Gitlab
karma\.config\.js |
webpack\.config\.js |
package\.json |
- yarn\.lock
+ yarn\.lock |
+ \.gitlab/ci/frontend\.gitlab-ci\.yml
)\z}x => :frontend,
%r{\A(ee/)?db/(?!fixtures)[^/]+} => :database,
@@ -124,8 +135,10 @@ module Gitlab
%r{\A(ee/)?spec/(?!javascripts|frontend)[^/]+} => :backend,
%r{\A(ee/)?vendor/(?!assets)[^/]+} => :backend,
%r{\A(ee/)?vendor/(languages\.yml|licenses\.csv)\z} => :backend,
- %r{\A(Dangerfile|Gemfile|Gemfile.lock|Procfile|Rakefile|\.gitlab-ci\.yml)\z} => :backend,
+ %r{\A(\.gitlab-ci\.yml\z|\.gitlab\/ci)} => :engineering_productivity,
+ %r{\A(Dangerfile|Gemfile|Gemfile.lock|Procfile|Rakefile)\z} => :backend,
%r{\A[A-Z_]+_VERSION\z} => :backend,
+ %r{\A\.rubocop(_todo)?\.yml\z} => :backend,
%r{\A(ee/)?qa/} => :qa,
diff --git a/lib/gitlab/danger/teammate.rb b/lib/gitlab/danger/teammate.rb
index 74cbcc11255..4ad66f61c2b 100644
--- a/lib/gitlab/danger/teammate.rb
+++ b/lib/gitlab/danger/teammate.rb
@@ -41,7 +41,9 @@ module Gitlab
when :test
area = role[/Test Automation Engineer(?:.*?, (\w+))/, 1]
- area && labels.any?(area) if kind == :reviewer
+ area && labels.any?("devops::#{area.downcase}") if kind == :reviewer
+ when :engineering_productivity
+ role[/Engineering Productivity/] if kind == :reviewer
else
capabilities(project).include?("#{kind} #{category}")
end
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index cbdff0ab060..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
@@ -195,13 +199,14 @@ module Gitlab
# pool_size - The size of the DB pool.
# host - An optional host name to use instead of the default one.
- def self.create_connection_pool(pool_size, host = nil)
+ def self.create_connection_pool(pool_size, host = nil, port = nil)
# See activerecord-4.2.7.1/lib/active_record/connection_adapters/connection_specification.rb
env = Rails.env
original_config = ActiveRecord::Base.configurations
env_config = original_config[env].merge('pool' => pool_size)
env_config['host'] = host if host
+ env_config['port'] = port if port
config = original_config.merge(env => env_config)
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 9bba4f6ce1e..5a42952796c 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -459,27 +459,27 @@ module Gitlab
check_trigger_permissions!(table)
- old_col = column_for(table, old)
- new_type = type || old_col.type
-
- add_column(table, new, new_type,
- limit: old_col.limit,
- precision: old_col.precision,
- scale: old_col.scale)
-
- # We set the default value _after_ adding the column so we don't end up
- # updating any existing data with the default value. This isn't
- # necessary since we copy over old values further down.
- change_column_default(table, new, old_col.default) if old_col.default
+ create_column_from(table, old, new, type: type)
install_rename_triggers(table, old, new)
+ end
- update_column_in_batches(table, new, Arel::Table.new(table)[old])
+ # Reverses operations performed by rename_column_concurrently.
+ #
+ # This method takes care of removing previously installed triggers as well
+ # as removing the new column.
+ #
+ # table - The name of the database table.
+ # old - The name of the old column.
+ # new - The name of the new column.
+ def undo_rename_column_concurrently(table, old, new)
+ trigger_name = rename_trigger_name(table, old, new)
- change_column_null(table, new, false) unless old_col.null
+ check_trigger_permissions!(table)
- copy_indexes(table, old, new)
- copy_foreign_keys(table, old, new)
+ remove_rename_triggers_for_postgresql(table, trigger_name)
+
+ remove_column(table, new)
end
# Installs triggers in a table that keep a new column in sync with an old
@@ -547,6 +547,30 @@ module Gitlab
remove_column(table, old)
end
+ # Reverses the operations performed by cleanup_concurrent_column_rename.
+ #
+ # This method adds back the old_column removed
+ # by cleanup_concurrent_column_rename.
+ # It also adds back the (old_column > new_column) trigger that is removed
+ # by cleanup_concurrent_column_rename.
+ #
+ # table - The name of the database table containing the column.
+ # old - The old column name.
+ # new - The new column name.
+ # type - The type of the old column. If no type is given the new column's
+ # type is used.
+ def undo_cleanup_concurrent_column_rename(table, old, new, type: nil)
+ if transaction_open?
+ raise 'undo_cleanup_concurrent_column_rename can not be run inside a transaction'
+ end
+
+ check_trigger_permissions!(table)
+
+ create_column_from(table, new, old, type: type)
+
+ install_rename_triggers(table, old, new)
+ end
+
# Changes the column type of a table using a background migration.
#
# Because this method uses a background migration it's more suitable for
@@ -747,6 +771,11 @@ module Gitlab
EOF
execute <<-EOF.strip_heredoc
+ DROP TRIGGER IF EXISTS #{trigger}
+ ON #{table}
+ EOF
+
+ execute <<-EOF.strip_heredoc
CREATE TRIGGER #{trigger}
BEFORE INSERT OR UPDATE
ON #{table}
@@ -1032,6 +1061,28 @@ into similar problems in the future (e.g. when new tables are created).
private
+ def create_column_from(table, old, new, type: nil)
+ old_col = column_for(table, old)
+ new_type = type || old_col.type
+
+ add_column(table, new, new_type,
+ limit: old_col.limit,
+ precision: old_col.precision,
+ scale: old_col.scale)
+
+ # We set the default value _after_ adding the column so we don't end up
+ # updating any existing data with the default value. This isn't
+ # necessary since we copy over old values further down.
+ change_column_default(table, new, old_col.default) unless old_col.default.nil?
+
+ update_column_in_batches(table, new, Arel::Table.new(table)[old])
+
+ change_column_null(table, new, false) unless old_col.null
+
+ copy_indexes(table, old, new)
+ copy_foreign_keys(table, old, new)
+ end
+
def validate_timestamp_column_name!(column_name)
return if PERMITTED_TIMESTAMP_COLUMNS.member?(column_name)
diff --git a/lib/gitlab/database/sha_attribute.rb b/lib/gitlab/database/sha_attribute.rb
index ddbabc9098e..776e80701f1 100644
--- a/lib/gitlab/database/sha_attribute.rb
+++ b/lib/gitlab/database/sha_attribute.rb
@@ -12,7 +12,7 @@ module Gitlab
# using them as if they were stored as string values. This gives you the
# ease of use of string values, but without the storage overhead.
class ShaAttribute < BINARY_TYPE
- PACK_FORMAT = 'H*'.freeze
+ PACK_FORMAT = 'H*'
# Casts binary data to a SHA1 in hexadecimal.
def deserialize(value)
diff --git a/lib/gitlab/database_importers/self_monitoring/project/create_service.rb b/lib/gitlab/database_importers/self_monitoring/project/create_service.rb
new file mode 100644
index 00000000000..5422a8631a0
--- /dev/null
+++ b/lib/gitlab/database_importers/self_monitoring/project/create_service.rb
@@ -0,0 +1,261 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module DatabaseImporters
+ module SelfMonitoring
+ module Project
+ class CreateService < ::BaseService
+ include Stepable
+
+ STEPS_ALLOWED_TO_FAIL = [
+ :validate_application_settings, :validate_project_created, :validate_admins
+ ].freeze
+
+ VISIBILITY_LEVEL = Gitlab::VisibilityLevel::INTERNAL
+ PROJECT_NAME = 'GitLab Instance Administration'
+
+ steps :validate_application_settings,
+ :validate_project_created,
+ :validate_admins,
+ :create_group,
+ :create_project,
+ :save_project_id,
+ :add_group_members,
+ :add_to_whitelist,
+ :add_prometheus_manual_configuration
+
+ def initialize
+ super(nil)
+ end
+
+ def execute!
+ result = execute_steps
+
+ if result[:status] == :success
+ result
+ elsif STEPS_ALLOWED_TO_FAIL.include?(result[:failed_step])
+ success
+ else
+ raise StandardError, result[:message]
+ end
+ end
+
+ private
+
+ def validate_application_settings
+ return success if application_settings
+
+ 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')
+ error(_('Project already created'))
+ end
+
+ def validate_admins
+ unless instance_admins.any?
+ log_error('No active admin user found')
+ return error(_('No active admin user found'))
+ end
+
+ success
+ end
+
+ def create_group
+ if project_created?
+ log_info(_('Instance administrators group already exists'))
+ @group = application_settings.instance_administration_project.owner
+ return success(group: @group)
+ end
+
+ @group = ::Groups::CreateService.new(group_owner, create_group_params).execute
+
+ if @group.persisted?
+ success(group: @group)
+ else
+ error(_('Could not create group'))
+ end
+ end
+
+ def create_project
+ if project_created?
+ log_info('Instance administration project already exists')
+ @project = application_settings.instance_administration_project
+ return success(project: project)
+ end
+
+ @project = ::Projects::CreateService.new(group_owner, create_project_params).execute
+
+ if project.persisted?
+ success(project: project)
+ else
+ log_error("Could not create instance administration project. Errors: %{errors}" % { errors: project.errors.full_messages })
+ error(_('Could not create project'))
+ end
+ end
+
+ def save_project_id
+ return success if project_created?
+
+ result = application_settings.update(instance_administration_project_id: @project.id)
+
+ if result
+ success
+ else
+ 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
+
+ def add_group_members
+ members = @group.add_users(members_to_add, Gitlab::Access::MAINTAINER)
+ 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 })
+ error(_('Could not add admins as members'))
+ else
+ success
+ end
+ end
+
+ def add_to_whitelist
+ return success unless prometheus_enabled?
+ return success unless prometheus_listen_address.present?
+
+ uri = parse_url(internal_prometheus_listen_address_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
+
+ if result
+ # Expire the Gitlab::CurrentSettings cache after updating the whitelist.
+ # This happens automatically in an after_commit hook, but in migrations,
+ # the after_commit hook only runs at the end of the migration.
+ 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 })
+ error(_('Could not add prometheus URL to whitelist'))
+ end
+ end
+
+ def add_prometheus_manual_configuration
+ return success unless prometheus_enabled?
+ return success unless prometheus_listen_address.present?
+
+ 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 })
+ return error(_('Could not save prometheus manual configuration'))
+ end
+
+ success
+ end
+
+ def application_settings
+ @application_settings ||= ApplicationSetting.current_without_cache
+ end
+
+ def project_created?
+ application_settings.instance_administration_project.present?
+ end
+
+ def parse_url(uri_string)
+ Addressable::URI.parse(uri_string)
+ rescue Addressable::URI::InvalidURIError, TypeError
+ end
+
+ def prometheus_enabled?
+ Gitlab.config.prometheus.enable if Gitlab.config.prometheus
+ rescue Settingslogic::MissingSetting
+ log_error('prometheus.enable is not present in config/gitlab.yml')
+
+ false
+ end
+
+ def prometheus_listen_address
+ Gitlab.config.prometheus.listen_address.to_s if Gitlab.config.prometheus
+ rescue Settingslogic::MissingSetting
+ log_error('Prometheus listen_address is not present in config/gitlab.yml')
+
+ nil
+ end
+
+ def instance_admins
+ @instance_admins ||= User.admins.active
+ end
+
+ def group_owner
+ instance_admins.first
+ end
+
+ def members_to_add
+ # Exclude admins who are already members of group because
+ # `@group.add_users(users)` returns an error if the users parameter contains
+ # users who are already members of the group.
+ instance_admins - @group.members.collect(&:user)
+ end
+
+ def create_group_params
+ {
+ name: 'GitLab Instance Administrators',
+ path: "gitlab-instance-administrators-#{SecureRandom.hex(4)}",
+ visibility_level: VISIBILITY_LEVEL
+ }
+ end
+
+ def docs_path
+ Rails.application.routes.url_helpers.help_page_path(
+ 'administration/monitoring/gitlab_instance_administration_project/index'
+ )
+ end
+
+ def create_project_params
+ {
+ initialize_with_readme: true,
+ visibility_level: VISIBILITY_LEVEL,
+ name: PROJECT_NAME,
+ description: "This project is automatically generated and will be used to help monitor this GitLab instance. [More information](#{docs_path})",
+ namespace_id: @group.id
+ }
+ end
+
+ def internal_prometheus_listen_address_uri
+ 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
+
+ def prometheus_service_attributes
+ {
+ api_url: internal_prometheus_listen_address_uri,
+ manual_configuration: true,
+ active: true
+ }
+ end
+ end
+ end
+ end
+ 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/downtime_check/message.rb b/lib/gitlab/downtime_check/message.rb
index ec38bd769a3..5debb754943 100644
--- a/lib/gitlab/downtime_check/message.rb
+++ b/lib/gitlab/downtime_check/message.rb
@@ -5,8 +5,8 @@ module Gitlab
class Message
attr_reader :path, :offline
- OFFLINE = "\e[31moffline\e[0m".freeze
- ONLINE = "\e[32monline\e[0m".freeze
+ OFFLINE = "\e[31moffline\e[0m"
+ ONLINE = "\e[32monline\e[0m"
# path - The file path of the migration.
# offline - When set to `true` the migration will require downtime.
diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb
index 86e532766b1..20c31e06905 100644
--- a/lib/gitlab/ee_compat_check.rb
+++ b/lib/gitlab/ee_compat_check.rb
@@ -1,11 +1,12 @@
+# coding: utf-8
# frozen_string_literal: true
# rubocop: disable Rails/Output
module Gitlab
# Checks if a set of migrations requires downtime or not.
class EeCompatCheck
- CANONICAL_CE_PROJECT_URL = 'https://gitlab.com/gitlab-org/gitlab-ce'.freeze
- CANONICAL_EE_REPO_URL = 'https://gitlab.com/gitlab-org/gitlab-ee.git'.freeze
+ CANONICAL_CE_PROJECT_URL = 'https://gitlab.com/gitlab-org/gitlab-ce'
+ CANONICAL_EE_REPO_URL = 'https://gitlab.com/gitlab-org/gitlab-ee.git'
CHECK_DIR = Rails.root.join('ee_compat_check')
IGNORED_FILES_REGEX = /VERSION|CHANGELOG\.md|doc\/.+/i.freeze
PLEASE_READ_THIS_BANNER = %Q{
diff --git a/lib/gitlab/etag_caching/store.rb b/lib/gitlab/etag_caching/store.rb
index 2395e7be026..1d2f0d7bbf4 100644
--- a/lib/gitlab/etag_caching/store.rb
+++ b/lib/gitlab/etag_caching/store.rb
@@ -4,7 +4,7 @@ module Gitlab
module EtagCaching
class Store
EXPIRY_TIME = 20.minutes
- SHARED_STATE_NAMESPACE = 'etag:'.freeze
+ SHARED_STATE_NAMESPACE = 'etag:'
def get(key)
Gitlab::Redis::SharedState.with { |redis| redis.get(redis_shared_state_key(key)) }
diff --git a/lib/gitlab/fogbugz_import/project_creator.rb b/lib/gitlab/fogbugz_import/project_creator.rb
index 3c71031a8d9..841f9de8d4a 100644
--- a/lib/gitlab/fogbugz_import/project_creator.rb
+++ b/lib/gitlab/fogbugz_import/project_creator.rb
@@ -20,7 +20,7 @@ module Gitlab
path: repo.path,
namespace: namespace,
creator: current_user,
- visibility_level: Gitlab::VisibilityLevel::INTERNAL,
+ visibility_level: Gitlab::VisibilityLevel::PRIVATE,
import_type: 'fogbugz',
import_source: repo.name,
import_url: Project::UNKNOWN_IMPORT_URL,
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index df9f33baec2..8d13c74dca2 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -7,11 +7,11 @@ module Gitlab
# The ID of empty tree.
# See http://stackoverflow.com/a/40884093/1856239 and
# https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012
- EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze
+ EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'
BLANK_SHA = ('0' * 40).freeze
COMMIT_ID = /\A[0-9a-f]{40}\z/.freeze
- TAG_REF_PREFIX = "refs/tags/".freeze
- BRANCH_REF_PREFIX = "refs/heads/".freeze
+ TAG_REF_PREFIX = "refs/tags/"
+ BRANCH_REF_PREFIX = "refs/heads/"
BaseError = Class.new(StandardError)
CommandError = Class.new(BaseError)
diff --git a/lib/gitlab/git/diff_collection.rb b/lib/gitlab/git/diff_collection.rb
index 5c70cb6c66c..cb9154cb1e8 100644
--- a/lib/gitlab/git/diff_collection.rb
+++ b/lib/gitlab/git/diff_collection.rb
@@ -81,6 +81,12 @@ module Gitlab
end
end
+ def line_count
+ populate!
+
+ @line_count
+ end
+
def decorate!
collection = each_with_index do |element, i|
@array[i] = yield(element)
diff --git a/lib/gitlab/git/lfs_pointer_file.rb b/lib/gitlab/git/lfs_pointer_file.rb
index b7019a221ac..efd84e30ad9 100644
--- a/lib/gitlab/git/lfs_pointer_file.rb
+++ b/lib/gitlab/git/lfs_pointer_file.rb
@@ -3,8 +3,8 @@
module Gitlab
module Git
class LfsPointerFile
- VERSION = "https://git-lfs.github.com/spec/v1".freeze
- VERSION_LINE = "version #{VERSION}".freeze
+ VERSION = "https://git-lfs.github.com/spec/v1"
+ VERSION_LINE = "version #{VERSION}"
def initialize(data)
@data = data
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 27032602828..4ea618f063b 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -15,9 +15,9 @@ module Gitlab
SEARCH_CONTEXT_LINES = 3
REV_LIST_COMMIT_LIMIT = 2_000
- GITALY_INTERNAL_URL = 'ssh://gitaly/internal.git'.freeze
+ GITALY_INTERNAL_URL = 'ssh://gitaly/internal.git'
GITLAB_PROJECTS_TIMEOUT = Gitlab.config.gitlab_shell.git_timeout
- EMPTY_REPOSITORY_CHECKSUM = '0000000000000000000000000000000000000000'.freeze
+ EMPTY_REPOSITORY_CHECKSUM = '0000000000000000000000000000000000000000'
NoRepository = Class.new(StandardError)
InvalidRepository = Class.new(StandardError)
diff --git a/lib/gitlab/git/util.rb b/lib/gitlab/git/util.rb
index 03c2c1367b0..cf8571bf917 100644
--- a/lib/gitlab/git/util.rb
+++ b/lib/gitlab/git/util.rb
@@ -5,7 +5,7 @@
module Gitlab
module Git
module Util
- LINE_SEP = "\n".freeze
+ LINE_SEP = "\n"
def self.count_lines(string)
case string[-1]
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index e6cbfb00f60..2ac99b1ff02 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -50,7 +50,7 @@ module Gitlab
def self.interceptors
return [] unless Labkit::Tracing.enabled?
- [Labkit::Tracing::GRPCInterceptor.instance]
+ [Labkit::Tracing::GRPC::ClientInterceptor.instance]
end
private_class_method :interceptors
@@ -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
@@ -406,7 +402,8 @@ module Gitlab
def self.filesystem_id(storage)
response = Gitlab::GitalyClient::ServerService.new(storage).info
storage_status = response.storage_statuses.find { |status| status.storage_name == storage }
- storage_status.filesystem_id
+
+ storage_status&.filesystem_id
end
def self.filesystem_id_from_disk(storage)
diff --git a/lib/gitlab/github_import/issuable_finder.rb b/lib/gitlab/github_import/issuable_finder.rb
index 211915f1d87..c81603a1aa9 100644
--- a/lib/gitlab/github_import/issuable_finder.rb
+++ b/lib/gitlab/github_import/issuable_finder.rb
@@ -10,7 +10,7 @@ module Gitlab
attr_reader :project, :object
# The base cache key to use for storing/retrieving issuable IDs.
- CACHE_KEY = 'github-import/issuable-finder/%{project}/%{type}/%{iid}'.freeze
+ CACHE_KEY = 'github-import/issuable-finder/%{project}/%{type}/%{iid}'
# project - An instance of `Project`.
# object - The object to look up or set a database ID for.
diff --git a/lib/gitlab/github_import/label_finder.rb b/lib/gitlab/github_import/label_finder.rb
index d2479a8f565..cad39e48e43 100644
--- a/lib/gitlab/github_import/label_finder.rb
+++ b/lib/gitlab/github_import/label_finder.rb
@@ -6,7 +6,7 @@ module Gitlab
attr_reader :project
# The base cache key to use for storing/retrieving label IDs.
- CACHE_KEY = 'github-import/label-finder/%{project}/%{name}'.freeze
+ CACHE_KEY = 'github-import/label-finder/%{project}/%{name}'
# project - An instance of `Project`.
def initialize(project)
diff --git a/lib/gitlab/github_import/milestone_finder.rb b/lib/gitlab/github_import/milestone_finder.rb
index 5625730e796..a157a1e1ff5 100644
--- a/lib/gitlab/github_import/milestone_finder.rb
+++ b/lib/gitlab/github_import/milestone_finder.rb
@@ -6,7 +6,7 @@ module Gitlab
attr_reader :project
# The base cache key to use for storing/retrieving milestone IDs.
- CACHE_KEY = 'github-import/milestone-finder/%{project}/%{iid}'.freeze
+ CACHE_KEY = 'github-import/milestone-finder/%{project}/%{iid}'
# project - An instance of `Project`
def initialize(project)
diff --git a/lib/gitlab/github_import/page_counter.rb b/lib/gitlab/github_import/page_counter.rb
index c3db2d0b469..a3e7b3c1afc 100644
--- a/lib/gitlab/github_import/page_counter.rb
+++ b/lib/gitlab/github_import/page_counter.rb
@@ -9,7 +9,7 @@ module Gitlab
attr_reader :cache_key
# The base cache key to use for storing the last page number.
- CACHE_KEY = 'github-importer/page-counter/%{project}/%{collection}'.freeze
+ CACHE_KEY = 'github-importer/page-counter/%{project}/%{collection}'
def initialize(project, collection)
@cache_key = CACHE_KEY % { project: project.id, collection: collection }
diff --git a/lib/gitlab/github_import/parallel_scheduling.rb b/lib/gitlab/github_import/parallel_scheduling.rb
index d4d1357f5a3..849a66d47ed 100644
--- a/lib/gitlab/github_import/parallel_scheduling.rb
+++ b/lib/gitlab/github_import/parallel_scheduling.rb
@@ -7,7 +7,7 @@ module Gitlab
# The base cache key to use for tracking already imported objects.
ALREADY_IMPORTED_CACHE_KEY =
- 'github-importer/already-imported/%{project}/%{collection}'.freeze
+ 'github-importer/already-imported/%{project}/%{collection}'
# project - An instance of `Project`.
# client - An instance of `Gitlab::GithubImport::Client`.
diff --git a/lib/gitlab/github_import/user_finder.rb b/lib/gitlab/github_import/user_finder.rb
index 30283f147ef..51a532437bd 100644
--- a/lib/gitlab/github_import/user_finder.rb
+++ b/lib/gitlab/github_import/user_finder.rb
@@ -16,17 +16,17 @@ module Gitlab
# The base cache key to use for caching user IDs for a given GitHub user
# ID.
- ID_CACHE_KEY = 'github-import/user-finder/user-id/%s'.freeze
+ ID_CACHE_KEY = 'github-import/user-finder/user-id/%s'
# The base cache key to use for caching user IDs for a given GitHub email
# address.
ID_FOR_EMAIL_CACHE_KEY =
- 'github-import/user-finder/id-for-email/%s'.freeze
+ 'github-import/user-finder/id-for-email/%s'
# The base cache key to use for caching the Email addresses of GitHub
# usernames.
EMAIL_FOR_USERNAME_CACHE_KEY =
- 'github-import/user-finder/email-for-username/%s'.freeze
+ 'github-import/user-finder/email-for-username/%s'
# project - An instance of `Project`
# client - An instance of `Gitlab::GithubImport::Client`
diff --git a/lib/gitlab/graphql/authorize/authorize_field_service.rb b/lib/gitlab/graphql/authorize/authorize_field_service.rb
index 3b5dde2fde5..c7f430490d6 100644
--- a/lib/gitlab/graphql/authorize/authorize_field_service.rb
+++ b/lib/gitlab/graphql/authorize/authorize_field_service.rb
@@ -54,14 +54,14 @@ module Gitlab
# The field is a built-in/scalar type, or a list of scalars
# authorize using the parent's object
parent_typed_object.object
- elsif resolved_type.respond_to?(:object)
- # The field is a type representing a single object, we'll authorize
- # against the object directly
- resolved_type.object
elsif @field.connection? || resolved_type.is_a?(Array)
# The field is a connection or a list of non-built-in types, we'll
# authorize each element when rendering
nil
+ elsif resolved_type.respond_to?(:object)
+ # The field is a type representing a single object, we'll authorize
+ # against the object directly
+ resolved_type.object
else
# Resolved type is a single object that might not be loaded yet by
# the batchloader, we'll authorize that
@@ -74,9 +74,9 @@ module Gitlab
# Authorizing fields representing scalars, or a simple field with an object
resolved_type if allowed_access?(current_user, authorizing_object)
elsif @field.connection?
- # A connection with pagination, modify the visible nodes in on the
+ # A connection with pagination, modify the visible nodes on the
# connection type in place
- resolved_type.edge_nodes.to_a.keep_if { |node| allowed_access?(current_user, node) }
+ resolved_type.object.edge_nodes.to_a.keep_if { |node| allowed_access?(current_user, node) }
resolved_type
elsif resolved_type.is_a? Array
# A simple list of rendered types each object being an object to authorize
diff --git a/lib/gitlab/graphql/authorize/authorize_resource.rb b/lib/gitlab/graphql/authorize/authorize_resource.rb
index ef5caaf5b0e..6844367454f 100644
--- a/lib/gitlab/graphql/authorize/authorize_resource.rb
+++ b/lib/gitlab/graphql/authorize/authorize_resource.rb
@@ -29,19 +29,25 @@ module Gitlab
def authorized_find!(*args)
object = find_object(*args)
+ object = object.sync if object.respond_to?(:sync)
+
authorize!(object)
object
end
def authorize!(object)
- unless authorized?(object)
+ unless authorized_resource?(object)
raise Gitlab::Graphql::Errors::ResourceNotAvailable,
"The resource that you are attempting to access does not exist or you don't have permission to perform this action"
end
end
- def authorized?(object)
+ # this was named `#authorized?`, however it conflicts with the native
+ # graphql gem version
+ # TODO consider adopting the gem's built in authorization system
+ # https://gitlab.com/gitlab-org/gitlab-ee/issues/13984
+ def authorized_resource?(object)
# Sanity check. We don't want to accidentally allow a developer to authorize
# without first adding permissions to authorize against
if self.class.required_permissions.empty?
diff --git a/lib/gitlab/graphql/loaders/batch_lfs_oid_loader.rb b/lib/gitlab/graphql/loaders/batch_lfs_oid_loader.rb
index 8f34e58a771..67511c124e4 100644
--- a/lib/gitlab/graphql/loaders/batch_lfs_oid_loader.rb
+++ b/lib/gitlab/graphql/loaders/batch_lfs_oid_loader.rb
@@ -9,7 +9,7 @@ module Gitlab
end
def find
- BatchLoader.for(blob_id).batch(key: repository) do |blob_ids, loader, batch_args|
+ BatchLoader::GraphQL.for(blob_id).batch(key: repository) do |blob_ids, loader, batch_args|
Gitlab::Git::Blob.batch_lfs_pointers(batch_args[:key], blob_ids).each do |loaded_blob|
loader.call(loaded_blob.id, loaded_blob.lfs_oid)
end
diff --git a/lib/gitlab/graphql/loaders/batch_model_loader.rb b/lib/gitlab/graphql/loaders/batch_model_loader.rb
index 50d3293fcbb..164fe74148c 100644
--- a/lib/gitlab/graphql/loaders/batch_model_loader.rb
+++ b/lib/gitlab/graphql/loaders/batch_model_loader.rb
@@ -12,7 +12,7 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def find
- BatchLoader.for({ model: model_class, id: model_id.to_i }).batch do |loader_info, loader|
+ BatchLoader::GraphQL.for({ model: model_class, id: model_id.to_i }).batch do |loader_info, loader|
per_model = loader_info.group_by { |info| info[:model] }
per_model.each do |model, info|
ids = info.map { |i| i[:id] }
diff --git a/lib/gitlab/graphql/loaders/batch_project_statistics_loader.rb b/lib/gitlab/graphql/loaders/batch_project_statistics_loader.rb
index 5e151f4dbd7..449f4160a6c 100644
--- a/lib/gitlab/graphql/loaders/batch_project_statistics_loader.rb
+++ b/lib/gitlab/graphql/loaders/batch_project_statistics_loader.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def find
- BatchLoader.for(project_id).batch do |project_ids, loader|
+ BatchLoader::GraphQL.for(project_id).batch do |project_ids, loader|
ProjectStatistics.for_project_ids(project_ids).each do |statistics|
loader.call(statistics.project_id, statistics)
end
diff --git a/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader.rb b/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader.rb
index a0312366d66..366aa74d435 100644
--- a/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader.rb
+++ b/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def find
- BatchLoader.for(namespace_id).batch do |namespace_ids, loader|
+ BatchLoader::GraphQL.for(namespace_id).batch do |namespace_ids, loader|
Namespace::RootStorageStatistics.for_namespace_ids(namespace_ids).each do |statistics|
loader.call(statistics.namespace_id, statistics)
end
diff --git a/lib/gitlab/graphql/loaders/pipeline_for_sha_loader.rb b/lib/gitlab/graphql/loaders/pipeline_for_sha_loader.rb
index 81c5cabf451..70344392138 100644
--- a/lib/gitlab/graphql/loaders/pipeline_for_sha_loader.rb
+++ b/lib/gitlab/graphql/loaders/pipeline_for_sha_loader.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def find_last
- BatchLoader.for(sha).batch(key: project) do |shas, loader, args|
+ BatchLoader::GraphQL.for(sha).batch(key: project) do |shas, loader, args|
pipelines = args[:key].ci_pipelines.latest_for_shas(shas)
pipelines.each do |pipeline|
diff --git a/lib/gitlab/graphql/present/instrumentation.rb b/lib/gitlab/graphql/present/instrumentation.rb
index ab03c40c22d..941a4f434a1 100644
--- a/lib/gitlab/graphql/present/instrumentation.rb
+++ b/lib/gitlab/graphql/present/instrumentation.rb
@@ -23,7 +23,9 @@ module Gitlab
end
presenter = presented_in.presenter_class.new(object, **context.to_h)
- wrapped = presented_type.class.new(presenter, context)
+
+ # we have to use the new `authorized_new` method, as `new` is protected
+ wrapped = presented_type.class.authorized_new(presenter, context)
old_resolver.call(wrapped, args, context)
end
diff --git a/lib/gitlab/graphql/representation/submodule_tree_entry.rb b/lib/gitlab/graphql/representation/submodule_tree_entry.rb
index 65716dff75d..8d17cb9eecc 100644
--- a/lib/gitlab/graphql/representation/submodule_tree_entry.rb
+++ b/lib/gitlab/graphql/representation/submodule_tree_entry.rb
@@ -4,6 +4,8 @@ module Gitlab
module Graphql
module Representation
class SubmoduleTreeEntry < SimpleDelegator
+ include GlobalID::Identification
+
class << self
def decorate(submodules, tree)
repository = tree.repository
diff --git a/lib/gitlab/graphql/representation/tree_entry.rb b/lib/gitlab/graphql/representation/tree_entry.rb
index 7ea83db5876..7e347081a9b 100644
--- a/lib/gitlab/graphql/representation/tree_entry.rb
+++ b/lib/gitlab/graphql/representation/tree_entry.rb
@@ -4,6 +4,8 @@ module Gitlab
module Graphql
module Representation
class TreeEntry < SimpleDelegator
+ include GlobalID::Identification
+
class << self
def decorate(entries, repository)
return if entries.nil?
diff --git a/lib/gitlab/health_checks/gitaly_check.rb b/lib/gitlab/health_checks/gitaly_check.rb
index 898733fea5d..e560f87bf98 100644
--- a/lib/gitlab/health_checks/gitaly_check.rb
+++ b/lib/gitlab/health_checks/gitaly_check.rb
@@ -5,7 +5,7 @@ module Gitlab
class GitalyCheck
extend BaseAbstractCheck
- METRIC_PREFIX = 'gitaly_health_check'.freeze
+ METRIC_PREFIX = 'gitaly_health_check'
class << self
def readiness
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index bb46bd657e8..d08848a65a8 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -7,7 +7,7 @@ module Gitlab
# For every version update the version history in these docs must be kept up to date:
# - development/import_export.md
# - user/project/settings/import_export.md
- VERSION = '0.2.4'.freeze
+ VERSION = '0.2.4'
FILENAME_LIMIT = 50
def export_path(relative_path:)
diff --git a/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb b/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb
index d39b6fe5955..c5fb39b7b52 100644
--- a/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb
+++ b/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb
@@ -10,7 +10,7 @@ module Gitlab
StrategyError = Class.new(StandardError)
- AFTER_EXPORT_LOCK_FILE_NAME = '.after_export_action'.freeze
+ AFTER_EXPORT_LOCK_FILE_NAME = '.after_export_action'
private
diff --git a/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb b/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb
index acb7f225b17..aaa70f0b36d 100644
--- a/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb
+++ b/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb
@@ -4,9 +4,9 @@ module Gitlab
module ImportExport
module AfterExportStrategies
class WebUploadStrategy < BaseAfterExportStrategy
- PUT_METHOD = 'PUT'.freeze
- POST_METHOD = 'POST'.freeze
- INVALID_HTTP_METHOD = 'invalid. Only PUT and POST methods allowed.'.freeze
+ PUT_METHOD = 'PUT'
+ POST_METHOD = 'POST'
+ INVALID_HTTP_METHOD = 'invalid. Only PUT and POST methods allowed.'
validates :url, addressable_url: true
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index bd0f3e70749..40acb24d191 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -64,6 +64,8 @@ project_tree:
- :push_event_payload
- stages:
- :statuses
+ - :external_pull_request
+ - :external_pull_requests
- :auto_devops
- :triggers
- :pipeline_schedules
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index 91fe4e5d074..685cf53f27f 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -107,7 +107,7 @@ module Gitlab
def project_params
@project_params ||= begin
- attrs = json_params.merge(override_params).merge(visibility_level)
+ attrs = json_params.merge(override_params).merge(visibility_level, external_label)
# Cleaning all imported and overridden params
Gitlab::ImportExport::AttributeCleaner.clean(relation_hash: attrs,
@@ -135,6 +135,13 @@ module Gitlab
{ 'visibility_level' => level }
end
+ def external_label
+ label = override_params['external_authorization_classification_label'].presence ||
+ json_params['external_authorization_classification_label'].presence
+
+ { 'external_authorization_classification_label' => label }
+ end
+
# Given a relation hash containing one or more models and its relationships,
# loops through each model and each object from a model type and
# and assigns its correspondent attributes hash from +tree_hash+
diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb
index 8b346f6d7d2..eece4edf895 100644
--- a/lib/gitlab/incoming_email.rb
+++ b/lib/gitlab/incoming_email.rb
@@ -2,9 +2,9 @@
module Gitlab
module IncomingEmail
- UNSUBSCRIBE_SUFFIX = '-unsubscribe'.freeze
- UNSUBSCRIBE_SUFFIX_LEGACY = '+unsubscribe'.freeze
- WILDCARD_PLACEHOLDER = '%{key}'.freeze
+ UNSUBSCRIBE_SUFFIX = '-unsubscribe'
+ UNSUBSCRIBE_SUFFIX_LEGACY = '+unsubscribe'
+ WILDCARD_PLACEHOLDER = '%{key}'
class << self
def enabled?
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/job_waiter.rb b/lib/gitlab/job_waiter.rb
index e97e961771c..90dbe4d005d 100644
--- a/lib/gitlab/job_waiter.rb
+++ b/lib/gitlab/job_waiter.rb
@@ -17,7 +17,7 @@ module Gitlab
# push to that array when done. Once the waiter has popped `count` items, it
# knows all the jobs are done.
class JobWaiter
- KEY_PREFIX = "gitlab:job_waiter".freeze
+ KEY_PREFIX = "gitlab:job_waiter"
def self.notify(key, jid)
Gitlab::Redis::SharedState.with { |redis| redis.lpush(key, jid) }
diff --git a/lib/gitlab/jwt_authenticatable.rb b/lib/gitlab/jwt_authenticatable.rb
new file mode 100644
index 00000000000..1270a148e8d
--- /dev/null
+++ b/lib/gitlab/jwt_authenticatable.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module JwtAuthenticatable
+ # Supposedly the effective key size for HMAC-SHA256 is 256 bits, i.e. 32
+ # bytes https://tools.ietf.org/html/rfc4868#section-2.6
+ SECRET_LENGTH = 32
+
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ include Gitlab::Utils::StrongMemoize
+
+ def decode_jwt_for_issuer(issuer, encoded_message)
+ JWT.decode(
+ encoded_message,
+ secret,
+ true,
+ { iss: issuer, verify_iss: true, algorithm: 'HS256' }
+ )
+ end
+
+ def secret
+ strong_memoize(:secret) do
+ Base64.strict_decode64(File.read(secret_path).chomp).tap do |bytes|
+ raise "#{secret_path} does not contain #{SECRET_LENGTH} bytes" if bytes.length != SECRET_LENGTH
+ end
+ end
+ end
+
+ def write_secret
+ bytes = SecureRandom.random_bytes(SECRET_LENGTH)
+ File.open(secret_path, 'w:BINARY', 0600) do |f|
+ f.chmod(0600) # If the file already existed, the '0600' passed to 'open' above was a no-op.
+ f.write(Base64.strict_encode64(bytes))
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/kubernetes/helm.rb b/lib/gitlab/kubernetes/helm.rb
index 6e4286589c1..16ed0cb0f8e 100644
--- a/lib/gitlab/kubernetes/helm.rb
+++ b/lib/gitlab/kubernetes/helm.rb
@@ -3,12 +3,12 @@
module Gitlab
module Kubernetes
module Helm
- HELM_VERSION = '2.14.3'.freeze
- KUBECTL_VERSION = '1.11.10'.freeze
- NAMESPACE = 'gitlab-managed-apps'.freeze
- SERVICE_ACCOUNT = 'tiller'.freeze
- CLUSTER_ROLE_BINDING = 'tiller-admin'.freeze
- CLUSTER_ROLE = 'cluster-admin'.freeze
+ HELM_VERSION = '2.14.3'
+ KUBECTL_VERSION = '1.11.10'
+ NAMESPACE = 'gitlab-managed-apps'
+ SERVICE_ACCOUNT = 'tiller'
+ CLUSTER_ROLE_BINDING = 'tiller-admin'
+ CLUSTER_ROLE = 'cluster-admin'
end
end
end
diff --git a/lib/gitlab/kubernetes/pod.rb b/lib/gitlab/kubernetes/pod.rb
index 81317e532b2..d247662dc3b 100644
--- a/lib/gitlab/kubernetes/pod.rb
+++ b/lib/gitlab/kubernetes/pod.rb
@@ -3,11 +3,11 @@
module Gitlab
module Kubernetes
module Pod
- PENDING = 'Pending'.freeze
- RUNNING = 'Running'.freeze
- SUCCEEDED = 'Succeeded'.freeze
- FAILED = 'Failed'.freeze
- UNKNOWN = 'Unknown'.freeze
+ PENDING = 'Pending'
+ RUNNING = 'Running'
+ SUCCEEDED = 'Succeeded'
+ FAILED = 'Failed'
+ UNKNOWN = 'Unknown'
PHASES = [PENDING, RUNNING, SUCCEEDED, FAILED, UNKNOWN].freeze
end
end
diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb
index 128a5dd8936..2ec8c268d09 100644
--- a/lib/gitlab/logger.rb
+++ b/lib/gitlab/logger.rb
@@ -40,7 +40,7 @@ module Gitlab
end
def self.cache_key
- 'logger:'.freeze + self.full_log_path.to_s
+ 'logger:' + self.full_log_path.to_s
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/metrics/subscribers/action_view.rb b/lib/gitlab/metrics/subscribers/action_view.rb
index c068f8017fd..2ed5878286a 100644
--- a/lib/gitlab/metrics/subscribers/action_view.rb
+++ b/lib/gitlab/metrics/subscribers/action_view.rb
@@ -15,7 +15,7 @@ module Gitlab
attach_to :action_view
- SERIES = 'views'.freeze
+ SERIES = 'views'
def render_template(event)
track(event) if current_transaction
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
index d7986685c65..ba2a0b2ecf8 100644
--- a/lib/gitlab/metrics/transaction.rb
+++ b/lib/gitlab/metrics/transaction.rb
@@ -14,7 +14,7 @@ module Gitlab
THREAD_KEY = :_gitlab_metrics_transaction
# The series to store events (e.g. Git pushes) in.
- EVENT_SERIES = 'events'.freeze
+ EVENT_SERIES = 'events'
attr_reader :tags, :values, :method, :metrics
@@ -78,7 +78,7 @@ module Gitlab
# tags - A set of tags to attach to the event.
def add_event(event_name, tags = {})
filtered_tags = filter_tags(tags)
- self.class.transaction_metric(event_name, :counter, prefix: 'event_', use_feature_flag: true, tags: filtered_tags).increment(filtered_tags.merge(labels))
+ self.class.transaction_metric(event_name, :counter, prefix: 'event_', tags: filtered_tags).increment(filtered_tags.merge(labels))
@metrics << Metric.new(EVENT_SERIES, { count: 1 }, filtered_tags.merge(event: event_name), :event)
end
@@ -155,12 +155,11 @@ module Gitlab
with_feature :prometheus_metrics_transaction_allocated_memory
end
- def self.transaction_metric(name, type, prefix: nil, use_feature_flag: false, tags: {})
+ def self.transaction_metric(name, type, prefix: nil, tags: {})
metric_name = "gitlab_transaction_#{prefix}#{name}_total".to_sym
fetch_metric(type, metric_name) do
docstring "Transaction #{prefix}#{name} #{type}"
base_labels tags.merge(BASE_LABELS)
- with_feature "prometheus_transaction_#{prefix}#{name}_total".to_sym if use_feature_flag
if type == :gauge
multiprocess_mode :livesum
diff --git a/lib/gitlab/metrics/web_transaction.rb b/lib/gitlab/metrics/web_transaction.rb
index b2a43d46fb2..fa17548723e 100644
--- a/lib/gitlab/metrics/web_transaction.rb
+++ b/lib/gitlab/metrics/web_transaction.rb
@@ -3,8 +3,8 @@
module Gitlab
module Metrics
class WebTransaction < Transaction
- CONTROLLER_KEY = 'action_controller.instance'.freeze
- ENDPOINT_KEY = 'api.endpoint'.freeze
+ CONTROLLER_KEY = 'action_controller.instance'
+ ENDPOINT_KEY = 'api.endpoint'
ALLOWED_SUFFIXES = Set.new(%w[json js atom rss xml zip])
def initialize(env)
diff --git a/lib/gitlab/middleware/multipart.rb b/lib/gitlab/middleware/multipart.rb
index 433151b80e7..0ee9563c227 100644
--- a/lib/gitlab/middleware/multipart.rb
+++ b/lib/gitlab/middleware/multipart.rb
@@ -28,11 +28,11 @@
module Gitlab
module Middleware
class Multipart
- RACK_ENV_KEY = 'HTTP_GITLAB_WORKHORSE_MULTIPART_FIELDS'.freeze
+ RACK_ENV_KEY = 'HTTP_GITLAB_WORKHORSE_MULTIPART_FIELDS'
class Handler
def initialize(env, message)
- @request = ActionDispatch::Request.new(env)
+ @request = Rack::Request.new(env)
@rewritten_fields = message['rewritten_fields']
@open_files = []
end
@@ -50,7 +50,7 @@ module Gitlab
value = decorate_params_value(value, @request.params[key])
end
- @request.update_param(key, value)
+ update_param(key, value)
end
yield
@@ -92,6 +92,20 @@ module Gitlab
::UploadedFile.from_params(params, key, allowed_paths)
end
+
+ # update_params ensures that both rails controllers and rack middleware can find
+ # workhorse accelerate files in the request
+ def update_param(key, value)
+ # we make sure we have key in POST otherwise update_params will add it in GET
+ @request.POST[key] ||= value
+
+ # this will force Rack::Request to properly update env keys
+ @request.update_param(key, value)
+
+ # ActionDispatch::Request is based on Rack::Request but it caches params
+ # inside other env keys, here we ensure everything is updated correctly
+ ActionDispatch::Request.new(@request.env).update_param(key, value)
+ end
end
def initialize(app)
diff --git a/lib/gitlab/middleware/read_only/controller.rb b/lib/gitlab/middleware/read_only/controller.rb
index 53e55269c6e..802ff18fc58 100644
--- a/lib/gitlab/middleware/read_only/controller.rb
+++ b/lib/gitlab/middleware/read_only/controller.rb
@@ -5,9 +5,9 @@ module Gitlab
class ReadOnly
class Controller
DISALLOWED_METHODS = %w(POST PATCH PUT DELETE).freeze
- APPLICATION_JSON = 'application/json'.freeze
+ APPLICATION_JSON = 'application/json'
APPLICATION_JSON_TYPES = %W{#{APPLICATION_JSON} application/vnd.git-lfs+json}.freeze
- ERROR_MESSAGE = 'You cannot perform write operations on a read-only instance'.freeze
+ ERROR_MESSAGE = 'You cannot perform write operations on a read-only instance'
WHITELISTED_GIT_ROUTES = {
'projects/git_http' => %w{git_upload_pack git_receive_pack}
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/patch/chronic_duration.rb b/lib/gitlab/patch/chronic_duration.rb
new file mode 100644
index 00000000000..ab3cba3657f
--- /dev/null
+++ b/lib/gitlab/patch/chronic_duration.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+# Fixes a bug where parsing months doesn't take into account
+# the ChronicDuration.days_per_week setting
+#
+# We can remove this when we do a refactor and push upstream in
+# https://gitlab.com/gitlab-org/gitlab-ce/issues/66637
+
+module Gitlab
+ module Patch
+ module ChronicDuration
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def duration_units_seconds_multiplier(unit)
+ return 0 unless duration_units_list.include?(unit)
+
+ case unit
+ when 'months'
+ 3600 * ::ChronicDuration.hours_per_day * ::ChronicDuration.days_per_month
+ else
+ super
+ end
+ end
+
+ # ChronicDuration#output uses 1mo = 4w as the conversion so we do the same here.
+ # We do need to add a special case for the default days_per_week value because
+ # we want to retain existing behavior for the default case
+ def days_per_month
+ ::ChronicDuration.days_per_week == 7 ? 30 : ::ChronicDuration.days_per_week * 4
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb
index f96466b2b00..ee2ef91c65c 100644
--- a/lib/gitlab/path_regex.rb
+++ b/lib/gitlab/path_regex.rb
@@ -125,14 +125,14 @@ module Gitlab
# allow non-regex validations, etc), `NAMESPACE_FORMAT_REGEX_JS` serves as a Javascript-compatible version of
# `NAMESPACE_FORMAT_REGEX`, with the negative lookbehind assertion removed. This means that the client-side validation
# will pass for usernames ending in `.atom` and `.git`, but will be caught by the server-side validation.
- PATH_START_CHAR = '[a-zA-Z0-9_\.]'.freeze
- PATH_REGEX_STR = PATH_START_CHAR + '[a-zA-Z0-9_\-\.]*'.freeze
- NAMESPACE_FORMAT_REGEX_JS = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze
+ PATH_START_CHAR = '[a-zA-Z0-9_\.]'
+ PATH_REGEX_STR = PATH_START_CHAR + '[a-zA-Z0-9_\-\.]*'
+ NAMESPACE_FORMAT_REGEX_JS = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'
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..4445c876e7a 100644
--- a/lib/gitlab/performance_bar.rb
+++ b/lib/gitlab/performance_bar.rb
@@ -2,11 +2,15 @@
module Gitlab
module PerformanceBar
- ALLOWED_USER_IDS_KEY = 'performance_bar_allowed_user_ids:v2'.freeze
+ ALLOWED_USER_IDS_KEY = 'performance_bar_allowed_user_ids:v2'
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/polling_interval.rb b/lib/gitlab/polling_interval.rb
index 0f69990df63..fd66787b029 100644
--- a/lib/gitlab/polling_interval.rb
+++ b/lib/gitlab/polling_interval.rb
@@ -2,7 +2,7 @@
module Gitlab
class PollingInterval
- HEADER_NAME = 'Poll-Interval'.freeze
+ HEADER_NAME = 'Poll-Interval'
def self.set_header(response, interval:)
if polling_enabled?
diff --git a/lib/gitlab/private_commit_email.rb b/lib/gitlab/private_commit_email.rb
index 536fc9dae3a..886c2c32d36 100644
--- a/lib/gitlab/private_commit_email.rb
+++ b/lib/gitlab/private_commit_email.rb
@@ -2,7 +2,7 @@
module Gitlab
module PrivateCommitEmail
- TOKEN = "_private".freeze
+ TOKEN = "_private"
class << self
def regex
diff --git a/lib/gitlab/profiler.rb b/lib/gitlab/profiler.rb
index 425c30d67fe..3f26b84be20 100644
--- a/lib/gitlab/profiler.rb
+++ b/lib/gitlab/profiler.rb
@@ -3,7 +3,7 @@
module Gitlab
module Profiler
- FILTERED_STRING = '[FILTERED]'.freeze
+ FILTERED_STRING = '[FILTERED]'
IGNORE_BACKTRACES = %w[
lib/gitlab/i18n.rb
diff --git a/lib/gitlab/prometheus/additional_metrics_parser.rb b/lib/gitlab/prometheus/additional_metrics_parser.rb
index bd4ca578840..ee3d98f3602 100644
--- a/lib/gitlab/prometheus/additional_metrics_parser.rb
+++ b/lib/gitlab/prometheus/additional_metrics_parser.rb
@@ -3,7 +3,7 @@
module Gitlab
module Prometheus
module AdditionalMetricsParser
- CONFIG_ROOT = 'config/prometheus'.freeze
+ CONFIG_ROOT = 'config/prometheus'
MUTEX = Mutex.new
extend self
diff --git a/lib/gitlab/push_options.rb b/lib/gitlab/push_options.rb
index 682edfc4259..a2296d265cd 100644
--- a/lib/gitlab/push_options.rb
+++ b/lib/gitlab/push_options.rb
@@ -7,10 +7,12 @@ module Gitlab
keys: [
:create,
:description,
+ :label,
:merge_when_pipeline_succeeds,
:remove_source_branch,
:target,
- :title
+ :title,
+ :unlabel
]
},
ci: {
@@ -18,6 +20,11 @@ module Gitlab
}
}).freeze
+ MULTI_VALUE_OPTIONS = [
+ %w[merge_request label],
+ %w[merge_request unlabel]
+ ].freeze
+
NAMESPACE_ALIASES = HashWithIndifferentAccess.new({
mr: :merge_request
}).freeze
@@ -50,12 +57,22 @@ module Gitlab
next if [namespace, key].any?(&:nil?)
options[namespace] ||= HashWithIndifferentAccess.new
- options[namespace][key] = value
+
+ if option_multi_value?(namespace, key)
+ options[namespace][key] ||= HashWithIndifferentAccess.new(0)
+ options[namespace][key][value] += 1
+ else
+ options[namespace][key] = value
+ end
end
options
end
+ def option_multi_value?(namespace, key)
+ MULTI_VALUE_OPTIONS.any? { |arr| arr == [namespace, key] }
+ end
+
def parse_option(option)
parts = OPTION_MATCHER.match(option)
return unless parts
diff --git a/lib/gitlab/query_limiting/middleware.rb b/lib/gitlab/query_limiting/middleware.rb
index 949ae79a047..f6ffbfe2645 100644
--- a/lib/gitlab/query_limiting/middleware.rb
+++ b/lib/gitlab/query_limiting/middleware.rb
@@ -5,8 +5,8 @@ module Gitlab
# Middleware for reporting (or raising) when a request performs more than a
# certain amount of database queries.
class Middleware
- CONTROLLER_KEY = 'action_controller.instance'.freeze
- ENDPOINT_KEY = 'api.endpoint'.freeze
+ CONTROLLER_KEY = 'action_controller.instance'
+ ENDPOINT_KEY = 'api.endpoint'
def initialize(app)
@app = app
diff --git a/lib/gitlab/quick_actions/issuable_actions.rb b/lib/gitlab/quick_actions/issuable_actions.rb
index e5d99ebee35..5cf24823ef5 100644
--- a/lib/gitlab/quick_actions/issuable_actions.rb
+++ b/lib/gitlab/quick_actions/issuable_actions.rb
@@ -1,3 +1,4 @@
+# coding: utf-8
# frozen_string_literal: true
module Gitlab
@@ -6,8 +7,8 @@ module Gitlab
extend ActiveSupport::Concern
include Gitlab::QuickActions::Dsl
- SHRUG = '¯\\_(ツ)_/¯'.freeze
- TABLEFLIP = '(╯°□°)╯︵ ┻━┻'.freeze
+ SHRUG = '¯\\_(ツ)_/¯'
+ TABLEFLIP = '(╯°□°)╯︵ ┻━┻'
included do
# Issue, MergeRequest, Epic: quick actions definitions
diff --git a/lib/gitlab/quick_actions/issue_actions.rb b/lib/gitlab/quick_actions/issue_actions.rb
index da28fbf5be0..869627ac585 100644
--- a/lib/gitlab/quick_actions/issue_actions.rb
+++ b/lib/gitlab/quick_actions/issue_actions.rb
@@ -25,7 +25,11 @@ module Gitlab
Chronic.parse(due_date_param).try(:to_date)
end
command :due do |due_date|
- @updates[:due_date] = due_date if due_date
+ if due_date
+ @updates[:due_date] = due_date
+ else
+ @execution_message[:due] = _('Failed to set due date because the date format is invalid.')
+ end
end
desc _('Remove due date')
diff --git a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
index 533c74ba9b4..183191f31a6 100644
--- a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
+++ b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
@@ -122,7 +122,7 @@ module Gitlab
params '#issue | !merge_request'
types Issue, MergeRequest
condition do
- current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
end
parse_params do |issuable_param|
extract_references(issuable_param, :issue).first ||
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/cache.rb b/lib/gitlab/redis/cache.rb
index 6e8403ad878..6e31c506438 100644
--- a/lib/gitlab/redis/cache.rb
+++ b/lib/gitlab/redis/cache.rb
@@ -6,9 +6,9 @@ require_relative 'wrapper' unless defined?(::Rails) && ::Rails.root.present?
module Gitlab
module Redis
class Cache < ::Gitlab::Redis::Wrapper
- CACHE_NAMESPACE = 'cache:gitlab'.freeze
- DEFAULT_REDIS_CACHE_URL = 'redis://localhost:6380'.freeze
- REDIS_CACHE_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_CACHE_CONFIG_FILE'.freeze
+ CACHE_NAMESPACE = 'cache:gitlab'
+ DEFAULT_REDIS_CACHE_URL = 'redis://localhost:6380'
+ REDIS_CACHE_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_CACHE_CONFIG_FILE'
class << self
def default_url
diff --git a/lib/gitlab/redis/queues.rb b/lib/gitlab/redis/queues.rb
index 8b42c269dd0..0375e4a221a 100644
--- a/lib/gitlab/redis/queues.rb
+++ b/lib/gitlab/redis/queues.rb
@@ -6,10 +6,10 @@ require_relative 'wrapper' unless defined?(::Gitlab::Redis::Wrapper)
module Gitlab
module Redis
class Queues < ::Gitlab::Redis::Wrapper
- SIDEKIQ_NAMESPACE = 'resque:gitlab'.freeze
- MAILROOM_NAMESPACE = 'mail_room:gitlab'.freeze
- DEFAULT_REDIS_QUEUES_URL = 'redis://localhost:6381'.freeze
- REDIS_QUEUES_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_QUEUES_CONFIG_FILE'.freeze
+ SIDEKIQ_NAMESPACE = 'resque:gitlab'
+ MAILROOM_NAMESPACE = 'mail_room:gitlab'
+ DEFAULT_REDIS_QUEUES_URL = 'redis://localhost:6381'
+ REDIS_QUEUES_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_QUEUES_CONFIG_FILE'
class << self
def default_url
diff --git a/lib/gitlab/redis/shared_state.rb b/lib/gitlab/redis/shared_state.rb
index 9066606ca21..35356083f26 100644
--- a/lib/gitlab/redis/shared_state.rb
+++ b/lib/gitlab/redis/shared_state.rb
@@ -6,11 +6,12 @@ require_relative 'wrapper' unless defined?(::Gitlab::Redis::Wrapper)
module Gitlab
module Redis
class SharedState < ::Gitlab::Redis::Wrapper
- SESSION_NAMESPACE = 'session:gitlab'.freeze
- USER_SESSIONS_NAMESPACE = 'session:user:gitlab'.freeze
- USER_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:user: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
+ SESSION_NAMESPACE = 'session:gitlab'
+ USER_SESSIONS_NAMESPACE = 'session:user:gitlab'
+ USER_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:user:gitlab'
+ IP_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:ip:gitlab'
+ DEFAULT_REDIS_SHARED_STATE_URL = 'redis://localhost:6382'
+ REDIS_SHARED_STATE_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_SHARED_STATE_CONFIG_FILE'
class << self
def default_url
diff --git a/lib/gitlab/redis/wrapper.rb b/lib/gitlab/redis/wrapper.rb
index 07a1e20b076..fa1615a5953 100644
--- a/lib/gitlab/redis/wrapper.rb
+++ b/lib/gitlab/redis/wrapper.rb
@@ -8,8 +8,8 @@ require 'active_support/core_ext/module/delegation'
module Gitlab
module Redis
class Wrapper
- DEFAULT_REDIS_URL = 'redis://localhost:6379'.freeze
- REDIS_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_CONFIG_FILE'.freeze
+ DEFAULT_REDIS_URL = 'redis://localhost:6379'
+ REDIS_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_CONFIG_FILE'
class << self
delegate :params, :url, to: :new
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/request_profiler.rb b/lib/gitlab/request_profiler.rb
index 033e451dbee..dd1482da40d 100644
--- a/lib/gitlab/request_profiler.rb
+++ b/lib/gitlab/request_profiler.rb
@@ -4,7 +4,7 @@ require 'fileutils'
module Gitlab
module RequestProfiler
- PROFILES_DIR = "#{Gitlab.config.shared.path}/tmp/requests_profiles".freeze
+ PROFILES_DIR = "#{Gitlab.config.shared.path}/tmp/requests_profiles"
def all
Dir["#{PROFILES_DIR}/*.{html,txt}"].map do |path|
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/shard_health_cache.rb b/lib/gitlab/shard_health_cache.rb
index 6612347438e..eeb0cc75ef9 100644
--- a/lib/gitlab/shard_health_cache.rb
+++ b/lib/gitlab/shard_health_cache.rb
@@ -2,7 +2,7 @@
module Gitlab
class ShardHealthCache
- HEALTHY_SHARDS_KEY = 'gitlab-healthy-shards'.freeze
+ HEALTHY_SHARDS_KEY = 'gitlab-healthy-shards'
HEALTHY_SHARDS_TIMEOUT = 300
# Clears the Redis set storing the list of healthy shards
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/sidekiq_monitor.rb b/lib/gitlab/sidekiq_monitor.rb
index 9842f1f53f7..a58b33534bf 100644
--- a/lib/gitlab/sidekiq_monitor.rb
+++ b/lib/gitlab/sidekiq_monitor.rb
@@ -4,7 +4,7 @@ module Gitlab
class SidekiqMonitor < Daemon
include ::Gitlab::Utils::StrongMemoize
- NOTIFICATION_CHANNEL = 'sidekiq:cancel:notifications'.freeze
+ NOTIFICATION_CHANNEL = 'sidekiq:cancel:notifications'
CANCEL_DEADLINE = 24.hours.seconds
RECONNECT_TIME = 3.seconds
diff --git a/lib/gitlab/sidekiq_status.rb b/lib/gitlab/sidekiq_status.rb
index 0f890a12134..0dafccb3d34 100644
--- a/lib/gitlab/sidekiq_status.rb
+++ b/lib/gitlab/sidekiq_status.rb
@@ -18,7 +18,7 @@ module Gitlab
# expire after a certain period of time to prevent storing too many keys in
# Redis.
module SidekiqStatus
- STATUS_KEY = 'gitlab-sidekiq-status:%s'.freeze
+ STATUS_KEY = 'gitlab-sidekiq-status:%s'
# The default time (in seconds) after which a status key is expired
# automatically. The default of 30 minutes should be more than sufficient
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/snowplow_tracker.rb b/lib/gitlab/snowplow_tracker.rb
deleted file mode 100644
index 9f12513e09e..00000000000
--- a/lib/gitlab/snowplow_tracker.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-require 'snowplow-tracker'
-
-module Gitlab
- module SnowplowTracker
- NAMESPACE = 'cf'
-
- class << self
- def track_event(category, action, label: nil, property: nil, value: nil, context: nil)
- tracker&.track_struct_event(category, action, label, property, value, context, Time.now.to_i)
- end
-
- private
-
- def tracker
- return unless enabled?
-
- @tracker ||= ::SnowplowTracker::Tracker.new(emitter, subject, NAMESPACE, Gitlab::CurrentSettings.snowplow_site_id)
- end
-
- def subject
- ::SnowplowTracker::Subject.new
- end
-
- def emitter
- ::SnowplowTracker::Emitter.new(Gitlab::CurrentSettings.snowplow_collector_hostname)
- end
-
- def enabled?
- Gitlab::CurrentSettings.snowplow_enabled?
- end
- 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/tracking.rb b/lib/gitlab/tracking.rb
new file mode 100644
index 00000000000..ef669b03c87
--- /dev/null
+++ b/lib/gitlab/tracking.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'snowplow-tracker'
+
+module Gitlab
+ module Tracking
+ SNOWPLOW_NAMESPACE = 'gl'
+
+ class << self
+ def enabled?
+ Gitlab::CurrentSettings.snowplow_enabled?
+ end
+
+ def event(category, action, label: nil, property: nil, value: nil, context: nil)
+ return unless enabled?
+
+ snowplow.track_struct_event(category, action, label, property, value, context, Time.now.to_i)
+ end
+
+ def snowplow_options(group)
+ additional_features = Feature.enabled?(:additional_snowplow_tracking, group)
+ {
+ namespace: SNOWPLOW_NAMESPACE,
+ hostname: Gitlab::CurrentSettings.snowplow_collector_hostname,
+ cookie_domain: Gitlab::CurrentSettings.snowplow_cookie_domain,
+ app_id: Gitlab::CurrentSettings.snowplow_site_id,
+ page_tracking_enabled: additional_features,
+ activity_tracking_enabled: additional_features
+ }.transform_keys! { |key| key.to_s.camelize(:lower).to_sym }
+ end
+
+ private
+
+ def snowplow
+ @snowplow ||= SnowplowTracker::Tracker.new(
+ SnowplowTracker::Emitter.new(Gitlab::CurrentSettings.snowplow_collector_hostname),
+ SnowplowTracker::Subject.new,
+ SNOWPLOW_NAMESPACE,
+ Gitlab::CurrentSettings.snowplow_site_id
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index 9c35d200dcb..fab504aa603 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -49,7 +49,7 @@ module Gitlab
hostname = uri.hostname
port = get_port(uri)
- address_info = get_address_info(hostname, port)
+ address_info = get_address_info(hostname, port, dns_rebind_protection)
return [uri, nil] unless address_info
ip_address = ip_address(address_info)
@@ -110,11 +110,15 @@ module Gitlab
validate_unicode_restriction(uri) if ascii_only
end
- def get_address_info(hostname, port)
+ def get_address_info(hostname, port, dns_rebind_protection)
Addrinfo.getaddrinfo(hostname, port, nil, :STREAM).map do |addr|
addr.ipv6_v4mapped? ? addr.ipv6_to_ipv4 : addr
end
rescue SocketError
+ # If the dns rebinding protection is not enabled, we allow
+ # urls that can't be resolved at this point.
+ return unless dns_rebind_protection
+
# In the test suite we use a lot of mocked urls that are either invalid or
# don't exist. In order to avoid modifying a ton of tests and factories
# we allow invalid urls unless the environment variable RSPEC_ALLOW_INVALID_URLS
diff --git a/lib/gitlab/url_helpers.rb b/lib/gitlab/url_helpers.rb
index 883585c196f..2a7919883c5 100644
--- a/lib/gitlab/url_helpers.rb
+++ b/lib/gitlab/url_helpers.rb
@@ -2,7 +2,7 @@
module Gitlab
class UrlHelpers
- WSS_PROTOCOL = "wss".freeze
+ WSS_PROTOCOL = "wss"
def self.as_wss(url)
return unless url.present?
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 353298e67b3..a93301cb4ce 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -142,7 +142,8 @@ module Gitlab
Gitlab::UsageDataCounters::SnippetCounter,
Gitlab::UsageDataCounters::SearchCounter,
Gitlab::UsageDataCounters::CycleAnalyticsCounter,
- Gitlab::UsageDataCounters::SourceCodeCounter
+ Gitlab::UsageDataCounters::SourceCodeCounter,
+ Gitlab::UsageDataCounters::MergeRequestCounter
]
end
diff --git a/lib/gitlab/usage_data_counters/merge_request_counter.rb b/lib/gitlab/usage_data_counters/merge_request_counter.rb
new file mode 100644
index 00000000000..e786e595f77
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/merge_request_counter.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module UsageDataCounters
+ class MergeRequestCounter < BaseCounter
+ KNOWN_EVENTS = %w[create].freeze
+ PREFIX = 'merge_request'
+ end
+ 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/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 3b77fe838ae..db67e4fd479 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -7,17 +7,15 @@ require 'uri'
module Gitlab
class Workhorse
- SEND_DATA_HEADER = 'Gitlab-Workhorse-Send-Data'.freeze
- VERSION_FILE = 'GITLAB_WORKHORSE_VERSION'.freeze
- INTERNAL_API_CONTENT_TYPE = 'application/vnd.gitlab-workhorse+json'.freeze
- INTERNAL_API_REQUEST_HEADER = 'Gitlab-Workhorse-Api-Request'.freeze
- NOTIFICATION_CHANNEL = 'workhorse:notifications'.freeze
+ SEND_DATA_HEADER = 'Gitlab-Workhorse-Send-Data'
+ VERSION_FILE = 'GITLAB_WORKHORSE_VERSION'
+ INTERNAL_API_CONTENT_TYPE = 'application/vnd.gitlab-workhorse+json'
+ INTERNAL_API_REQUEST_HEADER = 'Gitlab-Workhorse-Api-Request'
+ NOTIFICATION_CHANNEL = 'workhorse:notifications'
ALLOWED_GIT_HTTP_ACTIONS = %w[git_receive_pack git_upload_pack info_refs].freeze
- DETECT_HEADER = 'Gitlab-Workhorse-Detect-Content-Type'.freeze
+ DETECT_HEADER = 'Gitlab-Workhorse-Detect-Content-Type'
- # Supposedly the effective key size for HMAC-SHA256 is 256 bits, i.e. 32
- # bytes https://tools.ietf.org/html/rfc4868#section-2.6
- SECRET_LENGTH = 32
+ include JwtAuthenticatable
class << self
def git_http_ok(repository, repo_type, user, action, show_all_refs: false)
@@ -34,7 +32,8 @@ module Gitlab
GitConfigOptions: [],
GitalyServer: {
address: Gitlab::GitalyClient.address(project.repository_storage),
- token: Gitlab::GitalyClient.token(project.repository_storage)
+ token: Gitlab::GitalyClient.token(project.repository_storage),
+ features: Feature::Gitaly.server_feature_flags
}
}
@@ -186,34 +185,12 @@ module Gitlab
path.readable? ? path.read.chomp : 'unknown'
end
- def secret
- @secret ||= begin
- bytes = Base64.strict_decode64(File.read(secret_path).chomp)
- raise "#{secret_path} does not contain #{SECRET_LENGTH} bytes" if bytes.length != SECRET_LENGTH
-
- bytes
- end
- end
-
- def write_secret
- bytes = SecureRandom.random_bytes(SECRET_LENGTH)
- File.open(secret_path, 'w:BINARY', 0600) do |f|
- f.chmod(0600) # If the file already existed, the '0600' passed to 'open' above was a no-op.
- f.write(Base64.strict_encode64(bytes))
- end
- end
-
def verify_api_request!(request_headers)
decode_jwt(request_headers[INTERNAL_API_REQUEST_HEADER])
end
def decode_jwt(encoded_message)
- JWT.decode(
- encoded_message,
- secret,
- true,
- { iss: 'gitlab-workhorse', verify_iss: true, algorithm: 'HS256' }
- )
+ decode_jwt_for_issuer('gitlab-workhorse', encoded_message)
end
def secret_path
@@ -250,7 +227,8 @@ module Gitlab
def gitaly_server_hash(repository)
{
address: Gitlab::GitalyClient.address(repository.project.repository_storage),
- token: Gitlab::GitalyClient.token(repository.project.repository_storage)
+ token: Gitlab::GitalyClient.token(repository.project.repository_storage),
+ features: Feature::Gitaly.server_feature_flags
}
end
diff --git a/lib/gitlab_danger.rb b/lib/gitlab_danger.rb
new file mode 100644
index 00000000000..b4768a9546d
--- /dev/null
+++ b/lib/gitlab_danger.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+class GitlabDanger
+ LOCAL_RULES ||= %w[
+ changes_size
+ gemfile
+ documentation
+ frozen_string
+ duplicate_yarn_dependencies
+ prettier
+ eslint
+ database
+ ].freeze
+
+ CI_ONLY_RULES ||= %w[
+ metadata
+ changelog
+ specs
+ commit_messages
+ roulette
+ single_codebase
+ gitlab_ui_wg
+ ce_ee_vue_templates
+ only_documentation
+ ].freeze
+
+ MESSAGE_PREFIX = '==>'.freeze
+
+ attr_reader :gitlab_danger_helper
+
+ def initialize(gitlab_danger_helper)
+ @gitlab_danger_helper = gitlab_danger_helper
+ end
+
+ def self.local_warning_message
+ "#{MESSAGE_PREFIX} Only the following Danger rules can be run locally: #{LOCAL_RULES.join(', ')}"
+ end
+
+ def self.success_message
+ "#{MESSAGE_PREFIX} No Danger rule violations!"
+ end
+
+ def rule_names
+ ci? ? LOCAL_RULES | CI_ONLY_RULES : LOCAL_RULES
+ end
+
+ def html_link(str)
+ self.ci? ? gitlab_danger_helper.html_link(str) : str
+ end
+
+ def ci?
+ !gitlab_danger_helper.nil?
+ 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/assets.rake b/lib/tasks/gitlab/assets.rake
index a07ae3a418a..7a42e4e92a0 100644
--- a/lib/tasks/gitlab/assets.rake
+++ b/lib/tasks/gitlab/assets.rake
@@ -10,15 +10,9 @@ namespace :gitlab do
rake:assets:precompile
webpack:compile
gitlab:assets:fix_urls
- gitlab:assets:compile_vrt
].each(&Gitlab::TaskHelpers.method(:invoke_and_time_task))
end
- desc 'GitLab | Assets | Compile visual review toolbar'
- task :compile_vrt do
- system 'yarn', 'webpack-vrt'
- end
-
desc 'GitLab | Assets | Clean up old compiled frontend assets'
task clean: ['rake:assets:clean']
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/lib/tasks/gitlab_danger.rake b/lib/tasks/gitlab_danger.rake
new file mode 100644
index 00000000000..e75539f048c
--- /dev/null
+++ b/lib/tasks/gitlab_danger.rake
@@ -0,0 +1,17 @@
+desc 'Run local Danger rules'
+task :danger_local do
+ require 'gitlab_danger'
+ require 'gitlab/popen'
+
+ puts("#{GitlabDanger.local_warning_message}\n")
+
+ # _status will _always_ be 0, regardless of failure or success :(
+ output, _status = Gitlab::Popen.popen(%w{danger dry_run})
+
+ if output.empty?
+ puts(GitlabDanger.success_message)
+ else
+ puts(output)
+ exit(1)
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 4f3c8e8046d..d26ce9fa911 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -309,6 +309,9 @@ msgstr ""
msgid "(external source)"
msgstr ""
+msgid "(removed)"
+msgstr ""
+
msgid "+ %{amount} more"
msgstr ""
@@ -1101,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 ""
@@ -1513,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 ""
@@ -1948,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"
@@ -2971,12 +2986,18 @@ msgstr ""
msgid "ComboSearch is not defined"
msgstr ""
+msgid "Command"
+msgstr ""
+
msgid "Command line instructions"
msgstr ""
msgid "Commands applied"
msgstr ""
+msgid "Commands did not apply"
+msgstr ""
+
msgid "Comment"
msgstr ""
@@ -3353,10 +3374,10 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
-msgid "Could not add prometheus URL to whitelist"
+msgid "Could not add admins as members"
msgstr ""
-msgid "Could not add prometheus URL to whitelist, errors: %{errors}"
+msgid "Could not add prometheus URL to whitelist"
msgstr ""
msgid "Could not authorize chat nickname. Try again!"
@@ -3371,7 +3392,7 @@ msgstr ""
msgid "Could not create Wiki Repository at this time. Please try again later."
msgstr ""
-msgid "Could not create instance administration project. Errors: %{errors}"
+msgid "Could not create group"
msgstr ""
msgid "Could not create project"
@@ -3392,10 +3413,10 @@ msgstr ""
msgid "Could not revoke personal access token %{personal_access_token_name}."
msgstr ""
-msgid "Could not save instance administration project ID, errors: %{errors}"
+msgid "Could not save project ID"
msgstr ""
-msgid "Could not save project ID"
+msgid "Could not save prometheus manual configuration"
msgstr ""
msgid "Coverage"
@@ -3590,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 ""
@@ -3602,6 +3629,30 @@ msgstr ""
msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
msgstr ""
+msgid "CycleAnalyticsEvent|Issue created"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first associated with a milestone or issue first added to a board"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first mentioned in a commit"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request created"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request first deployed to production"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request last build finish time"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request last build start time"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request merged"
+msgstr ""
+
msgid "CycleAnalyticsStage|Code"
msgstr ""
@@ -3919,6 +3970,9 @@ msgstr ""
msgid "Description:"
msgstr ""
+msgid "Descriptive label"
+msgstr ""
+
msgid "Deselect all"
msgstr ""
@@ -4030,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 ""
@@ -4075,6 +4132,9 @@ msgstr ""
msgid "Download export"
msgstr ""
+msgid "Download image"
+msgstr ""
+
msgid "Download source code"
msgstr ""
@@ -4285,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"
@@ -4924,6 +4984,9 @@ msgstr ""
msgid "Failed to save preferences."
msgstr ""
+msgid "Failed to set due date because the date format is invalid."
+msgstr ""
+
msgid "Failed to update branch!"
msgstr ""
@@ -5277,9 +5340,6 @@ msgstr ""
msgid "GitLab.com import"
msgstr ""
-msgid "GitLab’s issue tracker"
-msgstr ""
-
msgid "Gitaly"
msgstr ""
@@ -5655,7 +5715,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"
@@ -6023,9 +6086,6 @@ msgstr ""
msgid "Instance Statistics visibility"
msgstr ""
-msgid "Instance administration project already exists"
-msgstr ""
-
msgid "Instance administrators group already exists"
msgstr ""
@@ -6065,6 +6125,9 @@ msgstr ""
msgid "Invalid Login or password"
msgstr ""
+msgid "Invalid URL"
+msgstr ""
+
msgid "Invalid date"
msgstr ""
@@ -6113,6 +6176,9 @@ msgstr ""
msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
msgstr ""
+msgid "IssuableStatus|moved"
+msgstr ""
+
msgid "Issue"
msgstr ""
@@ -6176,6 +6242,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 ""
@@ -6520,9 +6589,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 ""
@@ -6789,6 +6855,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 ""
@@ -6966,6 +7062,9 @@ msgstr ""
msgid "Messages"
msgstr ""
+msgid "Method"
+msgstr ""
+
msgid "Metrics"
msgstr ""
@@ -7071,9 +7170,6 @@ msgstr ""
msgid "Minimum length is %{minimum_password_length} characters."
msgstr ""
-msgid "Mirror a repository"
-msgstr ""
-
msgid "Mirror direction"
msgstr ""
@@ -7113,7 +7209,7 @@ msgstr ""
msgid "Monday"
msgstr ""
-msgid "Monitor your errors by integrating with Sentry"
+msgid "Monitor your errors by integrating with Sentry."
msgstr ""
msgid "Monitoring"
@@ -7376,9 +7472,15 @@ msgstr ""
msgid "No Tag"
msgstr ""
+msgid "No active admin user found"
+msgstr ""
+
msgid "No activities found"
msgstr ""
+msgid "No application_settings found"
+msgstr ""
+
msgid "No available namespaces to fork the project."
msgstr ""
@@ -7526,6 +7628,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 ""
@@ -7882,6 +7987,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 ""
@@ -8002,7 +8110,7 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
-msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgid "PipelineStatusTooltip|Pipeline: %{ciStatus}"
msgstr ""
msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
@@ -8080,6 +8188,9 @@ msgstr ""
msgid "Pipeline|Coverage"
msgstr ""
+msgid "Pipeline|Detached merge request pipeline"
+msgstr ""
+
msgid "Pipeline|Duration"
msgstr ""
@@ -8089,6 +8200,12 @@ msgstr ""
msgid "Pipeline|Key"
msgstr ""
+msgid "Pipeline|Merge train pipeline"
+msgstr ""
+
+msgid "Pipeline|Merged result pipeline"
+msgstr ""
+
msgid "Pipeline|Pipeline"
msgstr ""
@@ -8254,7 +8371,7 @@ msgstr ""
msgid "Preferences|Behavior"
msgstr ""
-msgid "Preferences|Choose between fixed (max. 1280px) and fluid (100%%) application layout."
+msgid "Preferences|Choose between fixed (max. 1280px) and fluid (%{percentage}) application layout."
msgstr ""
msgid "Preferences|Choose what content you want to see on a project’s overview page."
@@ -8722,6 +8839,9 @@ msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project already created"
+msgstr ""
+
msgid "Project and wiki repositories"
msgstr ""
@@ -8836,6 +8956,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 ""
@@ -9040,7 +9193,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"
@@ -9941,9 +10094,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 ""
@@ -10363,6 +10564,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 ""
@@ -10669,6 +10885,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 ""
@@ -10936,6 +11155,9 @@ msgstr ""
msgid "SuggestedColors|Very pale orange"
msgstr ""
+msgid "Suggestions:"
+msgstr ""
+
msgid "Sunday"
msgstr ""
@@ -11343,9 +11565,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 ""
@@ -11538,6 +11757,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 ""
@@ -11992,6 +12214,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 ""
@@ -12064,9 +12289,6 @@ msgstr ""
msgid "Toggles :%{name}: emoji award."
msgstr ""
-msgid "Token"
-msgstr ""
-
msgid "Tomorrow"
msgstr ""
@@ -12801,6 +13023,9 @@ msgid_plural "When these merge requests are accepted"
msgstr[0] ""
msgstr[1] ""
+msgid "When using the <code>http://</code> or <code>https://</code> protocols, please provide the exact URL to the repository. HTTP redirects will not be followed."
+msgstr ""
+
msgid "When:"
msgstr ""
@@ -13386,6 +13611,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 ""
@@ -13598,9 +13826,6 @@ msgstr ""
msgid "issue"
msgstr ""
-msgid "issue boards"
-msgstr ""
-
msgid "it is stored externally"
msgstr ""
@@ -13628,6 +13853,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] ""
@@ -13919,6 +14150,9 @@ msgstr ""
msgid "pending comment"
msgstr ""
+msgid "pipeline"
+msgstr ""
+
msgid "private"
msgstr ""
diff --git a/package.json b/package.json
index df5f758fa20..1c3eb409d9b 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 .",
@@ -16,6 +16,7 @@
"karma": "BABEL_ENV=${BABEL_ENV:=karma} karma start --single-run true config/karma.config.js",
"karma-coverage": "BABEL_ENV=coverage karma start --single-run true config/karma.config.js",
"karma-start": "BABEL_ENV=karma karma start config/karma.config.js",
+ "markdownlint": "markdownlint -c .markdownlint.json 'doc/**/*.md'",
"postinstall": "node ./scripts/frontend/postinstall.js",
"prettier-staged": "node ./scripts/frontend/prettier.js check",
"prettier-staged-save": "node ./scripts/frontend/prettier.js save",
@@ -26,8 +27,7 @@
"stylelint-create-utility-map": "node scripts/frontend/stylelint/stylelint-utility-map.js",
"test": "node scripts/frontend/test",
"webpack": "NODE_OPTIONS=\"--max-old-space-size=3584\" webpack --config config/webpack.config.js",
- "webpack-prod": "NODE_OPTIONS=\"--max-old-space-size=3584\" NODE_ENV=production webpack --config config/webpack.config.js",
- "webpack-vrt": "NODE_OPTIONS=\"--max-old-space-size=3584\" NODE_ENV=production webpack --config config/webpack.config.review_toolbar.js"
+ "webpack-prod": "NODE_OPTIONS=\"--max-old-space-size=3584\" NODE_ENV=production webpack --config config/webpack.config.js"
},
"dependencies": {
"@babel/core": "^7.5.5",
@@ -37,9 +37,9 @@
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-syntax-import-meta": "^7.2.0",
"@babel/preset-env": "^7.5.5",
- "@gitlab/csslab": "^1.9.0",
- "@gitlab/svgs": "^1.68.0",
- "@gitlab/ui": "5.18.0",
+ "@gitlab/svgs": "^1.72.0",
+ "@gitlab/ui": "5.21.1",
+ "@gitlab/visual-review-tools": "1.0.1",
"apollo-cache-inmemory": "^1.5.1",
"apollo-client": "^2.5.1",
"apollo-link": "^1.2.11",
@@ -97,7 +97,7 @@
"jszip-utils": "^0.0.2",
"katex": "^0.10.0",
"marked": "^0.3.12",
- "mermaid": "^8.2.3",
+ "mermaid": "^8.2.6",
"monaco-editor": "^0.15.6",
"monaco-editor-webpack-plugin": "^1.7.0",
"mousetrap": "^1.4.6",
@@ -186,6 +186,7 @@
"karma-mocha-reporter": "^2.2.5",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^4.0.2",
+ "markdownlint-cli": "0.18.0",
"md5": "^2.2.1",
"node-sass": "^4.12.0",
"nodemon": "^1.18.9",
diff --git a/plugins/.gitignore b/plugins/.gitignore
new file mode 100644
index 00000000000..e4ccdc9e2ec
--- /dev/null
+++ b/plugins/.gitignore
@@ -0,0 +1,5 @@
+*
+!*/
+!.gitignore
+!.gitkeep
+!examples/*
diff --git a/qa/Dockerfile b/qa/Dockerfile
index 3309f5b6ce3..84dbfae1008 100644
--- a/qa/Dockerfile
+++ b/qa/Dockerfile
@@ -50,6 +50,8 @@ RUN export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)" && \
WORKDIR /home/gitlab/qa
COPY ./qa/Gemfile* /home/gitlab/qa/
COPY ./config/initializers/0_inject_enterprise_edition_module.rb /home/gitlab/config/initializers/
+# Copy VERSION to ensure the COPY succeeds to copy at least one file since ee/app/models/license.rb isn't present in CE
+COPY VERSION ./ee/app/models/license.r[b] /home/gitlab/ee/app/models/
COPY ./lib/gitlab.rb /home/gitlab/lib/
COPY ./INSTALLATION_TYPE /home/gitlab/
COPY ./VERSION /home/gitlab/
diff --git a/qa/README.md b/qa/README.md
index 16e5e730a6b..dede3cd2473 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -105,6 +105,17 @@ If you need to authenticate as a different user, you can provide the
GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password bundle exec bin/qa Test::Instance::All https://gitlab.example.com
```
+Some QA tests require logging in as an admin user. By default, the QA
+tests will use the the same `root` user seeded by the GDK.
+
+If you need to authenticate with different admin credentials, you can
+provide the `GITLAB_ADMIN_USERNAME` and `GITLAB_ADMIN_PASSWORD`
+environment variables:
+
+```
+GITLAB_ADMIN_USERNAME=admin GITLAB_ADMIN_PASSWORD=myadminpassword GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password bundle exec bin/qa Test::Instance::All https://gitlab.example.com
+```
+
If your user doesn't have permission to default sandbox group
`gitlab-qa-sandbox`, you could also use another sandbox group by giving
`GITLAB_SANDBOX_NAME`:
diff --git a/qa/qa.rb b/qa/qa.rb
index 8be2a289422..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'
@@ -160,6 +161,10 @@ module QA
module Group
autoload :New, 'qa/page/group/new'
autoload :Show, 'qa/page/group/show'
+
+ module Settings
+ autoload :General, 'qa/page/group/settings/general'
+ end
end
module File
@@ -207,6 +212,7 @@ module QA
autoload :Main, 'qa/page/project/settings/main'
autoload :Repository, 'qa/page/project/settings/repository'
autoload :CICD, 'qa/page/project/settings/ci_cd'
+ autoload :AutoDevops, 'qa/page/project/settings/auto_devops'
autoload :DeployKeys, 'qa/page/project/settings/deploy_keys'
autoload :DeployTokens, 'qa/page/project/settings/deploy_tokens'
autoload :ProtectedBranches, 'qa/page/project/settings/protected_branches'
@@ -303,8 +309,10 @@ module QA
autoload :Repository, 'qa/page/admin/settings/repository'
autoload :General, 'qa/page/admin/settings/general'
autoload :MetricsAndProfiling, 'qa/page/admin/settings/metrics_and_profiling'
+ autoload :Network, 'qa/page/admin/settings/network'
module Component
+ autoload :IpLimits, 'qa/page/admin/settings/component/ip_limits'
autoload :RepositoryStorage, 'qa/page/admin/settings/component/repository_storage'
autoload :AccountAndLimit, 'qa/page/admin/settings/component/account_and_limit'
autoload :PerformanceBar, 'qa/page/admin/settings/component/performance_bar'
@@ -339,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
@@ -359,6 +371,13 @@ module QA
autoload :KubernetesCluster, 'qa/service/kubernetes_cluster'
autoload :Omnibus, 'qa/service/omnibus'
autoload :Runner, 'qa/service/runner'
+
+ module ClusterProvider
+ autoload :Base, 'qa/service/cluster_provider/base'
+ autoload :Gcloud, 'qa/service/cluster_provider/gcloud'
+ autoload :Minikube, 'qa/service/cluster_provider/minikube'
+ autoload :K3d, 'qa/service/cluster_provider/k3d'
+ end
end
##
diff --git a/qa/qa/page/admin/menu.rb b/qa/qa/page/admin/menu.rb
index 61ec9854726..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
@@ -49,6 +58,14 @@ module QA
end
end
+ def go_to_network_settings
+ hover_settings do
+ within_submenu do
+ click_element :admin_settings_network_item
+ end
+ end
+ end
+
private
def hover_settings
@@ -75,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/admin/settings/component/ip_limits.rb b/qa/qa/page/admin/settings/component/ip_limits.rb
new file mode 100644
index 00000000000..9db2ae8ba58
--- /dev/null
+++ b/qa/qa/page/admin/settings/component/ip_limits.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Admin
+ module Settings
+ module Component
+ class IpLimits < Page::Base
+ view 'app/views/admin/application_settings/_ip_limits.html.haml' do
+ element :throttle_unauthenticated_checkbox
+ element :throttle_authenticated_api_checkbox
+ element :throttle_authenticated_web_checkbox
+ element :save_changes_button
+ end
+
+ def enable_throttles
+ check_element :throttle_unauthenticated_checkbox
+ check_element :throttle_authenticated_api_checkbox
+ check_element :throttle_authenticated_web_checkbox
+ end
+
+ def save_settings
+ click_element :save_changes_button
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/admin/settings/network.rb b/qa/qa/page/admin/settings/network.rb
new file mode 100644
index 00000000000..fdb8fcda281
--- /dev/null
+++ b/qa/qa/page/admin/settings/network.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Admin
+ module Settings
+ class Network < Page::Base
+ include QA::Page::Settings::Common
+
+ view 'app/views/admin/application_settings/network.html.haml' do
+ element :ip_limits_section
+ end
+
+ def expand_ip_limits(&block)
+ expand_section(:ip_limits_section) do
+ Component::IpLimits.perform(&block)
+ end
+ end
+ end
+ end
+ end
+ end
+end
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/group/settings/general.rb b/qa/qa/page/group/settings/general.rb
new file mode 100644
index 00000000000..07b421f154a
--- /dev/null
+++ b/qa/qa/page/group/settings/general.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Group
+ module Settings
+ class General < QA::Page::Base
+ view 'app/views/groups/edit.html.haml' do
+ element :permission_lfs_2fa_section
+ end
+ view 'app/views/groups/settings/_permissions.html.haml' do
+ element :save_permissions_changes_button
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index e3039149ab4..4676dc8d077 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -61,6 +61,10 @@ module QA
end
end
+ def sign_out_if_signed_in
+ sign_out if has_personal_area?(wait: 0)
+ end
+
def click_settings_link
retry_until(reload: false) do
within_user_menu do
diff --git a/qa/qa/page/merge_request/new.rb b/qa/qa/page/merge_request/new.rb
index 67459208c8d..f877ba76b38 100644
--- a/qa/qa/page/merge_request/new.rb
+++ b/qa/qa/page/merge_request/new.rb
@@ -64,3 +64,5 @@ module QA
end
end
end
+
+QA::Page::MergeRequest::New.prepend_if_ee('QA::EE::Page::MergeRequest::New')
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/auto_devops.rb b/qa/qa/page/project/settings/auto_devops.rb
new file mode 100644
index 00000000000..827d5b072c3
--- /dev/null
+++ b/qa/qa/page/project/settings/auto_devops.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Settings
+ class AutoDevops < Page::Base
+ view 'app/views/projects/settings/ci_cd/_autodevops_form.html.haml' do
+ element :enable_autodevops_checkbox
+ element :save_changes_button
+ end
+
+ def enable_autodevops
+ check_element :enable_autodevops_checkbox
+ click_element :save_changes_button
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb
index ae826fb3a32..45040cf4660 100644
--- a/qa/qa/page/project/settings/ci_cd.rb
+++ b/qa/qa/page/project/settings/ci_cd.rb
@@ -13,11 +13,6 @@ module QA
element :variables_settings_content
end
- view 'app/views/projects/settings/ci_cd/_autodevops_form.html.haml' do
- element :enable_autodevops_checkbox
- element :save_changes_button
- end
-
def expand_runners_settings(&block)
expand_section(:runners_settings_content) do
Settings::Runners.perform(&block)
@@ -30,10 +25,9 @@ module QA
end
end
- def enable_auto_devops
+ def expand_auto_devops(&block)
expand_section(:autodevops_settings_content) do
- check_element :enable_autodevops_checkbox
- click_element :save_changes_button
+ Settings::AutoDevops.perform(&block)
end
end
end
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/sub_menus/issues.rb b/qa/qa/page/project/sub_menus/issues.rb
index 8fb8fa06346..d27a250a300 100644
--- a/qa/qa/page/project/sub_menus/issues.rb
+++ b/qa/qa/page/project/sub_menus/issues.rb
@@ -10,6 +10,7 @@ module QA
def self.included(base)
base.class_eval do
view 'app/views/layouts/nav/sidebar/_project.html.haml' do
+ element :issue_boards_link
element :issues_item
element :labels_link
element :milestones_link
@@ -29,6 +30,14 @@ module QA
end
end
+ def go_to_boards
+ hover_issues do
+ within_submenu do
+ click_element(:issue_boards_link)
+ end
+ end
+ end
+
def go_to_labels
hover_issues do
within_submenu do
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/group.rb b/qa/qa/resource/group.rb
index 44d9dc8f296..b5beba64c61 100644
--- a/qa/qa/resource/group.rb
+++ b/qa/qa/resource/group.rb
@@ -10,6 +10,7 @@ module QA
end
attribute :id
+ attribute :name
def initialize
@path = Runtime::Namespace.name
@@ -47,6 +48,11 @@ module QA
super
end
+ def add_member(user, access_level = '30')
+ # 30 = developer access
+ post Runtime::API::Request.new(api_client, api_members_path).url, { user_id: user.id, access_level: access_level }
+ end
+
def api_get_path
"/groups/#{CGI.escape("#{sandbox.path}/#{path}")}"
end
diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb
index 45ab2396a04..53126c67ba3 100644
--- a/qa/qa/resource/merge_request.rb
+++ b/qa/qa/resource/merge_request.rb
@@ -5,7 +5,8 @@ require 'securerandom'
module QA
module Resource
class MergeRequest < Base
- attr_accessor :id,
+ attr_accessor :approval_rules,
+ :id,
:title,
:description,
:source_branch,
@@ -46,6 +47,7 @@ module QA
end
def initialize
+ @approval_rules = nil
@title = 'QA test - merge request'
@description = 'This is a test merge request'
@source_branch = "qa-test-feature-#{SecureRandom.hex(8)}"
@@ -63,16 +65,17 @@ module QA
project.visit!
Page::Project::Show.perform(&:new_merge_request)
- Page::MergeRequest::New.perform do |page|
- page.fill_title(@title)
- page.fill_description(@description)
- page.choose_milestone(@milestone) if @milestone
- page.assign_to_me if @assignee == 'me'
+ Page::MergeRequest::New.perform do |new|
+ new.fill_title(@title)
+ new.fill_description(@description)
+ new.choose_milestone(@milestone) if @milestone
+ new.assign_to_me if @assignee == 'me'
labels.each do |label|
- page.select_label(label)
+ new.select_label(label)
end
+ new.add_approval_rules(approval_rules) if approval_rules
- page.create_merge_request
+ new.create_merge_request
end
end
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 93a82094776..157064dfe37 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!
@@ -74,6 +75,11 @@ module QA
super
end
+ def add_member(user, access_level = '30')
+ # 30 = developer access
+ post Runtime::API::Request.new(api_client, api_members_path).url, { user_id: user.id, access_level: access_level }
+ end
+
def api_get_path
"/projects/#{CGI.escape(path_with_namespace)}"
end
@@ -82,6 +88,10 @@ module QA
"#{api_get_path}/repository/archive.#{type}"
end
+ def api_members_path
+ "#{api_get_path}/members"
+ end
+
def api_post_path
'/projects'
end
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/resource/sandbox.rb b/qa/qa/resource/sandbox.rb
index e2b1c4c0831..6eff0e87ddc 100644
--- a/qa/qa/resource/sandbox.rb
+++ b/qa/qa/resource/sandbox.rb
@@ -7,7 +7,7 @@ module QA
# creating it if it doesn't yet exist.
#
class Sandbox < Base
- attr_reader :path
+ attr_accessor :path
attribute :id
diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb
index eec46f46d99..911d2b2f506 100644
--- a/qa/qa/resource/user.rb
+++ b/qa/qa/resource/user.rb
@@ -9,6 +9,7 @@ module QA
attr_writer :username, :password
attr_accessor :provider, :extern_uid
+ attribute :id
attribute :name
attribute :email
diff --git a/qa/qa/runtime/api/client.rb b/qa/qa/runtime/api/client.rb
index 663be27a849..25d8f3c0fbb 100644
--- a/qa/qa/runtime/api/client.rb
+++ b/qa/qa/runtime/api/client.rb
@@ -8,11 +8,12 @@ module QA
class Client
attr_reader :address, :user
- def initialize(address = :gitlab, personal_access_token: nil, is_new_session: true, user: nil)
+ def initialize(address = :gitlab, personal_access_token: nil, is_new_session: true, user: nil, ip_limits: false)
@address = address
@personal_access_token = personal_access_token
@is_new_session = is_new_session
@user = user
+ enable_ip_limits if ip_limits
end
def personal_access_token
@@ -26,6 +27,24 @@ module QA
private
+ def enable_ip_limits
+ Page::Main::Menu.perform(&:sign_out) if Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) }
+
+ Runtime::Browser.visit(@address, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_admin_credentials)
+ Page::Main::Menu.perform(&:click_admin_area)
+ Page::Admin::Menu.perform(&:go_to_network_settings)
+
+ Page::Admin::Settings::Network.perform do |setting|
+ setting.expand_ip_limits do |page|
+ page.enable_throttles
+ page.save_settings
+ end
+ end
+
+ Page::Main::Menu.perform(&:sign_out)
+ end
+
def create_personal_access_token
Page::Main::Menu.perform(&:sign_out) if @is_new_session && Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) }
diff --git a/qa/qa/runtime/api/request.rb b/qa/qa/runtime/api/request.rb
index 310c1dfeeb4..724b499d32f 100644
--- a/qa/qa/runtime/api/request.rb
+++ b/qa/qa/runtime/api/request.rb
@@ -12,6 +12,10 @@ module QA
@session_address = Runtime::Address.new(api_client.address, request_path)
end
+ def mask_url
+ @session_address.address.sub(/private_token=.*/, "private_token=[****]")
+ end
+
def url
@session_address.address
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/runtime/fixtures.rb b/qa/qa/runtime/fixtures.rb
index 72004d5b00a..02cecffd4df 100644
--- a/qa/qa/runtime/fixtures.rb
+++ b/qa/qa/runtime/fixtures.rb
@@ -3,10 +3,19 @@
module QA
module Runtime
module Fixtures
+ include Support::Api
+
+ TemplateNotFoundError = Class.new(RuntimeError)
+
def fetch_template_from_api(api_path, key)
request = Runtime::API::Request.new(api_client, "/templates/#{api_path}/#{key}")
- get request.url
- json_body[:content]
+ response = get(request.url)
+
+ unless response.code == HTTP_STATUS_OK
+ raise TemplateNotFoundError, "Template at #{request.mask_url} could not be found (#{response.code}): `#{response}`."
+ end
+
+ parse_body(response)[:content]
end
private
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/cluster_provider/base.rb b/qa/qa/service/cluster_provider/base.rb
new file mode 100644
index 00000000000..a9678557aca
--- /dev/null
+++ b/qa/qa/service/cluster_provider/base.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module QA
+ module Service
+ module ClusterProvider
+ class Base
+ include Service::Shellout
+
+ attr_reader :rbac
+
+ def initialize(rbac:)
+ @rbac = rbac
+ end
+
+ def cluster_name
+ @cluster_name ||= "qa-cluster-#{Time.now.utc.strftime("%Y%m%d%H%M%S")}-#{SecureRandom.hex(4)}"
+ end
+
+ def set_credentials(admin_user)
+ raise NotImplementedError
+ end
+
+ def validate_dependencies
+ raise NotImplementedError
+ end
+
+ def setup
+ raise NotImplementedError
+ end
+
+ def teardown
+ raise NotImplementedError
+ end
+
+ def filter_credentials(credentials)
+ credentials
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/service/cluster_provider/gcloud.rb b/qa/qa/service/cluster_provider/gcloud.rb
new file mode 100644
index 00000000000..9c82151666c
--- /dev/null
+++ b/qa/qa/service/cluster_provider/gcloud.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+module QA
+ module Service
+ module ClusterProvider
+ class Gcloud < Base
+ def validate_dependencies
+ find_executable('gcloud') || raise("You must first install `gcloud` executable to run these tests.")
+ end
+
+ def set_credentials(admin_user)
+ master_auth = JSON.parse(`gcloud container clusters describe #{cluster_name} --region #{Runtime::Env.gcloud_region} --format 'json(masterAuth.username, masterAuth.password)'`)
+
+ shell <<~CMD.tr("\n", ' ')
+ kubectl config set-credentials #{admin_user}
+ --username #{master_auth['masterAuth']['username']}
+ --password #{master_auth['masterAuth']['password']}
+ CMD
+ end
+
+ def setup
+ login_if_not_already_logged_in
+ create_cluster
+ end
+
+ def teardown
+ delete_cluster
+ end
+
+ private
+
+ def login_if_not_already_logged_in
+ if Runtime::Env.has_gcloud_credentials?
+ attempt_login_with_env_vars
+ else
+ account = `gcloud auth list --filter=status:ACTIVE --format="value(account)"`
+ if account.empty?
+ raise "Failed to login to gcloud. No credentials provided in environment and no credentials found locally."
+ else
+ puts "gcloud account found. Using: #{account} for creating K8s cluster."
+ end
+ end
+ end
+
+ def attempt_login_with_env_vars
+ puts "No gcloud account. Attempting to login from env vars GCLOUD_ACCOUNT_EMAIL and GCLOUD_ACCOUNT_KEY."
+ gcloud_account_key = Tempfile.new('gcloud-account-key')
+ gcloud_account_key.write(Runtime::Env.gcloud_account_key)
+ gcloud_account_key.close
+ gcloud_account_email = Runtime::Env.gcloud_account_email
+ shell("gcloud auth activate-service-account #{gcloud_account_email} --key-file #{gcloud_account_key.path}")
+ ensure
+ gcloud_account_key && gcloud_account_key.unlink
+ end
+
+ def auth_options
+ "--enable-legacy-authorization" unless rbac
+ end
+
+ def create_cluster
+ shell <<~CMD.tr("\n", ' ')
+ gcloud container clusters
+ create #{cluster_name}
+ #{auth_options}
+ --enable-basic-auth
+ --region #{Runtime::Env.gcloud_region}
+ --disk-size 10GB
+ --num-nodes #{Runtime::Env.gcloud_num_nodes}
+ && gcloud container clusters
+ get-credentials
+ --region #{Runtime::Env.gcloud_region}
+ #{cluster_name}
+ CMD
+ end
+
+ def delete_cluster
+ shell <<~CMD.tr("\n", ' ')
+ gcloud container clusters delete
+ --region #{Runtime::Env.gcloud_region}
+ #{cluster_name}
+ --quiet --async
+ CMD
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/service/cluster_provider/k3d.rb b/qa/qa/service/cluster_provider/k3d.rb
new file mode 100644
index 00000000000..8e117c2dbd5
--- /dev/null
+++ b/qa/qa/service/cluster_provider/k3d.rb
@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+
+module QA
+ module Service
+ module ClusterProvider
+ class K3d < Base
+ def validate_dependencies
+ find_executable('k3d') || raise("You must first install `k3d` executable to run these tests.")
+ end
+
+ def set_credentials(admin_user)
+ end
+
+ def setup
+ shell "k3d create --workers 1 --name #{cluster_name} --wait 0"
+
+ @old_kubeconfig = ENV['KUBECONFIG']
+ ENV['KUBECONFIG'] = fetch_kubeconfig
+ raise "Could not fetch kubeconfig" unless ENV['KUBECONFIG']
+
+ install_local_storage
+ end
+
+ def teardown
+ ENV['KUBECONFIG'] = @old_kubeconfig
+ shell "k3d delete --name #{cluster_name}"
+ end
+
+ # Fetch "real" certificate
+ # See https://github.com/rancher/k3s/issues/27
+ def filter_credentials(credentials)
+ kubeconfig = YAML.load_file(ENV['KUBECONFIG'])
+ ca_certificate = kubeconfig.dig('clusters', 0, 'cluster', 'certificate-authority-data')
+
+ credentials.merge('data' => credentials['data'].merge('ca.crt' => ca_certificate))
+ end
+
+ private
+
+ def retry_until(max_attempts: 10, wait: 1)
+ max_attempts.times do
+ result = yield
+ return result if result
+
+ sleep wait
+ end
+
+ raise "Retried #{max_attempts} times. Aborting"
+ end
+
+ def fetch_kubeconfig
+ retry_until do
+ config = `k3d get-kubeconfig --name #{cluster_name}`.chomp
+ config if config =~ /kubeconfig.yaml/
+ end
+ end
+
+ def install_local_storage
+ shell('kubectl apply -f -', stdin_data: local_storage_config)
+ end
+
+ # See https://github.com/rancher/k3d/issues/67
+ def local_storage_config
+ <<~YAML
+ ---
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+ name: storage-provisioner
+ namespace: kube-system
+ ---
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRoleBinding
+ metadata:
+ name: storage-provisioner
+ roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: system:persistent-volume-provisioner
+ subjects:
+ - kind: ServiceAccount
+ name: storage-provisioner
+ namespace: kube-system
+ ---
+ apiVersion: v1
+ kind: Pod
+ metadata:
+ name: storage-provisioner
+ namespace: kube-system
+ spec:
+ serviceAccountName: storage-provisioner
+ tolerations:
+ - effect: NoExecute
+ key: node.kubernetes.io/not-ready
+ operator: Exists
+ tolerationSeconds: 300
+ - effect: NoExecute
+ key: node.kubernetes.io/unreachable
+ operator: Exists
+ tolerationSeconds: 300
+ hostNetwork: true
+ containers:
+ - name: storage-provisioner
+ image: gcr.io/k8s-minikube/storage-provisioner:v1.8.1
+ command: ["/storage-provisioner"]
+ imagePullPolicy: IfNotPresent
+ volumeMounts:
+ - mountPath: /tmp
+ name: tmp
+ volumes:
+ - name: tmp
+ hostPath:
+ path: /tmp
+ type: Directory
+ ---
+ kind: StorageClass
+ apiVersion: storage.k8s.io/v1
+ metadata:
+ name: standard
+ namespace: kube-system
+ annotations:
+ storageclass.kubernetes.io/is-default-class: "true"
+ labels:
+ addonmanager.kubernetes.io/mode: EnsureExists
+ provisioner: k8s.io/minikube-hostpath
+ YAML
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/service/cluster_provider/minikube.rb b/qa/qa/service/cluster_provider/minikube.rb
new file mode 100644
index 00000000000..fc916245357
--- /dev/null
+++ b/qa/qa/service/cluster_provider/minikube.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module QA
+ module Service
+ module ClusterProvider
+ class Minikube < Base
+ def validate_dependencies
+ find_executable('minikube') || raise("You must first install `minikube` executable to run these tests.")
+ end
+
+ def set_credentials(admin_user)
+ end
+
+ def setup
+ shell 'minikube stop'
+ shell "minikube profile #{cluster_name}"
+ shell 'minikube start'
+ end
+
+ def teardown
+ shell 'minikube delete'
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/service/kubernetes_cluster.rb b/qa/qa/service/kubernetes_cluster.rb
index ac0b6313167..26b5f58d2d3 100644
--- a/qa/qa/service/kubernetes_cluster.rb
+++ b/qa/qa/service/kubernetes_cluster.rb
@@ -9,90 +9,63 @@ module QA
class KubernetesCluster
include Service::Shellout
- attr_reader :api_url, :ca_certificate, :token, :rbac
+ attr_reader :api_url, :ca_certificate, :token, :rbac, :provider
- def initialize(rbac: true)
+ def initialize(rbac: true, provider_class: QA::Service::ClusterProvider::Gcloud)
@rbac = rbac
- end
-
- def cluster_name
- @cluster_name ||= "qa-cluster-#{SecureRandom.hex(4)}-#{Time.now.utc.strftime("%Y%m%d%H%M%S")}"
+ @provider = provider_class.new(rbac: rbac)
end
def create!
validate_dependencies
- login_if_not_already_logged_in
-
- shell <<~CMD.tr("\n", ' ')
- gcloud container clusters
- create #{cluster_name}
- #{auth_options}
- --enable-basic-auth
- --region #{Runtime::Env.gcloud_region}
- --disk-size 10GB
- --num-nodes #{Runtime::Env.gcloud_num_nodes}
- && gcloud container clusters
- get-credentials
- --region #{Runtime::Env.gcloud_region}
- #{cluster_name}
- CMD
-
- @api_url = `kubectl config view --minify -o jsonpath='{.clusters[].cluster.server}'`
-
- @admin_user = "#{cluster_name}-admin"
- master_auth = JSON.parse(`gcloud container clusters describe #{cluster_name} --region #{Runtime::Env.gcloud_region} --format 'json(masterAuth.username, masterAuth.password)'`)
- shell <<~CMD.tr("\n", ' ')
- kubectl config set-credentials #{@admin_user}
- --username #{master_auth['masterAuth']['username']}
- --password #{master_auth['masterAuth']['password']}
- CMD
-
- if rbac
- create_service_account
-
- secrets = JSON.parse(`kubectl get secrets -o json`)
- gitlab_account = secrets['items'].find do |item|
- item['metadata']['annotations']['kubernetes.io/service-account.name'] == 'gitlab-account'
- end
-
- @ca_certificate = Base64.decode64(gitlab_account['data']['ca.crt'])
- @token = Base64.decode64(gitlab_account['data']['token'])
- else
- @ca_certificate = Base64.decode64(`kubectl get secrets -o jsonpath="{.items[0].data['ca\\.crt']}"`)
- @token = Base64.decode64(`kubectl get secrets -o jsonpath='{.items[0].data.token}'`)
- end
+
+ @provider.validate_dependencies
+ @provider.setup
+
+ @api_url = fetch_api_url
+
+ credentials = @provider.filter_credentials(fetch_credentials)
+ @ca_certificate = Base64.decode64(credentials.dig('data', 'ca.crt'))
+ @token = Base64.decode64(credentials.dig('data', 'token'))
self
end
def remove!
- shell <<~CMD.tr("\n", ' ')
- gcloud container clusters delete
- --region #{Runtime::Env.gcloud_region}
- #{cluster_name}
- --quiet --async
- CMD
+ @provider.teardown
+ end
+
+ def cluster_name
+ @provider.cluster_name
end
private
- def create_service_account
- shell('kubectl create -f -', stdin_data: service_account)
- shell("kubectl --user #{@admin_user} create -f -", stdin_data: service_account_role_binding)
+ def fetch_api_url
+ `kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'`
+ end
+
+ def fetch_credentials
+ return global_credentials unless rbac
+
+ @provider.set_credentials(admin_user)
+ create_service_account(admin_user)
+ account_credentials
end
- def service_account
- <<~YAML
+ def admin_user
+ @admin_user ||= "#{@provider.cluster_name}-admin"
+ end
+
+ def create_service_account(user)
+ service_account = <<~YAML
+ ---
apiVersion: v1
kind: ServiceAccount
metadata:
name: gitlab-account
namespace: default
- YAML
- end
-
- def service_account_role_binding
- <<~YAML
+ ---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
@@ -106,39 +79,24 @@ module QA
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
YAML
- end
- def auth_options
- "--enable-legacy-authorization" unless rbac
+ shell('kubectl apply -f -', stdin_data: service_account)
end
- def validate_dependencies
- find_executable('gcloud') || raise("You must first install `gcloud` executable to run these tests.")
- find_executable('kubectl') || raise("You must first install `kubectl` executable to run these tests.")
- end
+ def account_credentials
+ secrets = JSON.parse(`kubectl get secrets -o json`)
- def login_if_not_already_logged_in
- if Runtime::Env.has_gcloud_credentials?
- attempt_login_with_env_vars
- else
- account = `gcloud auth list --filter=status:ACTIVE --format="value(account)"`
- if account.empty?
- raise "Failed to login to gcloud. No credentials provided in environment and no credentials found locally."
- else
- puts "gcloud account found. Using: #{account} for creating K8s cluster."
- end
+ secrets['items'].find do |item|
+ item['metadata']['annotations']['kubernetes.io/service-account.name'] == 'gitlab-account'
end
end
- def attempt_login_with_env_vars
- puts "No gcloud account. Attempting to login from env vars GCLOUD_ACCOUNT_EMAIL and GCLOUD_ACCOUNT_KEY."
- gcloud_account_key = Tempfile.new('gcloud-account-key')
- gcloud_account_key.write(Runtime::Env.gcloud_account_key)
- gcloud_account_key.close
- gcloud_account_email = Runtime::Env.gcloud_account_email
- shell("gcloud auth activate-service-account #{gcloud_account_email} --key-file #{gcloud_account_key.path}")
- ensure
- gcloud_account_key && gcloud_account_key.unlink
+ def global_credentials
+ JSON.parse(`kubectl get secrets -o jsonpath='{.items[0]}'`)
+ end
+
+ def validate_dependencies
+ find_executable('kubectl') || raise("You must first install `kubectl` executable to run these tests.")
end
end
end
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/api/1_manage/rate_limits_spec.rb b/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb
new file mode 100644
index 00000000000..44c5e0b4196
--- /dev/null
+++ b/qa/qa/specs/features/api/1_manage/rate_limits_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Manage with IP rate limits', :requires_admin do
+ describe 'Users API' do
+ before(:context) do
+ @api_client = Runtime::API::Client.new(:gitlab, ip_limits: true)
+ end
+
+ let(:request) { Runtime::API::Request.new(@api_client, '/users') }
+
+ it 'GET /users' do
+ 5.times do
+ get request.url
+ expect_status(200)
+ end
+ 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/2_plan/issue/filter_issue_comments_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
index 317e31feea8..cdb85902758 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Plan' do
+ # Failure issue https://gitlab.com/gitlab-org/quality/staging/issues/68
+ context 'Plan', :quarantine do
describe 'filter issue comments activities' do
let(:issue_title) { 'issue title' }
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
index 458072b1507..567c6a83ddf 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
@@ -1,8 +1,7 @@
# frozen_string_literal: true
module QA
- # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/127
- context 'Create', :quarantine do
+ context 'Create' do
describe 'File templates' do
include Runtime::Fixtures
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
index 9ff7919f199..c09c65a57a5 100644
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
@@ -1,8 +1,7 @@
# frozen_string_literal: true
module QA
- context 'Create', :quarantine do
- # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/127
+ context 'Create' do
describe 'Web IDE file templates' do
include Runtime::Fixtures
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/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index 60c1e105ae6..3f99ae644c7 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
@@ -147,23 +147,31 @@ module QA
end
describe 'Auto DevOps', :smoke do
- it 'enables AutoDevOps by default' do
+ before do
login
- project = Resource::Project.fabricate! do |p|
- p.name = Runtime::Env.auto_devops_project_name || 'project-with-autodevops'
+ @project = Resource::Project.fabricate_via_browser_ui! do |p|
+ p.name = "project-with-autodevops-#{SecureRandom.hex(8)}"
p.description = 'Project with AutoDevOps'
end
+ Page::Project::Menu.perform(&:go_to_ci_cd_settings)
+ Page::Project::Settings::CICD.perform(&:expand_auto_devops)
+ Page::Project::Settings::AutoDevops.perform(&:enable_autodevops)
+
+ @project.visit!
+
# Create AutoDevOps repo
Resource::Repository::ProjectPush.fabricate! do |push|
- push.project = project
+ push.project = @project
push.directory = Pathname
.new(__dir__)
.join('../../../../../fixtures/auto_devops_rack')
push.commit_message = 'Create AutoDevOps compatible Project'
end
+ end
+ it 'runs an AutoDevOps pipeline' do
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
diff --git a/rubocop/cop/gitlab/httparty.rb b/rubocop/cop/gitlab/httparty.rb
index 215f18b6993..8acebff624d 100644
--- a/rubocop/cop/gitlab/httparty.rb
+++ b/rubocop/cop/gitlab/httparty.rb
@@ -1,11 +1,9 @@
-require_relative '../../spec_helpers'
+# frozen_string_literal: true
module RuboCop
module Cop
module Gitlab
class HTTParty < RuboCop::Cop::Cop
- include SpecHelpers
-
MSG_SEND = <<~EOL.freeze
Avoid calling `HTTParty` directly. Instead, use the Gitlab::HTTP
wrapper. To allow request to localhost or the private network set
@@ -27,8 +25,6 @@ module RuboCop
PATTERN
def on_send(node)
- return if in_spec?(node)
-
add_offense(node, location: :expression, message: MSG_SEND) if httparty_node?(node)
add_offense(node, location: :expression, message: MSG_INCLUDE) if includes_httparty?(node)
end
diff --git a/rubocop/cop/gitlab/union.rb b/rubocop/cop/gitlab/union.rb
index 09541d8af3b..c44c847657b 100644
--- a/rubocop/cop/gitlab/union.rb
+++ b/rubocop/cop/gitlab/union.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require_relative '../../spec_helpers'
module RuboCop
module Cop
@@ -7,8 +6,6 @@ module RuboCop
# Cop that disallows the use of `Gitlab::SQL::Union`, in favour of using
# the `FromUnion` module.
class Union < RuboCop::Cop::Cop
- include SpecHelpers
-
MSG = 'Use the `FromUnion` concern, instead of using `Gitlab::SQL::Union` directly'
def_node_matcher :raw_union?, <<~PATTERN
@@ -17,7 +14,6 @@ module RuboCop
def on_send(node)
return unless raw_union?(node)
- return if in_spec?(node)
add_offense(node, location: :expression)
end
diff --git a/rubocop/cop/graphql/authorize_types.rb b/rubocop/cop/graphql/authorize_types.rb
index 93fe80c3edf..cd8bdbaee59 100644
--- a/rubocop/cop/graphql/authorize_types.rb
+++ b/rubocop/cop/graphql/authorize_types.rb
@@ -1,13 +1,9 @@
# frozen_string_literal: true
-require_relative '../../spec_helpers'
-
module RuboCop
module Cop
module Graphql
class AuthorizeTypes < RuboCop::Cop::Cop
- include SpecHelpers
-
MSG = 'Add an `authorize :ability` call to the type: '\
'https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#type-authorization'
@@ -32,8 +28,6 @@ module RuboCop
private
def in_type?(node)
- return if in_spec?(node)
-
path = node.location.expression.source_buffer.name
path.include?(TYPES_DIR)
diff --git a/rubocop/cop/include_action_view_context.rb b/rubocop/cop/include_action_view_context.rb
index 14662a33e95..52c84a711c9 100644
--- a/rubocop/cop/include_action_view_context.rb
+++ b/rubocop/cop/include_action_view_context.rb
@@ -1,13 +1,9 @@
# frozen_string_literal: true
-require_relative '../spec_helpers'
-
module RuboCop
module Cop
# Cop that makes sure workers include `::Gitlab::ActionViewOutput::Context`, not `ActionView::Context`.
class IncludeActionViewContext < RuboCop::Cop::Cop
- include SpecHelpers
-
MSG = 'Include `::Gitlab::ActionViewOutput::Context`, not `ActionView::Context`, for Rails 5.'.freeze
def_node_matcher :includes_action_view_context?, <<~PATTERN
@@ -15,7 +11,6 @@ module RuboCop
PATTERN
def on_send(node)
- return if in_spec?(node)
return unless includes_action_view_context?(node)
add_offense(node.arguments.first, location: :expression)
diff --git a/rubocop/cop/include_sidekiq_worker.rb b/rubocop/cop/include_sidekiq_worker.rb
index 8da4a147219..e69bc018add 100644
--- a/rubocop/cop/include_sidekiq_worker.rb
+++ b/rubocop/cop/include_sidekiq_worker.rb
@@ -1,11 +1,9 @@
-require_relative '../spec_helpers'
+# frozen_string_literal: true
module RuboCop
module Cop
# Cop that makes sure workers include `ApplicationWorker`, not `Sidekiq::Worker`.
class IncludeSidekiqWorker < RuboCop::Cop::Cop
- include SpecHelpers
-
MSG = 'Include `ApplicationWorker`, not `Sidekiq::Worker`.'.freeze
def_node_matcher :includes_sidekiq_worker?, <<~PATTERN
@@ -13,7 +11,6 @@ module RuboCop
PATTERN
def on_send(node)
- return if in_spec?(node)
return unless includes_sidekiq_worker?(node)
add_offense(node.arguments.first, location: :expression)
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/rubocop/cop/migration/add_limit_to_string_columns.rb b/rubocop/cop/migration/add_limit_to_string_columns.rb
new file mode 100644
index 00000000000..30affcbb089
--- /dev/null
+++ b/rubocop/cop/migration/add_limit_to_string_columns.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require_relative '../../migration_helpers'
+
+module RuboCop
+ module Cop
+ module Migration
+ # Cop that enforces length constraints to string columns
+ class AddLimitToStringColumns < RuboCop::Cop::Cop
+ include MigrationHelpers
+
+ ADD_COLUMNS_METHODS = %i(add_column add_column_with_default).freeze
+
+ MSG = 'String columns should have a limit constraint. 255 is suggested'.freeze
+
+ def on_def(node)
+ return unless in_migration?(node)
+
+ node.each_descendant(:send) do |send_node|
+ next unless string_operation?(send_node)
+
+ add_offense(send_node, location: :selector) unless limit_on_string_column?(send_node)
+ end
+ end
+
+ private
+
+ def string_operation?(node)
+ modifier = node.children[0]
+ migration_method = node.children[1]
+
+ if migration_method == :string
+ modifier.type == :lvar
+ elsif ADD_COLUMNS_METHODS.include?(migration_method)
+ modifier.nil? && string_column?(node.children[4])
+ end
+ end
+
+ def string_column?(column_type)
+ column_type.type == :sym && column_type.value == :string
+ end
+
+ def limit_on_string_column?(node)
+ migration_method = node.children[1]
+
+ if migration_method == :string
+ limit_present?(node.children)
+ elsif ADD_COLUMNS_METHODS.include?(migration_method)
+ limit_present?(node)
+ end
+ end
+
+ def limit_present?(statement)
+ !(statement.to_s =~ /:limit/).nil?
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/rspec/be_success_matcher.rb b/rubocop/cop/rspec/be_success_matcher.rb
new file mode 100644
index 00000000000..dce9604b3d7
--- /dev/null
+++ b/rubocop/cop/rspec/be_success_matcher.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module RuboCop
+ module Cop
+ module RSpec
+ # This cop checks for `be_success` usage in controller specs
+ #
+ # @example
+ #
+ # # bad
+ # it "responds with success" do
+ # expect(response).to be_success
+ # end
+ #
+ # it { is_expected.to be_success }
+ #
+ # # good
+ # it "responds with success" do
+ # expect(response).to be_successful
+ # end
+ #
+ # it { is_expected.to be_successful }
+ #
+ class BeSuccessMatcher < RuboCop::Cop::Cop
+ MESSAGE = 'Do not use deprecated `success?` method, use `successful?` instead.'
+
+ def_node_search :expect_to_be_success?, <<~PATTERN
+ (send (send nil? :expect (send nil? ...)) {:to :not_to :to_not} (send nil? :be_success))
+ PATTERN
+
+ def_node_search :is_expected_to_be_success?, <<~PATTERN
+ (send (send nil? :is_expected) {:to :not_to :to_not} (send nil? :be_success))
+ PATTERN
+
+ def be_success_usage?(node)
+ expect_to_be_success?(node) || is_expected_to_be_success?(node)
+ end
+
+ def on_send(node)
+ return unless be_success_usage?(node)
+
+ add_offense(node, location: :expression, message: MESSAGE)
+ end
+
+ def autocorrect(node)
+ lambda do |corrector|
+ corrector.insert_after(node.loc.expression, 'ful')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/rspec/env_assignment.rb b/rubocop/cop/rspec/env_assignment.rb
index 8b61fa8e264..73e108c2232 100644
--- a/rubocop/cop/rspec/env_assignment.rb
+++ b/rubocop/cop/rspec/env_assignment.rb
@@ -1,4 +1,4 @@
-require_relative '../../spec_helpers'
+# frozen_string_literal: true
module RuboCop
module Cop
@@ -17,8 +17,6 @@ module RuboCop
# stub_env('FOO', 'bar')
# end
class EnvAssignment < RuboCop::Cop::Cop
- include SpecHelpers
-
MESSAGE = "Don't assign to ENV, use `stub_env` instead.".freeze
def_node_search :env_assignment?, <<~PATTERN
@@ -28,7 +26,6 @@ module RuboCop
# Following is what node.children looks like on a match
# [s(:const, nil, :ENV), :[]=, s(:str, "key"), s(:str, "value")]
def on_send(node)
- return unless in_spec?(node)
return unless env_assignment?(node)
add_offense(node, location: :expression, message: MESSAGE)
diff --git a/rubocop/cop/rspec/factories_in_migration_specs.rb b/rubocop/cop/rspec/factories_in_migration_specs.rb
index 0c5aa838a2c..65c7638a0f4 100644
--- a/rubocop/cop/rspec/factories_in_migration_specs.rb
+++ b/rubocop/cop/rspec/factories_in_migration_specs.rb
@@ -1,4 +1,4 @@
-require_relative '../../spec_helpers'
+# frozen_string_literal: true
module RuboCop
module Cop
@@ -14,8 +14,6 @@ module RuboCop
# let(:users) { table(:users) }
# let(:user) { users.create!(name: 'User 1', username: 'user1') }
class FactoriesInMigrationSpecs < RuboCop::Cop::Cop
- include SpecHelpers
-
MESSAGE = "Don't use FactoryBot.%s in migration specs, use `table` instead.".freeze
FORBIDDEN_METHODS = %i[build build_list create create_list].freeze
@@ -27,7 +25,6 @@ module RuboCop
# - Without FactoryBot namespace: [nil, :build, s(:sym, :user)]
# - With FactoryBot namespace: [s(:const, nil, :FactoryBot), :build, s(:sym, :user)]
def on_send(node)
- return unless in_migration_spec?(node)
return unless forbidden_factory_usage?(node)
method = node.children[1]
diff --git a/rubocop/cop/sidekiq_options_queue.rb b/rubocop/cop/sidekiq_options_queue.rb
index 253d2958866..499c712175e 100644
--- a/rubocop/cop/sidekiq_options_queue.rb
+++ b/rubocop/cop/sidekiq_options_queue.rb
@@ -1,11 +1,9 @@
-require_relative '../spec_helpers'
+# frozen_string_literal: true
module RuboCop
module Cop
# Cop that prevents manually setting a queue in Sidekiq workers.
class SidekiqOptionsQueue < RuboCop::Cop::Cop
- include SpecHelpers
-
MSG = 'Do not manually set a queue; `ApplicationWorker` sets one automatically.'.freeze
def_node_matcher :sidekiq_options?, <<~PATTERN
@@ -13,7 +11,6 @@ module RuboCop
PATTERN
def on_send(node)
- return if in_spec?(node)
return unless sidekiq_options?(node)
node.arguments.first.each_node(:pair) do |pair|
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 58a7ead6f13..c342df6d6c9 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -17,6 +17,7 @@ require_relative 'cop/migration/add_column'
require_relative 'cop/migration/add_concurrent_foreign_key'
require_relative 'cop/migration/add_concurrent_index'
require_relative 'cop/migration/add_index'
+require_relative 'cop/migration/add_limit_to_string_columns'
require_relative 'cop/migration/add_reference'
require_relative 'cop/migration/add_timestamps'
require_relative 'cop/migration/datetime'
@@ -30,6 +31,7 @@ require_relative 'cop/migration/timestamps'
require_relative 'cop/migration/update_column_in_batches'
require_relative 'cop/migration/update_large_table'
require_relative 'cop/project_path_helper'
+require_relative 'cop/rspec/be_success_matcher'
require_relative 'cop/rspec/env_assignment'
require_relative 'cop/rspec/factories_in_migration_specs'
require_relative 'cop/rspec/top_level_describe_path'
diff --git a/rubocop/spec_helpers.rb b/rubocop/spec_helpers.rb
deleted file mode 100644
index ecd77c4351d..00000000000
--- a/rubocop/spec_helpers.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-module RuboCop
- module SpecHelpers
- SPEC_HELPERS = %w[fast_spec_helper.rb rails_helper.rb spec_helper.rb].freeze
- MIGRATION_SPEC_DIRECTORIES = ['spec/migrations', 'spec/lib/gitlab/background_migration'].freeze
-
- # Returns true if the given node originated from the spec directory.
- def in_spec?(node)
- path = node.location.expression.source_buffer.name
- pwd = RuboCop::PathUtil.pwd
-
- !SPEC_HELPERS.include?(File.basename(path)) &&
- path.start_with?(File.join(pwd, 'spec'), File.join(pwd, 'ee', 'spec'))
- end
-
- def migration_directories
- @migration_directories ||= MIGRATION_SPEC_DIRECTORIES.map do |dir|
- pwd = RuboCop::PathUtil.pwd
- [File.join(pwd, dir), File.join(pwd, 'ee', dir)]
- end.flatten
- end
-
- # Returns true if the given node originated from a migration spec.
- def in_migration_spec?(node)
- path = node.location.expression.source_buffer.name
-
- in_spec?(node) &&
- path.start_with?(*migration_directories)
- end
- end
-end
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..a9549171b54 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -36,7 +36,7 @@ function previous_deploy_failed() {
return $status
}
-function delete() {
+function delete_release() {
if [ -z "$CI_ENVIRONMENT_SLUG" ]; then
echoerr "No release given, aborting the delete!"
return
@@ -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 --location -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}"
@@ -193,7 +193,8 @@ function deploy() {
HELM_CMD=$(cat << EOF
helm upgrade --install \
- --atomic \
+ --force \
+ --wait \
--timeout 900 \
--set releaseOverride="$CI_ENVIRONMENT_SLUG" \
--set global.appConfig.enableUsagePing=false \
diff --git a/shared/.gitignore b/shared/.gitignore
new file mode 100644
index 00000000000..5c0276adafd
--- /dev/null
+++ b/shared/.gitignore
@@ -0,0 +1,4 @@
+*
+!*/
+!.gitignore
+!.gitkeep
diff --git a/spec/config/smime_signature_settings_spec.rb b/spec/config/smime_signature_settings_spec.rb
index 4f0c227d866..38f96e9b330 100644
--- a/spec/config/smime_signature_settings_spec.rb
+++ b/spec/config/smime_signature_settings_spec.rb
@@ -1,4 +1,4 @@
-require 'fast_spec_helper'
+require 'spec_helper'
describe SmimeSignatureSettings do
describe '.parse' do
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/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index 35487682462..b79a1ac4810 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -83,6 +83,13 @@ describe RegistrationsController do
stub_application_setting(recaptcha_enabled: true)
end
+ after do
+ # Avoid test ordering issue and ensure `verify_recaptcha` returns true
+ unless Recaptcha.configuration.skip_verify_env.include?('test')
+ Recaptcha.configuration.skip_verify_env << 'test'
+ end
+ end
+
it 'displays an error when the reCAPTCHA is not solved' do
fail_recaptcha
@@ -93,11 +100,6 @@ describe RegistrationsController do
end
it 'redirects to the dashboard when the recaptcha is solved' do
- # Avoid test ordering issue and ensure `verify_recaptcha` returns true
- unless Recaptcha.configuration.skip_verify_env.include?('test')
- Recaptcha.configuration.skip_verify_env << 'test'
- end
-
post(:create, params: user_params)
expect(flash[:notice]).to include 'Welcome! You have signed up successfully.'
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/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb
index 2d68a8e9fe3..6f553cadfa3 100644
--- a/spec/factories/ci/job_artifacts.rb
+++ b/spec/factories/ci/job_artifacts.rb
@@ -8,6 +8,10 @@ FactoryBot.define do
file_type :archive
file_format :zip
+ trait :expired do
+ expire_at { Date.yesterday }
+ end
+
trait :remote_store do
file_store JobArtifactUploader::Store::REMOTE
end
diff --git a/spec/factories/deploy_tokens.rb b/spec/factories/deploy_tokens.rb
index a96258f5cbe..99486acc2ab 100644
--- a/spec/factories/deploy_tokens.rb
+++ b/spec/factories/deploy_tokens.rb
@@ -2,7 +2,8 @@
FactoryBot.define do
factory :deploy_token do
- token { SecureRandom.hex(50) }
+ token nil
+ token_encrypted { Gitlab::CryptoHelper.aes256_gcm_encrypt( SecureRandom.hex(50) ) }
sequence(:name) { |n| "PDT #{n}" }
read_repository true
read_registry true
diff --git a/spec/factories/external_pull_requests.rb b/spec/factories/external_pull_requests.rb
new file mode 100644
index 00000000000..08d0fa4d419
--- /dev/null
+++ b/spec/factories/external_pull_requests.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :external_pull_request do
+ sequence(:pull_request_iid)
+ project
+ source_branch 'feature'
+ source_repository 'the-repository'
+ source_sha '97de212e80737a608d939f648d959671fb0a0142'
+ target_branch 'master'
+ target_repository 'the-repository'
+ target_sha 'a09386439ca39abe575675ffd4b89ae824fec22f'
+ status :open
+
+ trait(:closed) { status 'closed' }
+ end
+end
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/sequences.rb b/spec/factories/sequences.rb
index b6f2d6d8389..17b54d69372 100644
--- a/spec/factories/sequences.rb
+++ b/spec/factories/sequences.rb
@@ -7,7 +7,7 @@ FactoryBot.define do
sequence(:email_alias) { |n| "user.alias#{n}@example.org" }
sequence(:title) { |n| "My title #{n}" }
sequence(:filename) { |n| "filename-#{n}.rb" }
- sequence(:url) { |n| "http://example#{n}.org" }
+ sequence(:url) { |n| "http://example#{n}.test" }
sequence(:label_title) { |n| "label#{n}" }
sequence(:branch) { |n| "my-branch-#{n}" }
sequence(:past_time) { |n| 4.hours.ago + (2 * n).seconds }
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_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index 4ad90c96558..0d5f5df71b6 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -282,10 +282,6 @@ describe "Admin Runners" do
visit admin_runner_path(runner)
end
- describe 'runner info' do
- it { expect(find_field('runner_token').value).to eq runner.token }
- end
-
describe 'projects' do
it 'contains project names' do
expect(page).to have_content(@project1.full_name)
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/admin/clusters/applications_spec.rb b/spec/features/admin/clusters/applications_spec.rb
new file mode 100644
index 00000000000..8310811b43d
--- /dev/null
+++ b/spec/features/admin/clusters/applications_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_relative '../../../../spec/features/clusters/installing_applications_shared_examples'
+
+describe 'Instance-level Cluster Applications', :js do
+ include GoogleApi::CloudPlatformHelpers
+
+ let(:user) { create(:admin) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'Installing applications' do
+ include_examples "installing applications on a cluster" do
+ let(:cluster_path) { admin_cluster_path(cluster) }
+ let(:cluster_factory_args) { [:instance] }
+ end
+ end
+end
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 4e7b25115d7..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
@@ -236,6 +236,15 @@ describe 'Issue Boards', :js do
expect(find('.board:nth-child(2)')).to have_content(planning.title)
end
+ it 'dragging does not duplicate list' do
+ selector = '.board:not(.is-ghost) .board-header'
+ expect(page).to have_selector(selector, text: development.title, count: 1)
+
+ drag(list_from_index: 2, list_to_index: 1, selector: '.board-header', perform_drop: false)
+
+ expect(page).to have_selector(selector, text: development.title, count: 1)
+ end
+
it 'issue moves between lists' do
drag(list_from_index: 1, from_index: 1, list_to_index: 2)
@@ -576,7 +585,7 @@ describe 'Issue Boards', :js do
end
end
- def drag(selector: '.board-list', list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0)
+ def drag(selector: '.board-list', list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0, perform_drop: true)
# ensure there is enough horizontal space for four boards
resize_window(2000, 800)
@@ -585,7 +594,8 @@ describe 'Issue Boards', :js do
list_from_index: list_from_index,
from_index: from_index,
to_index: to_index,
- list_to_index: list_to_index)
+ list_to_index: list_to_index,
+ perform_drop: perform_drop)
end
def wait_for_board_cards(board_number, expected_cards)
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..730887370dd 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) }
@@ -127,4 +127,44 @@ describe 'Issue Boards new issue', :js do
end
end
end
+
+ context 'group boards' do
+ set(:group) { create(:group, :public) }
+ set(:project) { create(:project, namespace: group) }
+ set(:group_board) { create(:board, group: group) }
+ set(:list) { create(:list, board: group_board, position: 0) }
+
+ context 'for unauthorized users' do
+ before do
+ sign_in(user)
+ visit group_board_path(group, group_board)
+ wait_for_requests
+ end
+
+ it 'displays new issue button in open list' do
+ expect(first('.board')).to have_selector('.issue-count-badge-add-button', count: 1)
+ end
+
+ it 'does not display new issue button in label list' do
+ page.within('.board.is-draggable') do
+ expect(page).not_to have_selector('.issue-count-badge-add-button')
+ end
+ end
+ end
+
+ context 'for authorized users' do
+ it 'display new issue button in label list' do
+ project = create(:project, namespace: group)
+ project.add_reporter(user)
+
+ sign_in(user)
+ visit group_board_path(group, group_board)
+ wait_for_requests
+
+ page.within('.board.is-draggable') do
+ expect(page).to have_selector('.issue-count-badge-add-button')
+ end
+ end
+ end
+ end
end
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/clusters/installing_applications_shared_examples.rb b/spec/features/clusters/installing_applications_shared_examples.rb
new file mode 100644
index 00000000000..cb8fd8c607c
--- /dev/null
+++ b/spec/features/clusters/installing_applications_shared_examples.rb
@@ -0,0 +1,228 @@
+# frozen_string_literal: true
+
+shared_examples "installing applications on a cluster" do
+ before do
+ visit cluster_path
+ end
+
+ context 'when cluster is being created' do
+ let(:cluster) { create(:cluster, :providing_by_gcp, *cluster_factory_args) }
+
+ it 'user is unable to install applications' do
+ expect(page).not_to have_text('Helm')
+ expect(page).not_to have_text('Install')
+ end
+ end
+
+ context 'when cluster is created' do
+ let(:cluster) { create(:cluster, :provided_by_gcp, *cluster_factory_args) }
+
+ it 'user can install applications' do
+ wait_for_requests
+
+ page.within('.js-cluster-application-row-helm') do
+ expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to be_nil
+ expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Install')
+ end
+ end
+
+ context 'when user installs Helm' do
+ before do
+ allow(ClusterInstallAppWorker).to receive(:perform_async)
+
+ page.within('.js-cluster-application-row-helm') do
+ page.find(:css, '.js-cluster-application-install-button').click
+ end
+
+ wait_for_requests
+ end
+
+ it 'shows the status transition' do
+ page.within('.js-cluster-application-row-helm') do
+ # FE sends request and gets the response, then the buttons is "Installing"
+ expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installing')
+
+ Clusters::Cluster.last.application_helm.make_installing!
+
+ # FE starts polling and update the buttons to "Installing"
+ expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installing')
+
+ Clusters::Cluster.last.application_helm.make_installed!
+
+ expect(page).not_to have_css('button', exact_text: 'Install', visible: :all)
+ expect(page).not_to have_css('button', exact_text: 'Installing', visible: :all)
+ expect(page).to have_css('.js-cluster-application-uninstall-button:not([disabled])', exact_text: 'Uninstall')
+ end
+
+ expect(page).to have_content('Helm Tiller was successfully installed on your Kubernetes cluster')
+ end
+ end
+
+ context 'when user installs Knative' do
+ before do
+ create(:clusters_applications_helm, :installed, cluster: cluster)
+ end
+
+ context 'on an abac cluster' do
+ let(:cluster) { create(:cluster, :provided_by_gcp, :rbac_disabled, *cluster_factory_args) }
+
+ it 'shows info block and not be installable' do
+ page.within('.js-cluster-application-row-knative') do
+ expect(page).to have_css('.rbac-notice')
+ expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true')
+ end
+ end
+ end
+
+ context 'on an rbac cluster' do
+ let(:cluster) { create(:cluster, :provided_by_gcp, *cluster_factory_args) }
+
+ it 'does not show callout block and be installable' do
+ page.within('.js-cluster-application-row-knative') do
+ expect(page).not_to have_css('p', text: 'You must have an RBAC-enabled cluster', visible: :all)
+ expect(page).to have_css('.js-cluster-application-install-button:not([disabled])')
+ end
+ end
+
+ describe 'when user clicks install button' do
+ def domainname_form_value
+ page.find('.js-knative-domainname').value
+ end
+
+ before do
+ allow(ClusterInstallAppWorker).to receive(:perform_async)
+ allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
+ allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
+
+ page.within('.js-cluster-application-row-knative') do
+ expect(page).to have_css('.js-cluster-application-install-button:not([disabled])')
+
+ page.find('.js-knative-domainname').set("domain.example.org")
+
+ click_button 'Install'
+
+ wait_for_requests
+
+ expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
+
+ Clusters::Cluster.last.application_knative.make_installing!
+ Clusters::Cluster.last.application_knative.make_installed!
+ Clusters::Cluster.last.application_knative.update_attribute(:external_ip, '127.0.0.1')
+ end
+ end
+
+ it 'shows status transition' do
+ page.within('.js-cluster-application-row-knative') do
+ expect(domainname_form_value).to eq('domain.example.org')
+ expect(page).to have_css('.js-cluster-application-uninstall-button', exact_text: 'Uninstall')
+ end
+
+ expect(page).to have_content('Knative was successfully installed on your Kubernetes cluster')
+ expect(page).to have_css('.js-knative-save-domain-button'), exact_text: 'Save changes'
+ end
+
+ it 'can then update the domain' do
+ page.within('.js-cluster-application-row-knative') do
+ expect(ClusterPatchAppWorker).to receive(:perform_async)
+
+ expect(domainname_form_value).to eq('domain.example.org')
+
+ page.find('.js-knative-domainname').set("new.domain.example.org")
+
+ click_button 'Save changes'
+
+ wait_for_requests
+
+ expect(domainname_form_value).to eq('new.domain.example.org')
+ end
+ end
+ end
+ end
+ end
+
+ context 'when user installs Cert Manager' do
+ before do
+ allow(ClusterInstallAppWorker).to receive(:perform_async)
+ allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
+ allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
+
+ create(:clusters_applications_helm, :installed, cluster: cluster)
+
+ page.within('.js-cluster-application-row-cert_manager') do
+ click_button 'Install'
+ end
+ end
+
+ it 'shows status transition' do
+ def email_form_value
+ page.find('.js-email').value
+ end
+
+ page.within('.js-cluster-application-row-cert_manager') do
+ expect(email_form_value).to eq(cluster.user.email)
+ expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
+
+ page.find('.js-email').set("new_email@example.org")
+ Clusters::Cluster.last.application_cert_manager.make_installing!
+
+ expect(email_form_value).to eq('new_email@example.org')
+ expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
+
+ Clusters::Cluster.last.application_cert_manager.make_installed!
+
+ expect(email_form_value).to eq('new_email@example.org')
+ expect(page).to have_css('.js-cluster-application-uninstall-button', exact_text: 'Uninstall')
+ end
+
+ expect(page).to have_content('Cert-Manager was successfully installed on your Kubernetes cluster')
+ end
+ end
+
+ context 'when user installs Ingress' do
+ before do
+ allow(ClusterInstallAppWorker).to receive(:perform_async)
+ allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
+ allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
+
+ create(:clusters_applications_helm, :installed, cluster: cluster)
+
+ page.within('.js-cluster-application-row-ingress') do
+ expect(page).to have_css('.js-cluster-application-install-button:not([disabled])')
+ page.find(:css, '.js-cluster-application-install-button').click
+
+ wait_for_requests
+ end
+ end
+
+ it 'shows the status transition' do
+ page.within('.js-cluster-application-row-ingress') do
+ # FE sends request and gets the response, then the buttons is "Installing"
+ expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installing')
+
+ Clusters::Cluster.last.application_ingress.make_installing!
+
+ # FE starts polling and update the buttons to "Installing"
+ expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installing')
+
+ # The application becomes installed but we keep waiting for external IP address
+ Clusters::Cluster.last.application_ingress.make_installed!
+
+ expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installed')
+ expect(page).to have_selector('.js-no-endpoint-message')
+ expect(page).to have_selector('.js-ingress-ip-loading-icon')
+
+ # We receive the external IP address and display
+ Clusters::Cluster.last.application_ingress.update!(external_ip: '192.168.1.100')
+
+ expect(page).not_to have_css('button', exact_text: 'Install', visible: :all)
+ expect(page).not_to have_css('button', exact_text: 'Installing', visible: :all)
+ expect(page).to have_css('.js-cluster-application-uninstall-button:not([disabled])', exact_text: 'Uninstall')
+ expect(page).not_to have_css('p', text: 'The endpoint is in the process of being assigned', visible: :all)
+ expect(page.find('.js-endpoint').value).to eq('192.168.1.100')
+ end
+
+ expect(page).to have_content('Ingress was successfully installed on your Kubernetes cluster')
+ end
+ end
+ end
+end
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/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index e2100c8562b..973d5a2dcfc 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -169,7 +169,7 @@ describe 'Dashboard Projects' do
expect(page).to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit, ref: pipeline.ref)}']")
expect(page).to have_css('.ci-status-link')
expect(page).to have_css('.ci-status-icon-success')
- expect(page).to have_link('Commit: passed')
+ expect(page).to have_link('Pipeline: passed')
end
end
@@ -189,7 +189,7 @@ describe 'Dashboard Projects' do
expect(page).not_to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit, ref: pipeline.ref)}']")
expect(page).not_to have_css('.ci-status-link')
expect(page).not_to have_css('.ci-status-icon-success')
- expect(page).not_to have_link('Commit: passed')
+ expect(page).not_to have_link('Pipeline: passed')
end
end
end
@@ -220,4 +220,26 @@ describe 'Dashboard Projects' do
expect(find('input#merge_request_target_branch', visible: false).value).to eq 'master'
end
end
+
+ it 'avoids an N+1 query in dashboard index' do
+ create(:ci_pipeline, :with_job, status: :success, project: project, ref: project.default_branch, sha: project.commit.sha)
+ visit dashboard_projects_path
+
+ control_count = ActiveRecord::QueryRecorder.new { visit dashboard_projects_path }.count
+
+ new_project = create(:project, :repository, name: 'new project')
+ create(:ci_pipeline, :with_job, status: :success, project: new_project, ref: new_project.commit.sha)
+ new_project.add_developer(user)
+
+ ActiveRecord::QueryRecorder.new { visit dashboard_projects_path }.count
+
+ # There are three known N+1 queries:
+ # 1. Project#open_issues_count
+ # 2. Project#open_merge_requests_count
+ # 3. Project#forks_count
+ #
+ # In addition, ProjectsHelper#load_pipeline_status also adds an
+ # additional query.
+ expect { visit dashboard_projects_path }.not_to exceed_query_limit(control_count + 4)
+ end
end
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/dashboard/todos/todos_filtering_spec.rb b/spec/features/dashboard/todos/todos_filtering_spec.rb
index f273e416597..efa163042f9 100644
--- a/spec/features/dashboard/todos/todos_filtering_spec.rb
+++ b/spec/features/dashboard/todos/todos_filtering_spec.rb
@@ -31,9 +31,9 @@ describe 'Dashboard > User filters todos', :js do
end
it 'displays all todos without a filter' do
- expect(page).to have_content issue1.to_reference(full: true)
- expect(page).to have_content merge_request.to_reference(full: true)
- expect(page).to have_content issue2.to_reference(full: true)
+ expect(page).to have_content issue1.to_reference(full: false)
+ expect(page).to have_content merge_request.to_reference(full: false)
+ expect(page).to have_content issue2.to_reference(full: false)
end
it 'filters by project' do
@@ -58,9 +58,9 @@ describe 'Dashboard > User filters todos', :js do
wait_for_requests
- expect(page).to have_content issue1.to_reference(full: true)
- expect(page).to have_content merge_request.to_reference(full: true)
- expect(page).not_to have_content issue2.to_reference(full: true)
+ expect(page).to have_content "issue #{issue1.to_reference} \"issue\" at #{group1.name} / project_1"
+ expect(page).to have_content "merge request #{merge_request.to_reference}"
+ expect(page).not_to have_content "issue #{issue2.to_reference} \"issue\" at #{group2.name} / project_3"
end
context 'Author filter' do
diff --git a/spec/features/dashboard/todos/todos_sorting_spec.rb b/spec/features/dashboard/todos/todos_sorting_spec.rb
index 3870c661784..421a66c6d48 100644
--- a/spec/features/dashboard/todos/todos_sorting_spec.rb
+++ b/spec/features/dashboard/todos/todos_sorting_spec.rb
@@ -42,33 +42,33 @@ describe 'Dashboard > User sorts todos' do
click_link 'Last created'
results_list = page.find('.todos-list')
- expect(results_list.all('p')[0]).to have_content('merge_request_1')
- expect(results_list.all('p')[1]).to have_content('issue_1')
- expect(results_list.all('p')[2]).to have_content('issue_3')
- expect(results_list.all('p')[3]).to have_content('issue_2')
- expect(results_list.all('p')[4]).to have_content('issue_4')
+ expect(results_list.all('.todo-title')[0]).to have_content('merge_request_1')
+ expect(results_list.all('.todo-title')[1]).to have_content('issue_1')
+ expect(results_list.all('.todo-title')[2]).to have_content('issue_3')
+ expect(results_list.all('.todo-title')[3]).to have_content('issue_2')
+ expect(results_list.all('.todo-title')[4]).to have_content('issue_4')
end
it 'sorts with newest created todos first' do
click_link 'Oldest created'
results_list = page.find('.todos-list')
- expect(results_list.all('p')[0]).to have_content('issue_4')
- expect(results_list.all('p')[1]).to have_content('issue_2')
- expect(results_list.all('p')[2]).to have_content('issue_3')
- expect(results_list.all('p')[3]).to have_content('issue_1')
- expect(results_list.all('p')[4]).to have_content('merge_request_1')
+ expect(results_list.all('.todo-title')[0]).to have_content('issue_4')
+ expect(results_list.all('.todo-title')[1]).to have_content('issue_2')
+ expect(results_list.all('.todo-title')[2]).to have_content('issue_3')
+ expect(results_list.all('.todo-title')[3]).to have_content('issue_1')
+ expect(results_list.all('.todo-title')[4]).to have_content('merge_request_1')
end
it 'sorts by label priority' do
click_link 'Label priority'
results_list = page.find('.todos-list')
- expect(results_list.all('p')[0]).to have_content('issue_3')
- expect(results_list.all('p')[1]).to have_content('merge_request_1')
- expect(results_list.all('p')[2]).to have_content('issue_1')
- expect(results_list.all('p')[3]).to have_content('issue_2')
- expect(results_list.all('p')[4]).to have_content('issue_4')
+ expect(results_list.all('.todo-title')[0]).to have_content('issue_3')
+ expect(results_list.all('.todo-title')[1]).to have_content('merge_request_1')
+ expect(results_list.all('.todo-title')[2]).to have_content('issue_1')
+ expect(results_list.all('.todo-title')[3]).to have_content('issue_2')
+ expect(results_list.all('.todo-title')[4]).to have_content('issue_4')
end
end
@@ -93,9 +93,9 @@ describe 'Dashboard > User sorts todos' do
click_link 'Label priority'
results_list = page.find('.todos-list')
- expect(results_list.all('p')[0]).to have_content('issue_1')
- expect(results_list.all('p')[1]).to have_content('issue_2')
- expect(results_list.all('p')[2]).to have_content('merge_request_1')
+ expect(results_list.all('.todo-title')[0]).to have_content('issue_1')
+ expect(results_list.all('.todo-title')[1]).to have_content('issue_2')
+ expect(results_list.all('.todo-title')[2]).to have_content('merge_request_1')
end
end
end
diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb
index b98a04b0bda..867281da1e6 100644
--- a/spec/features/dashboard/todos/todos_spec.rb
+++ b/spec/features/dashboard/todos/todos_spec.rb
@@ -3,10 +3,10 @@
require 'spec_helper'
describe 'Dashboard Todos' do
- let(:user) { create(:user) }
+ let(:user) { create(:user, username: 'john') }
let(:author) { create(:user) }
let(:project) { create(:project, :public) }
- let(:issue) { create(:issue, due_date: Date.today) }
+ let(:issue) { create(:issue, due_date: Date.today, title: "Fix bug") }
context 'User does not have todos' do
before do
@@ -135,7 +135,7 @@ describe 'Dashboard Todos' do
it 'shows issue assigned to yourself message' do
page.within('.js-todos-all') do
- expect(page).to have_content("You assigned issue #{issue.to_reference(full: true)} to yourself")
+ expect(page).to have_content("You assigned issue #{issue.to_reference} \"Fix bug\" at #{project.namespace.owner_name} / #{project.name} to yourself")
end
end
end
@@ -148,7 +148,7 @@ describe 'Dashboard Todos' do
it 'shows you added a todo message' do
page.within('.js-todos-all') do
- expect(page).to have_content("You added a todo for issue #{issue.to_reference(full: true)}")
+ expect(page).to have_content("You added a todo for issue #{issue.to_reference} \"Fix bug\" at #{project.namespace.owner_name} / #{project.name}")
expect(page).not_to have_content('to yourself')
end
end
@@ -162,7 +162,7 @@ describe 'Dashboard Todos' do
it 'shows you mentioned yourself message' do
page.within('.js-todos-all') do
- expect(page).to have_content("You mentioned yourself on issue #{issue.to_reference(full: true)}")
+ expect(page).to have_content("You mentioned yourself on issue #{issue.to_reference} \"Fix bug\" at #{project.namespace.owner_name} / #{project.name}")
expect(page).not_to have_content('to yourself')
end
end
@@ -176,14 +176,14 @@ describe 'Dashboard Todos' do
it 'shows you directly addressed yourself message' do
page.within('.js-todos-all') do
- expect(page).to have_content("You directly addressed yourself on issue #{issue.to_reference(full: true)}")
+ expect(page).to have_content("You directly addressed yourself on issue #{issue.to_reference} \"Fix bug\" at #{project.namespace.owner_name} / #{project.name}")
expect(page).not_to have_content('to yourself')
end
end
end
context 'approval todo' do
- let(:merge_request) { create(:merge_request) }
+ let(:merge_request) { create(:merge_request, title: "Fixes issue") }
before do
create(:todo, :approval_required, user: user, project: project, target: merge_request, author: user)
@@ -192,7 +192,7 @@ describe 'Dashboard Todos' do
it 'shows you set yourself as an approver message' do
page.within('.js-todos-all') do
- expect(page).to have_content("You set yourself as an approver for merge request #{merge_request.to_reference(full: true)}")
+ expect(page).to have_content("You set yourself as an approver for merge request #{merge_request.to_reference} \"Fixes issue\" at #{project.namespace.owner_name} / #{project.name}")
expect(page).not_to have_content('to yourself')
end
end
@@ -354,7 +354,7 @@ describe 'Dashboard Todos' do
it 'links to the pipelines for the merge request' do
href = pipelines_project_merge_request_path(project, todo.target)
- expect(page).to have_link "merge request #{todo.target.to_reference(full: true)}", href: href
+ expect(page).to have_link "merge request #{todo.target.to_reference}", href: href
end
end
end
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/applications_spec.rb b/spec/features/groups/clusters/applications_spec.rb
new file mode 100644
index 00000000000..5d48df234eb
--- /dev/null
+++ b/spec/features/groups/clusters/applications_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_relative '../../../../spec/features/clusters/installing_applications_shared_examples'
+
+describe 'Group-level Cluster Applications', :js do
+ include GoogleApi::CloudPlatformHelpers
+
+ let(:group) { create(:group) }
+ let(:user) { create(:user) }
+
+ before do
+ group.add_maintainer(user)
+ sign_in(user)
+ end
+
+ describe 'Installing applications' do
+ include_examples "installing applications on a cluster" do
+ let(:cluster_path) { group_cluster_path(group, cluster) }
+ let(:cluster_factory_args) { [:group, groups: [group]] }
+ end
+ end
+end
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/help_pages_spec.rb b/spec/features/help_pages_spec.rb
index bcd2b90d3bb..88a7aa51326 100644
--- a/spec/features/help_pages_spec.rb
+++ b/spec/features/help_pages_spec.rb
@@ -58,7 +58,7 @@ describe 'Help Pages' do
before do
stub_application_setting(version_check_enabled: true)
- allow(Rails.env).to receive(:production?).and_return(true)
+ stub_rails_env('production')
allow(VersionCheck).to receive(:url).and_return('/version-check-url')
sign_in(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..7d89b8e97a6 100644
--- a/spec/features/merge_request/user_posts_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_notes_spec.rb
@@ -1,11 +1,12 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Merge request > User posts notes', :js do
include NoteInteractionHelpers
- let(:project) { create(:project, :repository) }
+ set(:project) { create(:project, :repository) }
+
let(:user) { project.creator }
let(:merge_request) do
create(:merge_request, source_project: project, target_project: project)
@@ -33,17 +34,21 @@ describe 'Merge request > User posts notes', :js do
end
describe 'with text' do
+ let(:text) { 'This is awesome' }
+
before do
page.within('.js-main-target-form') do
- fill_in 'note[note]', with: 'This is awesome'
+ fill_in 'note[note]', with: text
end
end
- it 'has enable submit button and preview button' do
+ it 'has enable submit button, preview button and saves content to local storage' do
page.within('.js-main-target-form') do
expect(page).not_to have_css('.js-comment-button[disabled]')
expect(page).to have_css('.js-md-preview-button', visible: true)
end
+
+ expect(page.evaluate_script("localStorage['autosave/Note/MergeRequest/#{merge_request.id}']")).to eq(text)
end
end
end
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..c42eb8560a4 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) }
@@ -64,7 +64,7 @@ describe 'Merge request > User selects branches for new MR', :js do
click_button "Check out branch"
- expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch'
+ expect(page).to have_content 'git checkout -b "orphaned-branch" "origin/orphaned-branch"'
end
it 'allows filtering multiple dropdowns' do
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/oauth_login_spec.rb b/spec/features/oauth_login_spec.rb
index a47eaa9bda7..c6e69fa3fb0 100644
--- a/spec/features/oauth_login_spec.rb
+++ b/spec/features/oauth_login_spec.rb
@@ -55,6 +55,18 @@ describe 'OAuth Login', :js, :allow_forgery_protection do
expect(current_path).to eq root_path
end
+
+ it 'when bypass-two-factor is enabled' do
+ allow(Gitlab.config.omniauth).to receive_messages(allow_bypass_two_factor: true)
+ login_via(provider.to_s, user, uid, remember_me: false)
+ expect(current_path).to eq root_path
+ end
+
+ it 'when bypass-two-factor is disabled' do
+ allow(Gitlab.config.omniauth).to receive_messages(allow_bypass_two_factor: false)
+ login_with_provider(provider, enter_two_factor: true)
+ expect(current_path).to eq root_path
+ end
end
context 'when "remember me" is checked' do
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/applications_spec.rb b/spec/features/projects/clusters/applications_spec.rb
index 3d15095e2da..ce971b158a3 100644
--- a/spec/features/projects/clusters/applications_spec.rb
+++ b/spec/features/projects/clusters/applications_spec.rb
@@ -1,8 +1,9 @@
# frozen_string_literal: true
require 'spec_helper'
+require_relative '../../../../spec/features/clusters/installing_applications_shared_examples'
-describe 'Clusters Applications', :js do
+describe 'Project-level Cluster Applications', :js do
include GoogleApi::CloudPlatformHelpers
let(:project) { create(:project) }
@@ -14,229 +15,9 @@ describe 'Clusters Applications', :js do
end
describe 'Installing applications' do
- before do
- visit project_cluster_path(project, cluster)
- end
-
- context 'when cluster is being created' do
- let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) }
-
- it 'user is unable to install applications' do
- expect(page).not_to have_css('.js-cluster-application-row-helm')
- expect(page).not_to have_css('.js-cluster-application-install-button')
- end
- end
-
- context 'when cluster is created' do
- let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
-
- it 'user can install applications' do
- wait_for_requests
-
- page.within('.js-cluster-application-row-helm') do
- expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to be_nil
- expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Install')
- end
- end
-
- context 'when user installs Helm' do
- before do
- allow(ClusterInstallAppWorker).to receive(:perform_async)
-
- page.within('.js-cluster-application-row-helm') do
- page.find(:css, '.js-cluster-application-install-button').click
- end
-
- wait_for_requests
- end
-
- it 'they see status transition' do
- page.within('.js-cluster-application-row-helm') do
- # FE sends request and gets the response, then the buttons is "Installing"
- expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installing')
-
- Clusters::Cluster.last.application_helm.make_installing!
-
- # FE starts polling and update the buttons to "Installing"
- expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installing')
-
- Clusters::Cluster.last.application_helm.make_installed!
-
- expect(page).not_to have_css('.js-cluster-application-install-button')
- expect(page).to have_css('.js-cluster-application-uninstall-button:not([disabled])', exact_text: 'Uninstall')
- end
-
- expect(page).to have_content('Helm Tiller was successfully installed on your Kubernetes cluster')
- end
- end
-
- context 'when user installs Knative' do
- before do
- create(:clusters_applications_helm, :installed, cluster: cluster)
- end
-
- context 'on an abac cluster' do
- let(:cluster) { create(:cluster, :provided_by_gcp, :rbac_disabled, projects: [project]) }
-
- it 'shows info block and not be installable' do
- page.within('.js-cluster-application-row-knative') do
- expect(page).to have_css('.rbac-notice')
- expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true')
- end
- end
- end
-
- context 'on an rbac cluster' do
- let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
-
- it 'does not show callout block and be installable' do
- page.within('.js-cluster-application-row-knative') do
- expect(page).not_to have_css('.rbac-notice')
- expect(page).to have_css('.js-cluster-application-install-button:not([disabled])')
- end
- end
-
- describe 'when user clicks install button' do
- def domainname_form_value
- page.find('.js-knative-domainname').value
- end
-
- before do
- allow(ClusterInstallAppWorker).to receive(:perform_async)
- allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
- allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
-
- page.within('.js-cluster-application-row-knative') do
- expect(page).to have_css('.js-cluster-application-install-button:not([disabled])')
-
- page.find('.js-knative-domainname').set("domain.example.org")
-
- click_button 'Install'
-
- wait_for_requests
-
- expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
-
- Clusters::Cluster.last.application_knative.make_installing!
- Clusters::Cluster.last.application_knative.make_installed!
- Clusters::Cluster.last.application_knative.update_attribute(:external_ip, '127.0.0.1')
- end
- end
-
- it 'shows status transition' do
- page.within('.js-cluster-application-row-knative') do
- expect(domainname_form_value).to eq('domain.example.org')
- expect(page).to have_css('.js-cluster-application-uninstall-button', exact_text: 'Uninstall')
- end
-
- expect(page).to have_content('Knative was successfully installed on your Kubernetes cluster')
- expect(page).to have_css('.js-knative-save-domain-button'), exact_text: 'Save changes'
- end
-
- it 'can then update the domain' do
- page.within('.js-cluster-application-row-knative') do
- expect(ClusterPatchAppWorker).to receive(:perform_async)
-
- expect(domainname_form_value).to eq('domain.example.org')
-
- page.find('.js-knative-domainname').set("new.domain.example.org")
-
- click_button 'Save changes'
-
- wait_for_requests
-
- expect(domainname_form_value).to eq('new.domain.example.org')
- end
- end
- end
- end
- end
-
- context 'when user installs Cert Manager' do
- before do
- allow(ClusterInstallAppWorker).to receive(:perform_async)
- allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
- allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
-
- create(:clusters_applications_helm, :installed, cluster: cluster)
-
- page.within('.js-cluster-application-row-cert_manager') do
- click_button 'Install'
- end
- end
-
- it 'shows status transition' do
- def email_form_value
- page.find('.js-email').value
- end
-
- page.within('.js-cluster-application-row-cert_manager') do
- expect(email_form_value).to eq(cluster.user.email)
- expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
-
- page.find('.js-email').set("new_email@example.org")
- Clusters::Cluster.last.application_cert_manager.make_installing!
-
- expect(email_form_value).to eq('new_email@example.org')
- expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
-
- Clusters::Cluster.last.application_cert_manager.make_installed!
-
- expect(email_form_value).to eq('new_email@example.org')
- expect(page).to have_css('.js-cluster-application-uninstall-button', exact_text: 'Uninstall')
- end
-
- expect(page).to have_content('Cert-Manager was successfully installed on your Kubernetes cluster')
- end
- end
-
- context 'when user installs Ingress' do
- context 'when user installs application: Ingress' do
- before do
- allow(ClusterInstallAppWorker).to receive(:perform_async)
- allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
- allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
-
- create(:clusters_applications_helm, :installed, cluster: cluster)
-
- page.within('.js-cluster-application-row-ingress') do
- expect(page).to have_css('.js-cluster-application-install-button:not([disabled])')
- page.find(:css, '.js-cluster-application-install-button').click
-
- wait_for_requests
- end
- end
-
- it 'they see status transition' do
- page.within('.js-cluster-application-row-ingress') do
- # FE sends request and gets the response, then the buttons is "Installing"
- expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installing')
-
- Clusters::Cluster.last.application_ingress.make_installing!
-
- # FE starts polling and update the buttons to "Installing"
- expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installing')
-
- # The application becomes installed but we keep waiting for external IP address
- Clusters::Cluster.last.application_ingress.make_installed!
-
- expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installed')
- expect(page).to have_selector('.js-no-endpoint-message')
- expect(page).to have_selector('.js-ingress-ip-loading-icon')
-
- # We receive the external IP address and display
- Clusters::Cluster.last.application_ingress.update!(external_ip: '192.168.1.100')
-
- expect(page).not_to have_css('.js-cluster-application-install-button')
- expect(page).to have_css('.js-cluster-application-uninstall-button:not([disabled])', exact_text: 'Uninstall')
- expect(page).not_to have_selector('.js-no-endpoint-message')
- expect(page.find('.js-endpoint').value).to eq('192.168.1.100')
- end
-
- expect(page).to have_content('Ingress was successfully installed on your Kubernetes cluster')
- end
- end
- end
+ include_examples "installing applications on a cluster" do
+ let(:cluster_path) { project_cluster_path(project, cluster) }
+ let(:cluster_factory_args) { [projects: [project]] }
end
end
end
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_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb
index a090461261b..0b3f905b5de 100644
--- a/spec/features/projects/files/user_browses_files_spec.rb
+++ b/spec/features/projects/files/user_browses_files_spec.rb
@@ -14,7 +14,6 @@ describe "User browses files" do
before do
stub_feature_flags(vue_file_list: false)
- stub_feature_flags(csslab: false)
sign_in(user)
end
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/show/user_sees_last_commit_ci_status_spec.rb b/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb
index a1cad261875..fdc238d55cf 100644
--- a/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb
+++ b/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb
@@ -18,7 +18,7 @@ describe 'Projects > Show > User sees last commit CI status' do
page.within '.blob-commit-info' do
expect(page).to have_content(project.commit.sha[0..6])
- expect(page).to have_link('Commit: skipped')
+ expect(page).to have_link('Pipeline: skipped')
end
end
end
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..8b3f193f418 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
@@ -37,7 +37,7 @@ describe 'User can display performance bar', :js do
shared_examples 'performance bar is enabled by default in development' do
before do
- allow(Rails.env).to receive(:development?).and_return(true)
+ stub_rails_env('development')
end
it 'shows the performance bar by default' 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/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 879ff01f294..ef8749be0be 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -42,6 +42,24 @@ describe IssuesFinder do
end
end
+ context 'filtering by projects' do
+ context 'when projects are passed in a list of ids' do
+ let(:params) { { projects: [project1.id] } }
+
+ it 'returns the issue belonging to the projects' do
+ expect(issues).to contain_exactly(issue1)
+ end
+ end
+
+ context 'when projects are passed in a subquery' do
+ let(:params) { { projects: Project.id_in(project1.id) } }
+
+ it 'returns the issue belonging to the projects' do
+ expect(issues).to contain_exactly(issue1)
+ end
+ end
+ end
+
context 'filtering by group_id' do
let(:params) { { group_id: group.id } }
@@ -49,6 +67,30 @@ describe IssuesFinder do
it 'returns all group issues' do
expect(issues).to contain_exactly(issue1)
end
+
+ context 'when projects outside the group are passed' do
+ let(:params) { { group_id: group.id, projects: [project2.id] } }
+
+ it 'returns no issues' do
+ expect(issues).to be_empty
+ end
+ end
+
+ context 'when projects of the group are passed' do
+ let(:params) { { group_id: group.id, projects: [project1.id] } }
+
+ it 'returns the issue within the group and projects' do
+ expect(issues).to contain_exactly(issue1)
+ end
+ end
+
+ context 'when projects of the group are passed as a subquery' do
+ let(:params) { { group_id: group.id, projects: Project.id_in(project1.id) } }
+
+ it 'returns the issue within the group and projects' do
+ expect(issues).to contain_exactly(issue1)
+ end
+ end
end
context 'when include_subgroup param is true' do
@@ -59,6 +101,14 @@ describe IssuesFinder do
it 'returns all group and subgroup issues' do
expect(issues).to contain_exactly(issue1, issue4)
end
+
+ context 'when mixed projects are passed' do
+ let(:params) { { group_id: group.id, projects: [project2.id, project3.id] } }
+
+ it 'returns the issue within the group and projects' do
+ expect(issues).to contain_exactly(issue4)
+ end
+ end
end
end
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/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 78224f0b9da..6c0bbeff4f4 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -13,7 +13,7 @@ describe MergeRequestsFinder do
expect(merge_requests).to contain_exactly(merge_request1, merge_request4, merge_request5)
end
- it 'filters by project' do
+ it 'filters by project_id' do
params = { project_id: project1.id, scope: 'authored', state: 'opened' }
merge_requests = described_class.new(user, params).execute
@@ -21,6 +21,14 @@ describe MergeRequestsFinder do
expect(merge_requests).to contain_exactly(merge_request1)
end
+ it 'filters by projects' do
+ params = { projects: [project2.id, project3.id] }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request3, merge_request4)
+ end
+
it 'filters by commit sha' do
merge_requests = described_class.new(
user,
@@ -49,6 +57,16 @@ describe MergeRequestsFinder do
expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request5)
end
+
+ it 'filters by group projects including subgroups' do
+ # project3 is not in the group, so it should not return merge_request4
+ projects = [project3.id, project4.id]
+ params = { group_id: group.id, include_subgroups: true, projects: projects }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(merge_request5)
+ end
end
it 'filters by non_archived' do
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..d37f5b864d7
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/merge_request_noteable.json
@@ -0,0 +1,32 @@
+{
+ "type": "object",
+ "properties" : {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "title": { "type": "string" },
+ "description": { "type": "string" },
+ "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/components/applications_spec.js b/spec/frontend/clusters/components/applications_spec.js
index 221ebb143be..fbcab078993 100644
--- a/spec/frontend/clusters/components/applications_spec.js
+++ b/spec/frontend/clusters/components/applications_spec.js
@@ -85,11 +85,48 @@ describe('Applications', () => {
});
it('renders a row for Jupyter', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-jupyter')).toBeNull();
+ expect(vm.$el.querySelector('.js-cluster-application-row-jupyter')).not.toBeNull();
});
it('renders a row for Knative', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-knative')).toBeNull();
+ expect(vm.$el.querySelector('.js-cluster-application-row-knative')).not.toBeNull();
+ });
+ });
+
+ describe('Instance cluster applications', () => {
+ beforeEach(() => {
+ vm = mountComponent(Applications, {
+ type: CLUSTER_TYPE.INSTANCE,
+ applications: APPLICATIONS_MOCK_STATE,
+ });
+ });
+
+ it('renders a row for Helm Tiller', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-helm')).not.toBeNull();
+ });
+
+ it('renders a row for Ingress', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-ingress')).not.toBeNull();
+ });
+
+ it('renders a row for Cert-Manager', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).not.toBeNull();
+ });
+
+ it('renders a row for Prometheus', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).not.toBeNull();
+ });
+
+ it('renders a row for GitLab Runner', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-runner')).not.toBeNull();
+ });
+
+ it('renders a row for Jupyter', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-jupyter')).not.toBeNull();
+ });
+
+ it('renders a row for Knative', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-knative')).not.toBeNull();
});
});
diff --git a/spec/frontend/clusters/stores/clusters_store_spec.js b/spec/frontend/clusters/stores/clusters_store_spec.js
index f2cc413512d..98077498998 100644
--- a/spec/frontend/clusters/stores/clusters_store_spec.js
+++ b/spec/frontend/clusters/stores/clusters_store_spec.js
@@ -51,6 +51,9 @@ describe('Clusters Store', () => {
expect(store.state).toEqual({
helpPath: null,
ingressHelpPath: null,
+ environmentsHelpPath: null,
+ clustersHelpPath: null,
+ deployBoardsHelpPath: null,
status: mockResponseData.status,
statusReason: mockResponseData.status_reason,
rbac: false,
@@ -86,7 +89,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,
@@ -148,6 +151,7 @@ describe('Clusters Store', () => {
uninstallFailed: false,
},
},
+ environments: [],
});
});
diff --git a/spec/frontend/error_tracking/components/error_tracking_list_spec.js b/spec/frontend/error_tracking/components/error_tracking_list_spec.js
index 67e5dc399ac..ce8b8908026 100644
--- a/spec/frontend/error_tracking/components/error_tracking_list_spec.js
+++ b/spec/frontend/error_tracking/components/error_tracking_list_spec.js
@@ -11,19 +11,24 @@ describe('ErrorTrackingList', () => {
let wrapper;
let actions;
- function mountComponent({ errorTrackingEnabled = true } = {}) {
+ function mountComponent({
+ errorTrackingEnabled = true,
+ userCanEnableErrorTracking = true,
+ stubs = {
+ 'gl-link': GlLink,
+ },
+ } = {}) {
wrapper = shallowMount(ErrorTrackingList, {
localVue,
store,
propsData: {
indexPath: '/path',
enableErrorTrackingLink: '/link',
+ userCanEnableErrorTracking,
errorTrackingEnabled,
illustrationPath: 'illustration/path',
},
- stubs: {
- 'gl-link': GlLink,
- },
+ stubs,
});
}
@@ -115,4 +120,23 @@ describe('ErrorTrackingList', () => {
expect(wrapper.find(GlButton).exists()).toBeFalsy();
});
});
+
+ describe('When error tracking is disabled and user is not allowed to enable it', () => {
+ beforeEach(() => {
+ mountComponent({
+ errorTrackingEnabled: false,
+ userCanEnableErrorTracking: false,
+ stubs: {
+ 'gl-link': GlLink,
+ 'gl-empty-state': GlEmptyState,
+ },
+ });
+ });
+
+ it('shows empty state', () => {
+ expect(wrapper.find('a').attributes('href')).toBe(
+ '/help/user/project/operations/error_tracking.html',
+ );
+ });
+ });
});
diff --git a/spec/frontend/ide/components/preview/clientside_spec.js b/spec/frontend/ide/components/preview/clientside_spec.js
new file mode 100644
index 00000000000..dfc76628d0c
--- /dev/null
+++ b/spec/frontend/ide/components/preview/clientside_spec.js
@@ -0,0 +1,318 @@
+import Vuex from 'vuex';
+import { GlLoadingIcon } from '@gitlab/ui';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import smooshpack from 'smooshpack';
+import Clientside from '~/ide/components/preview/clientside.vue';
+
+jest.mock('smooshpack', () => ({
+ Manager: jest.fn(),
+}));
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+const dummyPackageJson = () => ({
+ raw: JSON.stringify({
+ main: 'index.js',
+ }),
+});
+
+describe('IDE clientside preview', () => {
+ let wrapper;
+ let store;
+ const storeActions = {
+ getFileData: jest.fn().mockReturnValue(Promise.resolve({})),
+ getRawFileData: jest.fn().mockReturnValue(Promise.resolve('')),
+ };
+
+ const waitForCalls = () => new Promise(setImmediate);
+
+ const createComponent = ({ state, getters } = {}) => {
+ store = new Vuex.Store({
+ state: {
+ entries: {},
+ links: {},
+ ...state,
+ },
+ getters: {
+ packageJson: () => '',
+ currentProject: () => ({
+ visibility: 'public',
+ }),
+ ...getters,
+ },
+ actions: storeActions,
+ });
+
+ wrapper = shallowMount(Clientside, {
+ sync: false,
+ store,
+ localVue,
+ });
+ };
+
+ beforeAll(() => {
+ jest.useFakeTimers();
+ });
+
+ afterAll(() => {
+ jest.useRealTimers();
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('without main entry', () => {
+ it('creates sandpack manager', () => {
+ createComponent();
+ expect(smooshpack.Manager).not.toHaveBeenCalled();
+ });
+ });
+ describe('with main entry', () => {
+ beforeEach(() => {
+ createComponent({ getters: { packageJson: dummyPackageJson } });
+ return wrapper.vm.initPreview();
+ });
+
+ it('creates sandpack manager', () => {
+ expect(smooshpack.Manager).toHaveBeenCalledWith(
+ '#ide-preview',
+ {
+ files: {},
+ entry: '/index.js',
+ showOpenInCodeSandbox: true,
+ },
+ {
+ fileResolver: {
+ isFile: expect.any(Function),
+ readFile: expect.any(Function),
+ },
+ },
+ );
+ });
+ });
+
+ describe('computed', () => {
+ describe('normalizedEntries', () => {
+ it('returns flattened list of blobs with content', () => {
+ createComponent({
+ state: {
+ entries: {
+ 'index.js': { type: 'blob', raw: 'test' },
+ 'index2.js': { type: 'blob', content: 'content' },
+ tree: { type: 'tree' },
+ empty: { type: 'blob' },
+ },
+ },
+ });
+
+ expect(wrapper.vm.normalizedEntries).toEqual({
+ '/index.js': {
+ code: 'test',
+ },
+ '/index2.js': {
+ code: 'content',
+ },
+ });
+ });
+ });
+
+ describe('mainEntry', () => {
+ it('returns false when package.json is empty', () => {
+ createComponent();
+ expect(wrapper.vm.mainEntry).toBe(false);
+ });
+
+ it('returns main key from package.json', () => {
+ createComponent({ getters: { packageJson: dummyPackageJson } });
+ expect(wrapper.vm.mainEntry).toBe('index.js');
+ });
+ });
+
+ describe('showPreview', () => {
+ it('returns false if no mainEntry', () => {
+ createComponent();
+ expect(wrapper.vm.showPreview).toBe(false);
+ });
+
+ it('returns false if loading and mainEntry exists', () => {
+ createComponent({ getters: { packageJson: dummyPackageJson } });
+ wrapper.setData({ loading: true });
+
+ expect(wrapper.vm.showPreview).toBe(false);
+ });
+
+ it('returns true if not loading and mainEntry exists', () => {
+ createComponent({ getters: { packageJson: dummyPackageJson } });
+ wrapper.setData({ loading: false });
+
+ expect(wrapper.vm.showPreview).toBe(true);
+ });
+ });
+
+ describe('showEmptyState', () => {
+ it('returns true if no mainEntry exists', () => {
+ createComponent();
+ wrapper.setData({ loading: false });
+ expect(wrapper.vm.showEmptyState).toBe(true);
+ });
+
+ it('returns false if loading', () => {
+ createComponent();
+ wrapper.setData({ loading: true });
+
+ expect(wrapper.vm.showEmptyState).toBe(false);
+ });
+
+ it('returns false if not loading and mainEntry exists', () => {
+ createComponent({ getters: { packageJson: dummyPackageJson } });
+ wrapper.setData({ loading: false });
+
+ expect(wrapper.vm.showEmptyState).toBe(false);
+ });
+ });
+
+ describe('showOpenInCodeSandbox', () => {
+ it('returns true when visiblity is public', () => {
+ createComponent({ getters: { currentProject: () => ({ visibility: 'public' }) } });
+
+ expect(wrapper.vm.showOpenInCodeSandbox).toBe(true);
+ });
+
+ it('returns false when visiblity is private', () => {
+ createComponent({ getters: { currentProject: () => ({ visibility: 'private' }) } });
+
+ expect(wrapper.vm.showOpenInCodeSandbox).toBe(false);
+ });
+ });
+
+ describe('sandboxOpts', () => {
+ beforeEach(() => {
+ createComponent({
+ state: {
+ entries: {
+ 'index.js': { type: 'blob', raw: 'test' },
+ 'package.json': dummyPackageJson(),
+ },
+ },
+ getters: {
+ packageJson: dummyPackageJson,
+ },
+ });
+ });
+
+ it('returns sandbox options', () => {
+ expect(wrapper.vm.sandboxOpts).toEqual({
+ files: {
+ '/index.js': {
+ code: 'test',
+ },
+ '/package.json': {
+ code: '{"main":"index.js"}',
+ },
+ },
+ entry: '/index.js',
+ showOpenInCodeSandbox: true,
+ });
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('loadFileContent', () => {
+ beforeEach(() => {
+ createComponent();
+ return wrapper.vm.loadFileContent('package.json');
+ });
+
+ it('calls getFileData', () => {
+ expect(storeActions.getFileData).toHaveBeenCalledWith(
+ expect.any(Object),
+ {
+ path: 'package.json',
+ makeFileActive: false,
+ },
+ undefined, // vuex callback
+ );
+ });
+
+ it('calls getRawFileData', () => {
+ expect(storeActions.getRawFileData).toHaveBeenCalledWith(
+ expect.any(Object),
+ {
+ path: 'package.json',
+ },
+ undefined, // vuex callback
+ );
+ });
+ });
+
+ describe('update', () => {
+ beforeEach(() => {
+ createComponent();
+ wrapper.setData({ sandpackReady: true });
+ });
+
+ it('initializes manager if manager is empty', () => {
+ createComponent({ getters: { packageJson: dummyPackageJson } });
+ wrapper.setData({ sandpackReady: true });
+ wrapper.vm.update();
+
+ jest.advanceTimersByTime(250);
+
+ return waitForCalls().then(() => {
+ expect(smooshpack.Manager).toHaveBeenCalled();
+ });
+ });
+
+ it('calls updatePreview', () => {
+ wrapper.setData({
+ manager: {
+ listener: jest.fn(),
+ updatePreview: jest.fn(),
+ },
+ });
+ wrapper.vm.update();
+
+ jest.advanceTimersByTime(250);
+ expect(wrapper.vm.manager.updatePreview).toHaveBeenCalledWith(wrapper.vm.sandboxOpts);
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders ide-preview element when showPreview is true', () => {
+ createComponent({ getters: { packageJson: dummyPackageJson } });
+ wrapper.setData({ loading: false });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find('#ide-preview').exists()).toBe(true);
+ });
+ });
+
+ it('renders empty state', () => {
+ createComponent();
+ wrapper.setData({ loading: false });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.text()).toContain(
+ 'Preview your web application using Web IDE client-side evaluation.',
+ );
+ });
+ });
+
+ it('renders loading icon', () => {
+ createComponent();
+ wrapper.setData({ loading: true });
+
+ return wrapper.vm.$nextTick(() => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ });
+ });
+ });
+});
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/lib/utils/axios_utils_spec.js b/spec/frontend/lib/utils/axios_utils_spec.js
new file mode 100644
index 00000000000..d5c39567f06
--- /dev/null
+++ b/spec/frontend/lib/utils/axios_utils_spec.js
@@ -0,0 +1,45 @@
+/* eslint-disable promise/catch-or-return */
+
+import AxiosMockAdapter from 'axios-mock-adapter';
+
+import axios from '~/lib/utils/axios_utils';
+
+describe('axios_utils', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new AxiosMockAdapter(axios);
+ mock.onAny('/ok').reply(200);
+ mock.onAny('/err').reply(500);
+ expect(axios.countActiveRequests()).toBe(0);
+ });
+
+ afterEach(() => axios.waitForAll().finally(() => mock.restore()));
+
+ describe('waitForAll', () => {
+ it('resolves if there are no requests', () => axios.waitForAll());
+
+ it('waits for all requests to finish', () => {
+ const handler = jest.fn();
+ axios.get('/ok').then(handler);
+ axios.get('/err').catch(handler);
+
+ return axios.waitForAll().finally(() => {
+ expect(handler).toHaveBeenCalledTimes(2);
+ expect(handler.mock.calls[0][0].status).toBe(200);
+ expect(handler.mock.calls[1][0].response.status).toBe(500);
+ });
+ });
+ });
+
+ describe('waitFor', () => {
+ it('waits for requests on a specific URL', () => {
+ const handler = jest.fn();
+ axios.get('/ok').finally(handler);
+ axios.waitFor('/err').finally(() => {
+ throw new Error('waitFor on /err should not be called');
+ });
+ return axios.waitFor('/ok');
+ });
+ });
+});
diff --git a/spec/frontend/mocks/ce/lib/utils/axios_utils.js b/spec/frontend/mocks/ce/lib/utils/axios_utils.js
index b4065626b09..85fad231d28 100644
--- a/spec/frontend/mocks/ce/lib/utils/axios_utils.js
+++ b/spec/frontend/mocks/ce/lib/utils/axios_utils.js
@@ -1,3 +1,5 @@
+import EventEmitter from 'events';
+
const axios = jest.requireActual('~/lib/utils/axios_utils').default;
axios.isMock = true;
@@ -6,10 +8,71 @@ 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;
};
+// Count active requests and provide a way to wait for them
+let activeRequests = 0;
+const events = new EventEmitter();
+const onRequest = () => {
+ activeRequests += 1;
+};
+
+// Use setImmediate to alloow the response interceptor to finish
+const onResponse = config => {
+ activeRequests -= 1;
+ setImmediate(() => {
+ events.emit('response', config);
+ });
+};
+
+const subscribeToResponse = (predicate = () => true) =>
+ new Promise(resolve => {
+ const listener = (config = {}) => {
+ if (predicate(config)) {
+ events.off('response', listener);
+ resolve(config);
+ }
+ };
+
+ events.on('response', listener);
+
+ // If a request has been made synchronously, setImmediate waits for it to be
+ // processed and the counter incremented.
+ setImmediate(listener);
+ });
+
+/**
+ * Registers a callback function to be run after a request to the given URL finishes.
+ */
+axios.waitFor = url => subscribeToResponse(({ url: configUrl }) => configUrl === url);
+
+/**
+ * Registers a callback function to be run after all requests have finished. If there are no requests waiting, the callback is executed immediately.
+ */
+axios.waitForAll = () => subscribeToResponse(() => activeRequests === 0);
+
+axios.countActiveRequests = () => activeRequests;
+
+axios.interceptors.request.use(config => {
+ onRequest();
+ return config;
+});
+
+// Remove the global counter
+axios.interceptors.response.use(
+ response => {
+ onResponse(response.config);
+ return response;
+ },
+ err => {
+ onResponse(err.config);
+ return Promise.reject(err);
+ },
+);
+
export default axios;
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/monitoring/embed/embed_spec.js b/spec/frontend/monitoring/embed/embed_spec.js
index 3b18a0f77c7..1ce14e2418a 100644
--- a/spec/frontend/monitoring/embed/embed_spec.js
+++ b/spec/frontend/monitoring/embed/embed_spec.js
@@ -1,7 +1,7 @@
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import Embed from '~/monitoring/components/embed.vue';
-import MonitorAreaChart from '~/monitoring/components/charts/area.vue';
+import MonitorTimeSeriesChart from '~/monitoring/components/charts/time_series.vue';
import { TEST_HOST } from 'helpers/test_constants';
import { groups, initialState, metricsData, metricsWithData } from './mock_data';
@@ -55,7 +55,7 @@ describe('Embed', () => {
it('shows an empty state when no metrics are present', () => {
expect(wrapper.find('.metrics-embed').exists()).toBe(true);
- expect(wrapper.find(MonitorAreaChart).exists()).toBe(false);
+ expect(wrapper.find(MonitorTimeSeriesChart).exists()).toBe(false);
});
});
@@ -71,8 +71,8 @@ describe('Embed', () => {
it('shows a chart when metrics are present', () => {
wrapper.setProps({});
expect(wrapper.find('.metrics-embed').exists()).toBe(true);
- expect(wrapper.find(MonitorAreaChart).exists()).toBe(true);
- expect(wrapper.findAll(MonitorAreaChart).length).toBe(2);
+ expect(wrapper.find(MonitorTimeSeriesChart).exists()).toBe(true);
+ expect(wrapper.findAll(MonitorTimeSeriesChart).length).toBe(2);
});
});
});
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/notes/old_notes_spec.js b/spec/frontend/notes/old_notes_spec.js
index b57041cf4d1..96133c601aa 100644
--- a/spec/frontend/notes/old_notes_spec.js
+++ b/spec/frontend/notes/old_notes_spec.js
@@ -49,17 +49,12 @@ describe('Old Notes (~/notes.js)', () => {
setTestTimeoutOnce(4000);
});
- afterEach(done => {
+ afterEach(() => {
// The Notes component sets a polling interval. Clear it after every run.
// Make sure to use jest.runOnlyPendingTimers() instead of runAllTimers().
jest.clearAllTimers();
- setImmediate(() => {
- // Wait for any requests to resolve, otherwise we get failures about
- // unmocked requests.
- mockAxios.restore();
- done();
- });
+ return axios.waitForAll().finally(() => mockAxios.restore());
});
it('loads the Notes class into the DOM', () => {
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/tracking_spec.js b/spec/frontend/tracking_spec.js
index 0e862c683d3..7c98a1a66c9 100644
--- a/spec/frontend/tracking_spec.js
+++ b/spec/frontend/tracking_spec.js
@@ -1,20 +1,56 @@
import $ from 'jquery';
import { setHTMLFixture } from './helpers/fixtures';
-import Tracking from '~/tracking';
+import Tracking, { initUserTracking } from '~/tracking';
describe('Tracking', () => {
+ let snowplowSpy;
+
beforeEach(() => {
window.snowplow = window.snowplow || (() => {});
+ window.snowplowOptions = {
+ namespace: '_namespace_',
+ hostname: 'app.gitfoo.com',
+ cookieDomain: '.gitfoo.com',
+ };
+ snowplowSpy = jest.spyOn(window, 'snowplow');
});
- describe('.event', () => {
- let snowplowSpy = null;
+ describe('initUserTracking', () => {
+ it('calls through to get a new tracker with the expected options', () => {
+ initUserTracking();
+ expect(snowplowSpy).toHaveBeenCalledWith('newTracker', '_namespace_', 'app.gitfoo.com', {
+ namespace: '_namespace_',
+ hostname: 'app.gitfoo.com',
+ cookieDomain: '.gitfoo.com',
+ appId: '',
+ userFingerprint: false,
+ respectDoNotTrack: true,
+ forceSecureTracker: true,
+ eventMethod: 'post',
+ contexts: { webPage: true },
+ activityTrackingEnabled: false,
+ pageTrackingEnabled: false,
+ });
+ });
- beforeEach(() => {
- snowplowSpy = jest.spyOn(window, 'snowplow');
+ it('should activate features based on what has been enabled', () => {
+ initUserTracking();
+ expect(snowplowSpy).not.toHaveBeenCalledWith('enableActivityTracking', 30, 30);
+ expect(snowplowSpy).not.toHaveBeenCalledWith('trackPageView');
+
+ window.snowplowOptions = Object.assign({}, window.snowplowOptions, {
+ activityTrackingEnabled: true,
+ pageTrackingEnabled: true,
+ });
+
+ initUserTracking();
+ expect(snowplowSpy).toHaveBeenCalledWith('enableActivityTracking', 30, 30);
+ expect(snowplowSpy).toHaveBeenCalledWith('trackPageView');
});
+ });
+ describe('.event', () => {
afterEach(() => {
window.doNotTrack = undefined;
navigator.doNotTrack = undefined;
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/graphql/features/authorization_spec.rb b/spec/graphql/features/authorization_spec.rb
index c427893f9cc..9a60ff3b78c 100644
--- a/spec/graphql/features/authorization_spec.rb
+++ b/spec/graphql/features/authorization_spec.rb
@@ -8,10 +8,10 @@ describe 'Gitlab::Graphql::Authorization' do
let(:permission_single) { :foo }
let(:permission_collection) { [:foo, :bar] }
let(:test_object) { double(name: 'My name') }
- let(:query_string) { '{ object() { name } }' }
+ let(:query_string) { '{ item() { name } }' }
let(:result) { execute_query(query_type)['data'] }
- subject { result['object'] }
+ subject { result['item'] }
shared_examples 'authorization with a single permission' do
it 'returns the protected field when user has permission' do
@@ -54,7 +54,7 @@ describe 'Gitlab::Graphql::Authorization' do
describe 'with a single permission' do
let(:query_type) do
query_factory do |query|
- query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object }, authorize: permission_single
+ query.field :item, type, null: true, resolve: ->(obj, args, ctx) { test_object }, authorize: permission_single
end
end
@@ -65,7 +65,7 @@ describe 'Gitlab::Graphql::Authorization' do
let(:query_type) do
permissions = permission_collection
query_factory do |qt|
- qt.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object } do
+ qt.field :item, type, null: true, resolve: ->(obj, args, ctx) { test_object } do
authorize permissions
end
end
@@ -78,7 +78,7 @@ describe 'Gitlab::Graphql::Authorization' do
describe 'Field authorizations when field is a built in type' do
let(:query_type) do
query_factory do |query|
- query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object }
+ query.field :item, type, null: true, resolve: ->(obj, args, ctx) { test_object }
end
end
@@ -131,7 +131,7 @@ describe 'Gitlab::Graphql::Authorization' do
describe 'Type authorizations' do
let(:query_type) do
query_factory do |query|
- query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object }
+ query.field :item, type, null: true, resolve: ->(obj, args, ctx) { test_object }
end
end
@@ -168,7 +168,7 @@ describe 'Gitlab::Graphql::Authorization' do
let(:query_type) do
query_factory do |query|
- query.field :object, type, null: true, resolve: ->(obj, args, ctx) { test_object }, authorize: permission_2
+ query.field :item, type, null: true, resolve: ->(obj, args, ctx) { test_object }, authorize: permission_2
end
end
@@ -176,7 +176,7 @@ describe 'Gitlab::Graphql::Authorization' do
end
describe 'type authorizations when applied to a relay connection' do
- let(:query_string) { '{ object() { edges { node { name } } } }' }
+ let(:query_string) { '{ item() { edges { node { name } } } }' }
let(:second_test_object) { double(name: 'Second thing') }
let(:type) do
@@ -187,11 +187,11 @@ describe 'Gitlab::Graphql::Authorization' do
let(:query_type) do
query_factory do |query|
- query.field :object, type.connection_type, null: true, resolve: ->(obj, args, ctx) { [test_object, second_test_object] }
+ query.field :item, type.connection_type, null: true, resolve: ->(obj, args, ctx) { [test_object, second_test_object] }
end
end
- subject { result.dig('object', 'edges') }
+ subject { result.dig('item', 'edges') }
it 'returns only the elements visible to the user' do
permit(permission_single)
@@ -207,13 +207,13 @@ describe 'Gitlab::Graphql::Authorization' do
describe 'limiting connections with multiple objects' do
let(:query_type) do
query_factory do |query|
- query.field :object, type.connection_type, null: true, resolve: ->(obj, args, ctx) do
+ query.field :item, type.connection_type, null: true, resolve: ->(obj, args, ctx) do
[test_object, second_test_object]
end
end
end
- let(:query_string) { '{ object(first: 1) { edges { node { name } } } }' }
+ let(:query_string) { '{ item(first: 1) { edges { node { name } } } }' }
it 'only checks permissions for the first object' do
expect(Ability).to receive(:allowed?).with(user, permission_single, test_object) { true }
@@ -233,11 +233,11 @@ describe 'Gitlab::Graphql::Authorization' do
let(:query_type) do
query_factory do |query|
- query.field :object, [type], null: true, resolve: ->(obj, args, ctx) { [test_object] }
+ query.field :item, [type], null: true, resolve: ->(obj, args, ctx) { [test_object] }
end
end
- subject { result['object'].first }
+ subject { result['item'].first }
include_examples 'authorization with a single permission'
end
diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb
index dec6b23d72a..0a27bbecfef 100644
--- a/spec/graphql/gitlab_schema_spec.rb
+++ b/spec/graphql/gitlab_schema_spec.rb
@@ -129,7 +129,7 @@ describe GitlabSchema do
result = described_class.object_from_id(user.to_global_id.to_s)
- expect(result.__sync).to eq(user)
+ expect(result.sync).to eq(user)
end
it 'batchloads the queries' do
@@ -138,7 +138,7 @@ describe GitlabSchema do
expect do
[described_class.object_from_id(user1.to_global_id),
- described_class.object_from_id(user2.to_global_id)].map(&:__sync)
+ described_class.object_from_id(user2.to_global_id)].map(&:sync)
end.not_to exceed_query_limit(1)
end
end
@@ -149,7 +149,7 @@ describe GitlabSchema do
result = described_class.object_from_id(note.to_global_id)
- expect(result.__sync).to eq(note)
+ expect(result.sync).to eq(note)
end
it 'batchloads the queries' do
@@ -158,7 +158,7 @@ describe GitlabSchema do
expect do
[described_class.object_from_id(note1.to_global_id),
- described_class.object_from_id(note2.to_global_id)].map(&:__sync)
+ described_class.object_from_id(note2.to_global_id)].map(&:sync)
end.not_to exceed_query_limit(1)
end
end
diff --git a/spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb b/spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb
index 19f5a8907a2..aa0f5c55902 100644
--- a/spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb
+++ b/spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb
@@ -14,6 +14,6 @@ describe Mutations::ResolvesProject do
project = create(:project)
expect(Resolvers::ProjectResolver).to receive(:new).with(object: nil, context: context).and_call_original
- expect(mutation.resolve_project(full_path: project.full_path)).to eq(project)
+ expect(mutation.resolve_project(full_path: project.full_path).sync).to eq(project)
end
end
diff --git a/spec/graphql/resolvers/echo_resolver_spec.rb b/spec/graphql/resolvers/echo_resolver_spec.rb
new file mode 100644
index 00000000000..466501a4227
--- /dev/null
+++ b/spec/graphql/resolvers/echo_resolver_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Resolvers::EchoResolver do
+ include GraphqlHelpers
+
+ let(:current_user) { create(:user) }
+ let(:text) { 'Message test' }
+
+ describe '#resolve' do
+ it 'echoes text and username' do
+ expect(resolve_echo(text)).to eq %Q("#{current_user.username}" says: #{text})
+ end
+
+ it 'echoes text and nil as username' do
+ expect(resolve_echo(text, { current_user: nil })).to eq "nil says: #{text}"
+ end
+ end
+
+ def resolve_echo(text, context = { current_user: current_user })
+ resolve(described_class, obj: nil, args: { text: text }, ctx: context)
+ end
+end
diff --git a/spec/graphql/resolvers/group_resolver_spec.rb b/spec/graphql/resolvers/group_resolver_spec.rb
index 5eb9cd06d15..7dec9ac1aa5 100644
--- a/spec/graphql/resolvers/group_resolver_spec.rb
+++ b/spec/graphql/resolvers/group_resolver_spec.rb
@@ -12,7 +12,7 @@ describe Resolvers::GroupResolver do
it 'batch-resolves groups by full path' do
paths = [group1.full_path, group2.full_path]
- result = batch(max_queries: 1) do
+ result = batch_sync(max_queries: 1) do
paths.map { |path| resolve_group(path) }
end
@@ -20,7 +20,7 @@ describe Resolvers::GroupResolver do
end
it 'resolves an unknown full_path to nil' do
- result = batch { resolve_group('unknown/project') }
+ result = batch_sync { resolve_group('unknown/project') }
expect(result).to be_nil
end
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
index 798fe00de97..d122c081069 100644
--- a/spec/graphql/resolvers/issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -110,7 +110,7 @@ describe Resolvers::IssuesResolver do
context "when passing a non existent, batch loaded project" do
let(:project) do
- BatchLoader.for("non-existent-path").batch do |_fake_paths, loader, _|
+ BatchLoader::GraphQL.for("non-existent-path").batch do |_fake_paths, loader, _|
loader.call("non-existent-path", nil)
end
end
diff --git a/spec/graphql/resolvers/merge_requests_resolver_spec.rb b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
index ab3c426b2cd..97b8e5ed41c 100644
--- a/spec/graphql/resolvers/merge_requests_resolver_spec.rb
+++ b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
@@ -17,7 +17,7 @@ describe Resolvers::MergeRequestsResolver do
describe '#resolve' do
it 'batch-resolves by target project full path and individual IID' do
- result = batch(max_queries: 2) do
+ result = batch_sync(max_queries: 2) do
resolve_mr(project, iid: iid_1) + resolve_mr(project, iid: iid_2)
end
@@ -25,7 +25,7 @@ describe Resolvers::MergeRequestsResolver do
end
it 'batch-resolves by target project full path and IIDS' do
- result = batch(max_queries: 2) do
+ result = batch_sync(max_queries: 2) do
resolve_mr(project, iids: [iid_1, iid_2])
end
@@ -33,7 +33,7 @@ describe Resolvers::MergeRequestsResolver do
end
it 'can batch-resolve merge requests from different projects' do
- result = batch(max_queries: 3) do
+ result = batch_sync(max_queries: 3) do
resolve_mr(project, iid: iid_1) +
resolve_mr(project, iid: iid_2) +
resolve_mr(other_project, iid: other_iid)
@@ -43,13 +43,13 @@ describe Resolvers::MergeRequestsResolver do
end
it 'resolves an unknown iid to be empty' do
- result = batch { resolve_mr(project, iid: -1) }
+ result = batch_sync { resolve_mr(project, iid: -1) }
- expect(result).to be_empty
+ expect(result.compact).to be_empty
end
it 'resolves empty iids to be empty' do
- result = batch { resolve_mr(project, iids: []) }
+ result = batch_sync { resolve_mr(project, iids: []) }
expect(result).to be_empty
end
diff --git a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
index 47591445fc0..639cc69650b 100644
--- a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
+++ b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
@@ -46,7 +46,7 @@ describe Resolvers::NamespaceProjectsResolver do
context "when passing a non existent, batch loaded namespace" do
let(:namespace) do
- BatchLoader.for("non-existent-path").batch do |_fake_paths, loader, _|
+ BatchLoader::GraphQL.for("non-existent-path").batch do |_fake_paths, loader, _|
loader.call("non-existent-path", nil)
end
end
diff --git a/spec/graphql/resolvers/project_resolver_spec.rb b/spec/graphql/resolvers/project_resolver_spec.rb
index 4fdbb3aa43e..d0fc2957909 100644
--- a/spec/graphql/resolvers/project_resolver_spec.rb
+++ b/spec/graphql/resolvers/project_resolver_spec.rb
@@ -12,7 +12,7 @@ describe Resolvers::ProjectResolver do
it 'batch-resolves projects by full path' do
paths = [project1.full_path, project2.full_path]
- result = batch(max_queries: 1) do
+ result = batch_sync(max_queries: 1) do
paths.map { |path| resolve_project(path) }
end
@@ -20,7 +20,7 @@ describe Resolvers::ProjectResolver do
end
it 'resolves an unknown full_path to nil' do
- result = batch { resolve_project('unknown/project') }
+ result = batch_sync { resolve_project('unknown/project') }
expect(result).to be_nil
end
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/boards_helper_spec.rb b/spec/helpers/boards_helper_spec.rb
index f014537eb54..ad088398ce9 100644
--- a/spec/helpers/boards_helper_spec.rb
+++ b/spec/helpers/boards_helper_spec.rb
@@ -40,7 +40,7 @@ describe BoardsHelper do
assign(:project, project)
allow(helper).to receive(:current_user) { user }
- allow(helper).to receive(:can?).with(user, :admin_list, project).and_return(true)
+ allow(helper).to receive(:can?).with(user, :create_non_backlog_issues, board).and_return(true)
end
it 'returns a board_lists_path as lists_endpoint' do
diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb
index bc2422aba90..4f665dc0514 100644
--- a/spec/helpers/ci_status_helper_spec.rb
+++ b/spec/helpers/ci_status_helper_spec.rb
@@ -53,4 +53,80 @@ describe CiStatusHelper do
expect(helper.pipeline_status_cache_key(pipeline_status)).to eq("pipeline-status/123abc-success")
end
end
+
+ describe "#render_status_with_link" do
+ subject { helper.render_status_with_link("success") }
+
+ it "renders a passed status icon" do
+ is_expected.to include("<span class=\"ci-status-link ci-status-icon-success d-inline-flex")
+ end
+
+ it "has 'Pipeline' as the status type in the title" do
+ is_expected.to include("title=\"Pipeline: passed\"")
+ end
+
+ it "has the success status icon" do
+ is_expected.to include("ci-status-icon-success")
+ end
+
+ context "when pipeline has commit path" do
+ subject { helper.render_status_with_link("success", "/commit-path") }
+
+ it "links to commit" do
+ is_expected.to include("href=\"/commit-path\"")
+ end
+
+ it "does not contain a span element" do
+ is_expected.not_to include("<span")
+ end
+
+ it "has 'Pipeline' as the status type in the title" do
+ is_expected.to include("title=\"Pipeline: passed\"")
+ end
+
+ it "has the correct status icon" do
+ is_expected.to include("ci-status-icon-success")
+ end
+ end
+
+ context "when different type than pipeline is provided" do
+ subject { helper.render_status_with_link("success", type: "commit") }
+
+ it "has the provided type in the title" do
+ is_expected.to include("title=\"Commit: passed\"")
+ end
+ end
+
+ context "when tooltip_placement is provided" do
+ subject { helper.render_status_with_link("success", tooltip_placement: "right") }
+
+ it "has the provided tooltip placement" do
+ is_expected.to include("data-placement=\"right\"")
+ end
+ end
+
+ context "when additional CSS classes are provided" do
+ subject { helper.render_status_with_link("success", cssclass: "extra-class") }
+
+ it "has appended extra class to icon classes" do
+ is_expected.to include("class=\"ci-status-link ci-status-icon-success d-inline-flex extra-class\"")
+ end
+ end
+
+ context "when container is provided" do
+ subject { helper.render_status_with_link("success", container: "my-container") }
+
+ it "has the provided container in data" do
+ is_expected.to include("data-container=\"my-container\"")
+ end
+ end
+
+ context "when icon_size is provided" do
+ subject { helper.render_status_with_link("success", icon_size: 24) }
+
+ it "has the svg class to change size" do
+ is_expected.to include("<svg class=\"s24\">")
+ 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/external_link_helper_spec.rb b/spec/helpers/external_link_helper_spec.rb
new file mode 100644
index 00000000000..7fc4ef18731
--- /dev/null
+++ b/spec/helpers/external_link_helper_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ExternalLinkHelper do
+ include IconsHelper
+
+ it 'returns external link with icon' do
+ expect(external_link('https://gitlab.com', 'https://gitlab.com').to_s)
+ .to eq('<a target="_blank" rel="noopener noreferrer" href="https://gitlab.com">https://gitlab.com <i aria-hidden="true" data-hidden="true" class="fa fa-external-link"></i></a>')
+ end
+
+ it 'allows options when creating external link with icon' do
+ expect(external_link('https://gitlab.com', 'https://gitlab.com', { "data-foo": "bar", class: "externalLink" }).to_s)
+ .to eq('<a target="_blank" rel="noopener noreferrer" data-foo="bar" class="externalLink" href="https://gitlab.com">https://gitlab.com <i aria-hidden="true" data-hidden="true" class="fa fa-external-link"></i></a>')
+ 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/icons_helper_spec.rb b/spec/helpers/icons_helper_spec.rb
index f92b94a9583..950f951e22e 100644
--- a/spec/helpers/icons_helper_spec.rb
+++ b/spec/helpers/icons_helper_spec.rb
@@ -60,20 +60,19 @@ describe IconsHelper do
non_existing = 'non_existing_icon_sprite'
it 'raises in development mode' do
- allow(Rails.env).to receive(:development?).and_return(true)
+ stub_rails_env('development')
expect { sprite_icon(non_existing) }.to raise_error(ArgumentError, /is not a known icon/)
end
it 'raises in test mode' do
- allow(Rails.env).to receive(:test?).and_return(true)
+ stub_rails_env('test')
expect { sprite_icon(non_existing) }.to raise_error(ArgumentError, /is not a known icon/)
end
it 'does not raise in production mode' do
- allow(Rails.env).to receive(:test?).and_return(false)
- allow(Rails.env).to receive(:development?).and_return(false)
+ stub_rails_env('production')
expect { sprite_icon(non_existing) }.not_to raise_error
end
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/error_tracking_helper_spec.rb b/spec/helpers/projects/error_tracking_helper_spec.rb
index 7516a636c93..064b3ad21cb 100644
--- a/spec/helpers/projects/error_tracking_helper_spec.rb
+++ b/spec/helpers/projects/error_tracking_helper_spec.rb
@@ -6,21 +6,31 @@ describe Projects::ErrorTrackingHelper do
include Gitlab::Routing.url_helpers
set(:project) { create(:project) }
+ set(:current_user) { create(:user) }
describe '#error_tracking_data' do
+ let(:can_enable_error_tracking) { true }
let(:setting_path) { project_settings_operations_path(project) }
let(:index_path) do
project_error_tracking_index_path(project, format: :json)
end
+ before do
+ allow(helper)
+ .to receive(:can?)
+ .with(current_user, :admin_operations, project)
+ .and_return(can_enable_error_tracking)
+ end
+
context 'without error_tracking_setting' do
it 'returns frontend configuration' do
- expect(error_tracking_data(project)).to eq(
+ expect(helper.error_tracking_data(current_user, project)).to match(
'index-path' => index_path,
+ 'user-can-enable-error-tracking' => 'true',
'enable-error-tracking-link' => setting_path,
'error-tracking-enabled' => 'false',
- "illustration-path" => "/images/illustrations/cluster_popover.svg"
+ 'illustration-path' => match_asset_path('/assets/illustrations/cluster_popover.svg')
)
end
end
@@ -36,7 +46,7 @@ describe Projects::ErrorTrackingHelper do
end
it 'show error tracking enabled' do
- expect(error_tracking_data(project)).to include(
+ expect(helper.error_tracking_data(current_user, project)).to include(
'error-tracking-enabled' => 'true'
)
end
@@ -48,11 +58,21 @@ describe Projects::ErrorTrackingHelper do
end
it 'show error tracking not enabled' do
- expect(error_tracking_data(project)).to include(
+ expect(helper.error_tracking_data(current_user, project)).to include(
'error-tracking-enabled' => 'false'
)
end
end
end
+
+ context 'when user is not maintainer' do
+ let(:can_enable_error_tracking) { false }
+
+ it 'shows error tracking enablement as disabled' do
+ expect(helper.error_tracking_data(current_user, project)).to include(
+ 'user-can-enable-error-tracking' => 'false'
+ )
+ end
+ end
end
end
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/helpers/version_check_helper_spec.rb b/spec/helpers/version_check_helper_spec.rb
index e384e2bf9a0..edc0d64d031 100644
--- a/spec/helpers/version_check_helper_spec.rb
+++ b/spec/helpers/version_check_helper_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe VersionCheckHelper do
describe '#version_status_badge' do
it 'returns nil if not dev environment and not enabled' do
- allow(Rails.env).to receive(:production?) { false }
+ stub_rails_env('development')
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:version_check_enabled) { false }
expect(helper.version_status_badge).to be(nil)
@@ -11,7 +11,7 @@ describe VersionCheckHelper do
context 'when production and enabled' do
before do
- allow(Rails.env).to receive(:production?) { true }
+ stub_rails_env('production')
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:version_check_enabled) { true }
allow(VersionCheck).to receive(:url) { 'https://version.host.com/check.svg?gitlab_info=xxx' }
end
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/board_new_issue_spec.js b/spec/javascripts/boards/board_new_issue_spec.js
index 721d0b8172d..76675a78db2 100644
--- a/spec/javascripts/boards/board_new_issue_spec.js
+++ b/spec/javascripts/boards/board_new_issue_spec.js
@@ -171,6 +171,32 @@ describe('Issue boards new issue form', () => {
.then(done)
.catch(done.fail);
});
+
+ it('sets detail weight after submit', done => {
+ boardsStore.weightFeatureAvailable = true;
+ vm.title = 'submit issue';
+
+ Vue.nextTick()
+ .then(submitIssue)
+ .then(() => {
+ expect(boardsStore.detail.list.weight).toBe(list.weight);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not set detail weight after submit', done => {
+ boardsStore.weightFeatureAvailable = false;
+ vm.title = 'submit issue';
+
+ Vue.nextTick()
+ .then(submitIssue)
+ .then(() => {
+ expect(boardsStore.detail.list.weight).toBe(list.weight);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
describe('submit error', () => {
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/boards/mock_data.js b/spec/javascripts/boards/mock_data.js
index ea22ae5c4e7..50ad1442873 100644
--- a/spec/javascripts/boards/mock_data.js
+++ b/spec/javascripts/boards/mock_data.js
@@ -12,6 +12,7 @@ export const listObj = {
position: 0,
title: 'Test',
list_type: 'label',
+ weight: 3,
label: {
id: 5000,
title: 'Testing',
@@ -26,6 +27,7 @@ export const listObjDuplicate = {
position: 1,
title: 'Test',
list_type: 'label',
+ weight: 3,
label: {
id: listObj.label.id,
title: 'Testing',
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown_spec.js b/spec/javascripts/create_cluster/gke_cluster/components/gke_machine_type_dropdown_spec.js
index fdecb823cd2..7aa7aa9a112 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown_spec.js
+++ b/spec/javascripts/create_cluster/gke_cluster/components/gke_machine_type_dropdown_spec.js
@@ -1,12 +1,12 @@
import Vue from 'vue';
-import GkeMachineTypeDropdown from '~/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue';
-import { createStore } from '~/projects/gke_cluster_dropdowns/store';
+import GkeMachineTypeDropdown from '~/create_cluster/gke_cluster/components/gke_machine_type_dropdown.vue';
+import { createStore } from '~/create_cluster/gke_cluster/store';
import {
SET_PROJECT,
SET_PROJECT_BILLING_STATUS,
SET_ZONE,
SET_MACHINE_TYPES,
-} from '~/projects/gke_cluster_dropdowns/store/mutation_types';
+} from '~/create_cluster/gke_cluster/store/mutation_types';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import {
selectedZoneMock,
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js b/spec/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js
index 1eb7cb4bd5b..809da3f9088 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js
+++ b/spec/javascripts/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
-import GkeProjectIdDropdown from '~/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue';
-import { createStore } from '~/projects/gke_cluster_dropdowns/store';
-import { SET_PROJECTS } from '~/projects/gke_cluster_dropdowns/store/mutation_types';
+import GkeProjectIdDropdown from '~/create_cluster/gke_cluster/components/gke_project_id_dropdown.vue';
+import { createStore } from '~/create_cluster/gke_cluster/store';
+import { SET_PROJECTS } from '~/create_cluster/gke_cluster/store/mutation_types';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { emptyProjectMock, selectedProjectMock } from '../mock_data';
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown_spec.js b/spec/javascripts/create_cluster/gke_cluster/components/gke_zone_dropdown_spec.js
index 95186e19ca1..9cb9419e433 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown_spec.js
+++ b/spec/javascripts/create_cluster/gke_cluster/components/gke_zone_dropdown_spec.js
@@ -1,11 +1,11 @@
import Vue from 'vue';
-import GkeZoneDropdown from '~/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue';
-import { createStore } from '~/projects/gke_cluster_dropdowns/store';
+import GkeZoneDropdown from '~/create_cluster/gke_cluster/components/gke_zone_dropdown.vue';
+import { createStore } from '~/create_cluster/gke_cluster/store';
import {
SET_PROJECT,
SET_ZONES,
SET_PROJECT_BILLING_STATUS,
-} from '~/projects/gke_cluster_dropdowns/store/mutation_types';
+} from '~/create_cluster/gke_cluster/store/mutation_types';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { selectedZoneMock, selectedProjectMock, gapiZonesResponseMock } from '../mock_data';
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/helpers.js b/spec/javascripts/create_cluster/gke_cluster/helpers.js
index 6df511e9157..6df511e9157 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/helpers.js
+++ b/spec/javascripts/create_cluster/gke_cluster/helpers.js
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/mock_data.js b/spec/javascripts/create_cluster/gke_cluster/mock_data.js
index d9f5dbc636f..d9f5dbc636f 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/mock_data.js
+++ b/spec/javascripts/create_cluster/gke_cluster/mock_data.js
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/stores/actions_spec.js b/spec/javascripts/create_cluster/gke_cluster/stores/actions_spec.js
index 9d892b8185b..a7591cc38c7 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/stores/actions_spec.js
+++ b/spec/javascripts/create_cluster/gke_cluster/stores/actions_spec.js
@@ -1,6 +1,6 @@
import testAction from 'spec/helpers/vuex_action_helper';
-import * as actions from '~/projects/gke_cluster_dropdowns/store/actions';
-import { createStore } from '~/projects/gke_cluster_dropdowns/store';
+import * as actions from '~/create_cluster/gke_cluster/store/actions';
+import { createStore } from '~/create_cluster/gke_cluster/store';
import { gapi } from '../helpers';
import { selectedProjectMock, selectedZoneMock, selectedMachineTypeMock } from '../mock_data';
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/stores/getters_spec.js b/spec/javascripts/create_cluster/gke_cluster/stores/getters_spec.js
index 6f89158f807..ac92716b0ab 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/stores/getters_spec.js
+++ b/spec/javascripts/create_cluster/gke_cluster/stores/getters_spec.js
@@ -1,4 +1,4 @@
-import * as getters from '~/projects/gke_cluster_dropdowns/store/getters';
+import * as getters from '~/create_cluster/gke_cluster/store/getters';
import { selectedProjectMock, selectedZoneMock, selectedMachineTypeMock } from '../mock_data';
describe('GCP Cluster Dropdown Store Getters', () => {
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/stores/mutations_spec.js b/spec/javascripts/create_cluster/gke_cluster/stores/mutations_spec.js
index 7f8c4f314e4..7ee6ff436e2 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/stores/mutations_spec.js
+++ b/spec/javascripts/create_cluster/gke_cluster/stores/mutations_spec.js
@@ -1,5 +1,5 @@
-import { createStore } from '~/projects/gke_cluster_dropdowns/store';
-import * as types from '~/projects/gke_cluster_dropdowns/store/mutation_types';
+import { createStore } from '~/create_cluster/gke_cluster/store';
+import * as types from '~/create_cluster/gke_cluster/store/mutation_types';
import {
selectedProjectMock,
selectedZoneMock,
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/ide/components/preview/clientside_spec.js b/spec/javascripts/ide/components/preview/clientside_spec.js
deleted file mode 100644
index b9bf5c51ffe..00000000000
--- a/spec/javascripts/ide/components/preview/clientside_spec.js
+++ /dev/null
@@ -1,363 +0,0 @@
-import Vue from 'vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { createStore } from '~/ide/stores';
-import Clientside from '~/ide/components/preview/clientside.vue';
-import timeoutPromise from 'spec/helpers/set_timeout_promise_helper';
-import { resetStore, file } from '../../helpers';
-
-describe('IDE clientside preview', () => {
- let vm;
- let Component;
-
- beforeAll(() => {
- Component = Vue.extend(Clientside);
- });
-
- beforeEach(done => {
- const store = createStore();
-
- Vue.set(store.state.entries, 'package.json', {
- ...file('package.json'),
- });
- Vue.set(store.state, 'currentProjectId', 'gitlab-ce');
- Vue.set(store.state.projects, 'gitlab-ce', {
- visibility: 'public',
- });
-
- vm = createComponentWithStore(Component, store);
-
- spyOn(vm, 'getFileData').and.returnValue(Promise.resolve());
- spyOn(vm, 'getRawFileData').and.returnValue(Promise.resolve(''));
- spyOn(vm, 'initManager');
-
- vm.$mount();
-
- timeoutPromise()
- .then(() => vm.$nextTick())
- .then(done)
- .catch(done.fail);
- });
-
- afterEach(() => {
- vm.$destroy();
- resetStore(vm.$store);
- });
-
- describe('without main entry', () => {
- it('creates sandpack manager', () => {
- expect(vm.initManager).not.toHaveBeenCalled();
- });
- });
-
- describe('with main entry', () => {
- beforeEach(done => {
- Vue.set(
- vm.$store.state.entries['package.json'],
- 'raw',
- JSON.stringify({
- main: 'index.js',
- }),
- );
-
- vm.$nextTick()
- .then(() => vm.initPreview())
- .then(vm.$nextTick)
- .then(done)
- .catch(done.fail);
- });
-
- it('creates sandpack manager', () => {
- expect(vm.initManager).toHaveBeenCalledWith(
- '#ide-preview',
- {
- files: jasmine.any(Object),
- entry: '/index.js',
- showOpenInCodeSandbox: true,
- },
- {
- fileResolver: {
- isFile: jasmine.any(Function),
- readFile: jasmine.any(Function),
- },
- },
- );
- });
- });
-
- describe('computed', () => {
- describe('normalizedEntries', () => {
- beforeEach(done => {
- vm.$store.state.entries['index.js'] = {
- ...file('index.js'),
- type: 'blob',
- raw: 'test',
- };
- vm.$store.state.entries['index2.js'] = {
- ...file('index2.js'),
- type: 'blob',
- content: 'content',
- };
- vm.$store.state.entries.tree = {
- ...file('tree'),
- type: 'tree',
- };
- vm.$store.state.entries.empty = {
- ...file('empty'),
- type: 'blob',
- };
-
- vm.$nextTick(done);
- });
-
- it('returns flattened list of blobs with content', () => {
- expect(vm.normalizedEntries).toEqual({
- '/index.js': {
- code: 'test',
- },
- '/index2.js': {
- code: 'content',
- },
- });
- });
- });
-
- describe('mainEntry', () => {
- it('returns false when package.json is empty', () => {
- expect(vm.mainEntry).toBe(false);
- });
-
- it('returns main key from package.json', done => {
- Vue.set(
- vm.$store.state.entries['package.json'],
- 'raw',
- JSON.stringify({
- main: 'index.js',
- }),
- );
-
- vm.$nextTick(() => {
- expect(vm.mainEntry).toBe('index.js');
-
- done();
- });
- });
- });
-
- describe('showPreview', () => {
- it('returns false if no mainEntry', () => {
- expect(vm.showPreview).toBe(false);
- });
-
- it('returns false if loading', done => {
- Vue.set(
- vm.$store.state.entries['package.json'],
- 'raw',
- JSON.stringify({
- main: 'index.js',
- }),
- );
- vm.loading = true;
-
- vm.$nextTick(() => {
- expect(vm.showPreview).toBe(false);
-
- done();
- });
- });
-
- it('returns true if not loading and mainEntry exists', done => {
- Vue.set(
- vm.$store.state.entries['package.json'],
- 'raw',
- JSON.stringify({
- main: 'index.js',
- }),
- );
- vm.loading = false;
-
- vm.$nextTick(() => {
- expect(vm.showPreview).toBe(true);
-
- done();
- });
- });
- });
-
- describe('showEmptyState', () => {
- it('returns true if no mainEnry exists', () => {
- expect(vm.showEmptyState).toBe(true);
- });
-
- it('returns false if loading', done => {
- Vue.set(
- vm.$store.state.entries['package.json'],
- 'raw',
- JSON.stringify({
- main: 'index.js',
- }),
- );
- vm.loading = true;
-
- vm.$nextTick(() => {
- expect(vm.showEmptyState).toBe(false);
-
- done();
- });
- });
-
- it('returns false if not loading and mainEntry exists', done => {
- Vue.set(
- vm.$store.state.entries['package.json'],
- 'raw',
- JSON.stringify({
- main: 'index.js',
- }),
- );
- vm.loading = false;
-
- vm.$nextTick(() => {
- expect(vm.showEmptyState).toBe(false);
-
- done();
- });
- });
- });
-
- describe('showOpenInCodeSandbox', () => {
- it('returns true when visiblity is public', () => {
- expect(vm.showOpenInCodeSandbox).toBe(true);
- });
-
- it('returns false when visiblity is private', done => {
- vm.$store.state.projects['gitlab-ce'].visibility = 'private';
-
- vm.$nextTick(() => {
- expect(vm.showOpenInCodeSandbox).toBe(false);
-
- done();
- });
- });
- });
-
- describe('sandboxOpts', () => {
- beforeEach(done => {
- vm.$store.state.entries['index.js'] = {
- ...file('index.js'),
- type: 'blob',
- raw: 'test',
- };
- Vue.set(
- vm.$store.state.entries['package.json'],
- 'raw',
- JSON.stringify({
- main: 'index.js',
- }),
- );
-
- vm.$nextTick(done);
- });
-
- it('returns sandbox options', () => {
- expect(vm.sandboxOpts).toEqual({
- files: {
- '/index.js': {
- code: 'test',
- },
- '/package.json': {
- code: '{"main":"index.js"}',
- },
- },
- entry: '/index.js',
- showOpenInCodeSandbox: true,
- });
- });
- });
- });
-
- describe('methods', () => {
- describe('loadFileContent', () => {
- it('calls getFileData', () => {
- expect(vm.getFileData).toHaveBeenCalledWith({
- path: 'package.json',
- makeFileActive: false,
- });
- });
-
- it('calls getRawFileData', () => {
- expect(vm.getRawFileData).toHaveBeenCalledWith({ path: 'package.json' });
- });
- });
-
- describe('update', () => {
- beforeEach(() => {
- jasmine.clock().install();
-
- vm.sandpackReady = true;
- vm.manager.updatePreview = jasmine.createSpy('updatePreview');
- vm.manager.listener = jasmine.createSpy('updatePreview');
- });
-
- afterEach(() => {
- jasmine.clock().uninstall();
- });
-
- it('calls initPreview if manager is empty', () => {
- spyOn(vm, 'initPreview');
- vm.manager = {};
-
- vm.update();
-
- jasmine.clock().tick(250);
-
- expect(vm.initPreview).toHaveBeenCalled();
- });
-
- it('calls updatePreview', () => {
- vm.update();
-
- jasmine.clock().tick(250);
-
- expect(vm.manager.updatePreview).toHaveBeenCalledWith(vm.sandboxOpts);
- });
- });
- });
-
- describe('template', () => {
- it('renders ide-preview element when showPreview is true', done => {
- Vue.set(
- vm.$store.state.entries['package.json'],
- 'raw',
- JSON.stringify({
- main: 'index.js',
- }),
- );
- vm.loading = false;
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('#ide-preview')).not.toBe(null);
- done();
- });
- });
-
- it('renders empty state', done => {
- vm.loading = false;
-
- vm.$nextTick(() => {
- expect(vm.$el.textContent).toContain(
- 'Preview your web application using Web IDE client-side evaluation.',
- );
-
- done();
- });
- });
-
- it('renders loading icon', done => {
- vm.loading = true;
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
- done();
- });
- });
- });
-});
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/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js
index d3c1cf831bb..57ab1aa73f7 100644
--- a/spec/javascripts/jobs/components/job_app_spec.js
+++ b/spec/javascripts/jobs/components/job_app_spec.js
@@ -25,6 +25,7 @@ describe('Job App ', () => {
terminalPath: 'jobs/123/terminal',
pagePath: `${gl.TEST_HOST}jobs/123`,
projectPath: 'user-name/project-name',
+ subscriptionsMoreMinutesUrl: 'https://customers.gitlab.com/buy_pipeline_minutes',
logState:
'eyJvZmZzZXQiOjE3NDUxLCJuX29wZW5fdGFncyI6MCwiZmdfY29sb3IiOm51bGwsImJnX2NvbG9yIjpudWxsLCJzdHlsZV9tYXNrIjowfQ%3D%3D',
};
diff --git a/spec/javascripts/jobs/components/log/line_header_spec.js b/spec/javascripts/jobs/components/log/line_header_spec.js
new file mode 100644
index 00000000000..4efd412a6cd
--- /dev/null
+++ b/spec/javascripts/jobs/components/log/line_header_spec.js
@@ -0,0 +1,84 @@
+import { mount } from '@vue/test-utils';
+import LineHeader from '~/jobs/components/log/line_header.vue';
+import LineNumber from '~/jobs/components/log/line_number.vue';
+
+describe('Job Log Header Line', () => {
+ let wrapper;
+
+ const data = {
+ line: {
+ content: [
+ {
+ text: 'Running with gitlab-runner 12.1.0 (de7731dd)',
+ style: 'term-fg-l-green',
+ },
+ ],
+ lineNumber: 0,
+ },
+ isClosed: true,
+ path: '/jashkenas/underscore/-/jobs/335',
+ };
+
+ const createComponent = (props = {}) => {
+ wrapper = mount(LineHeader, {
+ sync: false,
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('line', () => {
+ beforeEach(() => {
+ createComponent(data);
+ });
+
+ it('renders the line number component', () => {
+ expect(wrapper.contains(LineNumber)).toBe(true);
+ });
+
+ it('renders a span the provided text', () => {
+ expect(wrapper.find('span').text()).toBe(data.line.content[0].text);
+ });
+
+ it('renders the provided style as a class attribute', () => {
+ expect(wrapper.find('span').classes()).toContain(data.line.content[0].style);
+ });
+ });
+
+ describe('when isCloses is true', () => {
+ beforeEach(() => {
+ createComponent({ ...data, isClosed: true });
+ });
+
+ it('sets icon name to be angle-right', () => {
+ expect(wrapper.vm.iconName).toEqual('angle-right');
+ });
+ });
+
+ describe('when isCloses is false', () => {
+ beforeEach(() => {
+ createComponent({ ...data, isClosed: false });
+ });
+
+ it('sets icon name to be angle-down', () => {
+ expect(wrapper.vm.iconName).toEqual('angle-down');
+ });
+ });
+
+ describe('on click', () => {
+ beforeEach(() => {
+ createComponent(data);
+ });
+
+ it('emits toggleLine event', () => {
+ wrapper.trigger('click');
+
+ expect(wrapper.emitted().toggleLine.length).toBe(1);
+ });
+ });
+});
diff --git a/spec/javascripts/jobs/components/log/line_number_spec.js b/spec/javascripts/jobs/components/log/line_number_spec.js
new file mode 100644
index 00000000000..fcf2edf9159
--- /dev/null
+++ b/spec/javascripts/jobs/components/log/line_number_spec.js
@@ -0,0 +1,40 @@
+import { shallowMount } from '@vue/test-utils';
+import LineNumber from '~/jobs/components/log/line_number.vue';
+
+describe('Job Log Line Number', () => {
+ let wrapper;
+
+ const data = {
+ lineNumber: 0,
+ path: '/jashkenas/underscore/-/jobs/335',
+ };
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(LineNumber, {
+ sync: false,
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent(data);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders incremented lineNunber by 1', () => {
+ expect(wrapper.text()).toBe('1');
+ });
+
+ it('renders link with lineNumber as an ID', () => {
+ expect(wrapper.attributes().id).toBe('L1');
+ });
+
+ it('links to the provided path with line number as anchor', () => {
+ expect(wrapper.attributes().href).toBe(`${data.path}#L1`);
+ });
+});
diff --git a/spec/javascripts/jobs/components/log/line_spec.js b/spec/javascripts/jobs/components/log/line_spec.js
new file mode 100644
index 00000000000..ea593e3c39a
--- /dev/null
+++ b/spec/javascripts/jobs/components/log/line_spec.js
@@ -0,0 +1,49 @@
+import { shallowMount } from '@vue/test-utils';
+import Line from '~/jobs/components/log/line.vue';
+import LineNumber from '~/jobs/components/log/line_number.vue';
+
+describe('Job Log Line', () => {
+ let wrapper;
+
+ const data = {
+ line: {
+ content: [
+ {
+ text: 'Running with gitlab-runner 12.1.0 (de7731dd)',
+ style: 'term-fg-l-green',
+ },
+ ],
+ lineNumber: 0,
+ },
+ path: '/jashkenas/underscore/-/jobs/335',
+ };
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(Line, {
+ sync: false,
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent(data);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders the line number component', () => {
+ expect(wrapper.contains(LineNumber)).toBe(true);
+ });
+
+ it('renders a span the provided text', () => {
+ expect(wrapper.find('span').text()).toBe(data.line.content[0].text);
+ });
+
+ it('renders the provided style as a class attribute', () => {
+ expect(wrapper.find('span').classes()).toContain(data.line.content[0].style);
+ });
+});
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/monitoring/panel_type_spec.js b/spec/javascripts/monitoring/panel_type_spec.js
index 086be628093..a2366e74d43 100644
--- a/spec/javascripts/monitoring/panel_type_spec.js
+++ b/spec/javascripts/monitoring/panel_type_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import PanelType from '~/monitoring/components/panel_type.vue';
import EmptyChart from '~/monitoring/components/charts/empty_chart.vue';
-import AreaChart from '~/monitoring/components/charts/area.vue';
+import TimeSeriesChart from '~/monitoring/components/charts/time_series.vue';
import { graphDataPrometheusQueryRange } from './mock_data';
import { createStore } from '~/monitoring/stores';
@@ -62,9 +62,10 @@ describe('Panel Type component', () => {
});
});
- describe('Area Chart panel type', () => {
+ describe('Time Series Chart panel type', () => {
it('is rendered', () => {
- expect(panelType.find(AreaChart).exists()).toBe(true);
+ expect(panelType.find(TimeSeriesChart).isVueInstance()).toBe(true);
+ expect(panelType.find(TimeSeriesChart).exists()).toBe(true);
});
it('sets clipboard text on the dropdown', () => {
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index 5f81a168498..3812d46f838 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -8,7 +8,7 @@ export const notesDataMock = {
notesPath: '/gitlab-org/gitlab-ce/noteable/issue/98/notes',
quickActionsDocsPath: '/help/user/project/quick_actions',
registerPath: '/users/sign_in?redirect_to_referer=yes#register-pane',
- totalNotes: 1,
+ prerenderedNotesCount: 1,
closePath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=close',
reopenPath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=reopen',
canAwardEmoji: true,
diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js
index e55aa0e965a..1fd4a9a7612 100644
--- a/spec/javascripts/notes/stores/actions_spec.js
+++ b/spec/javascripts/notes/stores/actions_spec.js
@@ -336,7 +336,7 @@ describe('Actions Notes Store', () => {
});
});
- describe('deleteNote', () => {
+ describe('removeNote', () => {
const endpoint = `${TEST_HOST}/note`;
let axiosMock;
@@ -357,7 +357,7 @@ describe('Actions Notes Store', () => {
const note = { path: endpoint, id: 1 };
testAction(
- actions.deleteNote,
+ actions.removeNote,
note,
store.state,
[
@@ -384,7 +384,7 @@ describe('Actions Notes Store', () => {
$('body').attr('data-page', 'projects:merge_requests:show');
testAction(
- actions.deleteNote,
+ actions.removeNote,
note,
store.state,
[
@@ -409,6 +409,45 @@ describe('Actions Notes Store', () => {
});
});
+ describe('deleteNote', () => {
+ const endpoint = `${TEST_HOST}/note`;
+ let axiosMock;
+
+ beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
+ axiosMock.onDelete(endpoint).replyOnce(200, {});
+
+ $('body').attr('data-page', '');
+ });
+
+ afterEach(() => {
+ axiosMock.restore();
+
+ $('body').attr('data-page', '');
+ });
+
+ it('dispatches removeNote', done => {
+ const note = { path: endpoint, id: 1 };
+
+ testAction(
+ actions.deleteNote,
+ note,
+ {},
+ [],
+ [
+ {
+ type: 'removeNote',
+ payload: {
+ id: 1,
+ path: 'http://test.host/note',
+ },
+ },
+ ],
+ done,
+ );
+ });
+ });
+
describe('createNewNote', () => {
describe('success', () => {
const res = {
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/api/helpers/label_helpers_spec.rb b/spec/lib/api/helpers/label_helpers_spec.rb
new file mode 100644
index 00000000000..138e9a22d70
--- /dev/null
+++ b/spec/lib/api/helpers/label_helpers_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Helpers::LabelHelpers do
+ describe 'create_service_params' do
+ let(:label_helper) do
+ Class.new do
+ include API::Helpers::LabelHelpers
+ end.new
+ end
+
+ context 'when a project is given' do
+ it 'returns the expected params' do
+ project = create(:project)
+ expect(label_helper.create_service_params(project)).to eq({ project: project })
+ end
+ end
+
+ context 'when a group is given' do
+ it 'returns the expected params' do
+ group = create(:group)
+ expect(label_helper.create_service_params(group)).to eq({ group: group })
+ end
+ end
+
+ context 'when something else is given' do
+ it 'raises a type error' do
+ expect { label_helper.create_service_params(Class.new) }.to raise_error(TypeError)
+ end
+ end
+ end
+end
diff --git a/spec/lib/api/support/git_access_actor_spec.rb b/spec/lib/api/support/git_access_actor_spec.rb
new file mode 100644
index 00000000000..63f5966faea
--- /dev/null
+++ b/spec/lib/api/support/git_access_actor_spec.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Support::GitAccessActor do
+ let(:user) { nil }
+ let(:key) { nil }
+
+ subject { described_class.new(user: user, key: key) }
+
+ describe '.from_params' do
+ context 'with params that are valid' do
+ it 'returns an instance of API::Support::GitAccessActor' do
+ params = { key_id: create(:key).id }
+
+ expect(described_class.from_params(params)).to be_instance_of(described_class)
+ end
+ end
+
+ context 'with params that are invalid' do
+ it 'returns nil' do
+ expect(described_class.from_params({})).to be_nil
+ end
+ end
+ end
+
+ describe 'attributes' do
+ describe '#user' do
+ context 'when initialized with a User' do
+ let(:user) { create(:user) }
+
+ it 'returns the User' do
+ expect(subject.user).to eq(user)
+ end
+ end
+
+ context 'when initialized with a Key' do
+ let(:user_for_key) { create(:user) }
+ let(:key) { create(:key, user: user_for_key) }
+
+ it 'returns the User associated to the Key' do
+ expect(subject.user).to eq(user_for_key)
+ end
+ end
+ end
+ end
+
+ describe '#key_or_user' do
+ context 'when params contains a :key_id' do
+ it 'is an instance of Key' do
+ key = create(:key)
+ params = { key_id: key.id }
+
+ expect(described_class.from_params(params).key_or_user).to eq(key)
+ end
+ end
+
+ context 'when params contains a :user_id' do
+ it 'is an instance of User' do
+ user = create(:user)
+ params = { user_id: user.id }
+
+ expect(described_class.from_params(params).key_or_user).to eq(user)
+ end
+ end
+
+ context 'when params contains a :username' do
+ it 'is an instance of User (via UserFinder)' do
+ user = create(:user)
+ params = { username: user.username }
+
+ expect(described_class.from_params(params).key_or_user).to eq(user)
+ end
+ end
+ end
+
+ describe '#username' do
+ context 'when initialized with a User' do
+ let(:user) { create(:user) }
+
+ it 'returns the username' do
+ expect(subject.username).to eq(user.username)
+ end
+ end
+
+ context 'when initialized with a Key' do
+ let(:key) { create(:key, user: user_for_key) }
+
+ context 'that has no User associated' do
+ let(:user_for_key) { nil }
+
+ it 'returns nil' do
+ expect(subject.username).to be_nil
+ end
+ end
+
+ context 'that has a User associated' do
+ let(:user_for_key) { create(:user) }
+
+ it 'returns the username of the User associated to the Key' do
+ expect(subject.username).to eq(user_for_key.username)
+ end
+ end
+ end
+ end
+
+ describe '#update_last_used_at!' do
+ context 'when initialized with a User' do
+ let(:user) { create(:user) }
+
+ it 'does nothing' do
+ expect(user).not_to receive(:update_last_used_at)
+
+ subject.update_last_used_at!
+ end
+ end
+
+ context 'when initialized with a Key' do
+ let(:key) { create(:key) }
+
+ it 'updates update_last_used_at' do
+ expect(key).to receive(:update_last_used_at)
+
+ subject.update_last_used_at!
+ end
+ end
+ end
+end
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..0c4ccbf28f4
--- /dev/null
+++ b/spec/lib/banzai/filter/asset_proxy_filter_spec.rb
@@ -0,0 +1,106 @@
+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
+
+ context 'when whitelist is empty' do
+ it 'defaults to the install domain' do
+ stub_application_setting(asset_proxy_enabled: true)
+ stub_application_setting(asset_proxy_whitelist: [])
+
+ described_class.initialize_settings
+
+ expect(Gitlab.config.asset_proxy.whitelist).to eq [Gitlab.config.gitlab.host]
+ end
+ 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/analytics/cycle_analytics/stage_events/stage_event_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb
new file mode 100644
index 00000000000..29f4be76a65
--- /dev/null
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Analytics::CycleAnalytics::StageEvents::StageEvent do
+ it { expect(described_class).to respond_to(:name) }
+ it { expect(described_class).to respond_to(:identifier) }
+
+ it { expect(described_class.new({})).to respond_to(:object_type) }
+end
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/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb
index a9b15c411dc..1e3da4f7c2d 100644
--- a/spec/lib/gitlab/auth/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb
@@ -787,11 +787,25 @@ describe Gitlab::Auth::OAuth::User do
end
end
- describe '#bypass_two_factor?' do
- subject { oauth_user.bypass_two_factor? }
+ describe "#bypass_two_factor?" do
+ it "when with allow_bypass_two_factor disabled (Default)" do
+ stub_omniauth_config(allow_bypass_two_factor: false)
+ expect(oauth_user.bypass_two_factor?).to be_falsey
+ end
+
+ it "when with allow_bypass_two_factor enabled" do
+ stub_omniauth_config(allow_bypass_two_factor: true)
+ expect(oauth_user.bypass_two_factor?).to be_truthy
+ end
+
+ it "when provider in allow_bypass_two_factor array" do
+ stub_omniauth_config(allow_bypass_two_factor: [provider])
+ expect(oauth_user.bypass_two_factor?).to be_truthy
+ end
- it 'returns always false' do
- is_expected.to be_falsey
+ it "when provider not in allow_bypass_two_factor array" do
+ stub_omniauth_config(allow_bypass_two_factor: ["foo"])
+ expect(oauth_user.bypass_two_factor?).to be_falsey
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/diff_check_spec.rb b/spec/lib/gitlab/checks/diff_check_spec.rb
index a341dfa5636..b9134b8d6ab 100644
--- a/spec/lib/gitlab/checks/diff_check_spec.rb
+++ b/spec/lib/gitlab/checks/diff_check_spec.rb
@@ -20,9 +20,8 @@ describe Gitlab::Checks::DiffCheck do
allow(project).to receive(:lfs_enabled?).and_return(false)
end
- it 'skips the validation' do
- expect(subject).not_to receive(:validate_diff)
- expect(subject).not_to receive(:validate_file_paths)
+ it 'does not invoke :lfs_file_locks_validation' do
+ expect(subject).not_to receive(:lfs_file_locks_validation)
subject.validate!
end
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/policy/refs_spec.rb b/spec/lib/gitlab/ci/build/policy/refs_spec.rb
index 43c5d3ec980..8fc1e0a4e88 100644
--- a/spec/lib/gitlab/ci/build/policy/refs_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/refs_spec.rb
@@ -84,6 +84,20 @@ describe Gitlab::Ci::Build::Policy::Refs do
.not_to be_satisfied_by(pipeline)
end
end
+
+ context 'when source is external_pull_request_event' do
+ let(:pipeline) { build_stubbed(:ci_pipeline, source: :external_pull_request_event) }
+
+ it 'is satisfied with only: external_pull_request' do
+ expect(described_class.new(%w[external_pull_requests]))
+ .to be_satisfied_by(pipeline)
+ end
+
+ it 'is not satisfied with only: external_pull_request_event' do
+ expect(described_class.new(%w[external_pull_request_events]))
+ .not_to be_satisfied_by(pipeline)
+ end
+ end
end
context 'when matching a ref by a regular expression' do
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/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
index bf9ff922c05..ba4f841cf43 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
@@ -128,4 +128,38 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
expect(pipeline.target_sha).to eq(merge_request.target_branch_sha)
end
end
+
+ context 'when pipeline is running for an external pull request' do
+ let(:command) do
+ Gitlab::Ci::Pipeline::Chain::Command.new(
+ source: :external_pull_request_event,
+ origin_ref: 'feature',
+ checkout_sha: project.commit.id,
+ after_sha: nil,
+ before_sha: nil,
+ source_sha: external_pull_request.source_sha,
+ target_sha: external_pull_request.target_sha,
+ trigger_request: nil,
+ schedule: nil,
+ external_pull_request: external_pull_request,
+ project: project,
+ current_user: user)
+ end
+
+ let(:external_pull_request) { build(:external_pull_request, project: project) }
+
+ before do
+ step.perform!
+ end
+
+ it 'correctly indicated that this is an external pull request pipeline' do
+ expect(pipeline).to be_external_pull_request_event
+ expect(pipeline.external_pull_request).to eq(external_pull_request)
+ end
+
+ it 'correctly sets source sha and target sha to pipeline' do
+ expect(pipeline.source_sha).to eq(external_pull_request.source_sha)
+ expect(pipeline.target_sha).to eq(external_pull_request.target_sha)
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 91c559dcd9b..cf496b79a62 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -50,6 +50,32 @@ module Gitlab
end
end
+ describe 'interruptible entry' do
+ describe 'interruptible job' do
+ let(:config) do
+ YAML.dump(rspec: { script: 'rspec', interruptible: true })
+ end
+
+ it { expect(subject[:interruptible]).to be_truthy }
+ end
+
+ describe 'interruptible job with default value' do
+ let(:config) do
+ YAML.dump(rspec: { script: 'rspec' })
+ end
+
+ it { expect(subject).not_to have_key(:interruptible) }
+ end
+
+ describe 'uninterruptible job' do
+ let(:config) do
+ YAML.dump(rspec: { script: 'rspec', interruptible: false })
+ end
+
+ it { expect(subject[:interruptible]).to be_falsy }
+ end
+ end
+
describe 'retry entry' do
context 'when retry count is specified' do
let(:config) do
diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb
index 2990594c538..1b4d366ce7b 100644
--- a/spec/lib/gitlab/danger/helper_spec.rb
+++ b/spec/lib/gitlab/danger/helper_spec.rb
@@ -11,16 +11,62 @@ describe Gitlab::Danger::Helper do
class FakeDanger
include Gitlab::Danger::Helper
- attr_reader :git
+ attr_reader :git, :gitlab
- def initialize(git:)
+ def initialize(git:, gitlab:)
@git = git
+ @gitlab = gitlab
end
end
let(:fake_git) { double('fake-git') }
- subject(:helper) { FakeDanger.new(git: fake_git) }
+ let(:mr_author) { nil }
+ let(:fake_gitlab) { double('fake-gitlab', mr_author: mr_author) }
+
+ subject(:helper) { FakeDanger.new(git: fake_git, gitlab: fake_gitlab) }
+
+ describe '#gitlab_helper' do
+ context 'when gitlab helper is not available' do
+ let(:fake_gitlab) { nil }
+
+ it 'returns nil' do
+ expect(helper.gitlab_helper).to be_nil
+ end
+ end
+
+ context 'when gitlab helper is available' do
+ it 'returns the gitlab helper' do
+ expect(helper.gitlab_helper).to eq(fake_gitlab)
+ end
+ end
+ end
+
+ describe '#release_automation?' do
+ context 'when gitlab helper is not available' do
+ it 'returns false' do
+ expect(helper.release_automation?).to be_falsey
+ end
+ end
+
+ context 'when gitlab helper is available' do
+ context "but the MR author isn't the RELEASE_TOOLS_BOT" do
+ let(:mr_author) { 'johnmarston' }
+
+ it 'returns false' do
+ expect(helper.release_automation?).to be_falsey
+ end
+ end
+
+ context 'and the MR author is the RELEASE_TOOLS_BOT' do
+ let(:mr_author) { described_class::RELEASE_TOOLS_BOT }
+
+ it 'returns true' do
+ expect(helper.release_automation?).to be_truthy
+ end
+ end
+ end
+ end
describe '#all_changed_files' do
subject { helper.all_changed_files }
@@ -168,9 +214,13 @@ describe Gitlab::Danger::Helper do
'Gemfile.lock' | :backend
'Procfile' | :backend
'Rakefile' | :backend
- '.gitlab-ci.yml' | :backend
'FOO_VERSION' | :backend
+ '.gitlab-ci.yml' | :engineering_productivity
+ '.gitlab/ci/cng.gitlab-ci.yml' | :engineering_productivity
+ '.gitlab/ci/ee-specific-checks.gitlab-ci.yml' | :engineering_productivity
+ 'lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml' | :backend
+
'ee/FOO_VERSION' | :unknown
'db/schema.rb' | :database
diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb
index 171f2344e82..ca036390bde 100644
--- a/spec/lib/gitlab/danger/teammate_spec.rb
+++ b/spec/lib/gitlab/danger/teammate_spec.rb
@@ -28,7 +28,7 @@ describe Gitlab::Danger::Teammate do
end
context 'when labels contain Create and the category is test' do
- let(:labels) { ['Create'] }
+ let(:labels) { ['devops::create'] }
context 'when role is Test Automation Engineer, Create' do
let(:role) { 'Test Automation Engineer, Create' }
@@ -50,6 +50,14 @@ describe Gitlab::Danger::Teammate do
end
end
+ context 'when role is Test Automation Engineer' do
+ let(:role) { 'Test Automation Engineer' }
+
+ it '#reviewer? returns false' do
+ expect(subject.reviewer?(project, :test, labels)).to be_falsey
+ end
+ end
+
context 'when role is Test Automation Engineer, Manage' do
let(:role) { 'Test Automation Engineer, Manage' }
@@ -57,6 +65,18 @@ describe Gitlab::Danger::Teammate do
expect(subject.reviewer?(project, :test, labels)).to be_falsey
end
end
+
+ context 'when role is Backend Engineer, Engineering Productivity' do
+ let(:role) { 'Backend Engineer, Engineering Productivity' }
+
+ it '#reviewer? returns true' do
+ expect(subject.reviewer?(project, :engineering_productivity, labels)).to be_truthy
+ end
+
+ it '#maintainer? returns false' do
+ expect(subject.maintainer?(project, :engineering_productivity, labels)).to be_falsey
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 2731fc8573f..49f92f14559 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -576,6 +576,38 @@ describe Gitlab::Database::MigrationHelpers do
model.rename_column_concurrently(:users, :old, :new)
end
+
+ context 'when default is false' do
+ let(:old_column) do
+ double(:column,
+ type: :boolean,
+ limit: nil,
+ default: false,
+ null: false,
+ precision: nil,
+ scale: nil)
+ end
+
+ it 'copies the default to the new column' do
+ expect(model).to receive(:change_column_default)
+ .with(:users, :new, old_column.default)
+
+ model.rename_column_concurrently(:users, :old, :new)
+ end
+ end
+ end
+ end
+
+ describe '#undo_rename_column_concurrently' do
+ it 'reverses the operations of rename_column_concurrently' do
+ expect(model).to receive(:check_trigger_permissions!).with(:users)
+
+ expect(model).to receive(:remove_rename_triggers_for_postgresql)
+ .with(:users, /trigger_.{12}/)
+
+ expect(model).to receive(:remove_column).with(:users, :new)
+
+ model.undo_rename_column_concurrently(:users, :old, :new)
end
end
@@ -592,6 +624,80 @@ describe Gitlab::Database::MigrationHelpers do
end
end
+ describe '#undo_cleanup_concurrent_column_rename' do
+ context 'in a transaction' do
+ it 'raises RuntimeError' do
+ allow(model).to receive(:transaction_open?).and_return(true)
+
+ expect { model.undo_cleanup_concurrent_column_rename(:users, :old, :new) }
+ .to raise_error(RuntimeError)
+ end
+ end
+
+ context 'outside a transaction' do
+ let(:new_column) do
+ double(:column,
+ type: :integer,
+ limit: 8,
+ default: 0,
+ null: false,
+ precision: 5,
+ scale: 1)
+ end
+
+ let(:trigger_name) { model.rename_trigger_name(:users, :old, :new) }
+
+ before do
+ allow(model).to receive(:transaction_open?).and_return(false)
+ allow(model).to receive(:column_for).and_return(new_column)
+ end
+
+ it 'reverses the operations of cleanup_concurrent_column_rename' do
+ expect(model).to receive(:check_trigger_permissions!).with(:users)
+
+ expect(model).to receive(:install_rename_triggers_for_postgresql)
+ .with(trigger_name, '"users"', '"old"', '"new"')
+
+ expect(model).to receive(:add_column)
+ .with(:users, :old, :integer,
+ limit: new_column.limit,
+ precision: new_column.precision,
+ scale: new_column.scale)
+
+ expect(model).to receive(:change_column_default)
+ .with(:users, :old, new_column.default)
+
+ expect(model).to receive(:update_column_in_batches)
+
+ expect(model).to receive(:change_column_null).with(:users, :old, false)
+
+ expect(model).to receive(:copy_indexes).with(:users, :new, :old)
+ expect(model).to receive(:copy_foreign_keys).with(:users, :new, :old)
+
+ model.undo_cleanup_concurrent_column_rename(:users, :old, :new)
+ end
+
+ context 'when default is false' do
+ let(:new_column) do
+ double(:column,
+ type: :boolean,
+ limit: nil,
+ default: false,
+ null: false,
+ precision: nil,
+ scale: nil)
+ end
+
+ it 'copies the default to the old column' do
+ expect(model).to receive(:change_column_default)
+ .with(:users, :old, new_column.default)
+
+ model.undo_cleanup_concurrent_column_rename(:users, :old, :new)
+ end
+ end
+ end
+ end
+
describe '#change_column_type_concurrently' do
it 'changes the column type' do
expect(model).to receive(:rename_column_concurrently)
@@ -619,10 +725,18 @@ describe Gitlab::Database::MigrationHelpers do
.with(/CREATE OR REPLACE FUNCTION foo()/m)
expect(model).to receive(:execute)
+ .with(/DROP TRIGGER IF EXISTS foo/m)
+
+ expect(model).to receive(:execute)
.with(/CREATE TRIGGER foo/m)
model.install_rename_triggers_for_postgresql('foo', :users, :old, :new)
end
+
+ it 'does not fail if trigger already exists' do
+ model.install_rename_triggers_for_postgresql('foo', :users, :old, :new)
+ model.install_rename_triggers_for_postgresql('foo', :users, :old, :new)
+ end
end
describe '#remove_rename_triggers_for_postgresql' do
@@ -1169,33 +1283,19 @@ describe Gitlab::Database::MigrationHelpers do
describe '#perform_background_migration_inline?' do
it 'returns true in a test environment' do
- allow(Rails.env)
- .to receive(:test?)
- .and_return(true)
+ stub_rails_env('test')
expect(model.perform_background_migration_inline?).to eq(true)
end
it 'returns true in a development environment' do
- allow(Rails.env)
- .to receive(:test?)
- .and_return(false)
-
- allow(Rails.env)
- .to receive(:development?)
- .and_return(true)
+ stub_rails_env('development')
expect(model.perform_background_migration_inline?).to eq(true)
end
it 'returns false in a production environment' do
- allow(Rails.env)
- .to receive(:test?)
- .and_return(false)
-
- allow(Rails.env)
- .to receive(:development?)
- .and_return(false)
+ stub_rails_env('production')
expect(model.perform_background_migration_inline?).to eq(false)
end
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/services/self_monitoring/project/create_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
index def20448bd9..aab6fbcbbd1 100644
--- a/spec/services/self_monitoring/project/create_service_spec.rb
+++ b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
@@ -2,29 +2,48 @@
require 'spec_helper'
-describe SelfMonitoring::Project::CreateService do
+describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
describe '#execute' do
- let(:result) { subject.execute }
+ let(:result) { subject.execute! }
let(:prometheus_settings) do
- OpenStruct.new(
+ {
enable: true,
listen_address: 'localhost:9090'
- )
+ }
end
before do
- allow(Gitlab.config).to receive(:prometheus).and_return(prometheus_settings)
+ stub_config(prometheus: prometheus_settings)
+ end
+
+ context 'without application_settings' do
+ it 'does not fail' do
+ expect(subject).to receive(:log_error).and_call_original
+ expect(result).to eq(
+ status: :success
+ )
+
+ expect(Project.count).to eq(0)
+ expect(Group.count).to eq(0)
+ end
end
context 'without admin users' do
- it 'returns error' do
+ let(:application_setting) { Gitlab::CurrentSettings.current_application_settings }
+
+ before do
+ allow(ApplicationSetting).to receive(:current_without_cache) { application_setting }
+ end
+
+ it 'does not fail' do
expect(subject).to receive(:log_error).and_call_original
expect(result).to eq(
- status: :error,
- message: 'No active admin user found',
- failed_step: :validate_admins
+ status: :success
)
+
+ expect(Project.count).to eq(0)
+ expect(Group.count).to eq(0)
end
end
@@ -36,6 +55,7 @@ describe SelfMonitoring::Project::CreateService do
let!(:user) { create(:user, :admin) }
before do
+ allow(ApplicationSetting).to receive(:current_without_cache) { application_setting }
application_setting.allow_local_requests_from_web_hooks_and_services = true
end
@@ -56,8 +76,8 @@ describe SelfMonitoring::Project::CreateService do
it 'creates group' do
expect(result[:status]).to eq(:success)
expect(group).to be_persisted
- expect(group.name).to eq(described_class::GROUP_NAME)
- expect(group.path).to start_with(described_class::GROUP_PATH)
+ expect(group.name).to eq('GitLab Instance Administrators')
+ expect(group.path).to start_with('gitlab-instance-administrators')
expect(group.path.split('-').last.length).to eq(8)
expect(group.visibility_level).to eq(described_class::VISIBILITY_LEVEL)
end
@@ -77,9 +97,16 @@ describe SelfMonitoring::Project::CreateService do
end
it 'creates project with correct name and description' do
+ path = 'administration/monitoring/gitlab_instance_administration_project/index'
+ docs_path = Rails.application.routes.url_helpers.help_page_path(path)
+
expect(result[:status]).to eq(:success)
expect(project.name).to eq(described_class::PROJECT_NAME)
- expect(project.description).to eq(described_class::PROJECT_DESCRIPTION)
+ expect(project.description).to eq(
+ 'This project is automatically generated and will be used to help monitor this GitLab instance. ' \
+ "[More information](#{docs_path})"
+ )
+ expect(File).to exist("doc/#{path}.md")
end
it 'adds all admins as maintainers' do
@@ -103,21 +130,32 @@ describe 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[:status]).to eq(:error)
- expect(result[:failed_step]).to eq(:save_project_id)
- expect(result[:message]).to eq('Could not save project ID')
+ expect { result }.to raise_error(StandardError, 'Could not save project ID')
end
- it 'does not fail when a project already exists' do
- expect(result[:status]).to eq(:success)
+ context 'when project already exists' do
+ let(:existing_group) { create(:group) }
+ let(:existing_project) { create(:project, namespace: existing_group) }
+
+ before do
+ admin1 = create(:user, :admin)
+ admin2 = create(:user, :admin)
- second_result = subject.execute
+ existing_group.add_owner(user)
+ existing_group.add_users([admin1, admin2], Gitlab::Access::MAINTAINER)
+
+ application_setting.instance_administration_project_id = existing_project.id
+ end
+
+ it 'does not fail' do
+ expect(subject).to receive(:log_error).and_call_original
+ expect(result[:status]).to eq(:success)
- expect(second_result[:status]).to eq(:success)
- expect(second_result[:project]).to eq(project)
- expect(second_result[:group]).to eq(group)
+ expect(Project.count).to eq(1)
+ expect(Group.count).to eq(1)
+ end
end
context 'when local requests from hooks and services are not allowed' do
@@ -138,11 +176,28 @@ describe SelfMonitoring::Project::CreateService do
end
context 'with non default prometheus address' do
- before do
- prometheus_settings.listen_address = 'https://localhost:9090'
+ let(:listen_address) { 'https://localhost:9090' }
+
+ let(:prometheus_settings) do
+ {
+ enable: true,
+ 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
@@ -156,9 +211,23 @@ describe SelfMonitoring::Project::CreateService do
end
end
- context 'when prometheus setting is disabled in gitlab.yml' do
+ context 'when prometheus setting is nil' do
before do
- prometheus_settings.enable = false
+ stub_config(prometheus: nil)
+ end
+
+ it 'does not fail' do
+ expect(result).to include(status: :success)
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+
+ context 'when prometheus setting is disabled in gitlab.yml' do
+ let(:prometheus_settings) do
+ {
+ enable: false,
+ listen_address: 'http://localhost:9090'
+ }
end
it 'does not configure prometheus' do
@@ -168,9 +237,7 @@ describe SelfMonitoring::Project::CreateService do
end
context 'when prometheus listen address is blank in gitlab.yml' do
- before do
- prometheus_settings.listen_address = ''
- end
+ let(:prometheus_settings) { { enable: true, listen_address: '' } }
it 'does not configure prometheus' do
expect(result).to include(status: :success)
@@ -192,11 +259,7 @@ describe SelfMonitoring::Project::CreateService do
it 'returns error' do
expect(subject).to receive(:log_error).and_call_original
- expect(result).to eq({
- status: :error,
- message: 'Could not create project',
- failed_step: :create_project
- })
+ expect { result }.to raise_error(StandardError, 'Could not create project')
end
end
@@ -207,26 +270,21 @@ describe SelfMonitoring::Project::CreateService do
it 'returns error' do
expect(subject).to receive(:log_error).and_call_original
- expect(result).to eq({
- status: :error,
- message: 'Could not add admins as members',
- failed_step: :add_group_members
- })
+ expect { result }.to raise_error(StandardError, 'Could not add admins as members')
end
end
context 'when prometheus manual configuration cannot be saved' do
- before do
- prometheus_settings.listen_address = 'httpinvalid://localhost:9090'
+ let(:prometheus_settings) do
+ {
+ enable: true,
+ listen_address: 'httpinvalid://localhost:9090'
+ }
end
it 'returns error' do
expect(subject).to receive(:log_error).and_call_original
- expect(result).to eq(
- status: :error,
- message: 'Could not save prometheus manual configuration',
- failed_step: :add_prometheus_manual_configuration
- )
+ expect { result }.to raise_error(StandardError, 'Could not save prometheus manual configuration')
end
end
end
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index 77e58b6d5c7..8d37de32179 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -347,6 +347,17 @@ describe Gitlab::Database do
pool.disconnect!
end
end
+
+ it 'allows setting of a custom hostname and port' do
+ pool = described_class.create_connection_pool(5, '127.0.0.1', 5432)
+
+ begin
+ expect(pool.spec.config[:host]).to eq('127.0.0.1')
+ expect(pool.spec.config[:port]).to eq(5432)
+ ensure
+ pool.disconnect!
+ end
+ end
end
describe '.cached_column_exists?' 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..d221f39c2ed 100644
--- a/spec/lib/gitlab/favicon_spec.rb
+++ b/spec/lib/gitlab/favicon_spec.rb
@@ -1,14 +1,14 @@
-require 'rails_helper'
+require 'spec_helper'
RSpec.describe Gitlab::Favicon, :request_store do
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 +24,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/fogbugz_import/project_creator_spec.rb b/spec/lib/gitlab/fogbugz_import/project_creator_spec.rb
new file mode 100644
index 00000000000..503fe897e29
--- /dev/null
+++ b/spec/lib/gitlab/fogbugz_import/project_creator_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::FogbugzImport::ProjectCreator do
+ let(:user) { create(:user) }
+
+ let(:repo) do
+ instance_double(Gitlab::FogbugzImport::Repository,
+ name: 'Vim',
+ safe_name: 'vim',
+ path: 'vim',
+ raw_data: '')
+ end
+
+ let(:uri) { 'https://testing.fogbugz.com' }
+ let(:token) { 'token' }
+ let(:fb_session) { { uri: uri, token: token } }
+ let(:project_creator) { described_class.new(repo, fb_session, user.namespace, user) }
+
+ subject do
+ project_creator.execute
+ end
+
+ it 'creates project with private visibility level' do
+ expect(subject.persisted?).to eq(true)
+ expect(subject.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ end
+end
diff --git a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
index 4d2f08f95fc..790b0428d19 100644
--- a/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
+++ b/spec/lib/gitlab/gfm/reference_rewriter_spec.rb
@@ -102,6 +102,23 @@ describe Gitlab::Gfm::ReferenceRewriter do
end
end
+ context 'with a commit' do
+ let(:old_project) { create(:project, :repository, name: 'old-project', group: group) }
+ let(:commit) { old_project.commit }
+
+ context 'reference to an absolute URL to a commit' do
+ let(:text) { Gitlab::UrlBuilder.build(commit) }
+
+ it { is_expected.to eq(text) }
+ end
+
+ context 'reference to a commit' do
+ let(:text) { commit.id }
+
+ it { is_expected.to eq("#{old_project_ref}@#{text}") }
+ end
+ end
+
context 'reference contains project milestone' do
let!(:milestone) do
create(:milestone, title: '9.0', project: old_project)
diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb
index 81658874be7..be6ab0c1200 100644
--- a/spec/lib/gitlab/git/diff_collection_spec.rb
+++ b/spec/lib/gitlab/git/diff_collection_spec.rb
@@ -74,6 +74,11 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
end
end
+ describe '#line_count' do
+ subject { super().line_count }
+ it { is_expected.to eq file_count * line_count }
+ end
+
context 'when limiting is disabled' do
let(:limits) { false }
@@ -100,6 +105,11 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
expect(subject.size).to eq(3)
end
end
+
+ describe '#line_count' do
+ subject { super().line_count }
+ it { is_expected.to eq file_count * line_count }
+ end
end
end
@@ -120,6 +130,12 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
subject { super().real_size }
it { is_expected.to eq('0+') }
end
+
+ describe '#line_count' do
+ subject { super().line_count }
+ it { is_expected.to eq 1000 }
+ end
+
it { expect(subject.size).to eq(0) }
context 'when limiting is disabled' do
@@ -139,6 +155,12 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
subject { super().real_size }
it { is_expected.to eq('3') }
end
+
+ describe '#line_count' do
+ subject { super().line_count }
+ it { is_expected.to eq file_count * line_count }
+ end
+
it { expect(subject.size).to eq(3) }
end
end
@@ -164,6 +186,12 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
subject { super().real_size }
it { is_expected.to eq('10+') }
end
+
+ describe '#line_count' do
+ subject { super().line_count }
+ it { is_expected.to eq 10 }
+ end
+
it { expect(subject.size).to eq(10) }
context 'when limiting is disabled' do
@@ -183,6 +211,12 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
subject { super().real_size }
it { is_expected.to eq('11') }
end
+
+ describe '#line_count' do
+ subject { super().line_count }
+ it { is_expected.to eq file_count * line_count }
+ end
+
it { expect(subject.size).to eq(11) }
end
end
@@ -204,6 +238,12 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
subject { super().real_size }
it { is_expected.to eq('3+') }
end
+
+ describe '#line_count' do
+ subject { super().line_count }
+ it { is_expected.to eq 120 }
+ end
+
it { expect(subject.size).to eq(3) }
context 'when limiting is disabled' do
@@ -223,6 +263,12 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
subject { super().real_size }
it { is_expected.to eq('11') }
end
+
+ describe '#line_count' do
+ subject { super().line_count }
+ it { is_expected.to eq file_count * line_count }
+ end
+
it { expect(subject.size).to eq(11) }
end
end
@@ -248,6 +294,12 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
subject { super().real_size }
it { is_expected.to eq('10') }
end
+
+ describe '#line_count' do
+ subject { super().line_count }
+ it { is_expected.to eq file_count * line_count }
+ end
+
it { expect(subject.size).to eq(10) }
end
end
@@ -270,6 +322,12 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
subject { super().real_size }
it { is_expected.to eq('9+') }
end
+
+ describe '#line_count' do
+ subject { super().line_count }
+ it { is_expected.to eq file_count * line_count }
+ end
+
it { expect(subject.size).to eq(9) }
context 'when limiting is disabled' do
@@ -289,6 +347,12 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
subject { super().real_size }
it { is_expected.to eq('10') }
end
+
+ describe '#line_count' do
+ subject { super().line_count }
+ it { is_expected.to eq file_count * line_count }
+ end
+
it { expect(subject.size).to eq(10) }
end
end
@@ -316,6 +380,11 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
subject { super().real_size }
it { is_expected.to eq('0')}
end
+
+ describe '#line_count' do
+ subject { super().line_count }
+ it { is_expected.to eq 0 }
+ end
end
describe '#each' do
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/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index e9fb6c0125c..1c5f72a4396 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -27,6 +27,16 @@ describe Gitlab::GitalyClient do
end
end
+ describe '.filesystem_id' do
+ it 'returns an empty string when the storage is not found in the response' do
+ response = double("response")
+ allow(response).to receive(:storage_statuses).and_return([])
+ allow_any_instance_of(Gitlab::GitalyClient::ServerService).to receive(:info).and_return(response)
+
+ expect(described_class.filesystem_id('default')).to eq(nil)
+ end
+ end
+
describe '.stub_class' do
it 'returns the gRPC health check stub' do
expect(described_class.stub_class(:health_check)).to eq(::Grpc::Health::V1::Health::Stub)
@@ -255,7 +265,7 @@ describe Gitlab::GitalyClient do
context 'in production and when RequestStore is enabled', :request_store do
before do
- allow(Rails.env).to receive(:production?).and_return(true)
+ stub_rails_env('production')
end
context 'when the maximum number of calls is enforced by a feature flag' 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/graphql/authorize/authorize_field_service_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
index d60d1b7559a..aada9285b31 100644
--- a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
+++ b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb
@@ -30,7 +30,11 @@ describe Gitlab::Graphql::Authorize::AuthorizeFieldService do
describe '#authorized_resolve' do
let(:presented_object) { double('presented object') }
let(:presented_type) { double('parent type', object: presented_object) }
- subject(:resolved) { service.authorized_resolve.call(presented_type, {}, { current_user: current_user }) }
+ let(:query_type) { GraphQL::ObjectType.new }
+ let(:schema) { GraphQL::Schema.define(query: query_type, mutation: nil)}
+ let(:query_context) { OpenStruct.new(schema: schema) }
+ let(:context) { GraphQL::Query::Context.new(query: OpenStruct.new(schema: schema, context: query_context), values: { current_user: current_user }, object: nil) }
+ subject(:resolved) { service.authorized_resolve.call(presented_type, {}, context) }
context 'scalar types' do
shared_examples 'checking permissions on the presented object' do
diff --git a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
index 50138d272c4..23762666ba8 100644
--- a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
+++ b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
@@ -46,9 +46,9 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do
end
end
- describe '#authorized?' do
+ describe '#authorized_resource?' do
it 'is true' do
- expect(loading_resource.authorized?(project)).to be(true)
+ expect(loading_resource.authorized_resource?(project)).to be(true)
end
end
end
@@ -72,9 +72,9 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do
end
end
- describe '#authorized?' do
+ describe '#authorized_resource?' do
it 'is false' do
- expect(loading_resource.authorized?(project)).to be(false)
+ expect(loading_resource.authorized_resource?(project)).to be(false)
end
end
end
@@ -121,9 +121,9 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do
end
end
- describe '#authorized?' do
+ describe '#authorized_resource?' do
it 'raises a comprehensive error message' do
- expect { loading_resource.authorized?(project) }.to raise_error(error)
+ expect { loading_resource.authorized_resource?(project) }.to raise_error(error)
end
end
end
diff --git a/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb b/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb
index fefa2881b18..4eb121794e1 100644
--- a/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb
+++ b/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb
@@ -8,7 +8,7 @@ describe Gitlab::Graphql::Connections::KeysetConnection do
end
def encoded_property(value)
- Base64.strict_encode64(value.to_s)
+ Base64Bp.urlsafe_encode64(value.to_s, padding: false)
end
describe '#cursor_from_nodes' do
diff --git a/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb
index 46dd1777285..22d8aa4274a 100644
--- a/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb
+++ b/spec/lib/gitlab/graphql/loaders/batch_lfs_oid_loader_spec.rb
@@ -12,7 +12,7 @@ describe Gitlab::Graphql::Loaders::BatchLfsOidLoader do
it 'batch-resolves LFS blob IDs' do
expect(Gitlab::Git::Blob).to receive(:batch_lfs_pointers).once.and_call_original
- result = batch do
+ result = batch_sync do
[blob, otherblob].map { |b| described_class.new(repository, b.id).find }
end
diff --git a/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb
index 4609593ef6a..a4bbd868558 100644
--- a/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb
+++ b/spec/lib/gitlab/graphql/loaders/batch_model_loader_spec.rb
@@ -9,8 +9,8 @@ describe Gitlab::Graphql::Loaders::BatchModelLoader do
issue_result = described_class.new(Issue, issue.id).find
user_result = described_class.new(User, user.id).find
- expect(issue_result.__sync).to eq(issue)
- expect(user_result.__sync).to eq(user)
+ expect(issue_result.sync).to eq(issue)
+ expect(user_result.sync).to eq(user)
end
it 'only queries once per model' do
@@ -21,7 +21,7 @@ describe Gitlab::Graphql::Loaders::BatchModelLoader do
expect do
[described_class.new(User, other_user.id).find,
described_class.new(User, user.id).find,
- described_class.new(Issue, issue.id).find].map(&:__sync)
+ described_class.new(Issue, issue.id).find].map(&:sync)
end.not_to exceed_query_limit(2)
end
end
diff --git a/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb
index 927476cc655..136027736c3 100644
--- a/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb
+++ b/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb
@@ -10,7 +10,7 @@ describe Gitlab::Graphql::Loaders::PipelineForShaLoader do
pipeline2 = create(:ci_pipeline, project: project, ref: project.default_branch, sha: project.commit.sha)
pipeline3 = create(:ci_pipeline, project: project, ref: 'improve/awesome', sha: project.commit('improve/awesome').sha)
- result = batch(max_queries: 1) do
+ result = batch_sync(max_queries: 1) do
[pipeline1.sha, pipeline3.sha].map { |sha| described_class.new(project, sha).find_last }
end
diff --git a/spec/lib/gitlab/graphql/markdown_field_spec.rb b/spec/lib/gitlab/graphql/markdown_field_spec.rb
index a8566aa8e1c..866a20801d3 100644
--- a/spec/lib/gitlab/graphql/markdown_field_spec.rb
+++ b/spec/lib/gitlab/graphql/markdown_field_spec.rb
@@ -30,17 +30,20 @@ describe Gitlab::Graphql::MarkdownField do
let(:note) { build(:note, note: '# Markdown!') }
let(:thing_with_markdown) { double('markdown thing', object: note) }
let(:expected_markdown) { '<h1 data-sourcepos="1:1-1:11" dir="auto">Markdown!</h1>' }
+ let(:query_type) { GraphQL::ObjectType.new }
+ let(:schema) { GraphQL::Schema.define(query: query_type, mutation: nil)}
+ let(:context) { GraphQL::Query::Context.new(query: OpenStruct.new(schema: schema), values: nil, object: nil) }
it 'renders markdown from the same property as the field name without the `_html` suffix' do
field = class_with_markdown_field(:note_html, null: false).fields['noteHtml']
- expect(field.to_graphql.resolve(thing_with_markdown, {}, {})).to eq(expected_markdown)
+ expect(field.to_graphql.resolve(thing_with_markdown, {}, context)).to eq(expected_markdown)
end
it 'renders markdown from a specific property when a `method` argument is passed' do
field = class_with_markdown_field(:test_html, null: false, method: :note).fields['testHtml']
- expect(field.to_graphql.resolve(thing_with_markdown, {}, {})).to eq(expected_markdown)
+ expect(field.to_graphql.resolve(thing_with_markdown, {}, context)).to eq(expected_markdown)
end
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 3c6b17c10ec..dafa4243145 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -23,9 +23,11 @@ issues:
- epic_issue
- epic
- designs
+- design_versions
events:
- author
- project
+- group
- target
- push_event_payload
notes:
@@ -62,6 +64,8 @@ milestone:
- participants
- events
- boards
+- milestone_release
+- release
snippets:
- author
- project
@@ -72,6 +76,8 @@ releases:
- author
- project
- links
+- milestone_release
+- milestone
links:
- release
project_members:
@@ -121,6 +127,8 @@ merge_requests:
- blocks_as_blockee
- blocking_merge_requests
- blocked_merge_requests
+external_pull_requests:
+- project
merge_request_diff:
- merge_request
- merge_request_diff_commits
@@ -150,6 +158,7 @@ ci_pipelines:
- pipeline_schedule
- merge_requests_as_head_pipeline
- merge_request
+- external_pull_request
- deployments
- environments
- chat_data
@@ -397,6 +406,7 @@ project:
- merge_trains
- designs
- project_aliases
+- external_pull_requests
award_emoji:
- awardable
- user
@@ -483,3 +493,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..87be7857e67 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
@@ -491,6 +512,24 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(Milestone.find_by_title('Group-level milestone').iid).to eq(2)
end
end
+
+ context 'with external authorization classification labels' do
+ it 'converts empty external classification authorization labels to nil' do
+ project.create_import_data(data: { override_params: { external_authorization_classification_label: "" } })
+
+ restored_project_json
+
+ expect(project.external_authorization_classification_label).to be_nil
+ end
+
+ it 'preserves valid external classification authorization labels' do
+ project.create_import_data(data: { override_params: { external_authorization_classification_label: "foobar" } })
+
+ restored_project_json
+
+ expect(project.external_authorization_classification_label).to eq("foobar")
+ end
+ end
end
describe '#restored_project' 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..e9750d23c53 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
@@ -269,6 +270,7 @@ Ci::Pipeline:
- protected
- iid
- merge_request_id
+- external_pull_request_id
Ci::Stage:
- id
- name
@@ -328,6 +330,7 @@ CommitStatus:
- failure_reason
- scheduled_at
- upstream_pipeline_id
+- interruptible
Ci::Variable:
- id
- project_id
@@ -466,6 +469,7 @@ ProtectedBranch:
- name
- created_at
- updated_at
+- code_owner_approval_required
ProtectedTag:
- id
- project_id
@@ -712,3 +716,16 @@ List:
- updated_at
- milestone_id
- user_id
+ExternalPullRequest:
+- id
+- created_at
+- updated_at
+- project_id
+- pull_request_iid
+- status
+- source_branch
+- target_branch
+- source_repository
+- target_repository
+- source_sha
+- target_sha
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/jwt_authenticatable_spec.rb b/spec/lib/gitlab/jwt_authenticatable_spec.rb
new file mode 100644
index 00000000000..0c1c491b308
--- /dev/null
+++ b/spec/lib/gitlab/jwt_authenticatable_spec.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::JwtAuthenticatable do
+ let(:test_class) do
+ Class.new do
+ include Gitlab::JwtAuthenticatable
+
+ def self.secret_path
+ Rails.root.join('tmp', 'tests', '.jwt_shared_secret')
+ end
+ end
+ end
+
+ before do
+ begin
+ File.delete(test_class.secret_path)
+ rescue Errno::ENOENT
+ end
+
+ test_class.write_secret
+ end
+
+ describe '.secret' do
+ subject(:secret) { test_class.secret }
+
+ it 'returns 32 bytes' do
+ expect(secret).to be_a(String)
+ expect(secret.length).to eq(32)
+ expect(secret.encoding).to eq(Encoding::ASCII_8BIT)
+ end
+
+ it 'accepts a trailing newline' do
+ File.open(test_class.secret_path, 'a') { |f| f.write "\n" }
+
+ expect(secret.length).to eq(32)
+ end
+
+ it 'raises an exception if the secret file cannot be read' do
+ File.delete(test_class.secret_path)
+
+ expect { secret }.to raise_exception(Errno::ENOENT)
+ end
+
+ it 'raises an exception if the secret file contains the wrong number of bytes' do
+ File.truncate(test_class.secret_path, 0)
+
+ expect { secret }.to raise_exception(RuntimeError)
+ end
+ end
+
+ describe '.write_secret' do
+ it 'uses mode 0600' do
+ expect(File.stat(test_class.secret_path).mode & 0777).to eq(0600)
+ end
+
+ it 'writes base64 data' do
+ bytes = Base64.strict_decode64(File.read(test_class.secret_path))
+
+ expect(bytes).not_to be_empty
+ end
+ end
+
+ describe '.decode_jwt_for_issuer' do
+ let(:payload) { { 'iss' => 'test_issuer' } }
+
+ it 'accepts a correct header' do
+ encoded_message = JWT.encode(payload, test_class.secret, 'HS256')
+
+ expect { test_class.decode_jwt_for_issuer('test_issuer', encoded_message) }.not_to raise_error
+ end
+
+ it 'raises an error when the JWT is not signed' do
+ encoded_message = JWT.encode(payload, nil, 'none')
+
+ expect { test_class.decode_jwt_for_issuer('test_issuer', encoded_message) }.to raise_error(JWT::DecodeError)
+ end
+
+ it 'raises an error when the header is signed with the wrong secret' do
+ encoded_message = JWT.encode(payload, 'wrongsecret', 'HS256')
+
+ expect { test_class.decode_jwt_for_issuer('test_issuer', encoded_message) }.to raise_error(JWT::DecodeError)
+ end
+
+ it 'raises an error when the issuer is incorrect' do
+ payload['iss'] = 'somebody else'
+ encoded_message = JWT.encode(payload, test_class.secret, 'HS256')
+
+ expect { test_class.decode_jwt_for_issuer('test_issuer', encoded_message) }.to raise_error(JWT::DecodeError)
+ 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/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb
index e70fde09edd..45e74597a2e 100644
--- a/spec/lib/gitlab/metrics/transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/transaction_spec.rb
@@ -204,17 +204,17 @@ describe Gitlab::Metrics::Transaction do
end
it 'allows tracking of custom tags' do
- transaction.add_event(:meow, animal: 'cat')
+ transaction.add_event(:bau, animal: 'dog')
- expect(metric.tags).to eq(event: :meow, animal: 'cat')
+ expect(metric.tags).to eq(event: :bau, animal: 'dog')
end
context 'with sensitive tags' do
before do
- transaction.add_event(:meow, **sensitive_tags.merge(sane: 'yes'))
+ transaction.add_event(:baubau, **sensitive_tags.merge(sane: 'yes'))
end
- it_behaves_like 'tag filter', event: :meow, sane: 'yes'
+ it_behaves_like 'tag filter', event: :baubau, sane: 'yes'
end
end
diff --git a/spec/lib/gitlab/metrics/web_transaction_spec.rb b/spec/lib/gitlab/metrics/web_transaction_spec.rb
index 2b35f07cc0d..21a762dbf25 100644
--- a/spec/lib/gitlab/metrics/web_transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/web_transaction_spec.rb
@@ -253,11 +253,11 @@ describe Gitlab::Metrics::WebTransaction do
end
it 'allows tracking of custom tags' do
- transaction.add_event(:meow, animal: 'cat')
+ transaction.add_event(:bau, animal: 'dog')
metric = transaction.metrics[0]
- expect(metric.tags).to eq(event: :meow, animal: 'cat')
+ expect(metric.tags).to eq(event: :bau, animal: 'dog')
end
end
end
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/patch/chronic_duration_spec.rb b/spec/lib/gitlab/patch/chronic_duration_spec.rb
new file mode 100644
index 00000000000..541037ec1a2
--- /dev/null
+++ b/spec/lib/gitlab/patch/chronic_duration_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Patch::ChronicDuration do
+ subject { ChronicDuration.parse('1mo') }
+
+ it 'uses default conversions' do
+ expect(subject).to eq(2_592_000)
+ end
+
+ context 'with custom conversions' do
+ before do
+ ChronicDuration.hours_per_day = 8
+ ChronicDuration.days_per_week = 5
+ end
+
+ after do
+ ChronicDuration.hours_per_day = 24
+ ChronicDuration.days_per_week = 7
+ end
+
+ it 'uses custom conversions' do
+ expect(subject).to eq(576_000)
+ end
+ end
+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/query_limiting/transaction_spec.rb b/spec/lib/gitlab/query_limiting/transaction_spec.rb
index 39d5a575efc..4e906314b5a 100644
--- a/spec/lib/gitlab/query_limiting/transaction_spec.rb
+++ b/spec/lib/gitlab/query_limiting/transaction_spec.rb
@@ -86,9 +86,7 @@ describe Gitlab::QueryLimiting::Transaction do
it 'returns false in a production environment' do
transaction = described_class.new
- expect(Rails.env)
- .to receive(:test?)
- .and_return(false)
+ stub_rails_env('production')
expect(transaction.raise_error?).to eq(false)
end
diff --git a/spec/lib/gitlab/query_limiting_spec.rb b/spec/lib/gitlab/query_limiting_spec.rb
index f0d0340cd6e..e9c6bbc35a3 100644
--- a/spec/lib/gitlab/query_limiting_spec.rb
+++ b/spec/lib/gitlab/query_limiting_spec.rb
@@ -9,14 +9,14 @@ describe Gitlab::QueryLimiting do
end
it 'returns true in a development environment' do
- allow(Rails.env).to receive(:development?).and_return(true)
+ stub_rails_env('development')
+ stub_rails_env('development')
expect(described_class.enable?).to eq(true)
end
it 'returns false on GitLab.com' do
- expect(Rails.env).to receive(:development?).and_return(false)
- expect(Rails.env).to receive(:test?).and_return(false)
+ stub_rails_env('production')
allow(Gitlab).to receive(:com?).and_return(true)
expect(described_class.enable?).to eq(false)
@@ -24,8 +24,7 @@ describe Gitlab::QueryLimiting do
it 'returns false in a non GitLab.com' do
allow(Gitlab).to receive(:com?).and_return(false)
- expect(Rails.env).to receive(:development?).and_return(false)
- expect(Rails.env).to receive(:test?).and_return(false)
+ stub_rails_env('production')
expect(described_class.enable?).to eq(false)
end
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/snowplow_tracker_spec.rb b/spec/lib/gitlab/snowplow_tracker_spec.rb
deleted file mode 100644
index 073a33e5973..00000000000
--- a/spec/lib/gitlab/snowplow_tracker_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-require 'spec_helper'
-
-describe Gitlab::SnowplowTracker do
- let(:timestamp) { Time.utc(2017, 3, 22) }
-
- around do |example|
- Timecop.freeze(timestamp) { example.run }
- end
-
- subject { described_class.track_event('epics', 'action', property: 'what', value: 'doit') }
-
- context '.track_event' do
- context 'when Snowplow tracker is disabled' do
- it 'does not track the event' do
- expect(SnowplowTracker::Tracker).not_to receive(:new)
-
- subject
- end
- end
-
- context 'when Snowplow tracker is enabled' do
- before do
- stub_application_setting(snowplow_enabled: true)
- stub_application_setting(snowplow_site_id: 'awesome gitlab')
- stub_application_setting(snowplow_collector_hostname: 'url.com')
- end
-
- it 'tracks the event' do
- tracker = double
-
- expect(::SnowplowTracker::Tracker).to receive(:new)
- .with(
- an_instance_of(::SnowplowTracker::Emitter),
- an_instance_of(::SnowplowTracker::Subject),
- 'cf', 'awesome gitlab'
- ).and_return(tracker)
- expect(tracker).to receive(:track_struct_event)
- .with('epics', 'action', nil, 'what', 'doit', nil, timestamp.to_i)
-
- subject
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/time_tracking_formatter_spec.rb b/spec/lib/gitlab/time_tracking_formatter_spec.rb
index a85d418777f..cfc804c13a7 100644
--- a/spec/lib/gitlab/time_tracking_formatter_spec.rb
+++ b/spec/lib/gitlab/time_tracking_formatter_spec.rb
@@ -17,6 +17,14 @@ describe Gitlab::TimeTrackingFormatter do
it { expect(subject).to eq(-12_000) }
end
+
+ context 'durations with months' do
+ let(:duration_string) { '1mo' }
+
+ it 'uses our custom conversions' do
+ expect(subject).to eq(576_000)
+ end
+ end
end
describe '#output' do
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/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb
new file mode 100644
index 00000000000..f14e74427e1
--- /dev/null
+++ b/spec/lib/gitlab/tracking_spec.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::Tracking do
+ let(:timestamp) { Time.utc(2017, 3, 22) }
+
+ before do
+ stub_application_setting(snowplow_enabled: true)
+ stub_application_setting(snowplow_collector_hostname: 'gitfoo.com')
+ stub_application_setting(snowplow_cookie_domain: '.gitfoo.com')
+ stub_application_setting(snowplow_site_id: '_abc123_')
+ end
+
+ describe '.snowplow_options' do
+ subject(&method(:described_class))
+
+ it 'returns useful client options' do
+ expect(subject.snowplow_options(nil)).to eq(
+ namespace: 'gl',
+ hostname: 'gitfoo.com',
+ cookieDomain: '.gitfoo.com',
+ appId: '_abc123_',
+ pageTrackingEnabled: true,
+ activityTrackingEnabled: true
+ )
+ end
+
+ it 'enables features using feature flags' do
+ stub_feature_flags(additional_snowplow_tracking: true)
+ allow(Feature).to receive(:enabled?).with(
+ :additional_snowplow_tracking,
+ '_group_'
+ ).and_return(false)
+
+ expect(subject.snowplow_options('_group_')).to include(
+ pageTrackingEnabled: false,
+ activityTrackingEnabled: false
+ )
+ end
+ end
+
+ describe '.event' do
+ subject(&method(:described_class))
+
+ around do |example|
+ Timecop.freeze(timestamp) { example.run }
+ end
+
+ it 'can track events' do
+ tracker = double
+
+ expect(SnowplowTracker::Emitter).to receive(:new).with(
+ 'gitfoo.com'
+ ).and_return('_emitter_')
+
+ expect(SnowplowTracker::Tracker).to receive(:new).with(
+ '_emitter_',
+ an_instance_of(SnowplowTracker::Subject),
+ 'gl',
+ '_abc123_'
+ ).and_return(tracker)
+
+ expect(tracker).to receive(:track_struct_event).with(
+ 'category',
+ 'action',
+ '_label_',
+ '_property_',
+ '_value_',
+ '_context_',
+ timestamp.to_i
+ )
+
+ subject.event('category', 'action',
+ label: '_label_',
+ property: '_property_',
+ value: '_value_',
+ context: '_context_'
+ )
+ end
+
+ it 'does not track when not enabled' do
+ stub_application_setting(snowplow_enabled: false)
+ expect(SnowplowTracker::Tracker).not_to receive(:new)
+
+ subject.event('epics', 'action', property: 'what', value: 'doit')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index df8a1f82f81..6d1d7e48326 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -4,80 +4,114 @@
require 'spec_helper'
describe Gitlab::UrlBlocker do
+ include StubRequests
+
describe '#validate!' do
+ subject { described_class.validate!(import_url) }
+
+ shared_examples 'validates URI and hostname' do
+ it 'runs the url validations' do
+ uri, hostname = subject
+
+ expect(uri).to eq(Addressable::URI.parse(expected_uri))
+ expect(hostname).to eq(expected_hostname)
+ end
+ end
+
context 'when URI is nil' do
let(:import_url) { nil }
- it 'returns no URI and hostname' do
- uri, hostname = described_class.validate!(import_url)
-
- expect(uri).to be(nil)
- expect(hostname).to be(nil)
+ it_behaves_like 'validates URI and hostname' do
+ let(:expected_uri) { nil }
+ let(:expected_hostname) { nil }
end
end
context 'when URI is internal' do
let(:import_url) { 'http://localhost' }
- it 'returns URI and no hostname' do
- uri, hostname = described_class.validate!(import_url)
-
- expect(uri).to eq(Addressable::URI.parse('http://[::1]'))
- expect(hostname).to eq('localhost')
+ it_behaves_like 'validates URI and hostname' do
+ let(:expected_uri) { 'http://[::1]' }
+ let(:expected_hostname) { 'localhost' }
end
end
context 'when the URL hostname is a domain' do
- let(:import_url) { 'https://example.org' }
+ context 'when domain can be resolved' do
+ let(:import_url) { 'https://example.org' }
- it 'returns URI and hostname' do
- uri, hostname = described_class.validate!(import_url)
+ before do
+ stub_dns(import_url, ip_address: '93.184.216.34')
+ end
- expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34'))
- expect(hostname).to eq('example.org')
+ it_behaves_like 'validates URI and hostname' do
+ let(:expected_uri) { 'https://93.184.216.34' }
+ let(:expected_hostname) { 'example.org' }
+ end
+ end
+
+ context 'when domain cannot be resolved' do
+ let(:import_url) { 'http://foobar.x' }
+
+ it 'raises an error' do
+ stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
+
+ expect { subject }.to raise_error(described_class::BlockedUrlError)
+ end
end
end
context 'when the URL hostname is an IP address' do
let(:import_url) { 'https://93.184.216.34' }
- it 'returns URI and no hostname' do
- uri, hostname = described_class.validate!(import_url)
+ it_behaves_like 'validates URI and hostname' do
+ let(:expected_uri) { import_url }
+ let(:expected_hostname) { nil }
+ end
+
+ context 'when the address is invalid' do
+ let(:import_url) { 'http://1.1.1.1.1' }
- expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34'))
- expect(hostname).to be(nil)
+ it 'raises an error' do
+ stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
+
+ expect { subject }.to raise_error(described_class::BlockedUrlError)
+ end
end
end
context 'disabled DNS rebinding protection' do
+ subject { described_class.validate!(import_url, dns_rebind_protection: false) }
+
context 'when URI is internal' do
let(:import_url) { 'http://localhost' }
- it 'returns URI and no hostname' do
- uri, hostname = described_class.validate!(import_url, dns_rebind_protection: false)
-
- expect(uri).to eq(Addressable::URI.parse('http://localhost'))
- expect(hostname).to be(nil)
+ it_behaves_like 'validates URI and hostname' do
+ let(:expected_uri) { import_url }
+ let(:expected_hostname) { nil }
end
end
context 'when the URL hostname is a domain' do
let(:import_url) { 'https://example.org' }
- it 'returns URI and no hostname' do
- uri, hostname = described_class.validate!(import_url, dns_rebind_protection: false)
+ before do
+ stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
+ end
- expect(uri).to eq(Addressable::URI.parse('https://example.org'))
- expect(hostname).to eq(nil)
+ context 'when domain can be resolved' do
+ it_behaves_like 'validates URI and hostname' do
+ let(:expected_uri) { import_url }
+ let(:expected_hostname) { nil }
+ end
end
- context 'when it cannot be resolved' do
+ context 'when domain cannot be resolved' do
let(:import_url) { 'http://foobar.x' }
- it 'raises error' do
- stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
-
- expect { described_class.validate!(import_url) }.to raise_error(described_class::BlockedUrlError)
+ it_behaves_like 'validates URI and hostname' do
+ let(:expected_uri) { import_url }
+ let(:expected_hostname) { nil }
end
end
end
@@ -85,20 +119,17 @@ describe Gitlab::UrlBlocker do
context 'when the URL hostname is an IP address' do
let(:import_url) { 'https://93.184.216.34' }
- it 'returns URI and no hostname' do
- uri, hostname = described_class.validate!(import_url, dns_rebind_protection: false)
-
- expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34'))
- expect(hostname).to be(nil)
+ it_behaves_like 'validates URI and hostname' do
+ let(:expected_uri) { import_url }
+ let(:expected_hostname) { nil }
end
context 'when it is invalid' do
let(:import_url) { 'http://1.1.1.1.1' }
- it 'raises an error' do
- stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
-
- expect { described_class.validate!(import_url) }.to raise_error(described_class::BlockedUrlError)
+ it_behaves_like 'validates URI and hostname' do
+ let(:expected_uri) { import_url }
+ let(:expected_hostname) { nil }
end
end
end
diff --git a/spec/lib/gitlab/usage_data_counters/merge_request_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/merge_request_counter_spec.rb
new file mode 100644
index 00000000000..4be4a661260
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/merge_request_counter_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::MergeRequestCounter do
+ it_behaves_like 'a redis usage counter', 'Merge Request', :create
+
+ it_behaves_like 'a redis usage counter with totals', :merge_request, create: 5
+end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index dda119e09b1..b3a179e276b 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -69,6 +69,7 @@ describe Gitlab::UsageData do
snippet_update: a_kind_of(Integer),
snippet_comment: a_kind_of(Integer),
merge_request_comment: a_kind_of(Integer),
+ merge_request_create: a_kind_of(Integer),
commit_comment: a_kind_of(Integer),
wiki_pages_create: a_kind_of(Integer),
wiki_pages_update: a_kind_of(Integer),
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/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 5c5ff46112f..88bc5034da5 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -14,6 +14,12 @@ describe Gitlab::Workhorse do
[key, command, params]
end
+ before do
+ allow(Feature::Gitaly).to receive(:server_feature_flags).and_return({
+ 'gitaly-feature-foobar' => 'true'
+ })
+ end
+
describe ".send_git_archive" do
let(:ref) { 'master' }
let(:format) { 'zip' }
@@ -41,6 +47,7 @@ describe Gitlab::Workhorse do
expected_params = metadata.merge(
'GitalyRepository' => repository.gitaly_repository.to_h,
'GitalyServer' => {
+ features: { 'gitaly-feature-foobar' => 'true' },
address: Gitlab::GitalyClient.address(project.repository_storage),
token: Gitlab::GitalyClient.token(project.repository_storage)
}
@@ -69,6 +76,7 @@ describe Gitlab::Workhorse do
expect(command).to eq('git-archive')
expect(params).to eq({
'GitalyServer' => {
+ features: { 'gitaly-feature-foobar' => 'true' },
address: Gitlab::GitalyClient.address(project.repository_storage),
token: Gitlab::GitalyClient.token(project.repository_storage)
},
@@ -117,6 +125,7 @@ describe Gitlab::Workhorse do
expect(command).to eq("git-format-patch")
expect(params).to eq({
'GitalyServer' => {
+ features: { 'gitaly-feature-foobar' => 'true' },
address: Gitlab::GitalyClient.address(project.repository_storage),
token: Gitlab::GitalyClient.token(project.repository_storage)
},
@@ -178,6 +187,7 @@ describe Gitlab::Workhorse do
expect(command).to eq("git-diff")
expect(params).to eq({
'GitalyServer' => {
+ features: { 'gitaly-feature-foobar' => 'true' },
address: Gitlab::GitalyClient.address(project.repository_storage),
token: Gitlab::GitalyClient.token(project.repository_storage)
},
@@ -190,57 +200,6 @@ describe Gitlab::Workhorse do
end
end
- describe ".secret" do
- subject { described_class.secret }
-
- before do
- described_class.instance_variable_set(:@secret, nil)
- described_class.write_secret
- end
-
- it 'returns 32 bytes' do
- expect(subject).to be_a(String)
- expect(subject.length).to eq(32)
- expect(subject.encoding).to eq(Encoding::ASCII_8BIT)
- end
-
- it 'accepts a trailing newline' do
- File.open(described_class.secret_path, 'a') { |f| f.write "\n" }
- expect(subject.length).to eq(32)
- end
-
- it 'raises an exception if the secret file cannot be read' do
- File.delete(described_class.secret_path)
- expect { subject }.to raise_exception(Errno::ENOENT)
- end
-
- it 'raises an exception if the secret file contains the wrong number of bytes' do
- File.truncate(described_class.secret_path, 0)
- expect { subject }.to raise_exception(RuntimeError)
- end
- end
-
- describe ".write_secret" do
- let(:secret_path) { described_class.secret_path }
- before do
- begin
- File.delete(secret_path)
- rescue Errno::ENOENT
- end
-
- described_class.write_secret
- end
-
- it 'uses mode 0600' do
- expect(File.stat(secret_path).mode & 0777).to eq(0600)
- end
-
- it 'writes base64 data' do
- bytes = Base64.strict_decode64(File.read(secret_path))
- expect(bytes).not_to be_empty
- end
- end
-
describe '#verify_api_request!' do
let(:header_key) { described_class::INTERNAL_API_REQUEST_HEADER }
let(:payload) { { 'iss' => 'gitlab-workhorse' } }
@@ -315,6 +274,7 @@ describe Gitlab::Workhorse do
let(:gitaly_params) do
{
GitalyServer: {
+ features: { 'gitaly-feature-foobar' => 'true' },
address: Gitlab::GitalyClient.address('default'),
token: Gitlab::GitalyClient.token('default')
}
@@ -463,6 +423,7 @@ describe Gitlab::Workhorse do
expect(command).to eq('git-blob')
expect(params).to eq({
'GitalyServer' => {
+ features: { 'gitaly-feature-foobar' => 'true' },
address: Gitlab::GitalyClient.address(project.repository_storage),
token: Gitlab::GitalyClient.token(project.repository_storage)
},
@@ -504,6 +465,7 @@ describe Gitlab::Workhorse do
expect(command).to eq('git-snapshot')
expect(params).to eq(
'GitalyServer' => {
+ 'features' => { 'gitaly-feature-foobar' => 'true' },
'address' => Gitlab::GitalyClient.address(project.repository_storage),
'token' => Gitlab::GitalyClient.token(project.repository_storage)
},
diff --git a/spec/lib/gitlab_danger_spec.rb b/spec/lib/gitlab_danger_spec.rb
new file mode 100644
index 00000000000..623ac20fa7c
--- /dev/null
+++ b/spec/lib/gitlab_danger_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabDanger do
+ let(:gitlab_danger_helper) { nil }
+
+ subject { described_class.new(gitlab_danger_helper) }
+
+ describe '.local_warning_message' do
+ it 'returns an informational message with rules that can run' do
+ expect(described_class.local_warning_message).to eq('==> Only the following Danger rules can be run locally: changes_size, gemfile, documentation, frozen_string, duplicate_yarn_dependencies, prettier, eslint, database')
+ end
+ end
+
+ describe '.success_message' do
+ it 'returns an informational success message' do
+ expect(described_class.success_message).to eq('==> No Danger rule violations!')
+ end
+ end
+
+ describe '#rule_names' do
+ context 'when running locally' do
+ it 'returns local only rules' do
+ expect(subject.rule_names).to eq(described_class::LOCAL_RULES)
+ end
+ end
+
+ context 'when running under CI' do
+ let(:gitlab_danger_helper) { double('danger_gitlab_helper') }
+
+ it 'returns all rules' do
+ expect(subject.rule_names).to eq(described_class::LOCAL_RULES | described_class::CI_ONLY_RULES)
+ end
+ end
+ end
+
+ describe '#html_link' do
+ context 'when running locally' do
+ it 'returns the same string' do
+ str = 'something'
+
+ expect(subject.html_link(str)).to eq(str)
+ end
+ end
+
+ context 'when running under CI' do
+ let(:gitlab_danger_helper) { double('danger_gitlab_helper') }
+
+ it 'returns a HTML link formatted version of the string' do
+ str = 'something'
+ html_formatted_str = %Q{<a href="#{str}">#{str}</a>}
+
+ expect(gitlab_danger_helper).to receive(:html_link).with(str).and_return(html_formatted_str)
+
+ expect(subject.html_link(str)).to eq(html_formatted_str)
+ end
+ end
+ end
+
+ describe '#ci?' do
+ context 'when gitlab_danger_helper is not available' do
+ it 'returns false' do
+ expect(subject.ci?).to be_falsey
+ end
+ end
+
+ context 'when gitlab_danger_helper is available' do
+ let(:gitlab_danger_helper) { double('danger_gitlab_helper') }
+
+ it 'returns true' do
+ expect(subject.ci?).to be_truthy
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb
index cbc5649fc09..589dac61528 100644
--- a/spec/lib/gitlab_spec.rb
+++ b/spec/lib/gitlab_spec.rb
@@ -1,8 +1,6 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
-
-require_dependency 'gitlab'
+require 'spec_helper'
describe Gitlab do
describe '.root' do
@@ -98,6 +96,33 @@ describe Gitlab do
end
end
+ describe '.dev_env_org_or_com?' do
+ it 'is true when on .com' do
+ allow(described_class).to receive_messages(com?: true, org?: false)
+
+ expect(described_class.dev_env_org_or_com?).to eq true
+ end
+
+ it 'is true when org' do
+ allow(described_class).to receive_messages(com?: false, org?: true)
+
+ expect(described_class.dev_env_org_or_com?).to eq true
+ end
+
+ it 'is true when dev env' do
+ allow(described_class).to receive_messages(com?: false, org?: false)
+ stub_rails_env('development')
+
+ expect(described_class.dev_env_org_or_com?).to eq true
+ end
+
+ it 'is false when not dev, org or com' do
+ allow(described_class).to receive_messages(com?: false, org?: false)
+
+ expect(described_class.dev_env_org_or_com?).to eq false
+ end
+ end
+
describe '.ee?' do
before do
described_class.instance_variable_set(:@is_ee, nil)
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/migrations/add_gitlab_instance_administration_project_spec.rb b/spec/migrations/add_gitlab_instance_administration_project_spec.rb
new file mode 100644
index 00000000000..08e20a4e8ff
--- /dev/null
+++ b/spec/migrations/add_gitlab_instance_administration_project_spec.rb
@@ -0,0 +1,252 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190801072937_add_gitlab_instance_administration_project.rb')
+
+describe AddGitlabInstanceAdministrationProject, :migration do
+ let(:application_settings) { table(:application_settings) }
+ let(:users) { table(:users) }
+ let(:projects) { table(:projects) }
+ let(:namespaces) { table(:namespaces) }
+ let(:members) { table(:members) }
+
+ let(:service_class) do
+ Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService
+ end
+
+ let(:prometheus_settings) do
+ {
+ enable: true,
+ listen_address: 'localhost:9090'
+ }
+ end
+
+ before do
+ stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+
+ stub_config(prometheus: prometheus_settings)
+ end
+
+ describe 'down' do
+ let!(:application_setting) { application_settings.create! }
+ let!(:user) { users.create!(admin: true, email: 'admin1@example.com', projects_limit: 10, state: :active) }
+
+ it 'deletes group and project' do
+ migrate!
+
+ expect(Project.count).to eq(1)
+ expect(Group.count).to eq(1)
+
+ schema_migrate_down!
+
+ expect(Project.count).to eq(0)
+ expect(Group.count).to eq(0)
+ end
+ end
+
+ describe 'up' do
+ context 'without application_settings' do
+ it 'does not fail' do
+ migrate!
+
+ expect(Project.count).to eq(0)
+ end
+ end
+
+ context 'without admin users' do
+ let!(:application_setting) { application_settings.create! }
+
+ it 'does not fail' do
+ migrate!
+
+ expect(Project.count).to eq(0)
+ end
+ end
+
+ context 'with admin users' do
+ let(:project) { Project.last }
+ let(:group) { Group.last }
+ let!(:application_setting) { application_settings.create! }
+ let!(:user) { users.create!(admin: true, email: 'admin1@example.com', projects_limit: 10, state: :active) }
+
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
+ end
+
+ shared_examples 'has prometheus service' do |listen_address|
+ it do
+ migrate!
+
+ prometheus = project.prometheus_service
+ expect(prometheus).to be_persisted
+ expect(prometheus).not_to eq(nil)
+ expect(prometheus.api_url).to eq(listen_address)
+ expect(prometheus.active).to eq(true)
+ expect(prometheus.manual_configuration).to eq(true)
+ end
+ end
+
+ it_behaves_like 'has prometheus service', 'http://localhost:9090'
+
+ it 'creates GitLab Instance Administrator group' do
+ migrate!
+
+ expect(group).to be_persisted
+ expect(group.name).to eq('GitLab Instance Administrators')
+ expect(group.path).to start_with('gitlab-instance-administrators')
+ expect(group.path.split('-').last.length).to eq(8)
+ expect(group.visibility_level).to eq(service_class::VISIBILITY_LEVEL)
+ end
+
+ it 'creates project with internal visibility' do
+ migrate!
+
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+ expect(project).to be_persisted
+ end
+
+ it 'creates project with correct name and description' do
+ migrate!
+
+ path = 'administration/monitoring/gitlab_instance_administration_project/index'
+ docs_path = Rails.application.routes.url_helpers.help_page_path(path)
+
+ expect(project.name).to eq(service_class::PROJECT_NAME)
+ expect(project.description).to eq(
+ 'This project is automatically generated and will be used to help monitor this GitLab instance. ' \
+ "[More information](#{docs_path})"
+ )
+ expect(File).to exist("doc/#{path}.md")
+ end
+
+ it 'adds all admins as maintainers' do
+ admin1 = users.create!(admin: true, email: 'admin2@example.com', projects_limit: 10, state: :active)
+ admin2 = users.create!(admin: true, email: 'admin3@example.com', projects_limit: 10, state: :active)
+ users.create!(email: 'nonadmin1@example.com', projects_limit: 10, state: :active)
+
+ migrate!
+
+ expect(project.owner).to eq(group)
+ expect(group.members.collect(&:user).collect(&:id)).to contain_exactly(user.id, admin1.id, admin2.id)
+ expect(group.members.collect(&:access_level)).to contain_exactly(
+ Gitlab::Access::OWNER,
+ Gitlab::Access::MAINTAINER,
+ Gitlab::Access::MAINTAINER
+ )
+ end
+
+ it 'saves the project id' do
+ migrate!
+
+ application_setting.reload
+ expect(application_setting.instance_administration_project_id).to eq(project.id)
+ end
+
+ it 'does not fail when a project already exists' do
+ group = namespaces.create!(
+ path: 'gitlab-instance-administrators',
+ name: 'GitLab Instance Administrators',
+ type: 'Group'
+ )
+ project = projects.create!(
+ namespace_id: group.id,
+ name: 'GitLab Instance Administration'
+ )
+
+ admin1 = users.create!(admin: true, email: 'admin4@example.com', projects_limit: 10, state: :active)
+ admin2 = users.create!(admin: true, email: 'admin5@example.com', projects_limit: 10, state: :active)
+
+ members.create!(
+ user_id: admin1.id,
+ source_id: group.id,
+ source_type: 'Namespace',
+ type: 'GroupMember',
+ access_level: GroupMember::MAINTAINER,
+ notification_level: NotificationSetting.levels[:global]
+ )
+ members.create!(
+ user_id: admin2.id,
+ source_id: group.id,
+ source_type: 'Namespace',
+ type: 'GroupMember',
+ access_level: GroupMember::MAINTAINER,
+ notification_level: NotificationSetting.levels[:global]
+ )
+
+ stub_application_setting(instance_administration_project: project)
+
+ migrate!
+
+ expect(Project.last.id).to eq(project.id)
+ expect(Group.last.id).to eq(group.id)
+ end
+
+ context 'when local requests from hooks and services are not allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
+ end
+
+ it_behaves_like 'has prometheus service', 'http://localhost:9090'
+
+ it 'does not overwrite the existing whitelist' do
+ application_setting.update!(outbound_local_requests_whitelist: ['example.com'])
+
+ migrate!
+
+ application_setting.reload
+ expect(application_setting.outbound_local_requests_whitelist).to contain_exactly(
+ 'example.com', 'localhost'
+ )
+ end
+ end
+
+ context 'with non default prometheus address' do
+ let(:prometheus_settings) do
+ {
+ enable: true,
+ listen_address: 'https://localhost:9090'
+ }
+ end
+
+ it_behaves_like 'has prometheus service', 'https://localhost:9090'
+ end
+
+ context 'when prometheus setting is not present in gitlab.yml' do
+ before do
+ allow(Gitlab.config).to receive(:prometheus).and_raise(Settingslogic::MissingSetting)
+ end
+
+ it 'does not fail' do
+ migrate!
+
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+
+ context 'when prometheus setting is disabled in gitlab.yml' do
+ let(:prometheus_settings) do
+ {
+ enable: false,
+ listen_address: 'localhost:9090'
+ }
+ end
+
+ it 'does not configure prometheus' do
+ migrate!
+
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+
+ context 'when prometheus listen address is blank in gitlab.yml' do
+ let(:prometheus_settings) { { enable: true, listen_address: '' } }
+
+ it 'does not configure prometheus' do
+ migrate!
+
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/migrations/encrypt_deploy_tokens_tokens_spec.rb b/spec/migrations/encrypt_deploy_tokens_tokens_spec.rb
new file mode 100644
index 00000000000..a398e079731
--- /dev/null
+++ b/spec/migrations/encrypt_deploy_tokens_tokens_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require Rails.root.join('db', 'post_migrate', '20190711201818_encrypt_deploy_tokens_tokens.rb')
+
+describe EncryptDeployTokensTokens, :migration do
+ let(:migration) { described_class.new }
+ let(:deployment_tokens) { table(:deploy_tokens) }
+ let(:plaintext) { "secret-token" }
+ let(:expires_at) { DateTime.now + 1.year }
+ let(:ciphertext) { Gitlab::CryptoHelper.aes256_gcm_encrypt(plaintext) }
+
+ describe '#up' do
+ it 'keeps plaintext token the same and populates token_encrypted if not present' do
+ deploy_token = deployment_tokens.create!(
+ name: 'test_token',
+ read_repository: true,
+ expires_at: expires_at,
+ username: 'gitlab-token-1',
+ token: plaintext
+ )
+
+ migration.up
+
+ expect(deploy_token.reload.token).to eq(plaintext)
+ expect(deploy_token.reload.token_encrypted).to eq(ciphertext)
+ end
+ end
+
+ describe '#down' do
+ it 'decrypts encrypted token and saves it' do
+ deploy_token = deployment_tokens.create!(
+ name: 'test_token',
+ read_repository: true,
+ expires_at: expires_at,
+ username: 'gitlab-token-1',
+ token_encrypted: ciphertext
+ )
+
+ migration.down
+
+ expect(deploy_token.reload.token).to eq(plaintext)
+ expect(deploy_token.reload.token_encrypted).to eq(ciphertext)
+ end
+ end
+end
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/analytics/cycle_analytics/project_stage_spec.rb b/spec/models/analytics/cycle_analytics/project_stage_spec.rb
index 4e3923e82b1..83d6ff754c5 100644
--- a/spec/models/analytics/cycle_analytics/project_stage_spec.rb
+++ b/spec/models/analytics/cycle_analytics/project_stage_spec.rb
@@ -6,4 +6,18 @@ describe Analytics::CycleAnalytics::ProjectStage do
describe 'associations' do
it { is_expected.to belong_to(:project) }
end
+
+ it 'default stages must be valid' do
+ project = create(:project)
+
+ Gitlab::Analytics::CycleAnalytics::DefaultStages.all.each do |params|
+ stage = described_class.new(params.merge(project: project))
+ expect(stage).to be_valid
+ end
+ end
+
+ it_behaves_like "cycle analytics stage" do
+ let(:parent) { create(:project) }
+ let(:parent_name) { :project }
+ end
end
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 78be4a8131a..146e479adef 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Ci::Pipeline, :mailer do
include ProjectForksHelper
+ include StubRequests
let(:user) { create(:user) }
set(:project) { create(:project) }
@@ -19,6 +20,7 @@ describe Ci::Pipeline, :mailer do
it { is_expected.to belong_to(:auto_canceled_by) }
it { is_expected.to belong_to(:pipeline_schedule) }
it { is_expected.to belong_to(:merge_request) }
+ it { is_expected.to belong_to(:external_pull_request) }
it { is_expected.to have_many(:statuses) }
it { is_expected.to have_many(:trigger_requests) }
@@ -322,6 +324,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? }
@@ -332,6 +353,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? }
@@ -781,7 +844,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
@@ -822,6 +886,25 @@ describe Ci::Pipeline, :mailer do
end
end
end
+
+ context 'when source is external pull request' do
+ let(:pipeline) do
+ create(:ci_pipeline, source: :external_pull_request_event, external_pull_request: pull_request)
+ end
+
+ let(:pull_request) { create(:external_pull_request, project: project) }
+
+ it 'exposes external pull request pipeline variables' do
+ expect(subject.to_hash)
+ .to include(
+ 'CI_EXTERNAL_PULL_REQUEST_IID' => pull_request.pull_request_iid.to_s,
+ 'CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_SHA' => pull_request.source_sha,
+ 'CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_SHA' => pull_request.target_sha,
+ 'CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_NAME' => pull_request.source_branch,
+ 'CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME' => pull_request.target_branch
+ )
+ end
+ end
end
describe '#protected_ref?' do
@@ -2504,7 +2587,7 @@ describe Ci::Pipeline, :mailer do
let(:enabled) { true }
before do
- WebMock.stub_request(:post, hook.url)
+ stub_full_request(hook.url, method: :post)
end
context 'with multiple builds' do
@@ -2558,7 +2641,7 @@ describe Ci::Pipeline, :mailer do
end
def have_requested_pipeline_hook(status)
- have_requested(:post, hook.url).with do |req|
+ have_requested(:post, stubbed_hostname(hook.url)).with do |req|
json_body = JSON.parse(req.body)
json_body['object_attributes']['status'] == status &&
json_body['builds'].length == 2
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..e1eee014567 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
@@ -81,27 +81,45 @@ describe Clusters::Applications::Jupyter do
end
describe '#files' do
- let(:application) { create(:clusters_applications_jupyter) }
+ let(:cluster) { create(:cluster, :with_installed_helm, :provided_by_gcp, :project) }
+ let(:application) { create(:clusters_applications_jupyter, cluster: cluster) }
let(:values) { subject[:'values.yaml'] }
subject { application.files }
- it 'includes valid values' do
- expect(values).to include('ingress')
- expect(values).to include('hub')
- expect(values).to include('rbac')
- expect(values).to include('proxy')
- expect(values).to include('auth')
- expect(values).to include('singleuser')
- expect(values).to match(/clientId: '?#{application.oauth_application.uid}/)
- expect(values).to match(/callbackUrl: '?#{application.callback_url}/)
- expect(values).to include("gitlabProjectIdWhitelist:\n - #{application.cluster.project.id}")
- expect(values).to include("c.GitLabOAuthenticator.scope = ['api read_repository write_repository']")
- expect(values).to match(/GITLAB_HOST: '?#{Gitlab.config.gitlab.host}/)
+ context 'when cluster belongs to a project' do
+ it 'includes valid values' do
+ expect(values).to include('ingress')
+ expect(values).to include('hub')
+ expect(values).to include('rbac')
+ expect(values).to include('proxy')
+ expect(values).to include('auth')
+ expect(values).to include('singleuser')
+ expect(values).to match(/clientId: '?#{application.oauth_application.uid}/)
+ expect(values).to match(/callbackUrl: '?#{application.callback_url}/)
+ expect(values).to include("gitlabProjectIdWhitelist:\n - #{application.cluster.project.id}")
+ expect(values).to include("c.GitLabOAuthenticator.scope = ['api read_repository write_repository']")
+ expect(values).to match(/GITLAB_HOST: '?#{Gitlab.config.gitlab.host}/)
+ expect(values).to match(/GITLAB_CLUSTER_ID: '?#{application.cluster.id}/)
+ end
end
- context 'when cluster belongs to a project' do
- it 'sets GitLab project id' do
+ context 'when cluster belongs to a group' do
+ let(:group) { create(:group) }
+ let(:cluster) { create(:cluster, :with_installed_helm, :provided_by_gcp, :group, groups: [group]) }
+
+ it 'includes valid values' do
+ expect(values).to include('ingress')
+ expect(values).to include('hub')
+ expect(values).to include('rbac')
+ expect(values).to include('proxy')
+ expect(values).to include('auth')
+ expect(values).to include('singleuser')
+ expect(values).to match(/clientId: '?#{application.oauth_application.uid}/)
+ expect(values).to match(/callbackUrl: '?#{application.callback_url}/)
+ expect(values).to include("gitlabGroupWhitelist:\n - #{group.to_param}")
+ expect(values).to include("c.GitLabOAuthenticator.scope = ['api read_repository write_repository']")
+ expect(values).to match(/GITLAB_HOST: '?#{Gitlab.config.gitlab.host}/)
expect(values).to match(/GITLAB_CLUSTER_ID: '?#{application.cluster.id}/)
end
end
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/commit_spec.rb b/spec/models/commit_spec.rb
index 7b35c2ffd36..5ef824b9950 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -192,6 +192,24 @@ describe Commit do
end
end
+ describe '.reference_valid?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:ref, :result) do
+ '1234567' | true
+ '123456' | false
+ '1' | false
+ '0' * 40 | true
+ 'c1acaa58bbcbc3eafe538cb8274ba387047b69f8' | true
+ 'H1acaa58bbcbc3eafe538cb8274ba387047b69f8' | false
+ nil | false
+ end
+
+ with_them do
+ it { expect(described_class.reference_valid?(ref)).to eq(result) }
+ end
+ end
+
describe '#reference_link_text' do
let(:project) { create(:project, :repository, path: 'sample-project') }
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/cacheable_attributes_spec.rb b/spec/models/concerns/cacheable_attributes_spec.rb
index da46effe411..d8f940a808e 100644
--- a/spec/models/concerns/cacheable_attributes_spec.rb
+++ b/spec/models/concerns/cacheable_attributes_spec.rb
@@ -131,7 +131,7 @@ describe CacheableAttributes do
context 'in production environment' do
before do
- expect(Rails.env).to receive(:production?).and_return(true)
+ stub_rails_env('production')
end
it 'returns an uncached record and logs a warning' do
@@ -143,7 +143,7 @@ describe CacheableAttributes do
context 'in other environments' do
before do
- expect(Rails.env).to receive(:production?).and_return(false)
+ stub_rails_env('development')
end
it 'returns an uncached record and logs a warning' 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/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb
index a217dc42537..09fb2fff521 100644
--- a/spec/models/concerns/has_status_spec.rb
+++ b/spec/models/concerns/has_status_spec.rb
@@ -261,6 +261,18 @@ describe HasStatus do
end
end
+ describe '.alive_or_scheduled' do
+ subject { CommitStatus.alive_or_scheduled }
+
+ %i[running pending preparing created scheduled].each do |status|
+ it_behaves_like 'containing the job', status
+ end
+
+ %i[failed success canceled skipped].each do |status|
+ it_behaves_like 'not containing the job', status
+ end
+ end
+
describe '.created_or_pending' do
subject { CommitStatus.created_or_pending }
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/noteable_spec.rb b/spec/models/concerns/noteable_spec.rb
index e17b98536fa..929b5f52c7c 100644
--- a/spec/models/concerns/noteable_spec.rb
+++ b/spec/models/concerns/noteable_spec.rb
@@ -272,4 +272,22 @@ describe Noteable do
expect(described_class.resolvable_types).to include('MergeRequest')
end
end
+
+ describe '#capped_notes_count' do
+ context 'notes number < 10' do
+ it 'the number of notes is returned' do
+ expect(subject.capped_notes_count(10)).to eq(9)
+ end
+ end
+
+ context 'notes number > 10' do
+ before do
+ create_list(:note, 2, project: project, noteable: subject)
+ end
+
+ it '10 is returned' do
+ expect(subject.capped_notes_count(10)).to eq(10)
+ end
+ 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/sha_attribute_spec.rb b/spec/models/concerns/sha_attribute_spec.rb
index a4a81ae126d..0d4dbfb215e 100644
--- a/spec/models/concerns/sha_attribute_spec.rb
+++ b/spec/models/concerns/sha_attribute_spec.rb
@@ -17,7 +17,7 @@ describe ShaAttribute do
describe '#sha_attribute' do
context 'when in non-production' do
before do
- allow(Rails.env).to receive(:production?).and_return(false)
+ stub_rails_env('development')
end
context 'when the table exists' do
@@ -76,7 +76,7 @@ describe ShaAttribute do
context 'when in production' do
before do
- allow(Rails.env).to receive(:production?).and_return(true)
+ stub_rails_env('production')
end
it 'defines a SHA attribute' 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/deployment_spec.rb b/spec/models/deployment_spec.rb
index d4e631f109b..51ed8e9421b 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -322,4 +322,30 @@ describe Deployment do
end
end
end
+
+ describe '#deployed_by' do
+ it 'returns the deployment user if there is no deployable' do
+ deployment_user = create(:user)
+ deployment = create(:deployment, deployable: nil, user: deployment_user)
+
+ expect(deployment.deployed_by).to eq(deployment_user)
+ end
+
+ it 'returns the deployment user if the deployable have no user' do
+ deployment_user = create(:user)
+ build = create(:ci_build, user: nil)
+ deployment = create(:deployment, deployable: build, user: deployment_user)
+
+ expect(deployment.deployed_by).to eq(deployment_user)
+ end
+
+ it 'returns the deployable user if there is one' do
+ build_user = create(:user)
+ deployment_user = create(:user)
+ build = create(:ci_build, user: build_user)
+ deployment = create(:deployment, deployable: build, user: deployment_user)
+
+ expect(deployment.deployed_by).to eq(build_user)
+ end
+ end
end
diff --git a/spec/models/external_pull_request_spec.rb b/spec/models/external_pull_request_spec.rb
new file mode 100644
index 00000000000..e85d5b2f6c7
--- /dev/null
+++ b/spec/models/external_pull_request_spec.rb
@@ -0,0 +1,220 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ExternalPullRequest do
+ let(:project) { create(:project) }
+ let(:source_branch) { 'the-branch' }
+ let(:status) { :open }
+
+ it { is_expected.to belong_to(:project) }
+
+ shared_examples 'has errors on' do |attribute|
+ it "has errors for #{attribute}" do
+ expect(subject).not_to be_valid
+ expect(subject.errors[attribute]).not_to be_empty
+ end
+ end
+
+ describe 'validations' do
+ context 'when source branch not present' do
+ subject { build(:external_pull_request, source_branch: nil) }
+
+ it_behaves_like 'has errors on', :source_branch
+ end
+
+ context 'when status not present' do
+ subject { build(:external_pull_request, status: nil) }
+
+ it_behaves_like 'has errors on', :status
+ end
+
+ context 'when pull request is from a fork' do
+ subject { build(:external_pull_request, source_repository: 'the-fork', target_repository: 'the-target') }
+
+ it_behaves_like 'has errors on', :base
+ end
+ end
+
+ describe 'create_or_update_from_params' do
+ subject { described_class.create_or_update_from_params(params) }
+
+ context 'when pull request does not exist' do
+ context 'when params are correct' do
+ let(:params) do
+ {
+ project_id: project.id,
+ pull_request_iid: 123,
+ source_branch: 'feature',
+ target_branch: 'master',
+ source_repository: 'the-repository',
+ target_repository: 'the-repository',
+ source_sha: '97de212e80737a608d939f648d959671fb0a0142',
+ target_sha: 'a09386439ca39abe575675ffd4b89ae824fec22f',
+ status: :open
+ }
+ end
+
+ it 'saves the model successfully and returns it' do
+ expect(subject).to be_persisted
+ expect(subject).to be_valid
+ end
+
+ it 'yields the model' do
+ yielded_value = nil
+
+ result = described_class.create_or_update_from_params(params) do |pull_request|
+ yielded_value = pull_request
+ end
+
+ expect(result).to eq(yielded_value)
+ end
+ end
+
+ context 'when params are not correct' do
+ let(:params) do
+ {
+ pull_request_iid: 123,
+ source_branch: 'feature',
+ target_branch: 'master',
+ source_repository: 'the-repository',
+ target_repository: 'the-repository',
+ source_sha: nil,
+ target_sha: nil,
+ status: :open
+ }
+ end
+
+ it 'returns an invalid model' do
+ expect(subject).not_to be_persisted
+ expect(subject).not_to be_valid
+ end
+ end
+ end
+
+ context 'when pull request exists' do
+ let!(:pull_request) do
+ create(:external_pull_request,
+ project: project,
+ source_sha: '97de212e80737a608d939f648d959671fb0a0142')
+ end
+
+ context 'when params are correct' do
+ let(:params) do
+ {
+ pull_request_iid: pull_request.pull_request_iid,
+ source_branch: pull_request.source_branch,
+ target_branch: pull_request.target_branch,
+ source_repository: 'the-repository',
+ target_repository: 'the-repository',
+ source_sha: 'ce84140e8b878ce6e7c4d298c7202ff38170e3ac',
+ target_sha: pull_request.target_sha,
+ status: :open
+ }
+ end
+
+ it 'updates the model successfully and returns it' do
+ expect(subject).to be_valid
+ expect(subject.source_sha).to eq(params[:source_sha])
+ expect(pull_request.reload.source_sha).to eq(params[:source_sha])
+ end
+ end
+
+ context 'when params are not correct' do
+ let(:params) do
+ {
+ pull_request_iid: pull_request.pull_request_iid,
+ source_branch: pull_request.source_branch,
+ target_branch: pull_request.target_branch,
+ source_repository: 'the-repository',
+ target_repository: 'the-repository',
+ source_sha: nil,
+ target_sha: nil,
+ status: :open
+ }
+ end
+
+ it 'returns an invalid model' do
+ expect(subject).not_to be_valid
+ expect(pull_request.reload.source_sha).not_to be_nil
+ expect(pull_request.target_sha).not_to be_nil
+ end
+ end
+ end
+ end
+
+ describe '#open?' do
+ it 'returns true if status is open' do
+ pull_request = create(:external_pull_request, status: :open)
+
+ expect(pull_request).to be_open
+ end
+
+ it 'returns false if status is not open' do
+ pull_request = create(:external_pull_request, status: :closed)
+
+ expect(pull_request).not_to be_open
+ end
+ end
+
+ describe '#closed?' do
+ it 'returns true if status is closed' do
+ pull_request = build(:external_pull_request, status: :closed)
+
+ expect(pull_request).to be_closed
+ end
+
+ it 'returns false if status is not closed' do
+ pull_request = build(:external_pull_request, status: :open)
+
+ expect(pull_request).not_to be_closed
+ end
+ end
+
+ describe '#actual_branch_head?' do
+ let(:project) { create(:project, :repository) }
+ let(:branch) { project.repository.branches.first }
+ let(:source_branch) { branch.name }
+
+ let(:pull_request) do
+ create(:external_pull_request,
+ project: project,
+ source_branch: source_branch,
+ source_sha: source_sha)
+ end
+
+ context 'when source sha matches the head of the branch' do
+ let(:source_sha) { branch.target }
+
+ it 'returns true' do
+ expect(pull_request).to be_actual_branch_head
+ end
+ end
+
+ context 'when source sha does not match the head of the branch' do
+ let(:source_sha) { project.repository.commit('HEAD').sha }
+
+ it 'returns true' do
+ expect(pull_request).not_to be_actual_branch_head
+ end
+ end
+ end
+
+ describe '#from_fork?' do
+ it 'returns true if source_repository differs from target_repository' do
+ pull_request = build(:external_pull_request,
+ source_repository: 'repository-1',
+ target_repository: 'repository-2')
+
+ expect(pull_request).to be_from_fork
+ end
+
+ it 'returns false if source_repository is the same as target_repository' do
+ pull_request = build(:external_pull_request,
+ source_repository: 'repository-1',
+ target_repository: 'repository-1')
+
+ expect(pull_request).not_to be_from_fork
+ end
+ end
+end
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/internal_id_spec.rb b/spec/models/internal_id_spec.rb
index 28630f7d3fe..c73ade3f896 100644
--- a/spec/models/internal_id_spec.rb
+++ b/spec/models/internal_id_spec.rb
@@ -106,7 +106,8 @@ describe InternalId do
end
it 'always attempts to generate internal IDs in production mode' do
- allow(Rails.env).to receive(:test?).and_return(false)
+ stub_rails_env('production')
+
val = rand(1..100)
generator = double(generate: val)
expect(InternalId::InternalIdGenerator).to receive(:new).and_return(generator)
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/metrics_spec.rb b/spec/models/merge_request/metrics_spec.rb
index 49573af0fed..bd97cabc11e 100644
--- a/spec/models/merge_request/metrics_spec.rb
+++ b/spec/models/merge_request/metrics_spec.rb
@@ -3,8 +3,6 @@
require 'spec_helper'
describe MergeRequest::Metrics do
- subject { described_class.new }
-
describe 'associations' do
it { is_expected.to belong_to(:merge_request) }
it { is_expected.to belong_to(:latest_closed_by).class_name('User') }
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_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index e7dd7287a75..b86663fd7d9 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -400,6 +400,18 @@ describe MergeRequestDiff do
end
end
+ describe '#first_commit' do
+ it 'returns first commit' do
+ expect(diff_with_commits.first_commit.sha).to eq(diff_with_commits.merge_request_diff_commits.last.sha)
+ end
+ end
+
+ describe '#last_commit' do
+ it 'returns last commit' do
+ expect(diff_with_commits.last_commit.sha).to eq(diff_with_commits.merge_request_diff_commits.first.sha)
+ end
+ end
+
describe '#commits_by_shas' do
let(:commit_shas) { diff_with_commits.commit_shas }
@@ -489,7 +501,7 @@ describe MergeRequestDiff do
subject { diff_with_commits }
it 'returns sum of all changed lines count in diff files' do
- expect(subject.lines_count).to eq 109
+ expect(subject.lines_count).to eq 189
end
end
end
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 ff9e94afc12..e2a684c42ae 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -99,6 +99,7 @@ describe Project do
it { is_expected.to have_many(:project_deploy_tokens) }
it { is_expected.to have_many(:deploy_tokens).through(:project_deploy_tokens) }
it { is_expected.to have_many(:cycle_analytics_stages) }
+ it { is_expected.to have_many(:external_pull_requests) }
it 'has an inverse relationship with merge requests' do
expect(described_class.reflect_on_association(:merge_requests).has_inverse?).to eq(:target_project)
@@ -4877,35 +4878,22 @@ describe Project do
describe '#git_objects_poolable?' do
subject { project }
-
- context 'when the feature flag is turned off' do
- before do
- stub_feature_flags(object_pools: false)
- end
-
- let(:project) { create(:project, :repository, :public) }
+ context 'when not using hashed storage' do
+ let(:project) { create(:project, :legacy_storage, :public, :repository) }
it { is_expected.not_to be_git_objects_poolable }
end
- context 'when the feature flag is enabled' do
- context 'when not using hashed storage' do
- let(:project) { create(:project, :legacy_storage, :public, :repository) }
+ context 'when the project is not public' do
+ let(:project) { create(:project, :private) }
- it { is_expected.not_to be_git_objects_poolable }
- end
-
- context 'when the project is not public' do
- let(:project) { create(:project, :private) }
-
- it { is_expected.not_to be_git_objects_poolable }
- end
+ it { is_expected.not_to be_git_objects_poolable }
+ end
- context 'when objects are poolable' do
- let(:project) { create(:project, :repository, :public) }
+ context 'when objects are poolable' do
+ let(:project) { create(:project, :repository, :public) }
- it { is_expected.to be_git_objects_poolable }
- end
+ it { is_expected.to be_git_objects_poolable }
end
end
@@ -5004,6 +4992,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/todo_spec.rb b/spec/models/todo_spec.rb
index 9aeef7c3b4b..ce17704acbd 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -121,12 +121,12 @@ describe Todo do
subject.target_type = 'Commit'
subject.commit_id = commit.id
- expect(subject.target_reference).to eq commit.reference_link_text(full: true)
+ expect(subject.target_reference).to eq commit.reference_link_text(full: false)
end
it 'returns full reference for issuables' do
subject.target = issue
- expect(subject.target_reference).to eq issue.to_reference(full: true)
+ expect(subject.target_reference).to eq issue.to_reference(full: false)
end
end
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 46b86e8393d..b8c323904b8 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -103,6 +103,14 @@ describe User do
it { is_expected.to validate_length_of(:name).is_at_most(128) }
end
+ describe 'first name' do
+ it { is_expected.to validate_length_of(:first_name).is_at_most(255) }
+ end
+
+ describe 'last name' do
+ it { is_expected.to validate_length_of(:last_name).is_at_most(255) }
+ end
+
describe 'username' do
it 'validates presence' do
expect(subject).to validate_presence_of(:username)
@@ -678,6 +686,18 @@ describe User do
end
end
+ describe 'name getters' do
+ let(:user) { create(:user, name: 'Kane Martin William') }
+
+ it 'derives first name from full name, if not present' do
+ expect(user.first_name).to eq('Kane')
+ end
+
+ it 'derives last name from full name, if not present' do
+ expect(user.last_name).to eq('Martin William')
+ end
+ end
+
describe '#highest_role' do
let(:user) { create(:user) }
@@ -1156,7 +1176,7 @@ describe User do
expect(user.can_create_group).to eq(Gitlab.config.gitlab.default_can_create_group)
expect(user.theme_id).to eq(Gitlab.config.gitlab.default_theme)
expect(user.external).to be_falsey
- expect(user.private_profile).to eq false
+ expect(user.private_profile).to eq(false)
end
end
@@ -3045,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/board_policy_spec.rb b/spec/policies/board_policy_spec.rb
index 52c23951e37..35eac8a02c4 100644
--- a/spec/policies/board_policy_spec.rb
+++ b/spec/policies/board_policy_spec.rb
@@ -56,4 +56,57 @@ describe BoardPolicy do
end
end
end
+
+ context 'create_non_backlog_issues' do
+ context 'for project boards' do
+ let!(:current_user) { create(:user) }
+
+ subject { described_class.new(current_user, project_board) }
+
+ context 'when user can admin project issues' do
+ it 'allows to add non backlog issues from issue board' do
+ project.add_reporter(current_user)
+
+ expect_allowed(:create_non_backlog_issues)
+ end
+ end
+
+ context 'when user cannot admin project issues' do
+ it 'does not allow to add non backlog issues from issue board' do
+ project.add_guest(current_user)
+
+ expect_disallowed(:create_non_backlog_issues)
+ end
+ end
+ end
+
+ context 'for group boards' do
+ let!(:current_user) { create(:user) }
+ let!(:project_1) { create(:project, namespace: group) }
+ let!(:project_2) { create(:project, namespace: group) }
+ let!(:group_board) { create(:board, group: group) }
+
+ subject { described_class.new(current_user, group_board) }
+
+ before do
+ project_1.add_guest(current_user)
+ end
+
+ context 'when user is at least reporter in one of the child projects' do
+ it 'allows to add non backlog issues from issue board' do
+ project_2.add_reporter(current_user)
+
+ expect_allowed(:create_non_backlog_issues)
+ end
+ end
+
+ context 'when user is not a reporter from any child projects' do
+ it 'does not allow to add non backlog issues from issue board' do
+ project_2.add_guest(current_user)
+
+ expect_disallowed(:create_non_backlog_issues)
+ end
+ end
+ end
+ end
end
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/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 8fd54e0bf1d..71ba73d5661 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -94,6 +94,19 @@ describe ProjectPolicy do
permissions.each { |p| is_expected.not_to be_allowed(p) }
end
+ context 'with no project feature' do
+ subject { described_class.new(owner, project) }
+
+ before do
+ project.project_feature.destroy
+ project.reload
+ end
+
+ it 'returns false' do
+ is_expected.to be_disallowed(:read_build)
+ end
+ end
+
it 'does not include the read_issue permission when the issue author is not a member of the private project' do
project = create(:project, :private)
issue = create(:issue, project: project, author: create(:user))
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/discussions_spec.rb b/spec/requests/api/discussions_spec.rb
index ef09c6effbb..0420201efe3 100644
--- a/spec/requests/api/discussions_spec.rb
+++ b/spec/requests/api/discussions_spec.rb
@@ -9,59 +9,11 @@ describe API::Discussions do
project.add_developer(user)
end
- context 'with cross-reference system notes', :request_store do
- let(:merge_request) { create(:merge_request) }
- let(:project) { merge_request.project }
- let(:new_merge_request) { create(:merge_request) }
- let(:commit) { new_merge_request.project.commit }
- let!(:note) { create(:system_note, noteable: merge_request, project: project, note: cross_reference) }
- let!(:note_metadata) { create(:system_note_metadata, note: note, action: 'cross_reference') }
- let(:cross_reference) { "test commit #{commit.to_reference(project)}" }
- let(:pat) { create(:personal_access_token, user: user) }
-
+ context 'when discussions have cross-reference system notes' do
let(:url) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}/discussions" }
+ let(:notes_in_response) { json_response.first['notes'] }
- before do
- project.add_developer(user)
- new_merge_request.project.add_developer(user)
- end
-
- it 'returns only the note that the user should see' do
- hidden_merge_request = create(:merge_request)
- new_cross_reference = "test commit #{hidden_merge_request.project.commit}"
- new_note = create(:system_note, noteable: merge_request, project: project, note: new_cross_reference)
- create(:system_note_metadata, note: new_note, action: 'cross_reference')
-
- get api(url, user, personal_access_token: pat)
- expect(response).to have_gitlab_http_status(200)
- expect(json_response.count).to eq(1)
- expect(json_response.first['notes'].count).to eq(1)
-
- parsed_note = json_response.first['notes'].first
- expect(parsed_note['id']).to eq(note.id)
- expect(parsed_note['body']).to eq(cross_reference)
- expect(parsed_note['system']).to be true
- end
-
- it 'avoids Git calls and N+1 SQL queries' do
- expect_any_instance_of(Repository).not_to receive(:find_commit).with(commit.id)
-
- control = ActiveRecord::QueryRecorder.new do
- get api(url, user, personal_access_token: pat)
- end
-
- expect(response).to have_gitlab_http_status(200)
-
- RequestStore.clear!
-
- new_note = create(:system_note, noteable: merge_request, project: project, note: cross_reference)
- create(:system_note_metadata, note: new_note, action: 'cross_reference')
-
- RequestStore.clear!
-
- expect { get api(url, user, personal_access_token: pat) }.not_to exceed_query_limit(control)
- expect(response).to have_gitlab_http_status(200)
- end
+ it_behaves_like 'with cross-reference system notes'
end
context 'when noteable is an Issue' do
diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb
index 28676bb02f4..e1eb7c7f738 100644
--- a/spec/requests/api/graphql/gitlab_schema_spec.rb
+++ b/spec/requests/api/graphql/gitlab_schema_spec.rb
@@ -120,7 +120,7 @@ describe 'GitlabSchema configurations' do
query_string: query,
variables: {}.to_s,
complexity: 181,
- depth: 0,
+ depth: 13,
duration: 7
}
diff --git a/spec/requests/api/graphql/multiplexed_queries_spec.rb b/spec/requests/api/graphql/multiplexed_queries_spec.rb
index 844fd979285..9ebb57f6b9c 100644
--- a/spec/requests/api/graphql/multiplexed_queries_spec.rb
+++ b/spec/requests/api/graphql/multiplexed_queries_spec.rb
@@ -6,9 +6,9 @@ describe 'Multiplexed queries' do
it 'returns responses for multiple queries' do
queries = [
- { query: 'query($text: String) { echo(text: $text) }',
+ { query: 'query($text: String!) { echo(text: $text) }',
variables: { 'text' => 'Hello' } },
- { query: 'query($text: String) { echo(text: $text) }',
+ { query: 'query($text: String!) { echo(text: $text) }',
variables: { 'text' => 'World' } }
]
@@ -23,8 +23,8 @@ describe 'Multiplexed queries' do
it 'returns error and data combinations' do
queries = [
- { query: 'query($text: String) { broken query }' },
- { query: 'query working($text: String) { echo(text: $text) }',
+ { query: 'query($text: String!) { broken query }' },
+ { query: 'query working($text: String!) { echo(text: $text) }',
variables: { 'text' => 'World' } }
]
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
index d75f0df9fd3..bbc477ba485 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
@@ -13,7 +13,16 @@ describe 'Setting WIP status of a merge request' do
project_path: project.full_path,
iid: merge_request.iid.to_s
}
- graphql_mutation(:merge_request_set_wip, variables.merge(input))
+ graphql_mutation(:merge_request_set_wip, variables.merge(input),
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ mergeRequest {
+ id
+ title
+ }
+ QL
+ )
end
def mutation_response
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 50f36141aed..0893dcb39b6 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -483,6 +483,22 @@ describe API::Groups do
describe "GET /groups/:id/projects" do
context "when authenticated as user" do
+ context 'with min access level' do
+ it 'returns projects with min access level or higher' do
+ group_guest = create(:user)
+ group1.add_guest(group_guest)
+ project4 = create(:project, group: group1)
+ project1.add_guest(group_guest)
+ project3.add_reporter(group_guest)
+ project4.add_developer(group_guest)
+
+ get api("/groups/#{group1.id}/projects", group_guest), params: { min_access_level: Gitlab::Access::REPORTER }
+
+ project_ids = json_response.map { |proj| proj['id'] }
+ expect(project_ids).to match_array([project3.id, project4.id])
+ end
+ end
+
it "returns the group's projects" do
get api("/groups/#{group1.id}/projects", user1)
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal/base_spec.rb
index 3ab1818bebb..a56527073c7 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal/base_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe API::Internal do
+describe API::Internal::Base do
set(:user) { create(:user) }
let(:key) { create(:key, user: user) }
set(:project) { create(:project, :repository, :wiki_repo) }
@@ -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/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb
index 521d6b88734..b7aa3f93451 100644
--- a/spec/requests/api/issues/get_project_issues_spec.rb
+++ b/spec/requests/api/issues/get_project_issues_spec.rb
@@ -446,6 +446,14 @@ describe API::Issues do
expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id])
end
+ it 'exposes known attributes' do
+ get api("#{base_url}/issues", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.last.keys).to include(*%w(id iid project_id title description))
+ expect(json_response.last).not_to have_key('subscribed')
+ end
+
context 'issues_statistics' do
context 'no state is treated as all state' do
let(:params) { {} }
diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb
index 27cf66629fe..f19c2dcc6fe 100644
--- a/spec/requests/api/issues/issues_spec.rb
+++ b/spec/requests/api/issues/issues_spec.rb
@@ -607,6 +607,22 @@ describe API::Issues do
expect_paginated_array_response([closed_issue.id, issue.id])
end
+ context 'with issues list sort options' do
+ it 'accepts only predefined order by params' do
+ API::Helpers::IssuesHelpers.sort_options.each do |sort_opt|
+ get api('/issues', user), params: { order_by: sort_opt, sort: 'asc' }
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ it 'fails to sort with non predefined options' do
+ %w(milestone title abracadabra).each do |sort_opt|
+ get api('/issues', user), params: { order_by: sort_opt, sort: 'asc' }
+ expect(response).to have_gitlab_http_status(400)
+ end
+ end
+ end
+
it 'matches V4 response schema' do
get api('/issues', user)
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index ad0974f55a3..9aef67e28a7 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -6,6 +6,180 @@ describe API::Labels do
let!(:label1) { create(:label, title: 'label1', project: project) }
let!(:priority_label) { create(:label, title: 'bug', project: project, priority: 3) }
+ shared_examples 'label update API' do
+ it 'returns 200 if name is changed' do
+ request_params = {
+ new_name: 'New Label'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['name']).to eq('New Label')
+ expect(json_response['color']).to eq(label1.color)
+ end
+
+ it 'returns 200 if colors is changed' do
+ request_params = {
+ color: '#FFFFFF'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['name']).to eq(label1.name)
+ expect(json_response['color']).to eq('#FFFFFF')
+ end
+
+ it 'returns 200 if a priority is added' do
+ request_params = {
+ priority: 3
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response.status).to eq(200)
+ expect(json_response['name']).to eq(label1.name)
+ expect(json_response['priority']).to eq(3)
+ end
+
+ it 'returns 400 if no new parameters given' do
+ put api("/projects/#{project.id}/labels", user), params: spec_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('new_name, color, description, priority are missing, '\
+ 'at least one parameter must be provided')
+ end
+
+ it 'returns 400 when color code is too short' do
+ request_params = {
+ color: '#FF'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']['color']).to eq(['must be a valid color code'])
+ end
+
+ it 'returns 400 for too long color code' do
+ request_params = {
+ color: '#FFAAFFFF'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']['color']).to eq(['must be a valid color code'])
+ end
+
+ it 'returns 400 for invalid priority' do
+ request_params = {
+ priority: 'foo'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'returns 200 if name and colors and description are changed' do
+ request_params = {
+ new_name: 'New Label',
+ color: '#FFFFFF',
+ description: 'test'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['name']).to eq('New Label')
+ expect(json_response['color']).to eq('#FFFFFF')
+ expect(json_response['description']).to eq('test')
+ end
+
+ it 'returns 400 for invalid name' do
+ request_params = {
+ new_name: ',',
+ color: '#FFFFFF'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']['title']).to eq(['is invalid'])
+ end
+
+ it 'returns 200 if description is changed' do
+ request_params = {
+ description: 'test'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['id']).to eq(expected_response_label_id)
+ expect(json_response['description']).to eq('test')
+ end
+
+ it 'returns 200 if priority is changed' do
+ request_params = {
+ priority: 10
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response.status).to eq(200)
+ expect(json_response['id']).to eq(expected_response_label_id)
+ expect(json_response['priority']).to eq(10)
+ end
+
+ it 'returns 200 if a priority is removed' do
+ label = find_by_spec_params(spec_params)
+ expect(label).not_to be_nil
+
+ label.priorities.create(project: label.project, priority: 1)
+ label.save!
+
+ request_params = {
+ priority: nil
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response.status).to eq(200)
+ expect(json_response['id']).to eq(expected_response_label_id)
+ expect(json_response['priority']).to be_nil
+ end
+
+ def find_by_spec_params(params)
+ if params.key?(:label_id)
+ Label.find(params[:label_id])
+ else
+ Label.find_by(name: params[:name])
+ end
+ end
+ end
+
+ shared_examples 'label delete API' do
+ it 'returns 204 for existing label' do
+ delete api("/projects/#{project.id}/labels", user), params: spec_params
+
+ expect(response).to have_gitlab_http_status(204)
+ end
+ end
+
before do
project.add_maintainer(user)
end
@@ -208,20 +382,34 @@ describe API::Labels do
end
describe 'DELETE /projects/:id/labels' do
- it 'returns 204 for existing label' do
- delete api("/projects/#{project.id}/labels", user), params: { name: 'label1' }
+ it_behaves_like 'label delete API' do
+ let(:spec_params) { { name: 'label1' } }
+ end
- expect(response).to have_gitlab_http_status(204)
+ it_behaves_like 'label delete API' do
+ let(:spec_params) { { label_id: label1.id } }
end
it 'returns 404 for non existing label' do
delete api("/projects/#{project.id}/labels", user), params: { name: 'label2' }
+
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Label Not Found')
end
it 'returns 400 for wrong parameters' do
delete api("/projects/#{project.id}/labels", user)
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'fails if label_id and name are given in params' do
+ delete api("/projects/#{project.id}/labels", user),
+ params: {
+ label_id: label1.id,
+ name: priority_label.name
+ }
+
expect(response).to have_gitlab_http_status(400)
end
@@ -232,152 +420,105 @@ describe API::Labels do
end
describe 'PUT /projects/:id/labels' do
- it 'returns 200 if name and colors and description are changed' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- new_name: 'New Label',
- color: '#FFFFFF',
- description: 'test'
- }
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['name']).to eq('New Label')
- expect(json_response['color']).to eq('#FFFFFF')
- expect(json_response['description']).to eq('test')
+ context 'when using name' do
+ it_behaves_like 'label update API' do
+ let(:spec_params) { { name: 'label1' } }
+ let(:expected_response_label_id) { label1.id }
+ end
end
- it 'returns 200 if name is changed' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- new_name: 'New Label'
- }
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['name']).to eq('New Label')
- expect(json_response['color']).to eq(label1.color)
+ context 'when using label_id' do
+ it_behaves_like 'label update API' do
+ let(:spec_params) { { label_id: label1.id } }
+ let(:expected_response_label_id) { label1.id }
+ end
end
- it 'returns 200 if colors is changed' do
+ it 'returns 404 if label does not exist' do
put api("/projects/#{project.id}/labels", user),
params: {
- name: 'label1',
- color: '#FFFFFF'
+ name: 'label2',
+ new_name: 'label3'
}
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['name']).to eq(label1.name)
- expect(json_response['color']).to eq('#FFFFFF')
+
+ expect(response).to have_gitlab_http_status(404)
end
- it 'returns 200 if description is changed' do
+ it 'returns 404 if label by id does not exist' do
put api("/projects/#{project.id}/labels", user),
params: {
- name: 'bug',
- description: 'test'
+ label_id: 0,
+ new_name: 'label3'
}
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['name']).to eq(priority_label.name)
- expect(json_response['description']).to eq('test')
- expect(json_response['priority']).to eq(3)
- end
-
- it 'returns 200 if priority is changed' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'bug',
- priority: 10
- }
-
- expect(response.status).to eq(200)
- expect(json_response['name']).to eq(priority_label.name)
- expect(json_response['priority']).to eq(10)
+ expect(response).to have_gitlab_http_status(404)
end
- it 'returns 200 if a priority is added' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- priority: 3
- }
+ it 'returns 400 if no label name and id is given' do
+ put api("/projects/#{project.id}/labels", user), params: { new_name: 'label2' }
- expect(response.status).to eq(200)
- expect(json_response['name']).to eq(label1.name)
- expect(json_response['priority']).to eq(3)
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('label_id, name are missing, exactly one parameter must be provided')
end
- it 'returns 200 if the priority is removed' do
+ it 'fails if label_id and name are given in params' do
put api("/projects/#{project.id}/labels", user),
params: {
+ label_id: label1.id,
name: priority_label.name,
- priority: nil
+ new_name: 'New Label'
}
- expect(response.status).to eq(200)
- expect(json_response['name']).to eq(priority_label.name)
- expect(json_response['priority']).to be_nil
+ expect(response).to have_gitlab_http_status(400)
end
+ end
- it 'returns 404 if label does not exist' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label2',
- new_name: 'label3'
- }
- expect(response).to have_gitlab_http_status(404)
- end
+ describe 'PUT /projects/:id/labels/promote' do
+ let(:group) { create(:group) }
- it 'returns 400 if no label name given' do
- put api("/projects/#{project.id}/labels", user), params: { new_name: 'label2' }
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['error']).to eq('name is missing')
+ before do
+ group.add_owner(user)
+ project.update!(group: group)
end
- it 'returns 400 if no new parameters given' do
- put api("/projects/#{project.id}/labels", user), params: { name: 'label1' }
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['error']).to eq('new_name, color, description, priority are missing, '\
- 'at least one parameter must be provided')
+ it 'returns 200 if label is promoted' do
+ put api("/projects/#{project.id}/labels/promote", user), params: { name: label1.name }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['name']).to eq(label1.name)
+ expect(json_response['color']).to eq(label1.color)
end
- it 'returns 400 for invalid name' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- new_name: ',',
- color: '#FFFFFF'
- }
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['message']['title']).to eq(['is invalid'])
+ it 'returns 200 if group label already exists' do
+ create(:group_label, title: label1.name, group: group)
+
+ expect { put api("/projects/#{project.id}/labels/promote", user), params: { name: label1.name } }
+ .to change(project.labels, :count).by(-1)
+ .and change(group.labels, :count).by(0)
+
+ expect(response).to have_gitlab_http_status(200)
end
- it 'returns 400 when color code is too short' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- color: '#FF'
- }
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['message']['color']).to eq(['must be a valid color code'])
+ it 'returns 403 if guest promotes label' do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ put api("/projects/#{project.id}/labels/promote", guest), params: { name: label1.name }
+
+ expect(response).to have_gitlab_http_status(403)
end
- it 'returns 400 for too long color code' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- color: '#FFAAFFFF'
- }
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['message']['color']).to eq(['must be a valid color code'])
+ it 'returns 404 if label does not exist' do
+ put api("/projects/#{project.id}/labels/promote", user), params: { name: 'unknown' }
+
+ expect(response).to have_gitlab_http_status(404)
end
- it 'returns 400 for invalid priority' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- priority: 'foo'
- }
+ it 'returns 400 if no label name given' do
+ put api("/projects/#{project.id}/labels/promote", user)
expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('name is missing')
end
end
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 424f0a82e43..6c1e30791d2 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -9,6 +9,13 @@ describe API::Notes do
project.add_reporter(user)
end
+ context 'when there are cross-reference system notes' do
+ let(:url) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes" }
+ let(:notes_in_response) { json_response }
+
+ it_behaves_like 'with cross-reference system notes'
+ end
+
context "when noteable is an Issue" do
let!(:issue) { create(:issue, project: project, author: user) }
let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) }
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/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb
index 35b3dd219f7..174b3214d13 100644
--- a/spec/requests/api/pipelines_spec.rb
+++ b/spec/requests/api/pipelines_spec.rb
@@ -17,6 +17,8 @@ describe API::Pipelines do
end
describe 'GET /projects/:id/pipelines ' do
+ it_behaves_like 'pipelines visibility table'
+
context 'authorized user' do
it 'returns project pipelines' do
get api("/projects/#{project.id}/pipelines", user)
@@ -401,6 +403,15 @@ describe API::Pipelines do
end
describe 'GET /projects/:id/pipelines/:pipeline_id' do
+ it_behaves_like 'pipelines visibility table' do
+ let(:pipelines_api_path) do
+ "/projects/#{project.id}/pipelines/#{pipeline.id}"
+ end
+
+ let(:api_response) { response_status == 200 ? response : json_response }
+ let(:response_200) { match_response_schema('public_api/v4/pipeline/detail') }
+ end
+
context 'authorized user' do
it 'exposes known attributes' do
get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user)
diff --git a/spec/requests/api/project_snapshots_spec.rb b/spec/requests/api/project_snapshots_spec.rb
index 44b5ee1f130..2857715cdbe 100644
--- a/spec/requests/api/project_snapshots_spec.rb
+++ b/spec/requests/api/project_snapshots_spec.rb
@@ -6,6 +6,12 @@ describe API::ProjectSnapshots do
let(:project) { create(:project) }
let(:admin) { create(:admin) }
+ before do
+ allow(Feature::Gitaly).to receive(:server_feature_flags).and_return({
+ 'gitaly-feature-foobar' => 'true'
+ })
+ end
+
describe 'GET /projects/:id/snapshot' do
def expect_snapshot_response_for(repository)
type, params = workhorse_send_data
@@ -13,6 +19,7 @@ describe API::ProjectSnapshots do
expect(type).to eq('git-snapshot')
expect(params).to eq(
'GitalyServer' => {
+ 'features' => { 'gitaly-feature-foobar' => 'true' },
'address' => Gitlab::GitalyClient.address(repository.project.repository_storage),
'token' => Gitlab::GitalyClient.token(repository.project.repository_storage)
},
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/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb
index d1b58aac104..97de26650db 100644
--- a/spec/requests/api/wikis_spec.rb
+++ b/spec/requests/api/wikis_spec.rb
@@ -11,6 +11,8 @@ require 'spec_helper'
# because they are 3 edge cases of using wiki pages.
describe API::Wikis do
+ include WorkhorseHelpers
+
let(:user) { create(:user) }
let(:group) { create(:group).tap { |g| g.add_owner(user) } }
let(:project_wiki) { create(:project_wiki, project: project, user: user) }
@@ -155,7 +157,7 @@ describe API::Wikis do
it 'pushes attachment to the wiki repository' do
allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
- post(api(url, user), params: payload)
+ workhorse_post_with_file(api(url, user), file_key: :file, params: payload)
expect(response).to have_gitlab_http_status(201)
expect(json_response).to eq result_hash.deep_stringify_keys
@@ -180,6 +182,15 @@ describe API::Wikis do
expect(json_response.size).to eq(1)
expect(json_response['error']).to eq('file is invalid')
end
+
+ it 'is backward compatible with regular multipart uploads' do
+ allow(SecureRandom).to receive(:hex).and_return('fixed_hex')
+
+ post(api(url, user), params: payload)
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response).to eq result_hash.deep_stringify_keys
+ end
end
describe 'GET /projects/:id/wikis' do
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index bba473f1c20..8b2c698fee1 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -108,6 +108,14 @@ describe JwtController do
end
end
end
+
+ it 'does not cause session based checks to be activated' do
+ expect(Gitlab::Session).not_to receive(:with_session)
+
+ get '/jwt/auth', params: parameters, headers: headers
+
+ expect(response).to have_gitlab_http_status(200)
+ end
end
context 'using invalid login' do
diff --git a/spec/requests/projects/uploads_spec.rb b/spec/requests/projects/uploads_spec.rb
new file mode 100644
index 00000000000..aca4644289d
--- /dev/null
+++ b/spec/requests/projects/uploads_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'File uploads' do
+ include WorkhorseHelpers
+
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { create(:user) }
+
+ describe 'POST /:namespace/:project/create/:branch' do
+ let(:branch) { 'master' }
+ let(:create_url) { project_blob_path(project, branch) }
+ let(:blob_url) { project_blob_path(project, "#{branch}/dk.png") }
+ let(:params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: branch,
+ branch_name: branch,
+ file: fixture_file_upload('spec/fixtures/dk.png'),
+ commit_message: 'Add an image'
+ }
+ end
+
+ before do
+ project.add_maintainer(user)
+
+ login_as(user)
+ end
+
+ it 'redirects to blob' do
+ workhorse_post_with_file(create_url, file_key: :file, params: params)
+
+ expect(response).to redirect_to(blob_url)
+ end
+ end
+end
diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb
index 478f09a7881..cf459ba99c1 100644
--- a/spec/requests/rack_attack_global_spec.rb
+++ b/spec/requests/rack_attack_global_spec.rb
@@ -20,11 +20,6 @@ describe 'Rack Attack global throttles' do
let(:period_in_seconds) { 10000 }
let(:period) { period_in_seconds.seconds }
- let(:url_that_does_not_require_authentication) { '/users/sign_in' }
- let(:url_that_requires_authentication) { '/dashboard/snippets' }
- let(:url_api_internal) { '/api/v4/internal/check' }
- let(:api_partial_url) { '/todos' }
-
around do |example|
# Instead of test environment's :null_store so the throttles can increment
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
@@ -35,112 +30,10 @@ describe 'Rack Attack global throttles' do
Rack::Attack.cache.store = Rails.cache
end
- # Requires let variables:
- # * throttle_setting_prefix (e.g. "throttle_authenticated_api" or "throttle_authenticated_web")
- # * get_args
- # * other_user_get_args
- shared_examples_for 'rate-limited token-authenticated requests' do
- before do
- # Set low limits
- settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period
- settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds
- end
-
- context 'when the throttle is enabled' do
- before do
- settings_to_set[:"#{throttle_setting_prefix}_enabled"] = true
- stub_application_setting(settings_to_set)
- end
-
- it 'rejects requests over the rate limit' do
- # At first, allow requests under the rate limit.
- requests_per_period.times do
- get(*get_args)
- expect(response).to have_http_status 200
- end
-
- # the last straw
- expect_rejection { get(*get_args) }
- end
-
- it 'allows requests after throttling and then waiting for the next period' do
- requests_per_period.times do
- get(*get_args)
- expect(response).to have_http_status 200
- end
-
- expect_rejection { get(*get_args) }
-
- Timecop.travel(period.from_now) do
- requests_per_period.times do
- get(*get_args)
- expect(response).to have_http_status 200
- end
-
- expect_rejection { get(*get_args) }
- end
- end
-
- it 'counts requests from different users separately, even from the same IP' do
- requests_per_period.times do
- get(*get_args)
- expect(response).to have_http_status 200
- end
-
- # would be over the limit if this wasn't a different user
- get(*other_user_get_args)
- expect(response).to have_http_status 200
- end
-
- it 'counts all requests from the same user, even via different IPs' do
- requests_per_period.times do
- get(*get_args)
- expect(response).to have_http_status 200
- end
-
- expect_any_instance_of(Rack::Attack::Request).to receive(:ip).and_return('1.2.3.4')
-
- expect_rejection { get(*get_args) }
- end
-
- it 'logs RackAttack info into structured logs' do
- requests_per_period.times do
- get(*get_args)
- expect(response).to have_http_status 200
- end
-
- arguments = {
- message: 'Rack_Attack',
- env: :throttle,
- remote_ip: '127.0.0.1',
- request_method: 'GET',
- path: get_args.first,
- user_id: user.id,
- username: user.username
- }
-
- expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
-
- expect_rejection { get(*get_args) }
- end
- end
-
- context 'when the throttle is disabled' do
- before do
- settings_to_set[:"#{throttle_setting_prefix}_enabled"] = false
- stub_application_setting(settings_to_set)
- end
-
- it 'allows requests over the rate limit' do
- (1 + requests_per_period).times do
- get(*get_args)
- expect(response).to have_http_status 200
- end
- end
- end
- end
-
describe 'unauthenticated requests' do
+ let(:url_that_does_not_require_authentication) { '/users/sign_in' }
+ let(:url_api_internal) { '/api/v4/internal/check' }
+
before do
# Set low limits
settings_to_set[:throttle_unauthenticated_requests_per_period] = requests_per_period
@@ -245,6 +138,7 @@ describe 'Rack Attack global throttles' do
let(:other_user) { create(:user) }
let(:other_user_token) { create(:personal_access_token, user: other_user) }
let(:throttle_setting_prefix) { 'throttle_authenticated_api' }
+ let(:api_partial_url) { '/todos' }
context 'with the token in the query string' do
let(:get_args) { [api(api_partial_url, personal_access_token: token)] }
@@ -265,10 +159,13 @@ describe 'Rack Attack global throttles' do
let(:user) { create(:user) }
let(:application) { Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) }
let(:token) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "api") }
+
let(:other_user) { create(:user) }
let(:other_user_application) { Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: other_user) }
let(:other_user_token) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: other_user.id, scopes: "api") }
+
let(:throttle_setting_prefix) { 'throttle_authenticated_api' }
+ let(:api_partial_url) { '/todos' }
context 'with the token in the query string' do
let(:get_args) { [api(api_partial_url, oauth_access_token: token)] }
@@ -299,110 +196,11 @@ describe 'Rack Attack global throttles' do
end
describe 'web requests authenticated with regular login' do
+ let(:throttle_setting_prefix) { 'throttle_authenticated_web' }
let(:user) { create(:user) }
+ let(:url_that_requires_authentication) { '/dashboard/snippets' }
- before do
- login_as(user)
-
- # Set low limits
- settings_to_set[:throttle_authenticated_web_requests_per_period] = requests_per_period
- settings_to_set[:throttle_authenticated_web_period_in_seconds] = period_in_seconds
- end
-
- context 'when the throttle is enabled' do
- before do
- settings_to_set[:throttle_authenticated_web_enabled] = true
- stub_application_setting(settings_to_set)
- end
-
- it 'rejects requests over the rate limit' do
- # At first, allow requests under the rate limit.
- requests_per_period.times do
- get url_that_requires_authentication
- expect(response).to have_http_status 200
- end
-
- # the last straw
- expect_rejection { get url_that_requires_authentication }
- end
-
- it 'allows requests after throttling and then waiting for the next period' do
- requests_per_period.times do
- get url_that_requires_authentication
- expect(response).to have_http_status 200
- end
-
- expect_rejection { get url_that_requires_authentication }
-
- Timecop.travel(period.from_now) do
- requests_per_period.times do
- get url_that_requires_authentication
- expect(response).to have_http_status 200
- end
-
- expect_rejection { get url_that_requires_authentication }
- end
- end
-
- it 'counts requests from different users separately, even from the same IP' do
- requests_per_period.times do
- get url_that_requires_authentication
- expect(response).to have_http_status 200
- end
-
- # would be over the limit if this wasn't a different user
- login_as(create(:user))
-
- get url_that_requires_authentication
- expect(response).to have_http_status 200
- end
-
- it 'counts all requests from the same user, even via different IPs' do
- requests_per_period.times do
- get url_that_requires_authentication
- expect(response).to have_http_status 200
- end
-
- expect_any_instance_of(Rack::Attack::Request).to receive(:ip).and_return('1.2.3.4')
-
- expect_rejection { get url_that_requires_authentication }
- end
-
- it 'logs RackAttack info into structured logs' do
- requests_per_period.times do
- get url_that_requires_authentication
- expect(response).to have_http_status 200
- end
-
- arguments = {
- message: 'Rack_Attack',
- env: :throttle,
- remote_ip: '127.0.0.1',
- request_method: 'GET',
- path: '/dashboard/snippets',
- user_id: user.id,
- username: user.username
- }
-
- expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
-
- get url_that_requires_authentication
- end
- end
-
- context 'when the throttle is disabled' do
- before do
- settings_to_set[:throttle_authenticated_web_enabled] = false
- stub_application_setting(settings_to_set)
- end
-
- it 'allows requests over the rate limit' do
- (1 + requests_per_period).times do
- get url_that_requires_authentication
- expect(response).to have_http_status 200
- end
- end
- end
+ it_behaves_like 'rate-limited web authenticated requests'
end
def api_get_args_with_token_headers(partial_url, token_headers)
diff --git a/spec/rubocop/cop/gitlab/union_spec.rb b/spec/rubocop/cop/gitlab/union_spec.rb
index 5b06f30b25f..f0544fdb66e 100644
--- a/spec/rubocop/cop/gitlab/union_spec.rb
+++ b/spec/rubocop/cop/gitlab/union_spec.rb
@@ -16,10 +16,4 @@ describe RuboCop::Cop::Gitlab::Union do
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use the `FromUnion` concern, instead of using `Gitlab::SQL::Union` directly
SOURCE
end
-
- it 'does not flag the use of Gitlab::SQL::Union in a spec' do
- allow(cop).to receive(:in_spec?).and_return(true)
-
- expect_no_offenses('Gitlab::SQL::Union.new([foo])')
- end
end
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/rubocop/cop/migration/add_limit_to_string_columns_spec.rb b/spec/rubocop/cop/migration/add_limit_to_string_columns_spec.rb
new file mode 100644
index 00000000000..97a3ae8f2bc
--- /dev/null
+++ b/spec/rubocop/cop/migration/add_limit_to_string_columns_spec.rb
@@ -0,0 +1,268 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/migration/add_limit_to_string_columns'
+
+describe RuboCop::Cop::Migration::AddLimitToStringColumns do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'in migration' do
+ before do
+ allow(cop).to receive(:in_migration?).and_return(true)
+
+ inspect_source(migration)
+ end
+
+ context 'when creating a table' do
+ context 'with string columns and limit' do
+ let(:migration) do
+ %q(
+ class CreateUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ create_table :users do |t|
+ t.string :username, null: false, limit: 255
+ t.timestamps_with_timezone null: true
+ end
+ end
+ end
+ )
+ end
+
+ it 'register no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+
+ context 'with limit in a different position' do
+ let(:migration) do
+ %q(
+ class CreateUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ create_table :users do |t|
+ t.string :username, limit: 255, null: false
+ t.timestamps_with_timezone null: true
+ end
+ end
+ end
+ )
+ end
+
+ it 'registers an offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+ end
+
+ context 'with string columns and no limit' do
+ let(:migration) do
+ %q(
+ class CreateUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ create_table :users do |t|
+ t.string :username, null: false
+ t.timestamps_with_timezone null: true
+ end
+ end
+ end
+ )
+ end
+
+ it 'registers an offense' do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('String columns should have a limit constraint. 255 is suggested')
+ end
+ end
+
+ context 'with no string columns' do
+ let(:migration) do
+ %q(
+ class CreateMilestoneReleases < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ create_table :milestone_releases do |t|
+ t.integer :milestone_id
+ t.integer :release_id
+ end
+ end
+ end
+ )
+ end
+
+ it 'register no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+ end
+
+ context 'when adding columns' do
+ context 'with string columns with limit' do
+ let(:migration) do
+ %q(
+ class AddEmailToUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :users, :email, :string, limit: 255
+ end
+ end
+ )
+ end
+
+ it 'registers no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+
+ context 'with limit in a different position' do
+ let(:migration) do
+ %q(
+ class AddEmailToUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :users, :email, :string, limit: 255, default: 'example@email.com'
+ end
+ end
+ )
+ end
+
+ it 'registers no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+ end
+
+ context 'with string columns with no limit' do
+ let(:migration) do
+ %q(
+ class AddEmailToUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :users, :email, :string
+ end
+ end
+ )
+ end
+
+ it 'registers offense' do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('String columns should have a limit constraint. 255 is suggested')
+ end
+ end
+
+ context 'with no string columns' do
+ let(:migration) do
+ %q(
+ class AddEmailToUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :users, :active, :boolean, default: false
+ end
+ end
+ )
+ end
+
+ it 'registers no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+ end
+
+ context 'with add_column_with_default' do
+ context 'with a limit' do
+ let(:migration) do
+ %q(
+ class AddRuleTypeToApprovalMergeRequestRules < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column_with_default(:approval_merge_request_rules, :rule_type, :string, limit: 2, default: 1)
+ end
+ end
+ )
+ end
+
+ it 'registers no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+
+ context 'without a limit' do
+ let(:migration) do
+ %q(
+ class AddRuleTypeToApprovalMergeRequestRules < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column_with_default(:approval_merge_request_rules, :rule_type, :string, default: 1)
+ end
+ end
+ )
+ end
+
+ it 'registers an offense' do
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+ end
+
+ context 'with methods' do
+ let(:migration) do
+ %q(
+ class AddEmailToUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column_if_table_not_exists :users, :first_name, :string, limit: 255
+ search_namespace(user_name)
+ end
+
+ def add_column_if_not_exists(table, name, *args)
+ add_column(table, name, *args) unless column_exists?(table, name)
+ end
+
+ def search_namespace(username)
+ Uniquify.new.string(username) do |str|
+ query = "SELECT id FROM namespaces WHERE parent_id IS NULL AND path='#{str}' LIMIT 1"
+ connection.exec_query(query)
+ end
+ end
+ end
+ )
+ end
+
+ it 'registers no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+ end
+
+ context 'outside of migrations' do
+ let(:active_record_model) do
+ %q(
+ class User < ApplicationRecord
+ end
+ )
+ end
+
+ it 'registers no offense' do
+ inspect_source(active_record_model)
+
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/rspec/be_success_matcher_spec.rb b/spec/rubocop/cop/rspec/be_success_matcher_spec.rb
new file mode 100644
index 00000000000..12aa7d1643e
--- /dev/null
+++ b/spec/rubocop/cop/rspec/be_success_matcher_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_relative '../../../../rubocop/cop/rspec/be_success_matcher'
+
+describe RuboCop::Cop::RSpec::BeSuccessMatcher do
+ include CopHelper
+
+ let(:source_file) { 'spec/foo_spec.rb' }
+
+ subject(:cop) { described_class.new }
+
+ shared_examples 'cop' do |good:, bad:|
+ context "using #{bad} call" do
+ it 'registers an offense' do
+ inspect_source(bad, source_file)
+
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ expect(cop.highlights).to eq([bad])
+ end
+
+ it "autocorrects it to `#{good}`" do
+ autocorrected = autocorrect_source(bad, source_file)
+
+ expect(autocorrected).to eql(good)
+ end
+ end
+
+ context "using #{good} call" do
+ it 'does not register an offense' do
+ inspect_source(good)
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+ end
+
+ include_examples 'cop',
+ bad: 'expect(response).to be_success',
+ good: 'expect(response).to be_successful'
+
+ include_examples 'cop',
+ bad: 'expect(response).to_not be_success',
+ good: 'expect(response).to_not be_successful'
+
+ include_examples 'cop',
+ bad: 'expect(response).not_to be_success',
+ good: 'expect(response).not_to be_successful'
+
+ include_examples 'cop',
+ bad: 'is_expected.to be_success',
+ good: 'is_expected.to be_successful'
+
+ include_examples 'cop',
+ bad: 'is_expected.to_not be_success',
+ good: 'is_expected.to_not be_successful'
+
+ include_examples 'cop',
+ bad: 'is_expected.not_to be_success',
+ good: 'is_expected.not_to be_successful'
+end
diff --git a/spec/rubocop/cop/rspec/env_assignment_spec.rb b/spec/rubocop/cop/rspec/env_assignment_spec.rb
index 659633f6467..621afbad3ba 100644
--- a/spec/rubocop/cop/rspec/env_assignment_spec.rb
+++ b/spec/rubocop/cop/rspec/env_assignment_spec.rb
@@ -33,27 +33,13 @@ describe RuboCop::Cop::RSpec::EnvAssignment do
end
end
- context 'in a spec file' do
- before do
- allow(cop).to receive(:in_spec?).and_return(true)
- end
-
- context 'with a key using single quotes' do
- it_behaves_like 'an offensive ENV#[]= call', OFFENSE_CALL_SINGLE_QUOTES_KEY
- it_behaves_like 'an autocorrected ENV#[]= call', OFFENSE_CALL_SINGLE_QUOTES_KEY, %(stub_env('FOO', 'bar'))
- end
-
- context 'with a key using double quotes' do
- it_behaves_like 'an offensive ENV#[]= call', OFFENSE_CALL_DOUBLE_QUOTES_KEY
- it_behaves_like 'an autocorrected ENV#[]= call', OFFENSE_CALL_DOUBLE_QUOTES_KEY, %(stub_env("FOO", 'bar'))
- end
+ context 'with a key using single quotes' do
+ it_behaves_like 'an offensive ENV#[]= call', OFFENSE_CALL_SINGLE_QUOTES_KEY
+ it_behaves_like 'an autocorrected ENV#[]= call', OFFENSE_CALL_SINGLE_QUOTES_KEY, %(stub_env('FOO', 'bar'))
end
- context 'outside of a spec file' do
- it "does not register an offense for `#{OFFENSE_CALL_SINGLE_QUOTES_KEY}` in a non-spec file" do
- inspect_source(OFFENSE_CALL_SINGLE_QUOTES_KEY)
-
- expect(cop.offenses.size).to eq(0)
- end
+ context 'with a key using double quotes' do
+ it_behaves_like 'an offensive ENV#[]= call', OFFENSE_CALL_DOUBLE_QUOTES_KEY
+ it_behaves_like 'an autocorrected ENV#[]= call', OFFENSE_CALL_DOUBLE_QUOTES_KEY, %(stub_env("FOO", 'bar'))
end
end
diff --git a/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb b/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb
index 2763f2bda21..94324bc615d 100644
--- a/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb
+++ b/spec/rubocop/cop/rspec/factories_in_migration_specs_spec.rb
@@ -8,8 +8,6 @@ require_relative '../../../../rubocop/cop/rspec/factories_in_migration_specs'
describe RuboCop::Cop::RSpec::FactoriesInMigrationSpecs do
include CopHelper
- let(:source_file) { 'spec/migrations/foo_spec.rb' }
-
subject(:cop) { described_class.new }
shared_examples 'an offensive factory call' do |namespace|
@@ -27,22 +25,6 @@ describe RuboCop::Cop::RSpec::FactoriesInMigrationSpecs do
end
end
- context 'in a migration spec file' do
- before do
- allow(cop).to receive(:in_migration_spec?).and_return(true)
- end
-
- it_behaves_like 'an offensive factory call', ''
- it_behaves_like 'an offensive factory call', 'FactoryBot.'
- end
-
- context 'outside of a migration spec file' do
- it "does not register an offense" do
- expect_no_offenses(<<-RUBY)
- describe 'foo' do
- let(:user) { create(:user) }
- end
- RUBY
- end
- end
+ it_behaves_like 'an offensive factory call', ''
+ it_behaves_like 'an offensive factory call', 'FactoryBot.'
end
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..cf0b8ea9b40 100644
--- a/spec/serializers/merge_request_serializer_spec.rb
+++ b/spec/serializers/merge_request_serializer_spec.rb
@@ -1,8 +1,9 @@
require 'spec_helper'
describe MergeRequestSerializer do
- let(:user) { create(:user) }
- let(:resource) { create(:merge_request) }
+ set(:user) { create(:user) }
+ set(:resource) { create(:merge_request, description: "Description") }
+
let(:json_entity) do
described_class.new(current_user: user)
.represent(resource, serializer: serializer)
@@ -41,6 +42,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 adb5219d691..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
@@ -201,6 +234,24 @@ describe ApplicationSettings::UpdateService do
enable_external_authorization_service_check
end
+ it 'does not validate labels if external authorization gets disabled' do
+ expect_any_instance_of(described_class).not_to receive(:validate_classification_label)
+
+ described_class.new(application_settings, admin, { external_authorization_service_enabled: false }).execute
+ end
+
+ it 'does validate labels if external authorization gets enabled ' do
+ expect_any_instance_of(described_class).to receive(:validate_classification_label)
+
+ described_class.new(application_settings, admin, { external_authorization_service_enabled: true }).execute
+ end
+
+ it 'does validate labels if external authorization is left unchanged' do
+ expect_any_instance_of(described_class).to receive(:validate_classification_label)
+
+ described_class.new(application_settings, admin, { external_authorization_service_default_label: 'new-label' }).execute
+ end
+
it 'does not save the settings with an error if the service denies access' do
expect(::Gitlab::ExternalAuthorization)
.to receive(:access_allowed?).with(admin, 'new-label') { false }
diff --git a/spec/services/audit_event_service_spec.rb b/spec/services/audit_event_service_spec.rb
index e42bff607b2..96df6689bb0 100644
--- a/spec/services/audit_event_service_spec.rb
+++ b/spec/services/audit_event_service_spec.rb
@@ -47,4 +47,16 @@ describe AuditEventService do
expect(details[:target_id]).to eq(1)
end
end
+
+ describe '#log_security_event_to_file' do
+ it 'logs security event to file' do
+ expect(service).to receive(:file_logger).and_return(logger)
+ expect(logger).to receive(:info).with(author_id: user.id,
+ entity_type: 'Project',
+ entity_id: project.id,
+ action: :destroy)
+
+ service.log_security_event_to_file
+ end
+ end
end
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/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index deb68899309..d8880819d9f 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -23,6 +23,7 @@ describe Ci::CreatePipelineService do
trigger_request: nil,
variables_attributes: nil,
merge_request: nil,
+ external_pull_request: nil,
push_options: nil,
source_sha: nil,
target_sha: nil,
@@ -36,8 +37,11 @@ describe Ci::CreatePipelineService do
source_sha: source_sha,
target_sha: target_sha }
- described_class.new(project, user, params).execute(
- source, save_on_errors: save_on_errors, trigger_request: trigger_request, merge_request: merge_request)
+ described_class.new(project, user, params).execute(source,
+ save_on_errors: save_on_errors,
+ trigger_request: trigger_request,
+ merge_request: merge_request,
+ external_pull_request: external_pull_request)
end
# rubocop:enable Metrics/ParameterLists
@@ -220,11 +224,11 @@ describe Ci::CreatePipelineService do
expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: pipeline.id)
end
- it 'does not cancel running outdated pipelines' do
+ it 'cancels running outdated pipelines' do
pipeline_on_previous_commit.run
- execute_service
+ head_pipeline = execute_service
- expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'running', auto_canceled_by_id: nil)
+ expect(pipeline_on_previous_commit.reload).to have_attributes(status: 'canceled', auto_canceled_by_id: head_pipeline.id)
end
it 'cancel created outdated pipelines' do
@@ -243,6 +247,202 @@ describe Ci::CreatePipelineService do
expect(pending_pipeline.reload).to have_attributes(status: 'pending', auto_canceled_by_id: nil)
end
+
+ context 'when the interruptible attribute is' do
+ context 'not defined' do
+ before do
+ config = YAML.dump(rspec: { script: 'echo' })
+ stub_ci_pipeline_yaml_file(config)
+ end
+
+ it 'is cancelable' do
+ pipeline = execute_service
+
+ expect(pipeline.builds.find_by(name: 'rspec').interruptible).to be_nil
+ end
+ end
+
+ context 'set to true' do
+ before do
+ config = YAML.dump(rspec: { script: 'echo', interruptible: true })
+ stub_ci_pipeline_yaml_file(config)
+ end
+
+ it 'is cancelable' do
+ pipeline = execute_service
+
+ expect(pipeline.builds.find_by(name: 'rspec').interruptible).to be_truthy
+ end
+ end
+
+ context 'set to false' do
+ before do
+ config = YAML.dump(rspec: { script: 'echo', interruptible: false })
+ stub_ci_pipeline_yaml_file(config)
+ end
+
+ it 'is not cancelable' do
+ pipeline = execute_service
+
+ expect(pipeline.builds.find_by(name: 'rspec').interruptible).to be_falsy
+ end
+ end
+
+ context 'not defined, but an environment is' do
+ before do
+ config = YAML.dump(rspec: { script: 'echo', environment: { name: "review/$CI_COMMIT_REF_NAME" } })
+ stub_ci_pipeline_yaml_file(config)
+ end
+
+ it 'is not cancelable' do
+ pipeline = execute_service
+
+ expect(pipeline.builds.find_by(name: 'rspec').interruptible).to be_nil
+ end
+ end
+
+ context 'overriding the environment definition' do
+ before do
+ config = YAML.dump(rspec: { script: 'echo', environment: { name: "review/$CI_COMMIT_REF_NAME" }, interruptible: true })
+ stub_ci_pipeline_yaml_file(config)
+ end
+
+ it 'is cancelable' do
+ pipeline = execute_service
+
+ expect(pipeline.builds.find_by(name: 'rspec').interruptible).to be_truthy
+ end
+ end
+ end
+
+ context 'interruptible builds' do
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(config))
+ end
+
+ let(:config) do
+ {
+ stages: %w[stage1 stage2 stage3 stage4],
+
+ build_1_1: {
+ stage: 'stage1',
+ script: 'echo'
+ },
+ build_1_2: {
+ stage: 'stage1',
+ script: 'echo',
+ interruptible: true
+ },
+ build_2_1: {
+ stage: 'stage2',
+ script: 'echo',
+ when: 'delayed',
+ start_in: '10 minutes'
+ },
+ build_3_1: {
+ stage: 'stage3',
+ script: 'echo',
+ interruptible: false
+ },
+ build_4_1: {
+ stage: 'stage4',
+ script: 'echo'
+ }
+ }
+ end
+
+ it 'properly configures interruptible status' do
+ interruptible_status =
+ pipeline_on_previous_commit
+ .builds
+ .joins(:metadata)
+ .pluck(:name, 'ci_builds_metadata.interruptible')
+
+ expect(interruptible_status).to contain_exactly(
+ ['build_1_1', nil],
+ ['build_1_2', true],
+ ['build_2_1', nil],
+ ['build_3_1', false],
+ ['build_4_1', nil]
+ )
+ end
+
+ context 'when only interruptible builds are running' do
+ context 'when build marked explicitly by interruptible is running' do
+ it 'cancels running outdated pipelines' do
+ pipeline_on_previous_commit
+ .builds
+ .find_by_name('build_1_2')
+ .run!
+
+ pipeline
+
+ expect(pipeline_on_previous_commit.reload).to have_attributes(
+ status: 'canceled', auto_canceled_by_id: pipeline.id)
+ end
+ end
+
+ context 'when build that is not marked as interruptible is running' do
+ it 'cancels running outdated pipelines' do
+ pipeline_on_previous_commit
+ .builds
+ .find_by_name('build_2_1')
+ .tap(&:enqueue!)
+ .run!
+
+ pipeline
+
+ expect(pipeline_on_previous_commit.reload).to have_attributes(
+ status: 'canceled', auto_canceled_by_id: pipeline.id)
+ end
+ end
+ end
+
+ context 'when an uninterruptible build is running' do
+ it 'does not cancel running outdated pipelines' do
+ pipeline_on_previous_commit
+ .builds
+ .find_by_name('build_3_1')
+ .tap(&:enqueue!)
+ .run!
+
+ pipeline
+
+ expect(pipeline_on_previous_commit.reload).to have_attributes(
+ status: 'running', auto_canceled_by_id: nil)
+ end
+ end
+
+ context 'when an build is waiting on an interruptible scheduled task' do
+ it 'cancels running outdated pipelines' do
+ allow(Ci::BuildScheduleWorker).to receive(:perform_at)
+
+ pipeline_on_previous_commit
+ .builds
+ .find_by_name('build_2_1')
+ .schedule!
+
+ pipeline
+
+ expect(pipeline_on_previous_commit.reload).to have_attributes(
+ status: 'canceled', auto_canceled_by_id: pipeline.id)
+ end
+ end
+
+ context 'when a uninterruptible build has finished' do
+ it 'does not cancel running outdated pipelines' do
+ pipeline_on_previous_commit
+ .builds
+ .find_by_name('build_3_1')
+ .success!
+
+ pipeline
+
+ expect(pipeline_on_previous_commit.reload).to have_attributes(
+ status: 'running', auto_canceled_by_id: nil)
+ end
+ end
+ end
end
context 'auto-cancel disabled' do
@@ -773,6 +973,152 @@ describe Ci::CreatePipelineService do
end
end
+ describe 'Pipeline for external pull requests' do
+ let(:pipeline) do
+ execute_service(source: source,
+ external_pull_request: pull_request,
+ ref: ref_name,
+ source_sha: source_sha,
+ target_sha: target_sha)
+ end
+
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(config))
+ end
+
+ let(:ref_name) { 'refs/heads/feature' }
+ let(:source_sha) { project.commit(ref_name).id }
+ let(:target_sha) { nil }
+
+ context 'when source is external pull request' do
+ let(:source) { :external_pull_request_event }
+
+ context 'when config has external_pull_requests keywords' do
+ let(:config) do
+ {
+ build: {
+ stage: 'build',
+ script: 'echo'
+ },
+ test: {
+ stage: 'test',
+ script: 'echo',
+ only: ['external_pull_requests']
+ },
+ pages: {
+ stage: 'deploy',
+ script: 'echo',
+ except: ['external_pull_requests']
+ }
+ }
+ end
+
+ context 'when external pull request is specified' do
+ let(:pull_request) { create(:external_pull_request, project: project, source_branch: 'feature', target_branch: 'master') }
+ let(:ref_name) { pull_request.source_ref }
+
+ it 'creates an external pull request pipeline' do
+ expect(pipeline).to be_persisted
+ expect(pipeline).to be_external_pull_request_event
+ expect(pipeline.external_pull_request).to eq(pull_request)
+ expect(pipeline.source_sha).to eq(source_sha)
+ expect(pipeline.builds.order(:stage_id)
+ .map(&:name))
+ .to eq(%w[build test])
+ end
+
+ context 'when ref is tag' do
+ let(:ref_name) { 'refs/tags/v1.1.0' }
+
+ it 'does not create an extrnal pull request pipeline' do
+ expect(pipeline).not_to be_persisted
+ expect(pipeline.errors[:tag]).to eq(["is not included in the list"])
+ end
+ end
+
+ context 'when pull request is created from fork' do
+ it 'does not create an external pull request pipeline'
+ end
+
+ context "when there are no matched jobs" do
+ let(:config) do
+ {
+ test: {
+ stage: 'test',
+ script: 'echo',
+ except: ['external_pull_requests']
+ }
+ }
+ end
+
+ it 'does not create a detached merge request pipeline' do
+ expect(pipeline).not_to be_persisted
+ expect(pipeline.errors[:base]).to eq(["No stages / jobs for this pipeline."])
+ end
+ end
+ end
+
+ context 'when external pull request is not specified' do
+ let(:pull_request) { nil }
+
+ it 'does not create an external pull request pipeline' do
+ expect(pipeline).not_to be_persisted
+ expect(pipeline.errors[:external_pull_request]).to eq(["can't be blank"])
+ end
+ end
+ end
+
+ context "when config does not have external_pull_requests keywords" do
+ let(:config) do
+ {
+ build: {
+ stage: 'build',
+ script: 'echo'
+ },
+ test: {
+ stage: 'test',
+ script: 'echo'
+ },
+ pages: {
+ stage: 'deploy',
+ script: 'echo'
+ }
+ }
+ end
+
+ context 'when external pull request is specified' do
+ let(:pull_request) do
+ create(:external_pull_request,
+ project: project,
+ source_branch: Gitlab::Git.ref_name(ref_name),
+ target_branch: 'master')
+ end
+
+ it 'creates an external pull request pipeline' do
+ expect(pipeline).to be_persisted
+ expect(pipeline).to be_external_pull_request_event
+ expect(pipeline.external_pull_request).to eq(pull_request)
+ expect(pipeline.source_sha).to eq(source_sha)
+ expect(pipeline.builds.order(:stage_id)
+ .map(&:name))
+ .to eq(%w[build test pages])
+ end
+ end
+
+ context 'when external pull request is not specified' do
+ let(:pull_request) { nil }
+
+ it 'does not create an external pull request pipeline' do
+ expect(pipeline).not_to be_persisted
+
+ expect(pipeline.errors[:base])
+ .to eq(['Failed to build the pipeline!'])
+ end
+ end
+ end
+ end
+ end
+
describe 'Pipelines for merge requests' do
let(:pipeline) do
execute_service(source: source,
@@ -1140,10 +1486,26 @@ describe Ci::CreatePipelineService do
context 'when pipeline on feature is created' do
let(:ref_name) { 'refs/heads/feature' }
- it 'does not create a pipeline as test_a depends on build_a' do
- expect(pipeline).not_to be_persisted
- expect(pipeline.builds).to be_empty
- expect(pipeline.errors[:base]).to contain_exactly("test_a: needs 'build_a'")
+ context 'when save_on_errors is enabled' do
+ let(:pipeline) { execute_service(save_on_errors: true) }
+
+ it 'does create a pipeline as test_a depends on build_a' do
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds).to be_empty
+ expect(pipeline.yaml_errors).to eq("test_a: needs 'build_a'")
+ expect(pipeline.errors[:base]).to contain_exactly("test_a: needs 'build_a'")
+ end
+ end
+
+ context 'when save_on_errors is disabled' do
+ let(:pipeline) { execute_service(save_on_errors: false) }
+
+ it 'does not create a pipeline as test_a depends on build_a' do
+ expect(pipeline).not_to be_persisted
+ expect(pipeline.builds).to be_empty
+ expect(pipeline.yaml_errors).to be_nil
+ expect(pipeline.errors[:base]).to contain_exactly("test_a: needs 'build_a'")
+ 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..02fd4b63c89 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,11 +138,15 @@ 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
- expect(service).to receive(:remove_installation_pod).once
+ expect_any_instance_of(Gitlab::Kubernetes::Helm::Api)
+ .to receive(:delete_pod!)
+ .with(kind_of(String))
+ .once
+ expect(service).to receive(:remove_installation_pod).and_call_original
service.execute
end
@@ -162,7 +166,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 +181,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..68ad0208226 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,11 +47,15 @@ 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_any_instance_of(Gitlab::Kubernetes::Helm::Api)
+ .to receive(:delete_pod!)
+ .with(kind_of(String))
+ .once
+ expect(service).to receive(:pod_phase).once.and_return(phase)
end
it 'removes the installation POD' do
- expect(service).to receive(:remove_installation_pod).once
+ expect(service).to receive(:remove_uninstallation_pod).and_call_original
service.execute
end
@@ -76,7 +80,7 @@ describe Clusters::Applications::CheckUninstallProgressService do
end
it 'still removes the installation POD' do
- expect(service).to receive(:remove_installation_pod).once
+ expect(service).to receive(:remove_uninstallation_pod).and_call_original
service.execute
end
@@ -95,7 +99,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 +114,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 +135,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/applications/create_service_spec.rb b/spec/services/clusters/applications/create_service_spec.rb
index bb86a742f0e..8dd573c3698 100644
--- a/spec/services/clusters/applications/create_service_spec.rb
+++ b/spec/services/clusters/applications/create_service_spec.rb
@@ -147,12 +147,12 @@ describe Clusters::Applications::CreateService do
using RSpec::Parameterized::TableSyntax
- where(:application, :association, :allowed, :pre_create_helm) do
- 'helm' | :application_helm | true | false
- 'ingress' | :application_ingress | true | true
- 'runner' | :application_runner | true | true
- 'prometheus' | :application_prometheus | true | true
- 'jupyter' | :application_jupyter | false | true
+ where(:application, :association, :allowed, :pre_create_helm, :pre_create_ingress) do
+ 'helm' | :application_helm | true | false | false
+ 'ingress' | :application_ingress | true | true | false
+ 'runner' | :application_runner | true | true | false
+ 'prometheus' | :application_prometheus | true | true | false
+ 'jupyter' | :application_jupyter | true | true | true
end
with_them do
@@ -160,6 +160,7 @@ describe Clusters::Applications::CreateService do
klass = "Clusters::Applications::#{application.titleize}"
allow_any_instance_of(klass.constantize).to receive(:make_scheduled!).and_call_original
create(:clusters_applications_helm, :installed, cluster: cluster) if pre_create_helm
+ create(:clusters_applications_ingress, :installed, cluster: cluster, external_hostname: 'example.com') if pre_create_ingress
end
let(:params) { { application: application } }
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/external_pull_requests/create_pipeline_service_spec.rb b/spec/services/external_pull_requests/create_pipeline_service_spec.rb
new file mode 100644
index 00000000000..a4da5b38b97
--- /dev/null
+++ b/spec/services/external_pull_requests/create_pipeline_service_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ExternalPullRequests::CreatePipelineService do
+ describe '#execute' do
+ set(:project) { create(:project, :repository) }
+ set(:user) { create(:user) }
+ let(:pull_request) { create(:external_pull_request, project: project) }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ subject { described_class.new(project, user).execute(pull_request) }
+
+ context 'when pull request is open' do
+ before do
+ pull_request.update!(status: :open)
+ end
+
+ context 'when source sha is the head of the source branch' do
+ let(:source_branch) { project.repository.branches.last }
+ let(:create_pipeline_service) { instance_double(Ci::CreatePipelineService) }
+
+ before do
+ pull_request.update!(source_branch: source_branch.name, source_sha: source_branch.target)
+ end
+
+ it 'creates a pipeline for external pull request' do
+ expect(subject).to be_valid
+ expect(subject).to be_persisted
+ expect(subject).to be_external_pull_request_event
+ expect(subject).to eq(project.ci_pipelines.last)
+ expect(subject.external_pull_request).to eq(pull_request)
+ expect(subject.user).to eq(user)
+ expect(subject.status).to eq('pending')
+ expect(subject.ref).to eq(pull_request.source_branch)
+ expect(subject.sha).to eq(pull_request.source_sha)
+ expect(subject.source_sha).to eq(pull_request.source_sha)
+ end
+ end
+
+ context 'when source sha is not the head of the source branch (force push upon rebase)' do
+ let(:source_branch) { project.repository.branches.first }
+ let(:commit) { project.repository.commits(source_branch.name, limit: 2).last }
+
+ before do
+ pull_request.update!(source_branch: source_branch.name, source_sha: commit.sha)
+ end
+
+ it 'does nothing' do
+ expect(Ci::CreatePipelineService).not_to receive(:new)
+
+ expect(subject).to be_nil
+ end
+ end
+ end
+
+ context 'when pull request is not opened' do
+ before do
+ pull_request.update!(status: :closed)
+ end
+
+ it 'does nothing' do
+ expect(Ci::CreatePipelineService).not_to receive(:new)
+
+ expect(subject).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb
index d9e607cd251..c3a4f3dbe3f 100644
--- a/spec/services/git/branch_push_service_spec.rb
+++ b/spec/services/git/branch_push_service_spec.rb
@@ -99,6 +99,20 @@ describe Git::BranchPushService, services: true do
expect(pipeline).to be_push
expect(Gitlab::Git::BRANCH_REF_PREFIX + pipeline.ref).to eq(ref)
end
+
+ context 'when pipeline has errors' do
+ before do
+ config = YAML.dump({ test: { script: 'ls', only: ['feature'] } })
+ stub_ci_pipeline_yaml_file(config)
+ end
+
+ it 'reports an error' do
+ allow(Sidekiq).to receive(:server?).and_return(true)
+ expect(Sidekiq.logger).to receive(:warn)
+
+ expect { subject }.not_to change { Ci::Pipeline.count }
+ end
+ end
end
describe "Updates merge requests" 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/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index ed48f4b1e44..699f2a98088 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::CreateService do
+describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
include ProjectForksHelper
let(:project) { create(:project, :repository) }
@@ -285,6 +285,12 @@ describe MergeRequests::CreateService do
end
end
end
+
+ it 'increments the usage data counter of create event' do
+ counter = Gitlab::UsageDataCounters::MergeRequestCounter
+
+ expect { service.execute }.to change { counter.read(:create) }.by(1)
+ end
end
it_behaves_like 'new issuable record that supports quick actions' do
diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb
index a27fea0c90f..ff4cdd3e7e2 100644
--- a/spec/services/merge_requests/push_options_handler_service_spec.rb
+++ b/spec/services/merge_requests/push_options_handler_service_spec.rb
@@ -13,6 +13,9 @@ describe MergeRequests::PushOptionsHandlerService do
let(:target_branch) { 'feature' }
let(:title) { 'my title' }
let(:description) { 'my description' }
+ let(:label1) { 'mylabel1' }
+ let(:label2) { 'mylabel2' }
+ let(:label3) { 'mylabel3' }
let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
let(:deleted_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 #{Gitlab::Git::BLANK_SHA} refs/heads/#{source_branch}" }
@@ -122,6 +125,16 @@ describe MergeRequests::PushOptionsHandlerService do
end
end
+ shared_examples_for 'a service that can change labels of a merge request' do |count|
+ subject(:last_mr) { MergeRequest.last }
+
+ it 'changes label count' do
+ service.execute
+
+ expect(last_mr.label_ids.count).to eq(count)
+ end
+ end
+
shared_examples_for 'a service that does not create a merge request' do
it do
expect { service.execute }.not_to change { MergeRequest.count }
@@ -504,6 +517,138 @@ describe MergeRequests::PushOptionsHandlerService do
end
end
+ describe '`label` push option' do
+ let(:push_options) { { label: { label1 => 1, label2 => 1 } } }
+
+ context 'with a new branch' do
+ let(:changes) { new_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, label: { label1 => 1, label2 => 1 } } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can change labels of a merge request', 2
+ end
+ end
+
+ context 'with an existing branch but no open MR' do
+ let(:changes) { existing_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, label: { label1 => 1, label2 => 1 } } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can change labels of a merge request', 2
+ end
+ end
+
+ context 'with an existing branch that has a merge request open' do
+ let(:changes) { existing_branch_changes }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)}
+
+ it_behaves_like 'a service that does not create a merge request'
+ it_behaves_like 'a service that can change labels of a merge request', 2
+ end
+
+ context 'with a deleted branch' do
+ let(:changes) { deleted_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+
+ context 'with the project default branch' do
+ let(:changes) { default_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+ end
+
+ describe '`unlabel` push option' do
+ let(:push_options) { { label: { label1 => 1, label2 => 1 }, unlabel: { label1 => 1, label3 => 1 } } }
+
+ context 'with a new branch' do
+ let(:changes) { new_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, label: { label1 => 1, label2 => 1 }, unlabel: { label1 => 1, label3 => 1 } } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can change labels of a merge request', 1
+ end
+ end
+
+ context 'with an existing branch but no open MR' do
+ let(:changes) { existing_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, label: { label1 => 1, label2 => 1 }, unlabel: { label1 => 1, label3 => 1 } } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can change labels of a merge request', 1
+ end
+ end
+
+ context 'with an existing branch that has a merge request open' do
+ let(:changes) { existing_branch_changes }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)}
+
+ it_behaves_like 'a service that does not create a merge request'
+ it_behaves_like 'a service that can change labels of a merge request', 1
+ end
+
+ context 'with a deleted branch' do
+ let(:changes) { deleted_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+
+ context 'with the project default branch' do
+ let(:changes) { default_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+ end
+
describe 'multiple pushed branches' do
let(:push_options) { { create: true } }
let(:changes) do
diff --git a/spec/services/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/milestones/find_or_create_service_spec.rb b/spec/services/milestones/find_or_create_service_spec.rb
new file mode 100644
index 00000000000..ae3def30982
--- /dev/null
+++ b/spec/services/milestones/find_or_create_service_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Milestones::FindOrCreateService do
+ describe '#execute' do
+ subject(:service) { described_class.new(project, user, params) }
+
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project) { create(:project, namespace: group) }
+ let(:params) do
+ {
+ title: '1.0',
+ description: 'First Release',
+ start_date: Date.today,
+ due_date: Date.today + 1.month
+ }.with_indifferent_access
+ end
+
+ context 'when finding milestone on project level' do
+ let!(:existing_project_milestone) { create(:milestone, project: project, title: '1.0') }
+
+ it 'returns existing milestone' do
+ expect(service.execute).to eq(existing_project_milestone)
+ end
+ end
+
+ context 'when finding milestone on group level' do
+ let!(:existing_group_milestone) { create(:milestone, group: group, title: '1.0') }
+
+ it 'returns existing milestone' do
+ expect(service.execute).to eq(existing_group_milestone)
+ end
+ end
+
+ context 'when not finding milestone' do
+ context 'when user has permissions' do
+ before do
+ project.add_developer(user)
+ end
+
+ context 'when params are valid' do
+ it 'creates a new milestone at project level using params' do
+ expect { service.execute }.to change(project.milestones, :count).by(1)
+
+ milestone = project.reload.milestones.last
+
+ expect(milestone.title).to eq(params[:title])
+ expect(milestone.description).to eq(params[:description])
+ expect(milestone.start_date).to eq(params[:start_date])
+ expect(milestone.due_date).to eq(params[:due_date])
+ end
+ end
+
+ context 'when params are not valid' do
+ before do
+ params[:start_date] = Date.today + 2.months
+ end
+
+ it 'returns nil' do
+ expect(service.execute).to be_nil
+ end
+ end
+ end
+
+ context 'when user does not have permissions' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'does not create a new milestone' do
+ expect { service.execute }.not_to change(project.milestones, :count)
+ end
+
+ it 'returns nil' do
+ expect(service.execute).to be_nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/milestones/transfer_service_spec.rb b/spec/services/milestones/transfer_service_spec.rb
new file mode 100644
index 00000000000..b3d41eb0763
--- /dev/null
+++ b/spec/services/milestones/transfer_service_spec.rb
@@ -0,0 +1,122 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Milestones::TransferService do
+ describe '#execute' do
+ subject(:service) { described_class.new(user, old_group, project) }
+
+ context 'when old_group is present' do
+ let(:user) { create(:admin) }
+ let(:new_group) { create(:group) }
+ let(:old_group) { create(:group) }
+ let(:project) { create(:project, namespace: old_group) }
+ let(:group_milestone) { create(:milestone, group: old_group)}
+ let(:group_milestone2) { create(:milestone, group: old_group)}
+ let(:project_milestone) { create(:milestone, project: project)}
+ let!(:issue_with_group_milestone) { create(:issue, project: project, milestone: group_milestone) }
+ let!(:issue_with_project_milestone) { create(:issue, project: project, milestone: project_milestone) }
+ let!(:mr_with_group_milestone) { create(:merge_request, source_project: project, source_branch: 'branch-1', milestone: group_milestone) }
+ let!(:mr_with_project_milestone) { create(:merge_request, source_project: project, source_branch: 'branch-2', milestone: project_milestone) }
+
+ before do
+ new_group.add_maintainer(user)
+ project.add_maintainer(user)
+ # simulate project transfer
+ project.update(group: new_group)
+ end
+
+ context 'without existing milestone at the new group level' do
+ it 'recreates the missing group milestones at project level' do
+ expect { service.execute }.to change(project.milestones, :count).by(1)
+ end
+
+ it 'applies new project milestone to issues with group milestone' do
+ service.execute
+ new_milestone = issue_with_group_milestone.reload.milestone
+
+ expect(new_milestone).not_to eq(group_milestone)
+ expect(new_milestone.title).to eq(group_milestone.title)
+ expect(new_milestone.project_milestone?).to be_truthy
+ end
+
+ it 'does not apply new project milestone to issues with project milestone' do
+ service.execute
+
+ expect(issue_with_project_milestone.reload.milestone).to eq(project_milestone)
+ end
+
+ it 'applies new project milestone to merge_requests with group milestone' do
+ service.execute
+ new_milestone = mr_with_group_milestone.reload.milestone
+
+ expect(new_milestone).not_to eq(group_milestone)
+ expect(new_milestone.title).to eq(group_milestone.title)
+ expect(new_milestone.project_milestone?).to be_truthy
+ end
+
+ it 'does not apply new project milestone to issuables with project milestone' do
+ service.execute
+
+ expect(mr_with_project_milestone.reload.milestone).to eq(project_milestone)
+ end
+
+ it 'does not recreate missing group milestones that are not applied to issues or merge requests' do
+ service.execute
+ new_milestone_title = project.reload.milestones.pluck(:title)
+
+ expect(new_milestone_title).to include(group_milestone.title)
+ expect(new_milestone_title).not_to include(group_milestone2.title)
+ end
+
+ context 'when find_or_create_milestone returns nil' do
+ before do
+ allow_any_instance_of(Milestones::FindOrCreateService).to receive(:execute).and_return(nil)
+ end
+
+ it 'removes issues group milestone' do
+ service.execute
+
+ expect(mr_with_group_milestone.reload.milestone).to be_nil
+ end
+
+ it 'removes merge requests group milestone' do
+ service.execute
+
+ expect(issue_with_group_milestone.reload.milestone).to be_nil
+ end
+ end
+ end
+
+ context 'with existing milestone at the new group level' do
+ let!(:existing_milestone) { create(:milestone, group: new_group, title: group_milestone.title) }
+
+ it 'does not create a new milestone' do
+ expect { service.execute }.not_to change(project.milestones, :count)
+ end
+
+ it 'applies existing milestone to issues with group milestone' do
+ service.execute
+
+ expect(issue_with_group_milestone.reload.milestone).to eq(existing_milestone)
+ end
+
+ it 'applies existing milestone to merge_requests with group milestone' do
+ service.execute
+
+ expect(mr_with_group_milestone.reload.milestone).to eq(existing_milestone)
+ end
+ end
+ end
+ end
+
+ context 'when old_group is not present' do
+ let(:user) { create(:admin) }
+ let(:old_group) { project.group }
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ it 'returns nil' do
+ expect(described_class.new(user, old_group, project).execute).to be_nil
+ 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/projects/forks_count_service_spec.rb b/spec/services/projects/forks_count_service_spec.rb
index 7e35648e9ff..1b44782468a 100644
--- a/spec/services/projects/forks_count_service_spec.rb
+++ b/spec/services/projects/forks_count_service_spec.rb
@@ -2,15 +2,17 @@
require 'spec_helper'
-describe Projects::ForksCountService do
+describe Projects::ForksCountService, :use_clean_rails_memory_store_caching do
+ let(:project) { build(:project) }
+ subject { described_class.new(project) }
+
+ it_behaves_like 'a counter caching service'
+
describe '#count' do
it 'returns the number of forks' do
- project = build(:project, id: 42)
- service = described_class.new(project)
-
- allow(service).to receive(:uncached_count).and_return(1)
+ allow(subject).to receive(:uncached_count).and_return(1)
- expect(service.count).to eq(1)
+ expect(subject.count).to eq(1)
end
end
end
diff --git a/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb
index 849601c4a63..66233787d3a 100644
--- a/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_link_service_spec.rb
@@ -30,5 +30,23 @@ describe Projects::LfsPointers::LfsLinkService do
expect(subject.execute(new_oid_list.keys)).to eq linked
end
+
+ it 'links in batches' do
+ stub_const("#{described_class}::BATCH_SIZE", 3)
+
+ expect(Gitlab::Import::Logger)
+ .to receive(:info)
+ .with(class: described_class.name,
+ project_id: project.id,
+ project_path: project.full_path,
+ lfs_objects_linked_count: 7,
+ iterations: 3)
+
+ lfs_objects = create_list(:lfs_object, 7)
+ linked = subject.execute(lfs_objects.pluck(:oid))
+
+ expect(project.all_lfs_objects.count).to eq 9
+ expect(linked.size).to eq 7
+ end
end
end
diff --git a/spec/services/projects/open_issues_count_service_spec.rb b/spec/services/projects/open_issues_count_service_spec.rb
index 8efa34765d0..593a4df1f8f 100644
--- a/spec/services/projects/open_issues_count_service_spec.rb
+++ b/spec/services/projects/open_issues_count_service_spec.rb
@@ -2,10 +2,13 @@
require 'spec_helper'
-describe Projects::OpenIssuesCountService do
- describe '#count' do
- let(:project) { create(:project) }
+describe Projects::OpenIssuesCountService, :use_clean_rails_memory_store_caching do
+ let(:project) { create(:project) }
+ subject { described_class.new(project) }
+
+ it_behaves_like 'a counter caching service'
+ describe '#count' do
context 'when user is nil' do
it 'does not include confidential issues in the issue count' do
create(:issue, :opened, project: project)
@@ -53,9 +56,7 @@ describe Projects::OpenIssuesCountService do
end
end
- context '#refresh_cache', :use_clean_rails_memory_store_caching do
- let(:subject) { described_class.new(project) }
-
+ context '#refresh_cache' do
before do
create(:issue, :opened, project: project)
create(:issue, :opened, project: project)
diff --git a/spec/services/projects/open_merge_requests_count_service_spec.rb b/spec/services/projects/open_merge_requests_count_service_spec.rb
index 0d8227f7db5..f9fff4cbd4c 100644
--- a/spec/services/projects/open_merge_requests_count_service_spec.rb
+++ b/spec/services/projects/open_merge_requests_count_service_spec.rb
@@ -2,16 +2,21 @@
require 'spec_helper'
-describe Projects::OpenMergeRequestsCountService do
+describe Projects::OpenMergeRequestsCountService, :use_clean_rails_memory_store_caching do
+ set(:project) { create(:project) }
+
+ subject { described_class.new(project) }
+
+ it_behaves_like 'a counter caching service'
+
describe '#count' do
it 'returns the number of open merge requests' do
- project = create(:project)
create(:merge_request,
:opened,
source_project: project,
target_project: project)
- expect(described_class.new(project).count).to eq(1)
+ expect(subject.count).to eq(1)
end
end
end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index a47c10d991a..6b906f9372c 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -259,7 +259,7 @@ describe Projects::TransferService do
end
context 'missing group labels applied to issues or merge requests' do
- it 'delegates tranfer to Labels::TransferService' do
+ it 'delegates transfer to Labels::TransferService' do
group.add_owner(user)
expect_any_instance_of(Labels::TransferService).to receive(:execute).once.and_call_original
@@ -268,6 +268,17 @@ describe Projects::TransferService do
end
end
+ context 'missing group milestones applied to issues or merge requests' do
+ it 'delegates transfer to Milestones::TransferService' do
+ group.add_owner(user)
+
+ expect(Milestones::TransferService).to receive(:new).with(user, project.group, project).and_call_original
+ expect_any_instance_of(Milestones::TransferService).to receive(:execute).once
+
+ transfer_project(project, user, group)
+ end
+ end
+
context 'when hashed storage in use' do
let(:hashed_project) { create(:project, :repository, namespace: user.namespace) }
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index c9714964fc9..b65ee16c189 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -970,34 +970,6 @@ describe QuickActions::InterpretService do
let(:issuable) { merge_request }
end
- it_behaves_like 'due command' do
- let(:content) { '/due 2016-08-28' }
- let(:issuable) { issue }
- end
-
- it_behaves_like 'due command' do
- let(:content) { '/due tomorrow' }
- let(:issuable) { issue }
- let(:expected_date) { Date.tomorrow }
- end
-
- it_behaves_like 'due command' do
- let(:content) { '/due 5 days from now' }
- let(:issuable) { issue }
- let(:expected_date) { 5.days.from_now.to_date }
- end
-
- it_behaves_like 'due command' do
- let(:content) { '/due in 2 days' }
- let(:issuable) { issue }
- let(:expected_date) { 2.days.from_now.to_date }
- end
-
- it_behaves_like 'empty command' do
- let(:content) { '/due foo bar' }
- let(:issuable) { issue }
- end
-
it_behaves_like 'empty command' do
let(:content) { '/due 2016-08-28' }
let(:issuable) { merge_request }
@@ -1131,10 +1103,56 @@ describe QuickActions::InterpretService do
end
end
+ context '/due command' do
+ it 'returns invalid date format message when the due date is invalid' do
+ issue = build(:issue, project: project)
+
+ _, _, message = service.execute('/due invalid date', issue)
+
+ expect(message).to eq('Failed to set due date because the date format is invalid.')
+ end
+
+ it_behaves_like 'due command' do
+ let(:content) { '/due 2016-08-28' }
+ let(:issuable) { issue }
+ end
+
+ it_behaves_like 'due command' do
+ let(:content) { '/due tomorrow' }
+ let(:issuable) { issue }
+ let(:expected_date) { Date.tomorrow }
+ end
+
+ it_behaves_like 'due command' do
+ let(:content) { '/due 5 days from now' }
+ let(:issuable) { issue }
+ let(:expected_date) { 5.days.from_now.to_date }
+ end
+
+ it_behaves_like 'due command' do
+ let(:content) { '/due in 2 days' }
+ let(:issuable) { issue }
+ let(:expected_date) { 2.days.from_now.to_date }
+ end
+ end
+
context '/copy_metadata command' do
let(:todo_label) { create(:label, project: project, title: 'To Do') }
let(:inreview_label) { create(:label, project: project, title: 'In Review') }
+ it 'is available when the user is a developer' do
+ expect(service.available_commands(issue)).to include(a_hash_including(name: :copy_metadata))
+ end
+
+ context 'when the user does not have permission' do
+ let(:guest) { create(:user) }
+ let(:service) { described_class.new(project, guest) }
+
+ it 'is not available' do
+ expect(service.available_commands(issue)).not_to include(a_hash_including(name: :copy_metadata))
+ end
+ end
+
it_behaves_like 'empty command' do
let(:content) { '/copy_metadata' }
let(:issuable) { issue }
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/services/users/keys_count_service_spec.rb b/spec/services/users/keys_count_service_spec.rb
index bee8380e8b7..6b7493f343f 100644
--- a/spec/services/users/keys_count_service_spec.rb
+++ b/spec/services/users/keys_count_service_spec.rb
@@ -4,7 +4,9 @@ require 'spec_helper'
describe Users::KeysCountService, :use_clean_rails_memory_store_caching do
let(:user) { create(:user) }
- let(:service) { described_class.new(user) }
+ subject { described_class.new(user) }
+
+ it_behaves_like 'a counter caching service'
describe '#count' do
before do
@@ -12,53 +14,19 @@ describe Users::KeysCountService, :use_clean_rails_memory_store_caching do
end
it 'returns the number of SSH keys as an Integer' do
- expect(service.count).to eq(1)
- end
-
- it 'caches the number of keys in Redis', :request_store do
- service.delete_cache
- control_count = ActiveRecord::QueryRecorder.new { service.count }.count
- service.delete_cache
-
- expect { 2.times { service.count } }.not_to exceed_query_limit(control_count)
- end
- end
-
- describe '#refresh_cache' do
- it 'refreshes the Redis cache' do
- Rails.cache.write(service.cache_key, 10)
- service.refresh_cache
-
- expect(Rails.cache.fetch(service.cache_key, raw: true)).to be_zero
- end
- end
-
- describe '#delete_cache' do
- it 'removes the cache' do
- service.count
- service.delete_cache
-
- expect(Rails.cache.fetch(service.cache_key, raw: true)).to be_nil
+ expect(subject.count).to eq(1)
end
end
describe '#uncached_count' do
it 'returns the number of SSH keys' do
- expect(service.uncached_count).to be_zero
- end
-
- it 'does not cache the number of keys' do
- recorder = ActiveRecord::QueryRecorder.new do
- 2.times { service.uncached_count }
- end
-
- expect(recorder.count).to be > 0
+ expect(subject.uncached_count).to be_zero
end
end
describe '#cache_key' do
it 'returns the cache key' do
- expect(service.cache_key).to eq("users/key-count-service/#{user.id}")
+ expect(subject.cache_key).to eq("users/key-count-service/#{user.id}")
end
end
end
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index 50167a2e059..2a4368868d5 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -55,31 +55,38 @@ describe WebHookService do
describe '#execute' do
before do
project.hooks << [project_hook]
-
- WebMock.stub_request(:post, project_hook.url)
end
context 'when token is defined' do
let(:project_hook) { create(:project_hook, :token) }
it 'POSTs to the webhook URL' do
+ stub_full_request(project_hook.url, method: :post)
+
service_instance.execute
- expect(WebMock).to have_requested(:post, project_hook.url).with(
+
+ expect(WebMock).to have_requested(:post, stubbed_hostname(project_hook.url)).with(
headers: headers.merge({ 'X-Gitlab-Token' => project_hook.token })
).once
end
end
it 'POSTs to the webhook URL' do
+ stub_full_request(project_hook.url, method: :post)
+
service_instance.execute
- expect(WebMock).to have_requested(:post, project_hook.url).with(
+
+ expect(WebMock).to have_requested(:post, stubbed_hostname(project_hook.url)).with(
headers: headers
).once
end
it 'POSTs the data as JSON' do
+ stub_full_request(project_hook.url, method: :post)
+
service_instance.execute
- expect(WebMock).to have_requested(:post, project_hook.url).with(
+
+ expect(WebMock).to have_requested(:post, stubbed_hostname(project_hook.url)).with(
headers: headers
).once
end
@@ -115,7 +122,7 @@ describe WebHookService do
end
it 'catches exceptions' do
- WebMock.stub_request(:post, project_hook.url).to_raise(StandardError.new('Some error'))
+ stub_full_request(project_hook.url, method: :post).to_raise(StandardError.new('Some error'))
expect { service_instance.execute }.to raise_error(StandardError)
end
@@ -125,20 +132,20 @@ describe WebHookService do
exceptions.each do |exception_class|
exception = exception_class.new('Exception message')
- WebMock.stub_request(:post, project_hook.url).to_raise(exception)
+ stub_full_request(project_hook.url, method: :post).to_raise(exception)
expect(service_instance.execute).to eq({ status: :error, message: exception.to_s })
expect { service_instance.execute }.not_to raise_error
end
end
it 'handles 200 status code' do
- WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: 'Success')
+ stub_full_request(project_hook.url, method: :post).to_return(status: 200, body: 'Success')
expect(service_instance.execute).to include({ status: :success, http_status: 200, message: 'Success' })
end
it 'handles 2xx status codes' do
- WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: 'Success')
+ stub_full_request(project_hook.url, method: :post).to_return(status: 201, body: 'Success')
expect(service_instance.execute).to include({ status: :success, http_status: 201, message: 'Success' })
end
@@ -148,7 +155,7 @@ describe WebHookService do
context 'with success' do
before do
- WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: 'Success')
+ stub_full_request(project_hook.url, method: :post).to_return(status: 200, body: 'Success')
service_instance.execute
end
@@ -165,7 +172,7 @@ describe WebHookService do
context 'with exception' do
before do
- WebMock.stub_request(:post, project_hook.url).to_raise(SocketError.new('Some HTTP Post error'))
+ stub_full_request(project_hook.url, method: :post).to_raise(SocketError.new('Some HTTP Post error'))
service_instance.execute
end
@@ -182,7 +189,7 @@ describe WebHookService do
context 'with unsafe response body' do
before do
- WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: "\xBB")
+ stub_full_request(project_hook.url, method: :post).to_return(status: 200, body: "\xBB")
service_instance.execute
end
@@ -202,7 +209,7 @@ describe WebHookService do
let(:service_instance) { described_class.new(service_hook, data, 'service_hook') }
before do
- WebMock.stub_request(:post, service_hook.url).to_return(status: 200, body: 'Success')
+ stub_full_request(service_hook.url, method: :post).to_return(status: 200, body: 'Success')
end
it { expect { service_instance.execute }.not_to change(WebHookLog, :count) }
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index bd504f1553b..47f09bf14d0 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -109,6 +109,7 @@ RSpec.configure do |config|
config.include PolicyHelpers, type: :policy
config.include MemoryUsageHelper
config.include ExpectRequestWithStatus, type: :request
+ config.include RailsHelpers
if ENV['CI']
# This includes the first try, i.e. tests will be run 4 times before failing.
@@ -148,6 +149,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/drag_to_helper.rb b/spec/support/helpers/drag_to_helper.rb
index 6099f87323f..2e9932f2e8a 100644
--- a/spec/support/helpers/drag_to_helper.rb
+++ b/spec/support/helpers/drag_to_helper.rb
@@ -1,8 +1,23 @@
# frozen_string_literal: true
module DragTo
- def drag_to(list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0, selector: '', scrollable: 'body', duration: 1000)
- evaluate_script("simulateDrag({scrollable: $('#{scrollable}').get(0), duration: #{duration}, from: {el: $('#{selector}').eq(#{list_from_index}).get(0), index: #{from_index}}, to: {el: $('#{selector}').eq(#{list_to_index}).get(0), index: #{to_index}}});")
+ def drag_to(list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0, selector: '', scrollable: 'body', duration: 1000, perform_drop: true)
+ js = <<~JS
+ simulateDrag({
+ scrollable: document.querySelector('#{scrollable}'),
+ duration: #{duration},
+ from: {
+ el: document.querySelectorAll('#{selector}')[#{list_from_index}],
+ index: #{from_index}
+ },
+ to: {
+ el: document.querySelectorAll('#{selector}')[#{list_to_index}],
+ index: #{to_index}
+ },
+ performDrop: #{perform_drop}
+ });
+ JS
+ evaluate_script(js)
Timeout.timeout(Capybara.default_max_wait_time) do
loop while drag_active?
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index d86371d70b9..beb346b2855 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -34,6 +34,14 @@ module GraphqlHelpers
end
end
+ # BatchLoader::GraphQL returns a wrapper, so we need to :sync in order
+ # to get the actual values
+ def batch_sync(max_queries: nil, &blk)
+ result = batch(max_queries: nil, &blk)
+
+ result.is_a?(Array) ? result.map(&:sync) : result&.sync
+ end
+
def graphql_query_for(name, attributes = {}, fields = nil)
<<~QUERY
{
@@ -114,7 +122,11 @@ module GraphqlHelpers
FIELDS
end
- def all_graphql_fields_for(class_name, parent_types = Set.new)
+ def all_graphql_fields_for(class_name, parent_types = Set.new, max_depth: 3)
+ # pulling _all_ fields can generate a _huge_ query (like complexity 180,000),
+ # and significantly increase spec runtime. so limit the depth by default
+ return if max_depth <= 0
+
allow_unlimited_graphql_complexity
allow_unlimited_graphql_depth
@@ -133,9 +145,9 @@ module GraphqlHelpers
if nested_fields?(field)
fields =
- all_graphql_fields_for(singular_field_type, parent_types | [type])
+ all_graphql_fields_for(singular_field_type, parent_types | [type], max_depth: max_depth - 1)
- "#{name} { #{fields} }"
+ "#{name} { #{fields} }" unless fields.blank?
else
name
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/query_recorder.rb b/spec/support/helpers/query_recorder.rb
index d936dc6de41..9d47a0c23df 100644
--- a/spec/support/helpers/query_recorder.rb
+++ b/spec/support/helpers/query_recorder.rb
@@ -8,7 +8,10 @@ module ActiveRecord
@log = []
@cached = []
@skip_cached = skip_cached
- ActiveSupport::Notifications.subscribed(method(:callback), 'sql.active_record', &block)
+ # force replacement of bind parameters to give tests the ability to check for ids
+ ActiveRecord::Base.connection.unprepared_statement do
+ ActiveSupport::Notifications.subscribed(method(:callback), 'sql.active_record', &block)
+ end
end
def show_backtrace(values)
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 c8b2bf040e6..f364e4fd158 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -30,6 +30,10 @@ module StubConfiguration
allow(Gitlab.config.gitlab).to receive_messages(to_settings(messages))
end
+ def stub_config(messages)
+ allow(Gitlab.config).to receive_messages(to_settings(messages))
+ end
+
def stub_default_url_options(host: "localhost", protocol: "http")
url_options = { host: host, protocol: protocol }
allow(Rails.application.routes).to receive(:default_url_options).and_return(url_options)
@@ -101,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/helpers/workhorse_helpers.rb b/spec/support/helpers/workhorse_helpers.rb
index 4488e5f227e..fdbfe53fa39 100644
--- a/spec/support/helpers/workhorse_helpers.rb
+++ b/spec/support/helpers/workhorse_helpers.rb
@@ -17,7 +17,36 @@ module WorkhorseHelpers
end
def workhorse_internal_api_request_header
- jwt_token = JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256')
{ 'HTTP_' + Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER.upcase.tr('-', '_') => jwt_token }
end
+
+ # workhorse_post_with_file will transform file_key inside params as if it was disk accelerated by workhorse
+ def workhorse_post_with_file(url, file_key:, params:)
+ workhorse_params = params.dup
+ file = workhorse_params.delete(file_key)
+
+ workhorse_params.merge!(workhorse_disk_accelerated_file_params(file_key, file))
+
+ post(url,
+ params: workhorse_params,
+ headers: workhorse_rewritten_fields_header('file' => file.path)
+ )
+ end
+
+ private
+
+ def jwt_token(data = {})
+ JWT.encode({ 'iss' => 'gitlab-workhorse' }.merge(data), Gitlab::Workhorse.secret, 'HS256')
+ end
+
+ def workhorse_rewritten_fields_header(fields)
+ { Gitlab::Middleware::Multipart::RACK_ENV_KEY => jwt_token('rewritten_fields' => fields) }
+ end
+
+ def workhorse_disk_accelerated_file_params(key, file)
+ {
+ "#{key}.name" => file.original_filename,
+ "#{key}.path" => file.path
+ }
+ 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_contexts/finders/group_projects_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
index 38f6011646e..e7fee7239fc 100644
--- a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
@@ -6,9 +6,10 @@ RSpec.shared_context 'GroupProjectsFinder context' do
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
let(:current_user) { create(:user) }
+ let(:params) { {} }
let(:options) { {} }
- let(:finder) { described_class.new(group: group, current_user: current_user, options: options) }
+ let(:finder) { described_class.new(group: group, current_user: current_user, params: params, options: options) }
let!(:public_project) { create(:project, :public, group: group, path: '1') }
let!(:private_project) { create(:project, :private, group: group, path: '2') }
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/cycle_analytics_stage_examples.rb b/spec/support/shared_examples/cycle_analytics_stage_examples.rb
new file mode 100644
index 00000000000..151f5325e84
--- /dev/null
+++ b/spec/support/shared_examples/cycle_analytics_stage_examples.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+shared_examples_for 'cycle analytics stage' do
+ let(:valid_params) do
+ {
+ name: 'My Stage',
+ parent: parent,
+ start_event_identifier: :merge_request_created,
+ end_event_identifier: :merge_request_merged
+ }
+ end
+
+ describe 'validation' do
+ it 'is valid' do
+ expect(described_class.new(valid_params)).to be_valid
+ end
+
+ it 'validates presence of parent' do
+ stage = described_class.new(valid_params.except(:parent))
+
+ expect(stage).not_to be_valid
+ expect(stage.errors.details[parent_name]).to eq([{ error: :blank }])
+ end
+
+ it 'validates presence of start_event_identifier' do
+ stage = described_class.new(valid_params.except(:start_event_identifier))
+
+ expect(stage).not_to be_valid
+ expect(stage.errors.details[:start_event_identifier]).to eq([{ error: :blank }])
+ end
+
+ it 'validates presence of end_event_identifier' do
+ stage = described_class.new(valid_params.except(:end_event_identifier))
+
+ expect(stage).not_to be_valid
+ expect(stage.errors.details[:end_event_identifier]).to eq([{ error: :blank }])
+ end
+
+ it 'is invalid when end_event is not allowed for the given start_event' do
+ invalid_params = valid_params.merge(
+ start_event_identifier: :merge_request_merged,
+ end_event_identifier: :merge_request_created
+ )
+ stage = described_class.new(invalid_params)
+
+ expect(stage).not_to be_valid
+ expect(stage.errors.details[:end_event]).to eq([{ error: :not_allowed_for_the_given_start_event }])
+ end
+ end
+
+ describe '#subject_model' do
+ it 'infers the model from the start event' do
+ stage = described_class.new(valid_params)
+
+ expect(stage.subject_model).to eq(MergeRequest)
+ end
+ end
+
+ describe '#start_event' do
+ it 'builds start_event object based on start_event_identifier' do
+ stage = described_class.new(start_event_identifier: 'merge_request_created')
+
+ expect(stage.start_event).to be_a_kind_of(Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestCreated)
+ end
+ end
+
+ describe '#end_event' do
+ it 'builds end_event object based on end_event_identifier' do
+ stage = described_class.new(end_event_identifier: 'merge_request_merged')
+
+ expect(stage.end_event).to be_a_kind_of(Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestMerged)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb b/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb
index 323d1c51ffd..9a60825855f 100644
--- a/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/notes_on_noteables_shared_examples.rb
@@ -46,7 +46,7 @@ shared_context 'exposing regular notes on a noteable in GraphQL' do
discussions {
edges {
node {
- #{all_graphql_fields_for('Discussion')}
+ #{all_graphql_fields_for('Discussion', max_depth: 4)}
}
}
}
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/quick_actions/issue/move_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb
index a37b2392d52..bebc8509d53 100644
--- a/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb
@@ -89,5 +89,54 @@ shared_examples 'move quick action' do
it_behaves_like 'applies the commands to issues in both projects, target and source'
end
end
+
+ context 'when editing comments' do
+ let(:target_project) { create(:project, :public) }
+
+ before do
+ target_project.add_maintainer(user)
+
+ sign_in(user)
+ visit project_issue_path(project, issue)
+ wait_for_all_requests
+ end
+
+ it 'moves the issue after quickcommand note was updated' do
+ # misspelled quick action
+ add_note("test note.\n/mvoe #{target_project.full_path}")
+
+ expect(issue.reload).not_to be_closed
+
+ edit_note("/mvoe #{target_project.full_path}", "test note.\n/move #{target_project.full_path}")
+ wait_for_all_requests
+
+ expect(page).to have_content 'test note.'
+ expect(issue.reload).to be_closed
+
+ visit project_issue_path(target_project, issue)
+ wait_for_all_requests
+
+ expect(page).to have_content 'Issues 1'
+ end
+
+ it 'deletes the note if it was updated to just contain a command' do
+ # missspelled quick action
+ add_note("test note.\n/mvoe #{target_project.full_path}")
+
+ expect(page).not_to have_content 'Commands applied'
+ expect(issue.reload).not_to be_closed
+
+ edit_note("/mvoe #{target_project.full_path}", "/move #{target_project.full_path}")
+ wait_for_all_requests
+
+ expect(page).not_to have_content "/move #{target_project.full_path}"
+ expect(issue.reload).to be_closed
+
+ visit project_issue_path(target_project, issue)
+ wait_for_all_requests
+
+ expect(page).to have_content 'Issues 1'
+ end
+ end
end
end
diff --git a/spec/support/shared_examples/requests/api/discussions.rb b/spec/support/shared_examples/requests/api/discussions.rb
index fc72287f265..a36bc2dc9b5 100644
--- a/spec/support/shared_examples/requests/api/discussions.rb
+++ b/spec/support/shared_examples/requests/api/discussions.rb
@@ -1,5 +1,59 @@
# frozen_string_literal: true
+shared_examples 'with cross-reference system notes' do
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let(:new_merge_request) { create(:merge_request) }
+ let(:commit) { new_merge_request.project.commit }
+ let!(:note) { create(:system_note, noteable: merge_request, project: project, note: cross_reference) }
+ let!(:note_metadata) { create(:system_note_metadata, note: note, action: 'cross_reference') }
+ let(:cross_reference) { "test commit #{commit.to_reference(project)}" }
+ let(:pat) { create(:personal_access_token, user: user) }
+
+ before do
+ project.add_developer(user)
+ new_merge_request.project.add_developer(user)
+
+ hidden_merge_request = create(:merge_request)
+ new_cross_reference = "test commit #{hidden_merge_request.project.commit}"
+ new_note = create(:system_note, noteable: merge_request, project: project, note: new_cross_reference)
+ create(:system_note_metadata, note: new_note, action: 'cross_reference')
+ end
+
+ it 'returns only the note that the user should see' do
+ get api(url, user, personal_access_token: pat)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.count).to eq(1)
+ expect(notes_in_response.count).to eq(1)
+
+ parsed_note = notes_in_response.first
+ expect(parsed_note['id']).to eq(note.id)
+ expect(parsed_note['body']).to eq(cross_reference)
+ expect(parsed_note['system']).to be true
+ end
+
+ it 'avoids Git calls and N+1 SQL queries', :request_store do
+ expect_any_instance_of(Repository).not_to receive(:find_commit).with(commit.id)
+
+ control = ActiveRecord::QueryRecorder.new do
+ get api(url, user, personal_access_token: pat)
+ end
+
+ expect(response).to have_gitlab_http_status(200)
+
+ RequestStore.clear!
+
+ new_note = create(:system_note, noteable: merge_request, project: project, note: cross_reference)
+ create(:system_note_metadata, note: new_note, action: 'cross_reference')
+
+ RequestStore.clear!
+
+ expect { get api(url, user, personal_access_token: pat) }.not_to exceed_query_limit(control)
+ expect(response).to have_gitlab_http_status(200)
+ end
+end
+
shared_examples 'discussions API' do |parent_type, noteable_type, id_name, can_reply_to_individual_notes: false|
describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do
it "returns an array of discussions" do
diff --git a/spec/support/shared_examples/requests/api/pipelines/visibility_table_examples.rb b/spec/support/shared_examples/requests/api/pipelines/visibility_table_examples.rb
new file mode 100644
index 00000000000..dfd07176b1c
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/pipelines/visibility_table_examples.rb
@@ -0,0 +1,235 @@
+# frozen_string_literal: true
+
+shared_examples 'pipelines visibility table' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:ci_user) { create(:user) }
+ let(:api_user) { user_role && ci_user }
+
+ let(:pipelines_api_path) do
+ "/projects/#{project.id}/pipelines"
+ end
+
+ let(:response_200) do
+ a_collection_containing_exactly(
+ a_hash_including('sha', 'ref', 'status', 'web_url', 'id' => pipeline.id)
+ )
+ end
+
+ let(:response_40x) do
+ a_hash_including('message')
+ end
+
+ let(:expected_response) do
+ if response_status == 200
+ response_200
+ else
+ response_40x
+ end
+ end
+
+ let(:api_response) { json_response }
+
+ let(:visibility_levels) do
+ {
+ private: Gitlab::VisibilityLevel::PRIVATE,
+ internal: Gitlab::VisibilityLevel::INTERNAL,
+ public: Gitlab::VisibilityLevel::PUBLIC
+ }
+ end
+
+ let(:builds_access_levels) do
+ {
+ enabled: ProjectFeature::ENABLED,
+ private: ProjectFeature::PRIVATE
+ }
+ end
+
+ let(:project_attributes) do
+ {
+ visibility_level: visibility_levels[visibility_level],
+ public_builds: public_builds
+ }
+ end
+
+ let(:project_feature_attributes) do
+ {
+ builds_access_level: builds_access_levels[builds_access_level]
+ }
+ end
+
+ where(:visibility_level, :builds_access_level, :public_builds, :is_admin, :user_role, :response_status) do
+ :private | :enabled | true | true | :non_member | 200
+ :private | :enabled | true | true | :guest | 200
+ :private | :enabled | true | true | :reporter | 200
+ :private | :enabled | true | true | :developer | 200
+ :private | :enabled | true | true | :maintainer | 200
+
+ :private | :enabled | true | false | nil | 404
+ :private | :enabled | true | false | :non_member | 404
+ :private | :enabled | true | false | :guest | 200
+ :private | :enabled | true | false | :reporter | 200
+ :private | :enabled | true | false | :developer | 200
+ :private | :enabled | true | false | :maintainer | 200
+
+ :private | :enabled | false | true | :non_member | 200
+ :private | :enabled | false | true | :guest | 200
+ :private | :enabled | false | true | :reporter | 200
+ :private | :enabled | false | true | :developer | 200
+ :private | :enabled | false | true | :maintainer | 200
+
+ :private | :enabled | false | false | nil | 404
+ :private | :enabled | false | false | :non_member | 404
+ :private | :enabled | false | false | :guest | 403
+ :private | :enabled | false | false | :reporter | 200
+ :private | :enabled | false | false | :developer | 200
+ :private | :enabled | false | false | :maintainer | 200
+
+ :private | :private | true | true | :non_member | 200
+ :private | :private | true | true | :guest | 200
+ :private | :private | true | true | :reporter | 200
+ :private | :private | true | true | :developer | 200
+ :private | :private | true | true | :maintainer | 200
+
+ :private | :private | true | false | nil | 404
+ :private | :private | true | false | :non_member | 404
+ :private | :private | true | false | :guest | 200
+ :private | :private | true | false | :reporter | 200
+ :private | :private | true | false | :developer | 200
+ :private | :private | true | false | :maintainer | 200
+
+ :private | :private | false | true | :non_member | 200
+ :private | :private | false | true | :guest | 200
+ :private | :private | false | true | :reporter | 200
+ :private | :private | false | true | :developer | 200
+ :private | :private | false | true | :maintainer | 200
+
+ :private | :private | false | false | nil | 404
+ :private | :private | false | false | :non_member | 404
+ :private | :private | false | false | :guest | 403
+ :private | :private | false | false | :reporter | 200
+ :private | :private | false | false | :developer | 200
+ :private | :private | false | false | :maintainer | 200
+
+ :internal | :enabled | true | true | :non_member | 200
+ :internal | :enabled | true | true | :guest | 200
+ :internal | :enabled | true | true | :reporter | 200
+ :internal | :enabled | true | true | :developer | 200
+ :internal | :enabled | true | true | :maintainer | 200
+
+ :internal | :enabled | true | false | nil | 404
+ :internal | :enabled | true | false | :non_member | 200
+ :internal | :enabled | true | false | :guest | 200
+ :internal | :enabled | true | false | :reporter | 200
+ :internal | :enabled | true | false | :developer | 200
+ :internal | :enabled | true | false | :maintainer | 200
+
+ :internal | :enabled | false | true | :non_member | 200
+ :internal | :enabled | false | true | :guest | 200
+ :internal | :enabled | false | true | :reporter | 200
+ :internal | :enabled | false | true | :developer | 200
+ :internal | :enabled | false | true | :maintainer | 200
+
+ :internal | :enabled | false | false | nil | 404
+ :internal | :enabled | false | false | :non_member | 403
+ :internal | :enabled | false | false | :guest | 403
+ :internal | :enabled | false | false | :reporter | 200
+ :internal | :enabled | false | false | :developer | 200
+ :internal | :enabled | false | false | :maintainer | 200
+
+ :internal | :private | true | true | :non_member | 200
+ :internal | :private | true | true | :guest | 200
+ :internal | :private | true | true | :reporter | 200
+ :internal | :private | true | true | :developer | 200
+ :internal | :private | true | true | :maintainer | 200
+
+ :internal | :private | true | false | nil | 404
+ :internal | :private | true | false | :non_member | 403
+ :internal | :private | true | false | :guest | 200
+ :internal | :private | true | false | :reporter | 200
+ :internal | :private | true | false | :developer | 200
+ :internal | :private | true | false | :maintainer | 200
+
+ :internal | :private | false | true | :non_member | 200
+ :internal | :private | false | true | :guest | 200
+ :internal | :private | false | true | :reporter | 200
+ :internal | :private | false | true | :developer | 200
+ :internal | :private | false | true | :maintainer | 200
+
+ :internal | :private | false | false | nil | 404
+ :internal | :private | false | false | :non_member | 403
+ :internal | :private | false | false | :guest | 403
+ :internal | :private | false | false | :reporter | 200
+ :internal | :private | false | false | :developer | 200
+ :internal | :private | false | false | :maintainer | 200
+
+ :public | :enabled | true | true | :non_member | 200
+ :public | :enabled | true | true | :guest | 200
+ :public | :enabled | true | true | :reporter | 200
+ :public | :enabled | true | true | :developer | 200
+ :public | :enabled | true | true | :maintainer | 200
+
+ :public | :enabled | true | false | nil | 200
+ :public | :enabled | true | false | :non_member | 200
+ :public | :enabled | true | false | :guest | 200
+ :public | :enabled | true | false | :reporter | 200
+ :public | :enabled | true | false | :developer | 200
+ :public | :enabled | true | false | :maintainer | 200
+
+ :public | :enabled | false | true | :non_member | 200
+ :public | :enabled | false | true | :guest | 200
+ :public | :enabled | false | true | :reporter | 200
+ :public | :enabled | false | true | :developer | 200
+ :public | :enabled | false | true | :maintainer | 200
+
+ :public | :enabled | false | false | nil | 403
+ :public | :enabled | false | false | :non_member | 403
+ :public | :enabled | false | false | :guest | 403
+ :public | :enabled | false | false | :reporter | 200
+ :public | :enabled | false | false | :developer | 200
+ :public | :enabled | false | false | :maintainer | 200
+
+ :public | :private | true | true | :non_member | 200
+ :public | :private | true | true | :guest | 200
+ :public | :private | true | true | :reporter | 200
+ :public | :private | true | true | :developer | 200
+ :public | :private | true | true | :maintainer | 200
+
+ :public | :private | true | false | nil | 403
+ :public | :private | true | false | :non_member | 403
+ :public | :private | true | false | :guest | 200
+ :public | :private | true | false | :reporter | 200
+ :public | :private | true | false | :developer | 200
+ :public | :private | true | false | :maintainer | 200
+
+ :public | :private | false | true | :non_member | 200
+ :public | :private | false | true | :guest | 200
+ :public | :private | false | true | :reporter | 200
+ :public | :private | false | true | :developer | 200
+ :public | :private | false | true | :maintainer | 200
+
+ :public | :private | false | false | nil | 403
+ :public | :private | false | false | :non_member | 403
+ :public | :private | false | false | :guest | 403
+ :public | :private | false | false | :reporter | 200
+ :public | :private | false | false | :developer | 200
+ :public | :private | false | false | :maintainer | 200
+ end
+
+ with_them do
+ before do
+ ci_user.update!(admin: is_admin) if user_role
+
+ project.update!(project_attributes)
+ project.project_feature.update!(project_feature_attributes)
+ project.add_role(ci_user, user_role) if user_role && user_role != :non_member
+
+ get api(pipelines_api_path, api_user)
+ end
+
+ it do
+ expect(response).to have_gitlab_http_status(response_status)
+ expect(api_response).to match(expected_response)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
new file mode 100644
index 00000000000..afc6f59b773
--- /dev/null
+++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
@@ -0,0 +1,221 @@
+# frozen_string_literal: true
+#
+# Requires let variables:
+# * throttle_setting_prefix: "throttle_authenticated_api", "throttle_authenticated_web", "throttle_protected_paths"
+# * get_args
+# * other_user_get_args
+# * requests_per_period
+# * period_in_seconds
+# * period
+shared_examples_for 'rate-limited token-authenticated requests' do
+ before do
+ # Set low limits
+ settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period
+ settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds
+ end
+
+ context 'when the throttle is enabled' do
+ before do
+ settings_to_set[:"#{throttle_setting_prefix}_enabled"] = true
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'rejects requests over the rate limit' do
+ # At first, allow requests under the rate limit.
+ requests_per_period.times do
+ get(*get_args)
+ expect(response).to have_http_status 200
+ end
+
+ # the last straw
+ expect_rejection { get(*get_args) }
+ end
+
+ it 'allows requests after throttling and then waiting for the next period' do
+ requests_per_period.times do
+ get(*get_args)
+ expect(response).to have_http_status 200
+ end
+
+ expect_rejection { get(*get_args) }
+
+ Timecop.travel(period.from_now) do
+ requests_per_period.times do
+ get(*get_args)
+ expect(response).to have_http_status 200
+ end
+
+ expect_rejection { get(*get_args) }
+ end
+ end
+
+ it 'counts requests from different users separately, even from the same IP' do
+ requests_per_period.times do
+ get(*get_args)
+ expect(response).to have_http_status 200
+ end
+
+ # would be over the limit if this wasn't a different user
+ get(*other_user_get_args)
+ expect(response).to have_http_status 200
+ end
+
+ it 'counts all requests from the same user, even via different IPs' do
+ requests_per_period.times do
+ get(*get_args)
+ expect(response).to have_http_status 200
+ end
+
+ expect_any_instance_of(Rack::Attack::Request).to receive(:ip).and_return('1.2.3.4')
+
+ expect_rejection { get(*get_args) }
+ end
+
+ it 'logs RackAttack info into structured logs' do
+ requests_per_period.times do
+ get(*get_args)
+ expect(response).to have_http_status 200
+ end
+
+ arguments = {
+ message: 'Rack_Attack',
+ env: :throttle,
+ remote_ip: '127.0.0.1',
+ request_method: 'GET',
+ path: get_args.first,
+ user_id: user.id,
+ username: user.username
+ }
+
+ expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
+
+ expect_rejection { get(*get_args) }
+ end
+ end
+
+ context 'when the throttle is disabled' do
+ before do
+ settings_to_set[:"#{throttle_setting_prefix}_enabled"] = false
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'allows requests over the rate limit' do
+ (1 + requests_per_period).times do
+ get(*get_args)
+ expect(response).to have_http_status 200
+ end
+ end
+ end
+end
+
+# Requires let variables:
+# * throttle_setting_prefix: "throttle_authenticated_web" or "throttle_protected_paths"
+# * user
+# * url_that_requires_authentication
+# * requests_per_period
+# * period_in_seconds
+# * period
+shared_examples_for 'rate-limited web authenticated requests' do
+ before do
+ login_as(user)
+
+ # Set low limits
+ settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period
+ settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds
+ end
+
+ context 'when the throttle is enabled' do
+ before do
+ settings_to_set[:"#{throttle_setting_prefix}_enabled"] = true
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'rejects requests over the rate limit' do
+ # At first, allow requests under the rate limit.
+ requests_per_period.times do
+ get url_that_requires_authentication
+ expect(response).to have_http_status 200
+ end
+
+ # the last straw
+ expect_rejection { get url_that_requires_authentication }
+ end
+
+ it 'allows requests after throttling and then waiting for the next period' do
+ requests_per_period.times do
+ get url_that_requires_authentication
+ expect(response).to have_http_status 200
+ end
+
+ expect_rejection { get url_that_requires_authentication }
+
+ Timecop.travel(period.from_now) do
+ requests_per_period.times do
+ get url_that_requires_authentication
+ expect(response).to have_http_status 200
+ end
+
+ expect_rejection { get url_that_requires_authentication }
+ end
+ end
+
+ it 'counts requests from different users separately, even from the same IP' do
+ requests_per_period.times do
+ get url_that_requires_authentication
+ expect(response).to have_http_status 200
+ end
+
+ # would be over the limit if this wasn't a different user
+ login_as(create(:user))
+
+ get url_that_requires_authentication
+ expect(response).to have_http_status 200
+ end
+
+ it 'counts all requests from the same user, even via different IPs' do
+ requests_per_period.times do
+ get url_that_requires_authentication
+ expect(response).to have_http_status 200
+ end
+
+ expect_any_instance_of(Rack::Attack::Request).to receive(:ip).and_return('1.2.3.4')
+
+ expect_rejection { get url_that_requires_authentication }
+ end
+
+ it 'logs RackAttack info into structured logs' do
+ requests_per_period.times do
+ get url_that_requires_authentication
+ expect(response).to have_http_status 200
+ end
+
+ arguments = {
+ message: 'Rack_Attack',
+ env: :throttle,
+ remote_ip: '127.0.0.1',
+ request_method: 'GET',
+ path: '/dashboard/snippets',
+ user_id: user.id,
+ username: user.username
+ }
+
+ expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
+
+ get url_that_requires_authentication
+ end
+ end
+
+ context 'when the throttle is disabled' do
+ before do
+ settings_to_set[:"#{throttle_setting_prefix}_enabled"] = false
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'allows requests over the rate limit' do
+ (1 + requests_per_period).times do
+ get url_that_requires_authentication
+ expect(response).to have_http_status 200
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/count_service_shared_examples.rb b/spec/support/shared_examples/services/count_service_shared_examples.rb
new file mode 100644
index 00000000000..9bea180a778
--- /dev/null
+++ b/spec/support/shared_examples/services/count_service_shared_examples.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+# The calling spec should use `:use_clean_rails_memory_store_caching`
+# when including this shared example. E.g.:
+#
+# describe MyCountService, :use_clean_rails_memory_store_caching do
+# it_behaves_like 'a counter caching service'
+# end
+shared_examples 'a counter caching service' do
+ describe '#count' do
+ it 'caches the count', :request_store do
+ subject.delete_cache
+ control_count = ActiveRecord::QueryRecorder.new { subject.count }.count
+ subject.delete_cache
+
+ expect { 2.times { subject.count } }.not_to exceed_query_limit(control_count)
+ end
+ end
+
+ describe '#refresh_cache' do
+ it 'refreshes the cache' do
+ original_count = subject.count
+ Rails.cache.write(subject.cache_key, original_count + 1, raw: subject.raw?)
+
+ subject.refresh_cache
+
+ expect(fetch_cache || 0).to eq(original_count)
+ end
+ end
+
+ describe '#delete_cache' do
+ it 'removes the cache' do
+ subject.count
+ subject.delete_cache
+
+ expect(fetch_cache).to be_nil
+ end
+ end
+
+ describe '#uncached_count' do
+ it 'does not cache the count' do
+ subject.delete_cache
+ subject.uncached_count
+
+ expect(fetch_cache).to be_nil
+ end
+ end
+
+ private
+
+ def fetch_cache
+ Rails.cache.read(subject.cache_key, raw: subject.raw?)
+ 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/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb
index e6e4d9504d9..2f3fc7839c1 100644
--- a/spec/tasks/gitlab/gitaly_rake_spec.rb
+++ b/spec/tasks/gitlab/gitaly_rake_spec.rb
@@ -57,7 +57,7 @@ describe 'gitlab:gitaly namespace rake task' do
stub_env('CI', false)
FileUtils.mkdir_p(clone_path)
expect(Dir).to receive(:chdir).with(clone_path).and_call_original
- allow(Rails.env).to receive(:test?).and_return(false)
+ stub_rails_env('development')
end
context 'gmake is available' do
@@ -93,7 +93,7 @@ describe 'gitlab:gitaly namespace rake task' do
end
before do
- allow(Rails.env).to receive(:test?).and_return(true)
+ stub_rails_env('test')
end
it 'calls make in the gitaly directory with --no-deployment flag for bundle' do
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/validators/addressable_url_validator_spec.rb b/spec/validators/addressable_url_validator_spec.rb
index 387e84b2d04..6927a1f67a1 100644
--- a/spec/validators/addressable_url_validator_spec.rb
+++ b/spec/validators/addressable_url_validator_spec.rb
@@ -92,6 +92,15 @@ describe AddressableUrlValidator do
expect(badge.errors).to be_empty
expect(badge.link_url).to eq('https://127.0.0.1')
end
+
+ it 'allows urls that cannot be resolved' do
+ stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
+ badge.link_url = 'http://foobar.x'
+
+ subject
+
+ expect(badge.errors).to be_empty
+ end
end
context 'when message is set' do
@@ -312,4 +321,32 @@ describe AddressableUrlValidator do
end
end
end
+
+ context 'when dns_rebind_protection is' do
+ let(:not_resolvable_url) { 'http://foobar.x' }
+ let(:validator) { described_class.new(attributes: [:link_url], dns_rebind_protection: dns_value) }
+
+ before do
+ stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
+ badge.link_url = not_resolvable_url
+
+ subject
+ end
+
+ context 'true' do
+ let(:dns_value) { true }
+
+ it 'raises error' do
+ expect(badge.errors).to be_present
+ end
+ end
+
+ context 'false' do
+ let(:dns_value) { false }
+
+ it 'allows urls that cannot be resolved' do
+ expect(badge.errors).to be_empty
+ end
+ end
+ end
end
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/layouts/_head.html.haml_spec.rb b/spec/views/layouts/_head.html.haml_spec.rb
index 70cdc08b4b6..d7f24950e6f 100644
--- a/spec/views/layouts/_head.html.haml_spec.rb
+++ b/spec/views/layouts/_head.html.haml_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe 'layouts/_head' do
+ include StubConfiguration
+
before do
allow(view).to receive(:current_application_settings).and_return(Gitlab::CurrentSettings.current_application_settings)
end
@@ -87,6 +89,24 @@ describe 'layouts/_head' do
end
end
+ context 'when a Piwik config is set' do
+ let(:piwik_host) { 'piwik.example.com' }
+
+ before do
+ stub_config(extra: {
+ piwik_url: piwik_host,
+ piwik_site_id: 12345
+ })
+ end
+
+ it 'add a Piwik Javascript' do
+ render
+
+ expect(rendered).to match(/<script.*>.*var u="\/\/#{piwik_host}\/".*<\/script>/m)
+ expect(rendered).to match(%r(<noscript>.*<img src="//#{piwik_host}/piwik.php.*</noscript>))
+ end
+ end
+
def stub_helper_with_safe_string(method)
allow_any_instance_of(PageLayoutHelper).to receive(method)
.and_return(%q{foo" http-equiv="refresh}.html_safe)
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/update_external_pull_requests_worker_spec.rb b/spec/workers/update_external_pull_requests_worker_spec.rb
new file mode 100644
index 00000000000..f3956bb3514
--- /dev/null
+++ b/spec/workers/update_external_pull_requests_worker_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe UpdateExternalPullRequestsWorker do
+ describe '#perform' do
+ set(:project) { create(:project, import_source: 'tanuki/repository') }
+ set(:user) { create(:user) }
+ let(:worker) { described_class.new }
+
+ before do
+ create(:external_pull_request,
+ project: project,
+ source_repository: project.import_source,
+ target_repository: project.import_source,
+ source_branch: 'feature-1',
+ target_branch: 'master')
+
+ create(:external_pull_request,
+ project: project,
+ source_repository: project.import_source,
+ target_repository: project.import_source,
+ source_branch: 'feature-1',
+ target_branch: 'develop')
+ end
+
+ subject { worker.perform(project.id, user.id, ref) }
+
+ context 'when ref is a branch' do
+ let(:ref) { 'refs/heads/feature-1' }
+ let(:create_pipeline_service) { instance_double(ExternalPullRequests::CreatePipelineService) }
+
+ it 'runs CreatePipelineService for each pull request matching the source branch and repository' do
+ expect(ExternalPullRequests::CreatePipelineService)
+ .to receive(:new)
+ .and_return(create_pipeline_service)
+ .twice
+ expect(create_pipeline_service).to receive(:execute).twice
+
+ subject
+ end
+ end
+
+ context 'when ref is not a branch' do
+ let(:ref) { 'refs/tags/v1.2.3' }
+
+ it 'does nothing' do
+ expect(ExternalPullRequests::CreatePipelineService).not_to receive(:new)
+
+ subject
+ end
+ end
+ end
+end
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/tmp/.gitignore b/tmp/.gitignore
new file mode 100644
index 00000000000..a383b23fb78
--- /dev/null
+++ b/tmp/.gitignore
@@ -0,0 +1,5 @@
+*
+!*/
+!.gitignore
+!.gitkeep
+/tests/
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index 0c445aeac88..41dd9eb5256 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -67,7 +67,6 @@
@babel/template,7.1.2,MIT
@babel/traverse,7.1.0,MIT
@babel/types,7.1.2,MIT
-@gitlab/csslab,1.8.0,MIT
@gitlab/svgs,1.41.0,MIT
@gitlab/ui,1.15.0,MIT
@sindresorhus/is,0.7.0,MIT
@@ -153,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 4489b10815a..88f5b82b283 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -964,13 +964,6 @@
exec-sh "^0.3.2"
minimist "^1.2.0"
-"@gitlab/csslab@^1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@gitlab/csslab/-/csslab-1.9.0.tgz#22fca5b1a30cbd9ca46fc6f9485ecbaba4dc300c"
- integrity sha512-Zjayzokm7E2wgxUR/pxIMocdiBB5XHt2PEemdzD8qD+aQmMpMxSyIEMQk5Jq0Wgv+Rd5WXTolTw3kmb9l8ZeJg==
- dependencies:
- bootstrap "^4.1.3"
-
"@gitlab/eslint-config@^1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@gitlab/eslint-config/-/eslint-config-1.6.0.tgz#1fd247d6ab477d53d4c330e05f007e3afa303689"
@@ -998,15 +991,15 @@
dependencies:
vue-eslint-parser "^6.0.4"
-"@gitlab/svgs@^1.68.0":
- version "1.68.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.68.0.tgz#d631bd84ea7907592240d8417e82ba66d6a54c0c"
- integrity sha512-3JmIq0bHg4InjLooM+kQFPfg3d7B1Pye67pN9+12kZXIa0nRGuwKEq3WSbcS+ACdg5jcVPNPYqStItEO4teHdw==
+"@gitlab/svgs@^1.72.0":
+ version "1.72.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.72.0.tgz#78a29fd383a5a2b31ef91670068a6fea05ba234e"
+ integrity sha512-EVKRqrXsCY6tUiVAh+lpFMJAyNXZwfEqv5NeKH5ginhALMlOunRkY5rsDllyNvgZ0DWHJS1KEKJj2oVU1ouwAg==
-"@gitlab/ui@5.18.0":
- version "5.18.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.18.0.tgz#f7f93ad41673f5ae081cd2246dda5456d3c956a2"
- integrity sha512-umB7JCKVDZOajed0N563JBYSpiyK+VDL2eCdkinW+twWhMct8onoW4CLTpkgBi6Z9Y1Bq47aqnFtGf+1IKNIGw==
+"@gitlab/ui@5.21.1":
+ version "5.21.1"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.21.1.tgz#8215ab3eae4296845596d5b3a987d5460b030569"
+ integrity sha512-TjPVhex9sQGUVwebaJK5XuopDHWx4+Sh9N7yH5u8eXSFWa8vk11voR4qYVt7DZB7powAO/+iiXxYMLLNtXmC/g==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.2.1"
@@ -1021,6 +1014,11 @@
vue "^2.6.10"
vue-loader "^15.4.2"
+"@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"
resolved "https://registry.yarnpkg.com/@gitlab/vue-toasted/-/vue-toasted-1.2.1.tgz#f407b5aa710863e5b7f021f4a1f66160331ab263"
@@ -1632,7 +1630,7 @@ ansi-align@^2.0.0:
dependencies:
string-width "^2.0.0"
-ansi-colors@^3.0.0, ansi-colors@^3.2.4:
+ansi-colors@^3.0.0:
version "3.2.4"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==
@@ -1642,13 +1640,6 @@ ansi-escapes@^3.0.0:
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
-ansi-gray@^0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251"
- integrity sha1-KWLPVOyXksSFEKPetSRDaGHvclE=
- dependencies:
- ansi-wrap "0.1.0"
-
ansi-html@0.0.7, ansi-html@^0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
@@ -1681,11 +1672,6 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
-ansi-wrap@0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf"
- integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768=
-
anymatch@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
@@ -2275,7 +2261,7 @@ bootstrap-vue@2.0.0-rc.27:
portal-vue "^2.1.5"
vue-functional-data-merge "^3.1.0"
-bootstrap@4.3.1, bootstrap@^4.1.3, bootstrap@^4.3.1:
+bootstrap@4.3.1, bootstrap@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.3.1.tgz#280ca8f610504d99d7b6b4bfc4b68cec601704ac"
integrity sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==
@@ -2980,11 +2966,6 @@ color-name@1.1.3, color-name@^1.0.0:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
-color-support@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
- integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
-
colors@^1.1.0:
version "1.3.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d"
@@ -3012,6 +2993,13 @@ commander@~2.17.1:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
+commander@~2.9.0:
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
+ integrity sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=
+ dependencies:
+ graceful-readlink ">= 1.0.0"
+
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@@ -3909,6 +3897,11 @@ deep-extend@^0.6.0:
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
+deep-extend@~0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.1.tgz#b894a9dd90d3023fbf1c55a394fb858eb2066f1f"
+ integrity sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==
+
deep-is@~0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
@@ -4690,9 +4683,11 @@ eslint-scope@^4.0.0:
estraverse "^4.1.1"
eslint-utils@^1.3.1:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz#9a851ba89ee7c460346f97cf8939c7298827e512"
- integrity sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.2.tgz#166a5180ef6ab7eb462f162fd0e6f2463d7309ab"
+ integrity sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==
+ dependencies:
+ eslint-visitor-keys "^1.0.0"
eslint-visitor-keys@^1.0.0:
version "1.0.0"
@@ -4928,7 +4923,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"
@@ -5022,16 +5017,6 @@ extsprintf@1.3.0, extsprintf@^1.2.0:
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
-fancy-log@^1.3.3:
- version "1.3.3"
- resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.3.tgz#dbc19154f558690150a23953a0adbd035be45fc7"
- integrity sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==
- dependencies:
- ansi-gray "^0.1.1"
- color-support "^1.1.3"
- parse-node-version "^1.0.0"
- time-stamp "^1.0.0"
-
fast-deep-equal@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
@@ -5445,6 +5430,11 @@ get-stdin@^7.0.0:
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6"
integrity sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==
+get-stdin@~5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"
+ integrity sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=
+
get-stream@3.0.0, get-stream@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
@@ -5561,7 +5551,7 @@ glob-to-regexp@^0.4.0:
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
-"glob@5 - 7", glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.1:
+"glob@5 - 7", glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.1, glob@~7.1.2:
version "7.1.4"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
@@ -5753,6 +5743,11 @@ graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b"
integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==
+"graceful-readlink@>= 1.0.0":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
+ integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=
+
graphlibrary@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/graphlibrary/-/graphlibrary-2.2.0.tgz#017a14899775228dec4497a39babfdd6bf56eac6"
@@ -5777,16 +5772,6 @@ growly@^1.3.0:
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
-gulp-print@^5.0.2:
- version "5.0.2"
- resolved "https://registry.yarnpkg.com/gulp-print/-/gulp-print-5.0.2.tgz#8f379148218d2e168461baa74352e11d1bf7aa75"
- integrity sha512-iIpHMzC/b3gFvVXOfP9Jk94SWGIsDLVNUrxULRleQev+08ug07mh84b1AOlW6QDQdmInQiqDFqJN1UvhU2nXdg==
- dependencies:
- ansi-colors "^3.2.4"
- fancy-log "^1.3.3"
- map-stream "0.0.7"
- vinyl "^2.2.0"
-
gzip-size@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.0.0.tgz#a55ecd99222f4c48fd8c01c625ce3b349d0a0e80"
@@ -7783,11 +7768,21 @@ lodash.debounce@^4.0.8:
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
+lodash.differencewith@~4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.differencewith/-/lodash.differencewith-4.5.0.tgz#bafafbc918b55154e179176a00bb0aefaac854b7"
+ integrity sha1-uvr7yRi1UVTheRdqALsK76rIVLc=
+
lodash.escaperegexp@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=
+lodash.flatten@~4.4.0:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
+ integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
+
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
@@ -7960,11 +7955,6 @@ map-obj@^2.0.0:
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9"
integrity sha1-plzSkIepJZi4eRJXpSPgISIqwfk=
-map-stream@0.0.7:
- version "0.0.7"
- resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.0.7.tgz#8a1f07896d82b10926bd3744a2420009f88974a8"
- integrity sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=
-
map-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f"
@@ -7977,6 +7967,17 @@ markdown-escapes@^1.0.0:
resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.2.tgz#e639cbde7b99c841c0bacc8a07982873b46d2122"
integrity sha512-lbRZ2mE3Q9RtLjxZBZ9+IMl68DKIXaVAhwvwn9pmjnPLS0h/6kyBMgNhqi1xFJ/2yv6cSyv0jbiZavZv93JkkA==
+markdown-it@9.0.1:
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-9.0.1.tgz#aafe363c43718720b6575fd10625cde6e4ff2d47"
+ integrity sha512-XC9dMBHg28Xi7y5dPuLjM61upIGPJG8AiHNHYqIaXER2KNnn7eKnM5/sF0ImNnyoV224Ogn9b1Pck8VH4k0bxw==
+ dependencies:
+ argparse "^1.0.7"
+ entities "~1.1.1"
+ linkify-it "^2.0.0"
+ mdurl "^1.0.1"
+ uc.micro "^1.0.5"
+
markdown-it@^8.4.2:
version "8.4.2"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.2.tgz#386f98998dc15a37722aa7722084f4020bdd9b54"
@@ -7993,6 +7994,29 @@ markdown-table@^1.1.0:
resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.2.tgz#c78db948fa879903a41bce522e3b96f801c63786"
integrity sha512-NcWuJFHDA8V3wkDgR/j4+gZx+YQwstPgfQDV8ndUeWWzta3dnDTBxpVzqS9lkmJAuV5YX35lmyojl6HO5JXAgw==
+markdownlint-cli@0.18.0:
+ version "0.18.0"
+ resolved "https://registry.yarnpkg.com/markdownlint-cli/-/markdownlint-cli-0.18.0.tgz#bd1cee72739049d42dcea5f6db0c0f57c6eb8096"
+ integrity sha512-mQ2zvjMLoy0P2kb9Y03SqC24WPH4fTRN0/CyCorB122c4Chg9vWJKgUKBz3KR7swpzqmlI0SYq/7Blbqe4kb2g==
+ dependencies:
+ commander "~2.9.0"
+ deep-extend "~0.5.1"
+ get-stdin "~5.0.1"
+ glob "~7.1.2"
+ js-yaml "^3.13.1"
+ lodash.differencewith "~4.5.0"
+ lodash.flatten "~4.4.0"
+ markdownlint "~0.16.0"
+ minimatch "~3.0.4"
+ rc "~1.2.7"
+
+markdownlint@~0.16.0:
+ version "0.16.0"
+ resolved "https://registry.yarnpkg.com/markdownlint/-/markdownlint-0.16.0.tgz#69f73cc755a44231fbe5dc7c37a5909cedc0ac6e"
+ integrity sha512-Zo+iPezP3eM6lLhKepkUw+X98H44lipIdx4d6faaugfB0+7VuDB3R0hXmx7z9F1N3/ypn46oOFgAD9iF++Ie6A==
+ dependencies:
+ markdown-it "9.0.1"
+
marked@^0.3.12, marked@~0.3.6:
version "0.3.19"
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790"
@@ -8160,10 +8184,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.3:
- version "8.2.3"
- resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.2.3.tgz#609bad45bedc3ee1a935161c11c3c22689cfecd9"
- integrity sha512-G2p9BAAEeTtogPs4YXM8KyX+TsZULlgk0tGvmBPfBZ5j3YCPxgAxG9ZzleiYNItF7M1hGkE485BDLN8DbfR+/Q==
+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"
@@ -8171,7 +8195,6 @@ mermaid@^8.2.3:
dagre-layout "^0.8.8"
documentation "^12.0.1"
graphlibrary "^2.2.0"
- gulp-print "^5.0.2"
he "^1.2.0"
lodash "^4.17.11"
minify "^4.1.1"
@@ -8278,7 +8301,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
-minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2:
+minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2, minimatch@~3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
@@ -8349,7 +8372,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"
@@ -9140,11 +9163,6 @@ parse-json@^4.0.0:
error-ex "^1.3.1"
json-parse-better-errors "^1.0.1"
-parse-node-version@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b"
- integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==
-
parse-passwd@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
@@ -9981,7 +9999,7 @@ raw-loader@^1.0.0:
loader-utils "^1.1.0"
schema-utils "^1.0.0"
-rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
+rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@~1.2.7:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
@@ -11778,11 +11796,6 @@ thunky@^0.1.0:
resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e"
integrity sha1-vzAUaCTituZ7Dy16Ssi+smkIaE4=
-time-stamp@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3"
- integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=
-
timeago.js@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/timeago.js/-/timeago.js-3.0.2.tgz#32a67e7c0d887ea42ca588d3aae26f77de5e76cc"
@@ -12613,7 +12626,7 @@ vinyl-sourcemap@^1.1.0:
remove-bom-buffer "^3.0.0"
vinyl "^2.0.0"
-vinyl@^2.0.0, vinyl@^2.1.0, vinyl@^2.2.0:
+vinyl@^2.0.0, vinyl@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.0.tgz#d85b07da96e458d25b2ffe19fece9f2caa13ed86"
integrity sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==