summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrandon Labuschagne <blabuschagne@gitlab.com>2018-12-27 17:11:51 +0200
committerBrandon Labuschagne <blabuschagne@gitlab.com>2018-12-27 17:11:51 +0200
commit27fe9caed0ded80eb9a8474a9871f5abb77e1276 (patch)
treefcfc6e508d9b1df099858e05f11622d5e461873e
parente87e9a6d9fe03460269c215c764931295c082879 (diff)
parent77909a88460bbc864a5f95f3fa66053eb6cab5a8 (diff)
downloadgitlab-ce-27fe9caed0ded80eb9a8474a9871f5abb77e1276.tar.gz
Merge branch 'master' into 25341-add-what-s-new-menu-item-in-top-navigation
-rw-r--r--.eslintrc.yml1
-rw-r--r--.gitlab-ci.yml52
-rw-r--r--.gitlab/CODEOWNERS.disabled8
-rw-r--r--.gitlab/issue_templates/Feature proposal.md12
-rw-r--r--.rubocop.yml8
-rw-r--r--.rubocop_todo.yml17
-rw-r--r--CHANGELOG.md312
-rw-r--r--Dangerfile1
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile52
-rw-r--r--Gemfile.lock34
-rw-r--r--Gemfile.rails47
-rw-r--r--Gemfile.rails4.lock1161
-rw-r--r--PROCESS.md90
-rw-r--r--VERSION2
-rw-r--r--app/assets/images/none-scheme-preview.pngbin0 -> 5971 bytes
-rw-r--r--app/assets/javascripts/api.js44
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_gfm.js2
-rw-r--r--app/assets/javascripts/behaviors/requires_input.js7
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js1
-rw-r--r--app/assets/javascripts/blob_edit/blob_bundle.js5
-rw-r--r--app/assets/javascripts/boards/components/issue_due_date.vue20
-rw-r--r--app/assets/javascripts/boards/components/new_list_dropdown.js4
-rw-r--r--app/assets/javascripts/boards/components/project_select.vue15
-rw-r--r--app/assets/javascripts/boards/models/issue.js6
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js3
-rw-r--r--app/assets/javascripts/ci_variable_list/ci_variable_list.js4
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js10
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue87
-rw-r--r--app/assets/javascripts/clusters/constants.js1
-rw-r--r--app/assets/javascripts/clusters/stores/clusters_store.js6
-rw-r--r--app/assets/javascripts/diffs/components/app.vue24
-rw-r--r--app/assets/javascripts/diffs/components/diff_content.vue16
-rw-r--r--app/assets/javascripts/diffs/components/diff_discussions.vue12
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue23
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_note_form.vue1
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_comment_row.vue12
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_view.vue8
-rw-r--r--app/assets/javascripts/diffs/components/no_changes.vue53
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue9
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_view.vue10
-rw-r--r--app/assets/javascripts/diffs/index.js4
-rw-r--r--app/assets/javascripts/diffs/store/actions.js49
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js47
-rw-r--r--app/assets/javascripts/diffs/store/utils.js20
-rw-r--r--app/assets/javascripts/dismissable_callout.js27
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue11
-rw-r--r--app/assets/javascripts/environments/components/environment_terminal_button.vue6
-rw-r--r--app/assets/javascripts/environments/components/environments_table.vue2
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_utils.js61
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js19
-rw-r--r--app/assets/javascripts/ide/constants.js1
-rw-r--r--app/assets/javascripts/ide/index.js10
-rw-r--r--app/assets/javascripts/image_diff/helpers/badge_helper.js2
-rw-r--r--app/assets/javascripts/issuable_suggestions/components/app.vue2
-rw-r--r--app/assets/javascripts/jobs/components/artifacts_block.vue32
-rw-r--r--app/assets/javascripts/jobs/components/commit_block.vue21
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue35
-rw-r--r--app/assets/javascripts/jobs/components/sidebar_detail_row.vue3
-rw-r--r--app/assets/javascripts/jobs/components/stages_dropdown.vue6
-rw-r--r--app/assets/javascripts/jobs/components/trigger_block.vue69
-rw-r--r--app/assets/javascripts/labels_select.js33
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js6
-rw-r--r--app/assets/javascripts/lib/utils/dom_utils.js5
-rw-r--r--app/assets/javascripts/lib/utils/file_upload.js13
-rw-r--r--app/assets/javascripts/lib/utils/http_status.js2
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js42
-rw-r--r--app/assets/javascripts/lib/utils/users_cache.js28
-rw-r--r--app/assets/javascripts/main.js2
-rw-r--r--app/assets/javascripts/milestone_select.js8
-rw-r--r--app/assets/javascripts/monitoring/components/charts/area.vue97
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue10
-rw-r--r--app/assets/javascripts/monitoring/utils/multiple_time_series.js32
-rw-r--r--app/assets/javascripts/mr_notes/index.js2
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue16
-rw-r--r--app/assets/javascripts/notes/components/note_body.vue38
-rw-r--r--app/assets/javascripts/notes/components/note_edited_text.vue5
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue35
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue9
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue92
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue33
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue14
-rw-r--r--app/assets/javascripts/notes/mixins/discussion_navigation.js53
-rw-r--r--app/assets/javascripts/notes/services/notes_service.js4
-rw-r--r--app/assets/javascripts/notes/stores/actions.js28
-rw-r--r--app/assets/javascripts/notes/stores/getters.js13
-rw-r--r--app/assets/javascripts/notes/stores/modules/index.js1
-rw-r--r--app/assets/javascripts/notes/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js14
-rw-r--r--app/assets/javascripts/notifications_dropdown.js4
-rw-r--r--app/assets/javascripts/pages/dashboard/projects/index.js6
-rw-r--r--app/assets/javascripts/pages/groups/clusters/index/index.js6
-rw-r--r--app/assets/javascripts/pages/groups/index.js10
-rw-r--r--app/assets/javascripts/pages/profiles/show/emoji_menu.js1
-rw-r--r--app/assets/javascripts/pages/projects/clusters/index/index.js6
-rw-r--r--app/assets/javascripts/pages/projects/edit/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/index.js6
-rw-r--r--app/assets/javascripts/pages/projects/issues/form.js2
-rw-r--r--app/assets/javascripts/pages/projects/project.js9
-rw-r--r--app/assets/javascripts/pages/projects/releases/index/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/serverless/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/settings/repository/form.js2
-rw-r--r--app/assets/javascripts/pages/projects/shared/project_avatar.js16
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js8
-rw-r--r--app/assets/javascripts/persistent_user_callout.js34
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue10
-rw-r--r--app/assets/javascripts/registry/components/app.vue12
-rw-r--r--app/assets/javascripts/registry/components/collapsible_container.vue61
-rw-r--r--app/assets/javascripts/registry/components/table_registry.vue56
-rw-r--r--app/assets/javascripts/registry/stores/actions.js36
-rw-r--r--app/assets/javascripts/registry/stores/index.js28
-rw-r--r--app/assets/javascripts/registry/stores/mutations.js1
-rw-r--r--app/assets/javascripts/registry/stores/state.js26
-rw-r--r--app/assets/javascripts/releases/components/app.vue82
-rw-r--r--app/assets/javascripts/releases/components/release_block.vue129
-rw-r--r--app/assets/javascripts/releases/index.js24
-rw-r--r--app/assets/javascripts/releases/store/actions.js37
-rw-r--r--app/assets/javascripts/releases/store/index.js14
-rw-r--r--app/assets/javascripts/releases/store/mutation_types.js3
-rw-r--r--app/assets/javascripts/releases/store/mutations.js37
-rw-r--r--app/assets/javascripts/releases/store/state.js5
-rw-r--r--app/assets/javascripts/right_sidebar.js15
-rw-r--r--app/assets/javascripts/serverless/components/empty_state.vue40
-rw-r--r--app/assets/javascripts/serverless/components/function_row.vue40
-rw-r--r--app/assets/javascripts/serverless/components/functions.vue123
-rw-r--r--app/assets/javascripts/serverless/event_hub.js3
-rw-r--r--app/assets/javascripts/serverless/serverless_bundle.js106
-rw-r--r--app/assets/javascripts/serverless/services/get_functions_service.js11
-rw-r--r--app/assets/javascripts/serverless/stores/serverless_store.js24
-rw-r--r--app/assets/javascripts/sidebar/stores/sidebar_store.js2
-rw-r--r--app/assets/javascripts/star.js15
-rw-r--r--app/assets/javascripts/terminal/index.js2
-rw-r--r--app/assets/javascripts/terminal/terminal.js57
-rw-r--r--app/assets/javascripts/user_popovers.js107
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue8
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js8
-rw-r--r--app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue23
-rw-r--r--app/assets/javascripts/vue_shared/components/diff_viewer/viewers/empty_file.vue3
-rw-r--r--app/assets/javascripts/vue_shared/components/diff_viewer/viewers/mode_changed.vue30
-rw-r--r--app/assets/javascripts/vue_shared/components/diff_viewer/viewers/renamed.vue3
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue94
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue90
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue100
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue23
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue74
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue60
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestions.vue136
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue12
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue3
-rw-r--r--app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue91
-rw-r--r--app/assets/stylesheets/application.scss6
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss6
-rw-r--r--app/assets/stylesheets/components/popover.scss9
-rw-r--r--app/assets/stylesheets/csslab.scss1
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/avatar.scss4
-rw-r--r--app/assets/stylesheets/framework/buttons.scss10
-rw-r--r--app/assets/stylesheets/framework/common.scss15
-rw-r--r--app/assets/stylesheets/framework/contextual_sidebar.scss2
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss38
-rw-r--r--app/assets/stylesheets/framework/flex_grid.scss52
-rw-r--r--app/assets/stylesheets/framework/forms.scss7
-rw-r--r--app/assets/stylesheets/framework/gitlab_theme.scss10
-rw-r--r--app/assets/stylesheets/framework/header.scss31
-rw-r--r--app/assets/stylesheets/framework/highlight.scss7
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss23
-rw-r--r--app/assets/stylesheets/framework/mobile.scss9
-rw-r--r--app/assets/stylesheets/framework/modal.scss4
-rw-r--r--app/assets/stylesheets/framework/selects.scss5
-rw-r--r--app/assets/stylesheets/framework/typography.scss4
-rw-r--r--app/assets/stylesheets/framework/variables.scss17
-rw-r--r--app/assets/stylesheets/framework/variables_overrides.scss14
-rw-r--r--app/assets/stylesheets/highlight/none.scss242
-rw-r--r--app/assets/stylesheets/page_bundles/_ide_mixins.scss18
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss17
-rw-r--r--app/assets/stylesheets/pages/boards.scss2
-rw-r--r--app/assets/stylesheets/pages/builds.scss23
-rw-r--r--app/assets/stylesheets/pages/diff.scss1
-rw-r--r--app/assets/stylesheets/pages/issuable.scss4
-rw-r--r--app/assets/stylesheets/pages/issues.scss10
-rw-r--r--app/assets/stylesheets/pages/login.scss5
-rw-r--r--app/assets/stylesheets/pages/merge_conflicts.scss24
-rw-r--r--app/assets/stylesheets/pages/note_form.scss14
-rw-r--r--app/assets/stylesheets/pages/notes.scss57
-rw-r--r--app/assets/stylesheets/pages/profile.scss18
-rw-r--r--app/assets/stylesheets/pages/projects.scss389
-rw-r--r--app/assets/stylesheets/pages/search.scss2
-rw-r--r--app/assets/stylesheets/pages/wiki.scss2
-rw-r--r--app/controllers/admin/health_check_controller.rb8
-rw-r--r--app/controllers/application_controller.rb15
-rw-r--r--app/controllers/clusters/applications_controller.rb2
-rw-r--r--app/controllers/clusters/clusters_controller.rb28
-rw-r--r--app/controllers/concerns/group_tree.rb4
-rw-r--r--app/controllers/concerns/invalid_utf8_error_handler.rb27
-rw-r--r--app/controllers/concerns/issuable_actions.rb10
-rw-r--r--app/controllers/concerns/issuable_collections.rb10
-rw-r--r--app/controllers/concerns/lfs_request.rb6
-rw-r--r--app/controllers/concerns/preview_markdown.rb10
-rw-r--r--app/controllers/concerns/renders_commits.rb6
-rw-r--r--app/controllers/concerns/service_params.rb6
-rw-r--r--app/controllers/concerns/snippets_actions.rb2
-rw-r--r--app/controllers/concerns/uploads_actions.rb1
-rw-r--r--app/controllers/dashboard/projects_controller.rb2
-rw-r--r--app/controllers/explore/projects_controller.rb2
-rw-r--r--app/controllers/graphql_controller.rb2
-rw-r--r--app/controllers/import/github_controller.rb2
-rw-r--r--app/controllers/notification_settings_controller.rb10
-rw-r--r--app/controllers/profiles/keys_controller.rb1
-rw-r--r--app/controllers/projects/commits_controller.rb5
-rw-r--r--app/controllers/projects/compare_controller.rb6
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb2
-rw-r--r--app/controllers/projects/environments_controller.rb4
-rw-r--r--app/controllers/projects/imports_controller.rb10
-rw-r--r--app/controllers/projects/issues_controller.rb3
-rw-r--r--app/controllers/projects/jobs_controller.rb20
-rw-r--r--app/controllers/projects/merge_requests/conflicts_controller.rb8
-rw-r--r--app/controllers/projects/merge_requests_controller.rb24
-rw-r--r--app/controllers/projects/protected_branches_controller.rb4
-rw-r--r--app/controllers/projects/protected_refs_controller.rb4
-rw-r--r--app/controllers/projects/protected_tags_controller.rb4
-rw-r--r--app/controllers/projects/releases_controller.rb33
-rw-r--r--app/controllers/projects/serverless/functions_controller.rb37
-rw-r--r--app/controllers/projects/settings/repository_controller.rb14
-rw-r--r--app/controllers/projects/tags/releases_controller.rb42
-rw-r--r--app/controllers/projects/tags_controller.rb4
-rw-r--r--app/controllers/users_controller.rb4
-rw-r--r--app/finders/cluster_ancestors_finder.rb35
-rw-r--r--app/finders/concerns/finder_with_cross_project_access.rb30
-rw-r--r--app/finders/events_finder.rb45
-rw-r--r--app/finders/group_descendants_finder.rb6
-rw-r--r--app/finders/groups_finder.rb4
-rw-r--r--app/finders/issuable_finder.rb42
-rw-r--r--app/finders/projects/serverless/functions_finder.rb31
-rw-r--r--app/finders/remote_mirror_finder.rb15
-rw-r--r--app/helpers/appearances_helper.rb11
-rw-r--r--app/helpers/application_helper.rb7
-rw-r--r--app/helpers/application_settings_helper.rb15
-rw-r--r--app/helpers/blob_helper.rb26
-rw-r--r--app/helpers/button_helper.rb5
-rw-r--r--app/helpers/ci_variables_helper.rb15
-rw-r--r--app/helpers/dropdowns_helper.rb9
-rw-r--r--app/helpers/emails_helper.rb27
-rw-r--r--app/helpers/events_helper.rb4
-rw-r--r--app/helpers/groups_helper.rb2
-rw-r--r--app/helpers/icons_helper.rb2
-rw-r--r--app/helpers/ide_helper.rb16
-rw-r--r--app/helpers/issuables_helper.rb111
-rw-r--r--app/helpers/milestones_helper.rb8
-rw-r--r--app/helpers/nav_helper.rb4
-rw-r--r--app/helpers/projects_helper.rb38
-rw-r--r--app/helpers/selects_helper.rb5
-rw-r--r--app/helpers/sentry_helper.rb11
-rw-r--r--app/helpers/sorting_helper.rb48
-rw-r--r--app/helpers/users_helper.rb2
-rw-r--r--app/helpers/version_check_helper.rb3
-rw-r--r--app/helpers/visibility_level_helper.rb2
-rw-r--r--app/helpers/workhorse_helper.rb12
-rw-r--r--app/mailers/emails/issues.rb4
-rw-r--r--app/mailers/emails/merge_requests.rb4
-rw-r--r--app/mailers/emails/projects.rb15
-rw-r--r--app/mailers/emails/remote_mirrors.rb12
-rw-r--r--app/mailers/notify.rb10
-rw-r--r--app/mailers/previews/notify_preview.rb8
-rw-r--r--app/models/appearance.rb28
-rw-r--r--app/models/application_setting.rb7
-rw-r--r--app/models/broadcast_message.rb27
-rw-r--r--app/models/ci/bridge.rb34
-rw-r--r--app/models/ci/build.rb5
-rw-r--r--app/models/ci/pipeline.rb37
-rw-r--r--app/models/ci/pipeline_enums.rb10
-rw-r--r--app/models/ci/runner.rb5
-rw-r--r--app/models/clusters/applications/cert_manager.rb4
-rw-r--r--app/models/clusters/applications/knative.rb38
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/models/clusters/cluster.rb10
-rw-r--r--app/models/clusters/platforms/kubernetes.rb4
-rw-r--r--app/models/commit.rb4
-rw-r--r--app/models/concerns/avatarable.rb13
-rw-r--r--app/models/concerns/awardable.rb13
-rw-r--r--app/models/concerns/cacheable_attributes.rb7
-rw-r--r--app/models/concerns/descendant.rb11
-rw-r--r--app/models/concerns/discussion_on_diff.rb26
-rw-r--r--app/models/concerns/enum_with_nil.rb3
-rw-r--r--app/models/concerns/fast_destroy_all.rb5
-rw-r--r--app/models/concerns/issuable.rb28
-rw-r--r--app/models/concerns/noteable.rb8
-rw-r--r--app/models/concerns/redis_cacheable.rb6
-rw-r--r--app/models/concerns/storage/legacy_namespace.rb2
-rw-r--r--app/models/concerns/with_uploads.rb31
-rw-r--r--app/models/dashboard_group_milestone.rb1
-rw-r--r--app/models/diff_note.rb13
-rw-r--r--app/models/environment.rb5
-rw-r--r--app/models/event.rb15
-rw-r--r--app/models/group.rb5
-rw-r--r--app/models/member.rb19
-rw-r--r--app/models/members/project_member.rb2
-rw-r--r--app/models/merge_request.rb118
-rw-r--r--app/models/milestone.rb2
-rw-r--r--app/models/namespace.rb18
-rw-r--r--app/models/note.rb14
-rw-r--r--app/models/note_diff_file.rb15
-rw-r--r--app/models/pool_repository.rb92
-rw-r--r--app/models/project.rb80
-rw-r--r--app/models/project_import_data.rb8
-rw-r--r--app/models/prometheus_metric.rb7
-rw-r--r--app/models/protected_branch.rb1
-rw-r--r--app/models/protected_tag.rb1
-rw-r--r--app/models/release.rb1
-rw-r--r--app/models/remote_mirror.rb8
-rw-r--r--app/models/repository.rb1
-rw-r--r--app/models/service.rb6
-rw-r--r--app/models/shard.rb4
-rw-r--r--app/models/suggestion.rb55
-rw-r--r--app/models/upload.rb19
-rw-r--r--app/models/uploads/base.rb19
-rw-r--r--app/models/uploads/fog.rb43
-rw-r--r--app/models/uploads/local.rb56
-rw-r--r--app/models/user.rb11
-rw-r--r--app/policies/group_policy.rb3
-rw-r--r--app/policies/suggestion_policy.rb11
-rw-r--r--app/presenters/clusters/cluster_presenter.rb50
-rw-r--r--app/presenters/group_clusterable_presenter.rb2
-rw-r--r--app/presenters/member_presenter.rb8
-rw-r--r--app/presenters/project_presenter.rb140
-rw-r--r--app/serializers/cluster_application_entity.rb1
-rw-r--r--app/serializers/diff_file_base_entity.rb101
-rw-r--r--app/serializers/diff_file_entity.rb98
-rw-r--r--app/serializers/diff_line_entity.rb2
-rw-r--r--app/serializers/discussion_diff_file_entity.rb4
-rw-r--r--app/serializers/discussion_entity.rb15
-rw-r--r--app/serializers/entity_date_helper.rb16
-rw-r--r--app/serializers/environment_entity.rb16
-rw-r--r--app/serializers/issuable_sidebar_basic_entity.rb106
-rw-r--r--app/serializers/issuable_sidebar_extras_entity.rb (renamed from app/serializers/issuable_sidebar_entity.rb)4
-rw-r--r--app/serializers/issuable_sidebar_todo_entity.rb11
-rw-r--r--app/serializers/issue_board_entity.rb4
-rw-r--r--app/serializers/issue_serializer.rb6
-rw-r--r--app/serializers/issue_sidebar_basic_entity.rb6
-rw-r--r--app/serializers/issue_sidebar_extras_entity.rb (renamed from app/serializers/issue_sidebar_entity.rb)2
-rw-r--r--app/serializers/merge_request_basic_entity.rb2
-rw-r--r--app/serializers/merge_request_basic_serializer.rb5
-rw-r--r--app/serializers/merge_request_serializer.rb9
-rw-r--r--app/serializers/merge_request_sidebar_basic_entity.rb11
-rw-r--r--app/serializers/merge_request_widget_entity.rb2
-rw-r--r--app/serializers/note_entity.rb1
-rw-r--r--app/serializers/pipeline_entity.rb1
-rw-r--r--app/serializers/projects/serverless/service_entity.rb33
-rw-r--r--app/serializers/projects/serverless/service_serializer.rb9
-rw-r--r--app/serializers/suggestion_entity.rb17
-rw-r--r--app/serializers/trigger_variable_entity.rb3
-rw-r--r--app/services/ci/compare_reports_base_service.rb47
-rw-r--r--app/services/ci/compare_test_reports_service.rb36
-rw-r--r--app/services/ci/register_job_service.rb2
-rw-r--r--app/services/clusters/applications/create_service.rb2
-rw-r--r--app/services/clusters/build_service.rb21
-rw-r--r--app/services/clusters/gcp/fetch_operation_service.rb13
-rw-r--r--app/services/clusters/gcp/finalize_creation_service.rb24
-rw-r--r--app/services/create_release_service.rb9
-rw-r--r--app/services/deploy_keys/create_service.rb2
-rw-r--r--app/services/groups/nested_create_service.rb2
-rw-r--r--app/services/groups/transfer_service.rb2
-rw-r--r--app/services/groups/update_service.rb2
-rw-r--r--app/services/issuable/common_system_notes_service.rb21
-rw-r--r--app/services/issuable_base_service.rb6
-rw-r--r--app/services/labels/promote_service.rb8
-rw-r--r--app/services/merge_requests/refresh_service.rb22
-rw-r--r--app/services/merge_requests/update_service.rb9
-rw-r--r--app/services/notes/create_service.rb1
-rw-r--r--app/services/notes/update_service.rb11
-rw-r--r--app/services/notification_recipient_service.rb23
-rw-r--r--app/services/notification_service.rb40
-rw-r--r--app/services/preview_markdown_service.rb8
-rw-r--r--app/services/projects/after_rename_service.rb1
-rw-r--r--app/services/projects/cleanup_service.rb52
-rw-r--r--app/services/projects/destroy_service.rb2
-rw-r--r--app/services/projects/fork_service.rb2
-rw-r--r--app/services/projects/lfs_pointers/lfs_download_service.rb3
-rw-r--r--app/services/projects/transfer_service.rb7
-rw-r--r--app/services/suggestions/apply_service.rb54
-rw-r--r--app/services/suggestions/create_service.rb56
-rw-r--r--app/services/tags/create_service.rb2
-rw-r--r--app/services/users/refresh_authorized_projects_service.rb2
-rw-r--r--app/services/users/update_service.rb10
-rw-r--r--app/validators/url_validator.rb1
-rw-r--r--app/views/admin/appearances/_form.html.haml6
-rw-r--r--app/views/admin/application_settings/_ci_cd.html.haml7
-rw-r--r--app/views/admin/dashboard/index.html.haml2
-rw-r--r--app/views/admin/groups/show.html.haml2
-rw-r--r--app/views/admin/hooks/edit.html.haml2
-rw-r--r--app/views/admin/hooks/index.html.haml2
-rw-r--r--app/views/admin/runners/_sort_dropdown.html.haml4
-rw-r--r--app/views/ci/variables/_content.html.haml2
-rw-r--r--app/views/ci/variables/_header.html.haml11
-rw-r--r--app/views/ci/variables/_index.html.haml5
-rw-r--r--app/views/ci/variables/_variable_row.html.haml6
-rw-r--r--app/views/clusters/clusters/_buttons.html.haml7
-rw-r--r--app/views/clusters/clusters/_cluster.html.haml4
-rw-r--r--app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml4
-rw-r--r--app/views/clusters/clusters/index.html.haml7
-rw-r--r--app/views/dashboard/activity.html.haml3
-rw-r--r--app/views/dashboard/groups/index.html.haml2
-rw-r--r--app/views/dashboard/issues.html.haml2
-rw-r--r--app/views/dashboard/merge_requests.html.haml2
-rw-r--r--app/views/dashboard/projects/index.html.haml2
-rw-r--r--app/views/dashboard/projects/starred.html.haml2
-rw-r--r--app/views/dashboard/todos/index.html.haml2
-rw-r--r--app/views/errors/access_denied.html.haml2
-rw-r--r--app/views/explore/groups/index.html.haml2
-rw-r--r--app/views/explore/projects/index.html.haml2
-rw-r--r--app/views/explore/projects/starred.html.haml2
-rw-r--r--app/views/explore/projects/trending.html.haml2
-rw-r--r--app/views/groups/_home_panel.html.haml2
-rw-r--r--app/views/groups/group_members/_new_group_member.html.haml2
-rw-r--r--app/views/groups/settings/ci_cd/show.html.haml8
-rw-r--r--app/views/ide/_show.html.haml10
-rw-r--r--app/views/ide/index.html.haml18
-rw-r--r--app/views/issues/_issues_calendar.ics.ruby2
-rw-r--r--app/views/layouts/_head.html.haml1
-rw-r--r--app/views/layouts/_search.html.haml2
-rw-r--r--app/views/layouts/devise.html.haml4
-rw-r--r--app/views/layouts/header/_default.html.haml4
-rw-r--r--app/views/layouts/header/_new_dropdown.haml2
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml39
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml26
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml17
-rw-r--r--app/views/notify/_note_email.html.haml33
-rw-r--r--app/views/notify/_note_email.text.erb15
-rw-r--r--app/views/notify/changed_milestone_email.html.haml (renamed from app/views/notify/changed_milestone_issue_email.html.haml)2
-rw-r--r--app/views/notify/changed_milestone_email.text.erb1
-rw-r--r--app/views/notify/changed_milestone_issue_email.text.erb1
-rw-r--r--app/views/notify/changed_milestone_merge_request_email.html.haml3
-rw-r--r--app/views/notify/changed_milestone_merge_request_email.text.erb1
-rw-r--r--app/views/notify/remote_mirror_update_failed_email.html.haml46
-rw-r--r--app/views/notify/remote_mirror_update_failed_email.text.erb7
-rw-r--r--app/views/notify/repository_cleanup_failure_email.text.erb3
-rw-r--r--app/views/notify/repository_cleanup_success_email.text.erb3
-rw-r--r--app/views/profiles/show.html.haml38
-rw-r--r--app/views/profiles/two_factor_auths/show.html.haml3
-rw-r--r--app/views/projects/_files.html.haml6
-rw-r--r--app/views/projects/_home_panel.html.haml130
-rw-r--r--app/views/projects/_stat_anchor_list.html.haml4
-rw-r--r--app/views/projects/blob/_blob.html.haml2
-rw-r--r--app/views/projects/blob/preview.html.haml2
-rw-r--r--app/views/projects/blob/viewers/_markup.html.haml2
-rw-r--r--app/views/projects/buttons/_clone.html.haml28
-rw-r--r--app/views/projects/buttons/_download.html.haml2
-rw-r--r--app/views/projects/buttons/_fork.html.haml6
-rw-r--r--app/views/projects/buttons/_notifications.html.haml27
-rw-r--r--app/views/projects/buttons/_star.html.haml12
-rw-r--r--app/views/projects/cleanup/_show.html.haml29
-rw-r--r--app/views/projects/commit/_commit_box.html.haml6
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/projects/empty.html.haml122
-rw-r--r--app/views/projects/environments/index.html.haml1
-rw-r--r--app/views/projects/issues/show.html.haml2
-rw-r--r--app/views/projects/labels/index.html.haml2
-rw-r--r--app/views/projects/merge_requests/conflicts.html.haml36
-rw-r--r--app/views/projects/merge_requests/conflicts/show.html.haml2
-rw-r--r--app/views/projects/merge_requests/show.html.haml9
-rw-r--r--app/views/projects/mirrors/_authentication_method.html.haml4
-rw-r--r--app/views/projects/mirrors/_mirror_repos.html.haml12
-rw-r--r--app/views/projects/mirrors/_mirror_repos_form.html.haml2
-rw-r--r--app/views/projects/pipelines/_info.html.haml32
-rw-r--r--app/views/projects/project_members/_new_project_group.html.haml2
-rw-r--r--app/views/projects/project_members/_new_project_member.html.haml2
-rw-r--r--app/views/projects/releases/index.html.haml5
-rw-r--r--app/views/projects/serverless/functions/index.html.haml15
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml8
-rw-r--r--app/views/projects/settings/repository/show.html.haml1
-rw-r--r--app/views/projects/show.html.haml16
-rw-r--r--app/views/projects/snippets/show.html.haml2
-rw-r--r--app/views/projects/tags/releases/edit.html.haml (renamed from app/views/projects/releases/edit.html.haml)0
-rw-r--r--app/views/projects/tree/_tree_header.html.haml8
-rw-r--r--app/views/projects/wikis/show.html.haml2
-rw-r--r--app/views/search/results/_blob.html.haml6
-rw-r--r--app/views/search/results/_wiki_blob.html.haml4
-rw-r--r--app/views/shared/_milestones_sort_dropdown.html.haml12
-rw-r--r--app/views/shared/_mobile_clone_panel.html.haml20
-rw-r--r--app/views/shared/_remote_mirror_update_button.html.haml2
-rw-r--r--app/views/shared/_sort_dropdown.html.haml16
-rw-r--r--app/views/shared/issuable/_filter.html.haml32
-rw-r--r--app/views/shared/issuable/_form.html.haml2
-rw-r--r--app/views/shared/issuable/_nav.html.haml8
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml5
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml167
-rw-r--r--app/views/shared/issuable/_sidebar_assignees.html.haml51
-rw-r--r--app/views/shared/issuable/_sidebar_todo.html.haml18
-rw-r--r--app/views/shared/issuable/_sort_dropdown.html.haml21
-rw-r--r--app/views/shared/issuable/form/_metadata.html.haml3
-rw-r--r--app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml2
-rw-r--r--app/views/shared/issuable/nav_links/_all.html.haml2
-rw-r--r--app/views/shared/labels/_sort_dropdown.html.haml2
-rw-r--r--app/views/shared/members/_access_request_links.html.haml17
-rw-r--r--app/views/shared/members/_member.html.haml2
-rw-r--r--app/views/shared/milestones/_sidebar.html.haml2
-rw-r--r--app/views/shared/notes/_note.html.haml2
-rw-r--r--app/views/shared/notifications/_button.html.haml6
-rw-r--r--app/views/shared/projects/_list.html.haml13
-rw-r--r--app/views/shared/projects/_project.html.haml141
-rw-r--r--app/views/users/_overview.html.haml30
-rw-r--r--app/views/users/show.html.haml12
-rw-r--r--app/workers/all_queues.yml11
-rw-r--r--app/workers/cluster_configure_worker.rb (renamed from app/workers/cluster_platform_configure_worker.rb)2
-rw-r--r--app/workers/cluster_provision_worker.rb2
-rw-r--r--app/workers/concerns/object_pool_queue.rb12
-rw-r--r--app/workers/delete_stored_files_worker.rb22
-rw-r--r--app/workers/git_garbage_collect_worker.rb2
-rw-r--r--app/workers/new_note_worker.rb9
-rw-r--r--app/workers/object_pool/create_worker.rb44
-rw-r--r--app/workers/object_pool/destroy_worker.rb16
-rw-r--r--app/workers/object_pool/join_worker.rb20
-rw-r--r--app/workers/object_pool/schedule_join_worker.rb19
-rw-r--r--app/workers/remote_mirror_notification_worker.rb15
-rw-r--r--app/workers/remove_old_web_hook_logs_worker.rb14
-rw-r--r--app/workers/repository_cleanup_worker.rb39
-rw-r--r--app/workers/repository_update_remote_mirror_worker.rb3
-rw-r--r--app/workers/stuck_merge_jobs_worker.rb6
-rwxr-xr-xbin/rails10
-rwxr-xr-xbin/rake10
-rwxr-xr-xbin/rspec5
-rwxr-xr-xbin/secpick23
-rwxr-xr-xbin/setup42
-rw-r--r--changelogs/unreleased/1979-redesign-mr-widget-approvals-ce.yml5
-rw-r--r--changelogs/unreleased/23367-clarify-docs-allow-failure.yml5
-rw-r--r--changelogs/unreleased/28682-can-merge-branch-before-build-is-started.yml5
-rw-r--r--changelogs/unreleased/33705-merge-request-rebase-api.yml5
-rw-r--r--changelogs/unreleased/34758-deployment-cluster.yml5
-rw-r--r--changelogs/unreleased/34758-group-cluster-controller.yml5
-rw-r--r--changelogs/unreleased/34758-list-ancestor-clusters.yml5
-rw-r--r--changelogs/unreleased/38495-calendar-activities-in-timezone.yml5
-rw-r--r--changelogs/unreleased/40085-add-a-create_merge_request-quick-action.yml5
-rw-r--r--changelogs/unreleased/40260-reduce-gitaly-calls-project-pipeline-status.yml5
-rw-r--r--changelogs/unreleased/40270-remove-gitlab-upgrader-completely.yml5
-rw-r--r--changelogs/unreleased/40385-prohibit_impersonation.yml5
-rw-r--r--changelogs/unreleased/41766-vue-component.yml5
-rw-r--r--changelogs/unreleased/41766-vuex-store.yml5
-rw-r--r--changelogs/unreleased/41875-allow-pipelines-to-be-deleted-by-project-owners.yml5
-rw-r--r--changelogs/unreleased/42125-extend-override-check-to-also-check-arity.yml5
-rw-r--r--changelogs/unreleased/44984-use-serializer-for-issuable-sidebar.yml5
-rw-r--r--changelogs/unreleased/46544-webide-ctrl-enter-commit-shortcut.yml5
-rw-r--r--changelogs/unreleased/46950-systemcheck-ruby-version.yml5
-rw-r--r--changelogs/unreleased/47052-merge-button-does-not-appear-after-rebase-ing.yml5
-rw-r--r--changelogs/unreleased/48475-gitlab-pages-settings-regressions.yml5
-rw-r--r--changelogs/unreleased/48496-merge-request-refactor-does-not-highlight-selected-line.yml5
-rw-r--r--changelogs/unreleased/49479-hide-unmerged-env-perf-stats.yml5
-rw-r--r--changelogs/unreleased/49565-ssh-push-mirroring.yml5
-rw-r--r--changelogs/unreleased/49726-upgrade-helm-to-2-11.yml5
-rw-r--r--changelogs/unreleased/50264-add-border-around-the-repository-file-tree.yml5
-rw-r--r--changelogs/unreleased/50341-cleanup-useless-project-import-attributes.yml6
-rw-r--r--changelogs/unreleased/50626-searching-users-by-the-admin-panel-wipes-query-when-using-sort.yml5
-rw-r--r--changelogs/unreleased/50839-webide-mr-dropdown-filter.yml5
-rw-r--r--changelogs/unreleased/51029-status-emoji-currently-replaces-avatar-on-mobile.yml5
-rw-r--r--changelogs/unreleased/51061-readme-url-n-1-rpc-call-resolved.yml5
-rw-r--r--changelogs/unreleased/51083-fix-move-api.yml5
-rw-r--r--changelogs/unreleased/51259-ci-cd-gitlab-ui-1.yml5
-rw-r--r--changelogs/unreleased/51259-ci-cd-tooltips.yml6
-rw-r--r--changelogs/unreleased/51485-new-issue-labels-note.yml5
-rw-r--r--changelogs/unreleased/51606-expanding-a-diff-while-having-an-open-comment-form-will-always-scroll-down-to-the-comment.yml5
-rw-r--r--changelogs/unreleased/51668-fix-line-numbers.yml5
-rw-r--r--changelogs/unreleased/51792-dont-delete-failed-install-pods.yml5
-rw-r--r--changelogs/unreleased/51944-redesign-project-lists-ui.yml5
-rw-r--r--changelogs/unreleased/51959-branch-and-tag-name-links.yml5
-rw-r--r--changelogs/unreleased/51994-disable-merging-labels-in-dropdowns.yml5
-rw-r--r--changelogs/unreleased/52276-jump-to-top-in-merge-request.yml5
-rw-r--r--changelogs/unreleased/52370-filter-by-none-any-for-labels-in-issues-mrs-boards.yml5
-rw-r--r--changelogs/unreleased/52371-filter-by-none-any-for-labels-in-issues-mrs-api.yml5
-rw-r--r--changelogs/unreleased/52371-removes-patially-matching-no-label-and-makes-it-case-insensitive.yml5
-rw-r--r--changelogs/unreleased/52385-search-bar-for-dashboard-list.yml5
-rw-r--r--changelogs/unreleased/52453-show-subgroups-in-group-create-issue.yml5
-rw-r--r--changelogs/unreleased/52620-fix-loader-animation-alignment.yml5
-rw-r--r--changelogs/unreleased/52712-further-ui-improvements-to-profile-overview-tab.yml5
-rw-r--r--changelogs/unreleased/52828-inconsistency-in-fonts-used-for-branch-name-and-create-from-fields-when-creating-new-branch-from-ui.yml5
-rw-r--r--changelogs/unreleased/52940-fix-internal-email-pattern-not-respected.yml5
-rw-r--r--changelogs/unreleased/53020-user-specific-profile-page-settings-fields-don-t-have-help-text-placeholders.yml5
-rw-r--r--changelogs/unreleased/53289-update-haml_lint-to-0-28-0.yml5
-rw-r--r--changelogs/unreleased/53290-incorrect-project-list-order-select-default-label.yml5
-rw-r--r--changelogs/unreleased/53291-update-ffaker-to-2-10-0.yml5
-rw-r--r--changelogs/unreleased/53326-improve-issues-empty-state.yml5
-rw-r--r--changelogs/unreleased/53400-unstar-icon-button-is-misaligned.yml5
-rw-r--r--changelogs/unreleased/53493-list-id-email-header.yml5
-rw-r--r--changelogs/unreleased/53578-fe-deployment-status.yml5
-rw-r--r--changelogs/unreleased/53626-update-config-map-on-install-retry.yml5
-rw-r--r--changelogs/unreleased/53640-follow-up-from-resolve-redesign-activity-feed.yml4
-rw-r--r--changelogs/unreleased/53659-use-padded-key-for-gcm-ciphers.yml5
-rw-r--r--changelogs/unreleased/53700-hashed-storagemigration.yml5
-rw-r--r--changelogs/unreleased/53728-warn-in-web-editor-when-user-navigates-away.yml5
-rw-r--r--changelogs/unreleased/53816-empty-label-menu-if-not-logged-in.yml5
-rw-r--r--changelogs/unreleased/53874-navbar-lowres.yml5
-rw-r--r--changelogs/unreleased/53907-improve-milestone-links.yml5
-rw-r--r--changelogs/unreleased/53933-include-dates-in-milestone-change-email.yml5
-rw-r--r--changelogs/unreleased/53954-resolved-non-diff-discussions-on-merge-requests-no-longer-show-who-resolved-them-and-when-at-a-glance.yml5
-rw-r--r--changelogs/unreleased/53988-remove-notes-index-on-updated-at.yml5
-rw-r--r--changelogs/unreleased/53992-add-events-index-on-project-id-and-created-at.yml5
-rw-r--r--changelogs/unreleased/54004-update-asana-to-0-8-1.yml5
-rw-r--r--changelogs/unreleased/54010-update-asciidoctor-to-1-5-8.yml5
-rw-r--r--changelogs/unreleased/54015-Markdown-Editor-improve-Cursor-placement.yml5
-rw-r--r--changelogs/unreleased/54021-empty-button.yml5
-rw-r--r--changelogs/unreleased/54032-reply-shortcut-only-discussion-text.yml5
-rw-r--r--changelogs/unreleased/54048-Line-numbers-are-misaligned-in-file-blame-view.yml5
-rw-r--r--changelogs/unreleased/54093-the-default_value_for-gem-doesn-t-handle-actioncontroller-parameters-correctly.yml7
-rw-r--r--changelogs/unreleased/54146-fix-calendar-query.yml5
-rw-r--r--changelogs/unreleased/54201-update-rack-to-2-0-6.yml5
-rw-r--r--changelogs/unreleased/54218-fix-mergeUrlParams.yml5
-rw-r--r--changelogs/unreleased/54391-tag.yml5
-rw-r--r--changelogs/unreleased/54407-fix-limited-intersection-observers.yml5
-rw-r--r--changelogs/unreleased/54571-runner-tags.yml5
-rw-r--r--changelogs/unreleased/54648-fix-order-by-dropdown-tablet-screens.yml5
-rw-r--r--changelogs/unreleased/54736-sign-in-bottom-margin.yml5
-rw-r--r--changelogs/unreleased/54786-mr-empty-file-display.yml5
-rw-r--r--changelogs/unreleased/54814-sidebar-styling-updates.yml5
-rw-r--r--changelogs/unreleased/54826-use-read_repository-scope-on-read-only-files-endpoints.yml5
-rw-r--r--changelogs/unreleased/54953-error-500-viewing-merge-request-due-to-nil-commit_email_hostname.yml5
-rw-r--r--changelogs/unreleased/54953-fix-commit_email_hostname-accessor-in-fake_application_settings.yml5
-rw-r--r--changelogs/unreleased/55103-hide-group-cluster-features.yml5
-rw-r--r--changelogs/unreleased/55191-update-workhorse.yml5
-rw-r--r--changelogs/unreleased/55293-split-bio-into-individual-line-in-extended-user-tooltips.yml5
-rw-r--r--changelogs/unreleased/55344-only-prompt-user-once-when-navigating-away-from-file-editor.yml5
-rw-r--r--changelogs/unreleased/55484-fix-edit-button.yml4
-rw-r--r--changelogs/unreleased/_acet-fix-flash-styling.yml5
-rw-r--r--changelogs/unreleased/ab-approximate-counts.yml5
-rw-r--r--changelogs/unreleased/ac-releases-name-sha-author.yml5
-rw-r--r--changelogs/unreleased/add-new-nginx-metrics.yml5
-rw-r--r--changelogs/unreleased/added-glob-for-ci-changes-detection.yml5
-rw-r--r--changelogs/unreleased/an-gitaly-version-0-133-0.yml5
-rw-r--r--changelogs/unreleased/ashmckenzie-hmac-token-decode-and-tests.yml5
-rw-r--r--changelogs/unreleased/auto_devops_kubernetes_active.yml5
-rw-r--r--changelogs/unreleased/blackst0ne-add-discord-service.yml5
-rw-r--r--changelogs/unreleased/blackst0ne-convert-specs-rails5-style.yml5
-rw-r--r--changelogs/unreleased/blackst0ne-improve-encoding-helper-spec.yml5
-rw-r--r--changelogs/unreleased/bump_gpgme_gem.yml5
-rw-r--r--changelogs/unreleased/bvl-hide-confidential-events-take2.yml5
-rw-r--r--changelogs/unreleased/bvl-use-shell-writeref.yml5
-rw-r--r--changelogs/unreleased/ce-52811-fix_namespaces_api_routing.yml5
-rw-r--r--changelogs/unreleased/ce-54109-fix_user_by_any_email.yml5
-rw-r--r--changelogs/unreleased/certmanager-temp.yml5
-rw-r--r--changelogs/unreleased/check-if-fetched-data-does-is-complete.yml5
-rw-r--r--changelogs/unreleased/define-default-value-for-only-except-keys.yml5
-rw-r--r--changelogs/unreleased/depracated-migration-inheritance.yml5
-rw-r--r--changelogs/unreleased/deprecated-actiondispatch-paramsparser.yml5
-rw-r--r--changelogs/unreleased/deprecated-alias-method-chain.yml6
-rw-r--r--changelogs/unreleased/deprecated-comparing-actioncontroller-params-hash.yml6
-rw-r--r--changelogs/unreleased/deprecated-delete-all-params.yml5
-rw-r--r--changelogs/unreleased/deprecated-directly-inheriting-migration.yml5
-rw-r--r--changelogs/unreleased/deprecated-passing-activerecord-objects.yml5
-rw-r--r--changelogs/unreleased/deprecated-positional-seperator-parameter.yml5
-rw-r--r--changelogs/unreleased/diff-empty-state-fixes.yml5
-rw-r--r--changelogs/unreleased/diff-fix-expanding.yml5
-rw-r--r--changelogs/unreleased/discussion-perf-improvement.yml5
-rw-r--r--changelogs/unreleased/dm-batch-loader-key.yml5
-rw-r--r--changelogs/unreleased/dm-note-email-image-diff-discussion.yml5
-rw-r--r--changelogs/unreleased/docs-minor-aws-fixes.yml5
-rw-r--r--changelogs/unreleased/document-raw-snippet-api.yml5
-rw-r--r--changelogs/unreleased/drop-default-value-status-deployments.yml5
-rw-r--r--changelogs/unreleased/drop-gcp-cluster-table.yml5
-rw-r--r--changelogs/unreleased/feature-option-to-make-variables-protected.yml5
-rw-r--r--changelogs/unreleased/fix-55448.yml5
-rw-r--r--changelogs/unreleased/fix-calendar-events-fetching-error.yml5
-rw-r--r--changelogs/unreleased/fix-gb-encrypt-runners-tokens.yml5
-rw-r--r--changelogs/unreleased/fix-gb-improve-timeout-inputs-help-sections.yml5
-rw-r--r--changelogs/unreleased/fix-mr-widget-unrelated-deployment-status.yml5
-rw-r--r--changelogs/unreleased/fix-multiple-comments-shade-overlap.yml5
-rw-r--r--changelogs/unreleased/fix-n-plus-1-queries-projects.yml6
-rw-r--r--changelogs/unreleased/fj-47494-upgrade-git-to-2-18-0.yml5
-rw-r--r--changelogs/unreleased/fj-force-content-disposition.yml5
-rw-r--r--changelogs/unreleased/force-reload-arguments-2.yml (renamed from changelogs/unreleased/force-reload-arguments-1.yml)2
-rw-r--r--changelogs/unreleased/frozen-string-lib-gitlab-even-even-even-more.yml5
-rw-r--r--changelogs/unreleased/frozen-string-lib-gitlab-even-even-more.yml5
-rw-r--r--changelogs/unreleased/frozen-string-lib-gitlab-even-more.yml5
-rw-r--r--changelogs/unreleased/frozen-string-lib-gitlab-more.yml5
-rw-r--r--changelogs/unreleased/frozen-string-lib-rubocop.yml5
-rw-r--r--changelogs/unreleased/gt-align-issue-status-and-confidential-icon.yml5
-rw-r--r--changelogs/unreleased/gt-change-breadcrumb-title-for-contribution-charts.yml5
-rw-r--r--changelogs/unreleased/gt-change-container-width-for-project-import.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-invites.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-project-runners.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-snippets.yml5
-rw-r--r--changelogs/unreleased/gt-fix-typo-in-notebook-props.yml5
-rw-r--r--changelogs/unreleased/gt-fix-typos-in-lib.yml5
-rw-r--r--changelogs/unreleased/gt-remove-instances-of-extend-monospace.yml5
-rw-r--r--changelogs/unreleased/gt-remove-unnecessary-line-before-reply-holder.yml5
-rw-r--r--changelogs/unreleased/gt-remove-unused-project-method.yml5
-rw-r--r--changelogs/unreleased/gt-rename-diffs-store-variable.yml5
-rw-r--r--changelogs/unreleased/gt-reorder-group-sidebar-menu-items.yml5
-rw-r--r--changelogs/unreleased/gt-update-env-metrics-empty-state.yml5
-rw-r--r--changelogs/unreleased/gt-update-environment-breadcrumb.yml5
-rw-r--r--changelogs/unreleased/gt-update-navigation-theme-colors.yml5
-rw-r--r--changelogs/unreleased/gt-use-gl-tooltip-directive.yml5
-rw-r--r--changelogs/unreleased/ide-open-all-mr-files.yml5
-rw-r--r--changelogs/unreleased/ignore-failed-pipeline-creation-on-pipeline-schedule.yml5
-rw-r--r--changelogs/unreleased/improve_auto_devops_migration_debug.yml6
-rw-r--r--changelogs/unreleased/include-new-link-in-breadcrumb.yml5
-rw-r--r--changelogs/unreleased/jivl-add-empty-state-graphs-null-values.yml5
-rw-r--r--changelogs/unreleased/jlenny-CI_COMMIT_SHORT_SHA.yml5
-rw-r--r--changelogs/unreleased/jupyter-tls.yml5
-rw-r--r--changelogs/unreleased/kcj-add-philosophy.yml5
-rw-r--r--changelogs/unreleased/kubernetes-http-response-code.yml5
-rw-r--r--changelogs/unreleased/legacy_fallback_for_project_clusters_only.yml5
-rw-r--r--changelogs/unreleased/lock-trace-writes.yml5
-rw-r--r--changelogs/unreleased/markdown-toolbar-btn-fix.yml (renamed from changelogs/unreleased/fix-deadlock-chunked-io.yml)2
-rw-r--r--changelogs/unreleased/mk-avoid-read-only-error.yml5
-rw-r--r--changelogs/unreleased/mr-file-tree-commit.yml5
-rw-r--r--changelogs/unreleased/mr-origin-23218.yml5
-rw-r--r--changelogs/unreleased/mr-pipelines-2.yml5
-rw-r--r--changelogs/unreleased/mr-sticky-headers.yml5
-rw-r--r--changelogs/unreleased/mr-tree-filter-path-name.yml5
-rw-r--r--changelogs/unreleased/multiple-diff-line-discussions-fix.yml5
-rw-r--r--changelogs/unreleased/non-webkit-scrollbar-fixing.yml5
-rw-r--r--changelogs/unreleased/none-syntax-highlighting.yml5
-rw-r--r--changelogs/unreleased/optimise-job-request.yml5
-rw-r--r--changelogs/unreleased/order-of-notification-settings.yml5
-rw-r--r--changelogs/unreleased/osw-cache-discussions-diff-highlighting.yml6
-rw-r--r--changelogs/unreleased/osw-fallback-on-blank-refs.yml5
-rw-r--r--changelogs/unreleased/osw-fix-grouping-by-file-path.yml5
-rw-r--r--changelogs/unreleased/project_identicon_fix.yml5
-rw-r--r--changelogs/unreleased/rails5-active-record-class-value.yml5
-rw-r--r--changelogs/unreleased/rails5-deprecation-render-nothing.yml6
-rw-r--r--changelogs/unreleased/rails5-env-deprecated.yml5
-rw-r--r--changelogs/unreleased/remote-mirror-update-failed-notification.yml5
-rw-r--r--changelogs/unreleased/remove-deployment-status-hack-from-backend.yml5
-rw-r--r--changelogs/unreleased/remove-duplicate-primary-button-in-dashboard-snippets.yml5
-rw-r--r--changelogs/unreleased/remove-rails4-specific-code.yml5
-rw-r--r--changelogs/unreleased/remove-rails4-support.yml5
-rw-r--r--changelogs/unreleased/render-text-deprecated.yml6
-rw-r--r--changelogs/unreleased/retryable_create_or_update_kubernetes_namespace.yml6
-rw-r--r--changelogs/unreleased/rs-cherry-pick-api.yml5
-rw-r--r--changelogs/unreleased/security-182-update-workhorse.yml5
-rw-r--r--changelogs/unreleased/security-2717-xss-username-autocomplete.yml5
-rw-r--r--changelogs/unreleased/security-2736-prometheus-ssrf.yml5
-rw-r--r--changelogs/unreleased/security-bvl-exposure-in-commits-list.yml5
-rw-r--r--changelogs/unreleased/security-email-change-notification.yml5
-rw-r--r--changelogs/unreleased/security-fix-pat-web-access.yml5
-rw-r--r--changelogs/unreleased/security-fix-uri-xss-applications.yml5
-rw-r--r--changelogs/unreleased/security-fix-webhook-ssrf-ipv6.yml5
-rw-r--r--changelogs/unreleased/security-fj-crlf-injection.yml5
-rw-r--r--changelogs/unreleased/security-guest-comments.yml5
-rw-r--r--changelogs/unreleased/security-guest-comments_2.yml5
-rw-r--r--changelogs/unreleased/security-issue_51301.yml5
-rw-r--r--changelogs/unreleased/security-mermaid-xss.yml5
-rw-r--r--changelogs/unreleased/security-pages-toctou-race.yml6
-rw-r--r--changelogs/unreleased/security-private-group.yml6
-rw-r--r--changelogs/unreleased/security-stored-xss-for-environments.yml5
-rw-r--r--changelogs/unreleased/security-xss-in-markdown-following-unrecognized-html-element.yml5
-rw-r--r--changelogs/unreleased/set-kubeconfig-nil-when-token-nil.yml5
-rw-r--r--changelogs/unreleased/sh-53180-append-path.yml5
-rw-r--r--changelogs/unreleased/sh-bump-gems-security.yml5
-rw-r--r--changelogs/unreleased/sh-bump-ruby-2-5-3.yml5
-rw-r--r--changelogs/unreleased/sh-cache-avatar-paths.yml5
-rw-r--r--changelogs/unreleased/sh-carrierwave-patch-google-acl.yml5
-rw-r--r--changelogs/unreleased/sh-disable-autocomplete-mirror-settings.yml5
-rw-r--r--changelogs/unreleased/sh-fix-clone-geo-dropdown.yml5
-rw-r--r--changelogs/unreleased/sh-fix-github-import-without-oauth2-config.yml5
-rw-r--r--changelogs/unreleased/sh-fix-http-clone-panel.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-38317.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-51220.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-53783-ce.yml5
-rw-r--r--changelogs/unreleased/sh-fix-mirrors-protected-branches.yml5
-rw-r--r--changelogs/unreleased/sh-handle-string-null-bytes.yml5
-rw-r--r--changelogs/unreleased/sh-remove-local-sidekiq-admin-check.yml5
-rw-r--r--changelogs/unreleased/sh-use-nakayoshi-fork.yml5
-rw-r--r--changelogs/unreleased/sh-use-nokogiri-xml-backend.yml5
-rw-r--r--changelogs/unreleased/spec-positional-arguments.yml5
-rw-r--r--changelogs/unreleased/specs-positional-arguments.yml5
-rw-r--r--changelogs/unreleased/speed-up-relative-positioning.yml5
-rw-r--r--changelogs/unreleased/support-gitaly-tls.yml5
-rw-r--r--changelogs/unreleased/switch-rails.yml5
-rw-r--r--changelogs/unreleased/tc-backfill-full-path-config.yml5
-rw-r--r--changelogs/unreleased/tc-repo-full-path-in-db.yml5
-rw-r--r--changelogs/unreleased/triggermesh-knative-version.yml5
-rw-r--r--changelogs/unreleased/triggermesh-phase2-external-ip.yml5
-rw-r--r--changelogs/unreleased/triggermesh-phase2-knative-description.yml5
-rw-r--r--changelogs/unreleased/triggermesh-phase2-serverless.yml5
-rw-r--r--changelogs/unreleased/unicorn-monkey-patch.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-version.yml5
-rw-r--r--changelogs/unreleased/upgrade_kubeclient_400.yml5
-rw-r--r--changelogs/unreleased/validate-foreign-keys-being-indexed.yml5
-rw-r--r--changelogs/unreleased/winh-collapse-discussions.yml5
-rw-r--r--changelogs/unreleased/winh-divider-margin.yml5
-rw-r--r--changelogs/unreleased/winh-dropdown-title-padding.yml5
-rw-r--r--changelogs/unreleased/winh-merge-request-commit-context.yml5
-rw-r--r--changelogs/unreleased/winh-merge-request-commit-discussion.yml5
-rw-r--r--changelogs/unreleased/winh-princess-mononospace.yml5
-rw-r--r--changelogs/unreleased/winh-upgrade-gitlab-ui.yml5
-rw-r--r--changelogs/unreleased/workhorse-7-3-0.yml5
-rw-r--r--changelogs/unreleased/zj-backup-restore-object-pools.yml5
-rw-r--r--changelogs/unreleased/zj-improve-gitaly-pb.yml5
-rw-r--r--changelogs/unreleased/zj-remove-broken-storage.yml5
-rw-r--r--config/application.rb14
-rw-r--r--config/boot.rb9
-rw-r--r--config/dependency_decisions.yml11
-rw-r--r--config/environment.rb7
-rw-r--r--config/environments/production.rb6
-rw-r--r--config/environments/test.rb9
-rw-r--r--config/gitlab.yml.example18
-rw-r--r--config/initializers/1_settings.rb4
-rw-r--r--config/initializers/8_devise.rb (renamed from config/initializers/devise.rb)0
-rw-r--r--config/initializers/active_record_array_type_casting.rb23
-rw-r--r--config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb104
-rw-r--r--config/initializers/active_record_data_types.rb2
-rw-r--r--config/initializers/active_record_locking.rb20
-rw-r--r--config/initializers/application_controller_renderer.rb12
-rw-r--r--config/initializers/ar5_batching.rb40
-rw-r--r--config/initializers/ar5_pg_10_support.rb58
-rw-r--r--config/initializers/carrierwave_patch.rb29
-rw-r--r--config/initializers/correlation_id.rb3
-rw-r--r--config/initializers/lograge.rb1
-rw-r--r--config/initializers/mysql_set_length_for_binary_indexes.rb43
-rw-r--r--config/initializers/new_framework_defaults.rb40
-rw-r--r--config/initializers/postgresql_opclasses_support.rb23
-rw-r--r--config/initializers/sentry.rb2
-rw-r--r--config/initializers/sidekiq.rb9
-rw-r--r--config/initializers/static_files.rb23
-rw-r--r--config/initializers/trusted_proxies.rb12
-rw-r--r--config/initializers/zz_metrics.rb (renamed from config/initializers/8_metrics.rb)3
-rw-r--r--config/prometheus/common_metrics.yml48
-rw-r--r--config/routes/api.rb2
-rw-r--r--config/routes/project.rb6
-rw-r--r--config/routes/repository.rb2
-rw-r--r--config/routes/uploads.rb3
-rw-r--r--config/sidekiq_queues.yml4
-rw-r--r--danger/commit_messages/Dangerfile2
-rw-r--r--danger/documentation/Dangerfile21
-rw-r--r--danger/duplicate_yarn_dependencies/Dangerfile27
-rw-r--r--danger/gemfile/Dangerfile2
-rw-r--r--db/fixtures/development/03_settings.rb8
-rw-r--r--db/fixtures/development/04_project.rb14
-rw-r--r--db/fixtures/development/10_merge_requests.rb12
-rw-r--r--db/fixtures/development/24_forks.rb22
-rw-r--r--db/fixtures/production/001_application_settings.rb2
-rw-r--r--db/fixtures/production/002_admin.rb (renamed from db/fixtures/production/001_admin.rb)0
-rw-r--r--db/importers/common_metrics_importer.rb4
-rw-r--r--db/migrate/20160226114608_add_trigram_indexes_for_searching.rb7
-rw-r--r--db/migrate/20161207231620_fixup_environment_name_uniqueness.rb3
-rw-r--r--db/migrate/20161207231626_add_environment_slug.rb3
-rw-r--r--db/migrate/20181006004100_import_common_metrics_nginx_vts.rb15
-rw-r--r--db/migrate/20181031145139_add_protected_ci_variables_to_application_settings.rb17
-rw-r--r--db/migrate/20181101191341_create_clusters_applications_cert_manager.rb2
-rw-r--r--db/migrate/20181108091549_cleanup_environments_external_url.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/20181119081539_add_merge_request_id_to_ci_pipelines.rb2
-rw-r--r--db/migrate/20181120091639_add_foreign_key_to_ci_pipelines_merge_requests.rb6
-rw-r--r--db/migrate/20181120151656_add_token_encrypted_to_ci_runners.rb2
-rw-r--r--db/migrate/20181121101842_add_ci_builds_partial_index_on_project_id_and_status.rb33
-rw-r--r--db/migrate/20181121101843_remove_redundant_ci_builds_partial_index.rb33
-rw-r--r--db/migrate/20181123144235_create_suggestions.rb20
-rw-r--r--db/migrate/20181128123704_add_state_to_pool_repository.rb19
-rw-r--r--db/migrate/20181129104854_add_token_encrypted_to_ci_builds.rb11
-rw-r--r--db/migrate/20181129104944_add_index_to_ci_builds_token_encrypted.rb17
-rw-r--r--db/migrate/20181203002526_add_project_bfg_object_map_column.rb9
-rw-r--r--db/migrate/20181211092510_add_name_author_id_and_sha_to_releases.rb13
-rw-r--r--db/migrate/20181211092514_add_author_id_index_and_fk_to_releases.rb21
-rw-r--r--db/migrate/20181212104941_backfill_releases_name_with_tag_name.rb17
-rw-r--r--db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb3
-rw-r--r--db/post_migrate/20161221153951_rename_reserved_project_names.rb1
-rw-r--r--db/post_migrate/20170313133418_rename_more_reserved_project_names.rb1
-rw-r--r--db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb3
-rw-r--r--db/post_migrate/20181010133639_backfill_store_project_full_path_in_repo.rb2
-rw-r--r--db/post_migrate/20181026091631_migrate_forbidden_redirect_uris.rb2
-rw-r--r--db/post_migrate/20181121111200_schedule_runners_token_encryption.rb2
-rw-r--r--db/post_migrate/20181130102132_backfill_hashed_project_repositories.rb26
-rw-r--r--db/post_migrate/20181204154019_populate_mr_metrics_with_events_data.rb38
-rw-r--r--db/post_migrate/20181218192239_backfill_project_repositories_for_legacy_storage_projects.rb26
-rw-r--r--db/post_migrate/20181219145520_migrate_cluster_configure_worker_sidekiq_queue.rb15
-rw-r--r--doc/administration/auth/README.md2
-rw-r--r--doc/administration/auth/jwt.md36
-rw-r--r--doc/administration/container_registry.md46
-rw-r--r--doc/administration/gitaly/index.md84
-rw-r--r--doc/administration/index.md1
-rw-r--r--doc/administration/monitoring/performance/img/request_profiling_token.pngbin50774 -> 17425 bytes
-rw-r--r--doc/administration/operations/filesystem_benchmarking.md16
-rw-r--r--doc/administration/repository_storage_paths.md19
-rw-r--r--doc/administration/repository_storage_types.md17
-rw-r--r--doc/administration/uploads.md52
-rw-r--r--doc/api/README.md96
-rw-r--r--doc/api/jobs.md177
-rw-r--r--doc/api/merge_requests.md5
-rw-r--r--doc/api/milestones.md3
-rw-r--r--doc/api/search.md28
-rw-r--r--doc/api/suggestions.md36
-rw-r--r--doc/ci/README.md1
-rw-r--r--doc/ci/caching/index.md16
-rw-r--r--doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/cloud_foundry_variables.pngbin28170 -> 11852 bytes
-rw-r--r--doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md2
-rw-r--r--doc/ci/examples/laravel_with_gitlab_and_envoy/img/variables_page.pngbin27538 -> 11836 bytes
-rw-r--r--doc/ci/img/pipelines-goal.pngbin36933 -> 15284 bytes
-rw-r--r--doc/ci/img/types-of-pipelines.pngbin31245 -> 12268 bytes
-rw-r--r--doc/ci/img/view_on_mr_widget.pngbin21969 -> 21942 bytes
-rw-r--r--doc/ci/interactive_web_terminal/index.md13
-rw-r--r--doc/ci/merge_request_pipelines/img/merge_request.pngbin0 -> 18834 bytes
-rw-r--r--doc/ci/merge_request_pipelines/img/pipeline_detail.pngbin0 -> 15561 bytes
-rw-r--r--doc/ci/merge_request_pipelines/index.md83
-rw-r--r--doc/ci/triggers/README.md3
-rw-r--r--doc/ci/triggers/img/trigger_variables.pngbin3637 -> 10853 bytes
-rw-r--r--doc/ci/variables/README.md166
-rw-r--r--doc/ci/variables/img/variables.pngbin116263 -> 34838 bytes
-rw-r--r--doc/ci/yaml/README.md216
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/automatic_ce_ee_merge.md258
-rw-r--r--doc/development/background_migrations.md4
-rw-r--r--doc/development/code_review.md4
-rw-r--r--doc/development/contributing/index.md9
-rw-r--r--doc/development/contributing/merge_request_workflow.md1
-rw-r--r--doc/development/documentation/index.md24
-rw-r--r--doc/development/documentation/site_architecture/global_nav.md342
-rw-r--r--doc/development/documentation/site_architecture/index.md59
-rw-r--r--doc/development/fe_guide/vuex.md6
-rw-r--r--doc/development/feature_flags.md10
-rw-r--r--doc/development/i18n/proofreader.md2
-rw-r--r--doc/development/instrumentation.md2
-rw-r--r--doc/development/migration_style_guide.md16
-rw-r--r--doc/development/new_fe_guide/style/prettier.md35
-rw-r--r--doc/development/profiling.md7
-rw-r--r--doc/development/prometheus_metrics.md2
-rw-r--r--doc/development/sql.md4
-rw-r--r--doc/development/switching_to_rails5.md27
-rw-r--r--doc/development/testing_guide/best_practices.md10
-rw-r--r--doc/development/testing_guide/ci.md4
-rw-r--r--doc/development/testing_guide/review_apps.md35
-rw-r--r--doc/development/what_requires_downtime.md14
-rw-r--r--doc/img/devops-stages.pngbin35549 -> 10666 bytes
-rw-r--r--doc/install/README.md103
-rw-r--r--doc/install/aws/img/associate_subnet_gateway_2.pngbin10617 -> 10616 bytes
-rw-r--r--doc/install/aws/img/choose_ami.pngbin4892 -> 4888 bytes
-rw-r--r--doc/install/aws/img/ec_az.pngbin10476 -> 10470 bytes
-rw-r--r--doc/install/aws/img/ec_subnet.pngbin23517 -> 23505 bytes
-rw-r--r--doc/install/docker.md6
-rw-r--r--doc/install/kubernetes/gitlab_chart.md14
-rw-r--r--doc/install/kubernetes/index.md42
-rw-r--r--doc/integration/img/github_app.pngbin128040 -> 42278 bytes
-rw-r--r--doc/integration/img/github_app_entry.pngbin83603 -> 26765 bytes
-rw-r--r--doc/integration/img/github_register_app.pngbin120981 -> 40228 bytes
-rw-r--r--doc/integration/recaptcha.md6
-rw-r--r--doc/raketasks/backup_restore.md1
-rw-r--r--doc/raketasks/web_hooks.md6
-rw-r--r--doc/security/rack_attack.md3
-rw-r--r--doc/ssh/README.md21
-rw-r--r--doc/topics/autodevops/index.md11
-rw-r--r--doc/university/high-availability/aws/img/reference-arch2.pngbin184033 -> 53523 bytes
-rw-r--r--doc/update/upgrader.md91
-rw-r--r--doc/user/abuse_reports.md53
-rw-r--r--doc/user/admin_area/abuse_reports.md31
-rw-r--r--doc/user/admin_area/img/abuse_report_blocked_user.pngbin0 -> 13821 bytes
-rw-r--r--doc/user/admin_area/img/abuse_reports_page.pngbin0 -> 215813 bytes
-rw-r--r--doc/user/admin_area/img/admin_area_settings_button.pngbin7993 -> 7974 bytes
-rw-r--r--doc/user/admin_area/settings/img/import_sources.pngbin10971 -> 5891 bytes
-rw-r--r--doc/user/discussions/img/insert_suggestion.pngbin0 -> 19913 bytes
-rw-r--r--doc/user/discussions/img/make_suggestion.pngbin0 -> 28447 bytes
-rw-r--r--doc/user/discussions/img/suggestion.pngbin0 -> 39775 bytes
-rw-r--r--doc/user/discussions/index.md45
-rw-r--r--doc/user/group/clusters/index.md132
-rw-r--r--doc/user/group/img/add_new_members.pngbin66523 -> 66513 bytes
-rw-r--r--doc/user/group/img/create_new_project_from_group.pngbin37234 -> 37231 bytes
-rw-r--r--doc/user/group/index.md3
-rw-r--r--doc/user/group/subgroups/index.md1
-rw-r--r--doc/user/img/color_inline_colorchip_render_gfm.pngbin11534 -> 4724 bytes
-rw-r--r--doc/user/img/math_inline_sup_render_gfm.pngbin1359 -> 1119 bytes
-rw-r--r--doc/user/img/mermaid_diagram_render_gfm.pngbin4587 -> 2202 bytes
-rw-r--r--doc/user/img/task_list_ordered_render_gfm.pngbin6247 -> 2860 bytes
-rw-r--r--doc/user/img/unordered_check_list_render_gfm.pngbin6207 -> 2789 bytes
-rw-r--r--doc/user/index.md1
-rw-r--r--doc/user/markdown.md77
-rw-r--r--doc/user/permissions.md1
-rw-r--r--doc/user/profile/account/delete_account.md3
-rw-r--r--doc/user/project/clusters/eks_and_gitlab/index.md2
-rw-r--r--doc/user/project/clusters/index.md148
-rw-r--r--doc/user/project/clusters/runbooks/img/authorize-jupyter.pngbin126425 -> 35652 bytes
-rw-r--r--doc/user/project/clusters/runbooks/img/demo-runbook.pngbin132436 -> 36091 bytes
-rw-r--r--doc/user/project/clusters/runbooks/img/gitlab-variables.pngbin179611 -> 54167 bytes
-rw-r--r--doc/user/project/clusters/runbooks/img/helm-install.pngbin201348 -> 71741 bytes
-rw-r--r--doc/user/project/clusters/runbooks/img/ingress-install.pngbin140880 -> 44380 bytes
-rw-r--r--doc/user/project/clusters/runbooks/img/jupyterhub-install.pngbin116775 -> 41655 bytes
-rw-r--r--doc/user/project/clusters/runbooks/img/postgres-query.pngbin209435 -> 63480 bytes
-rw-r--r--doc/user/project/clusters/runbooks/img/sample-runbook.pngbin145728 -> 40947 bytes
-rw-r--r--doc/user/project/clusters/serverless/img/deploy-stage.pngbin12029 -> 5036 bytes
-rw-r--r--doc/user/project/clusters/serverless/img/dns-entry.pngbin56600 -> 19583 bytes
-rw-r--r--doc/user/project/clusters/serverless/img/function-execution.pngbin0 -> 223762 bytes
-rw-r--r--doc/user/project/clusters/serverless/img/install-knative.pngbin102861 -> 13003 bytes
-rw-r--r--doc/user/project/clusters/serverless/img/knative-app.pngbin28998 -> 9440 bytes
-rw-r--r--doc/user/project/clusters/serverless/img/serverless-page.pngbin0 -> 194708 bytes
-rw-r--r--doc/user/project/clusters/serverless/index.md266
-rw-r--r--doc/user/project/cycle_analytics.md3
-rw-r--r--doc/user/project/img/issue_board.pngbin289964 -> 284759 bytes
-rw-r--r--doc/user/project/img/issue_board_summed_weights.pngbin26691 -> 26687 bytes
-rw-r--r--doc/user/project/img/issue_boards_core.pngbin119989 -> 119602 bytes
-rw-r--r--doc/user/project/img/issue_boards_premium.pngbin99171 -> 98490 bytes
-rw-r--r--doc/user/project/img/releases.pngbin0 -> 43612 bytes
-rw-r--r--doc/user/project/integrations/img/jira_api_token.pngbin160587 -> 61394 bytes
-rw-r--r--doc/user/project/integrations/img/jira_api_token_menu.pngbin68564 -> 25059 bytes
-rw-r--r--doc/user/project/integrations/img/jira_service_page.pngbin74893 -> 30398 bytes
-rw-r--r--doc/user/project/integrations/prometheus.md1
-rw-r--r--doc/user/project/issues/img/issue_board.pngbin86095 -> 85331 bytes
-rw-r--r--doc/user/project/issues/img/similar_issues.pngbin0 -> 25407 bytes
-rw-r--r--doc/user/project/issues/index.md4
-rw-r--r--doc/user/project/issues/similar_issues.md16
-rw-r--r--doc/user/project/merge_requests/img/comment-on-any-diff-line.pngbin177323 -> 55614 bytes
-rw-r--r--doc/user/project/merge_requests/img/filter_wip_merge_requests.pngbin17346 -> 6285 bytes
-rw-r--r--doc/user/project/merge_requests/img/merge_request_pipeline.pngbin31046 -> 31026 bytes
-rw-r--r--doc/user/project/merge_requests/index.md20
-rw-r--r--doc/user/project/pages/getting_started_part_three.md27
-rw-r--r--doc/user/project/pages/img/icons/click.pngbin10148 -> 4863 bytes
-rw-r--r--doc/user/project/pages/img/icons/cogs.pngbin9670 -> 4425 bytes
-rw-r--r--doc/user/project/pages/img/icons/fork.pngbin9597 -> 4562 bytes
-rw-r--r--doc/user/project/pages/img/icons/free.pngbin8689 -> 3681 bytes
-rw-r--r--doc/user/project/pages/img/icons/lock.pngbin7892 -> 3426 bytes
-rw-r--r--doc/user/project/pages/img/icons/monitor.pngbin5039 -> 2025 bytes
-rw-r--r--doc/user/project/pages/img/icons/terminal.pngbin4972 -> 1983 bytes
-rw-r--r--doc/user/project/pages/index.md2
-rw-r--r--doc/user/project/pages/lets_encrypt_for_gitlab_pages.md158
-rw-r--r--doc/user/project/releases.md12
-rw-r--r--doc/user/project/repository/branches/img/branch_filter_search_box.pngbin83225 -> 23539 bytes
-rw-r--r--doc/user/project/repository/img/repository_cleanup.pngbin0 -> 8117 bytes
-rw-r--r--doc/user/project/repository/index.md2
-rw-r--r--doc/user/project/repository/reducing_the_repo_size_using_git.md109
-rw-r--r--doc/user/project/settings/import_export.md4
-rw-r--r--doc/user/project/web_ide/index.md12
-rw-r--r--doc/user/search/img/dashboard_links.pngbin27164 -> 10220 bytes
-rw-r--r--doc/user/search/img/issues_assigned_to_you.pngbin50433 -> 19706 bytes
-rw-r--r--doc/workflow/img/repository_mirroring_force_update.pngbin45730 -> 13586 bytes
-rw-r--r--doc/workflow/notifications.md1
-rw-r--r--jest.config.js (renamed from config/jest.config.js)9
-rw-r--r--lib/api/api.rb5
-rw-r--r--lib/api/commit_statuses.rb2
-rw-r--r--lib/api/entities.rb14
-rw-r--r--lib/api/events.rb42
-rw-r--r--lib/api/groups.rb2
-rw-r--r--lib/api/helpers.rb16
-rw-r--r--lib/api/job_artifacts.rb24
-rw-r--r--lib/api/namespaces.rb17
-rw-r--r--lib/api/search.rb7
-rw-r--r--lib/api/suggestions.rb31
-rw-r--r--lib/api/templates.rb2
-rw-r--r--lib/backup/repository.rb16
-rw-r--r--lib/banzai/filter/front_matter_filter.rb34
-rw-r--r--lib/banzai/filter/milestone_reference_filter.rb66
-rw-r--r--lib/banzai/filter/suggestion_filter.rb24
-rw-r--r--lib/banzai/filter/syntax_highlight_filter.rb2
-rw-r--r--lib/banzai/filter/user_reference_filter.rb2
-rw-r--r--lib/banzai/filter/yaml_front_matter_filter.rb27
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb1
-rw-r--r--lib/banzai/pipeline/post_process_pipeline.rb3
-rw-r--r--lib/banzai/pipeline/pre_process_pipeline.rb2
-rw-r--r--lib/banzai/suggestions_parser.rb14
-rw-r--r--lib/constraints/feature_constrainer.rb8
-rw-r--r--lib/declarative_policy.rb12
-rw-r--r--lib/gitlab/auth.rb4
-rw-r--r--lib/gitlab/background_migration/backfill_hashed_project_repositories.rb15
-rw-r--r--lib/gitlab/background_migration/backfill_legacy_project_repositories.rb15
-rw-r--r--lib/gitlab/background_migration/backfill_project_repositories.rb219
-rw-r--r--lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_improved.rb99
-rw-r--r--lib/gitlab/bitbucket_server_import/importer.rb2
-rw-r--r--lib/gitlab/branch_push_merge_commit_analyzer.rb132
-rw-r--r--lib/gitlab/checks/diff_check.rb3
-rw-r--r--lib/gitlab/ci/build/policy/refs.rb12
-rw-r--r--lib/gitlab/ci/config/entry/except_policy.rb17
-rw-r--r--lib/gitlab/ci/config/entry/job.rb15
-rw-r--r--lib/gitlab/ci/config/entry/only_policy.rb18
-rw-r--r--lib/gitlab/ci/config/entry/policy.rb15
-rw-r--r--lib/gitlab/ci/parsers.rb21
-rw-r--r--lib/gitlab/ci/parsers/parser_error.rb9
-rw-r--r--lib/gitlab/ci/parsers/test.rb21
-rw-r--r--lib/gitlab/ci/parsers/test/junit.rb2
-rw-r--r--lib/gitlab/ci/status/bridge/common.rb27
-rw-r--r--lib/gitlab/ci/status/bridge/factory.rb15
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml29
-rw-r--r--lib/gitlab/color_schemes.rb3
-rw-r--r--lib/gitlab/correlation_id.rb40
-rw-r--r--lib/gitlab/current_settings.rb31
-rw-r--r--lib/gitlab/database.rb6
-rw-r--r--lib/gitlab/database/arel_methods.rb20
-rw-r--r--lib/gitlab/database/count.rb2
-rw-r--r--lib/gitlab/database/count/exact_count_strategy.rb2
-rw-r--r--lib/gitlab/database/median.rb17
-rw-r--r--lib/gitlab/database/migration_helpers.rb7
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb4
-rw-r--r--lib/gitlab/database/sha_attribute.rb37
-rw-r--r--lib/gitlab/database/subquery.rb14
-rw-r--r--lib/gitlab/diff/file.rb40
-rw-r--r--lib/gitlab/diff/line.rb4
-rw-r--r--lib/gitlab/discussions_diff/file_collection.rb76
-rw-r--r--lib/gitlab/discussions_diff/highlight_cache.rb67
-rw-r--r--lib/gitlab/fake_application_settings.rb4
-rw-r--r--lib/gitlab/file_finder.rb57
-rw-r--r--lib/gitlab/git/object_pool.rb55
-rw-r--r--lib/gitlab/git/repository.rb7
-rw-r--r--lib/gitlab/git/repository_cleaner.rb28
-rw-r--r--lib/gitlab/gitaly_client.rb43
-rw-r--r--lib/gitlab/gitaly_client/cleanup_service.rb37
-rw-r--r--lib/gitlab/gitaly_client/object_pool_service.rb48
-rw-r--r--lib/gitlab/gpg.rb8
-rw-r--r--lib/gitlab/gpg/commit.rb24
-rw-r--r--lib/gitlab/grape_logging/loggers/correlation_id_logger.rb14
-rw-r--r--lib/gitlab/graphql.rb4
-rw-r--r--lib/gitlab/import_export/command_line_util.rb8
-rw-r--r--lib/gitlab/import_export/import_export.yml5
-rw-r--r--lib/gitlab/import_export/repo_restorer.rb1
-rw-r--r--lib/gitlab/import_sources.rb4
-rw-r--r--lib/gitlab/json_cache.rb87
-rw-r--r--lib/gitlab/json_logger.rb1
-rw-r--r--lib/gitlab/lfs_token.rb121
-rw-r--r--lib/gitlab/middleware/correlation_id.rb31
-rw-r--r--lib/gitlab/object_hierarchy.rb (renamed from lib/gitlab/group_hierarchy.rb)52
-rw-r--r--lib/gitlab/project_search_results.rb43
-rw-r--r--lib/gitlab/safe_request_store.rb8
-rw-r--r--lib/gitlab/search/found_blob.rb162
-rw-r--r--lib/gitlab/search/query.rb6
-rw-r--r--lib/gitlab/search_results.rb36
-rw-r--r--lib/gitlab/sentry.rb13
-rw-r--r--lib/gitlab/sidekiq_middleware/correlation_injector.rb14
-rw-r--r--lib/gitlab/sidekiq_middleware/correlation_logger.rb15
-rw-r--r--lib/gitlab/ssh_public_key.rb6
-rw-r--r--lib/gitlab/template/finders/global_template_finder.rb4
-rw-r--r--lib/gitlab/template/finders/repo_template_finder.rb5
-rw-r--r--lib/gitlab/upgrader.rb111
-rw-r--r--lib/gitlab/url_blocker.rb9
-rw-r--r--lib/gitlab/url_sanitizer.rb1
-rw-r--r--lib/gitlab/usage_data.rb18
-rw-r--r--lib/gitlab/utils.rb11
-rw-r--r--lib/gitlab/utils/override.rb109
-rw-r--r--lib/gitlab/wiki_file_finder.rb6
-rw-r--r--lib/gitlab/workhorse.rb2
-rw-r--r--lib/mysql_zero_date.rb2
-rw-r--r--lib/omni_auth/strategies/jwt.rb17
-rw-r--r--lib/rails4_migration_version.rb16
-rw-r--r--lib/tasks/gitlab/storage.rake10
-rw-r--r--lib/tasks/gitlab/web_hook.rake45
-rw-r--r--lib/version_check.rb7
-rw-r--r--locale/gitlab.pot351
-rw-r--r--package.json21
-rw-r--r--qa/qa.rb6
-rw-r--r--qa/qa/page/base.rb32
-rw-r--r--qa/qa/page/component/clone_panel.rb31
-rw-r--r--qa/qa/page/component/legacy_clone_panel.rb52
-rw-r--r--qa/qa/page/main/login.rb12
-rw-r--r--qa/qa/page/main/menu.rb8
-rw-r--r--qa/qa/page/merge_request/new.rb8
-rw-r--r--qa/qa/page/merge_request/show.rb7
-rw-r--r--qa/qa/page/project/commit/show.rb27
-rw-r--r--qa/qa/page/project/issue/show.rb6
-rw-r--r--qa/qa/page/project/menu.rb82
-rw-r--r--qa/qa/page/project/settings/mirroring_repositories.rb91
-rw-r--r--qa/qa/page/project/settings/repository.rb10
-rw-r--r--qa/qa/page/project/show.rb82
-rw-r--r--qa/qa/page/project/wiki/show.rb2
-rw-r--r--qa/qa/resource/file.rb2
-rw-r--r--qa/qa/resource/merge_request.rb6
-rw-r--r--qa/qa/resource/project.rb6
-rw-r--r--qa/qa/resource/repository/project_push.rb21
-rw-r--r--qa/qa/resource/repository/wiki_push.rb5
-rw-r--r--qa/qa/runtime/browser.rb7
-rw-r--r--qa/qa/runtime/env.rb5
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb10
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb35
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb14
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb18
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb45
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb61
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb12
-rw-r--r--qa/qa/support/page/logging.rb30
-rw-r--r--qa/spec/page/logging_spec.rb7
-rw-r--r--rubocop/cop/inject_enterprise_edition_module.rb47
-rw-r--r--rubocop/rubocop.rb1
-rw-r--r--scripts/prepare_build.sh25
-rwxr-xr-xscripts/rails4-gemfile-lock-check19
-rwxr-xr-xscripts/review_apps/review-apps.sh16
-rwxr-xr-xscripts/trigger-build8
-rw-r--r--spec/controllers/abuse_reports_controller_spec.rb12
-rw-r--r--spec/controllers/admin/application_settings_controller_spec.rb10
-rw-r--r--spec/controllers/admin/applications_controller_spec.rb10
-rw-r--r--spec/controllers/admin/groups_controller_spec.rb28
-rw-r--r--spec/controllers/admin/hooks_controller_spec.rb2
-rw-r--r--spec/controllers/admin/identities_controller_spec.rb4
-rw-r--r--spec/controllers/admin/projects_controller_spec.rb6
-rw-r--r--spec/controllers/admin/runners_controller_spec.rb12
-rw-r--r--spec/controllers/admin/services_controller_spec.rb6
-rw-r--r--spec/controllers/admin/spam_logs_controller_spec.rb6
-rw-r--r--spec/controllers/admin/users_controller_spec.rb36
-rw-r--r--spec/controllers/application_controller_spec.rb36
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb40
-rw-r--r--spec/controllers/boards/issues_controller_spec.rb24
-rw-r--r--spec/controllers/boards/lists_controller_spec.rb42
-rw-r--r--spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb6
-rw-r--r--spec/controllers/concerns/group_tree_spec.rb12
-rw-r--r--spec/controllers/concerns/issuable_collections_spec.rb4
-rw-r--r--spec/controllers/concerns/lfs_request_spec.rb4
-rw-r--r--spec/controllers/dashboard/groups_controller_spec.rb2
-rw-r--r--spec/controllers/dashboard/milestones_controller_spec.rb2
-rw-r--r--spec/controllers/dashboard/todos_controller_spec.rb24
-rw-r--r--spec/controllers/explore/projects_controller_spec.rb4
-rw-r--r--spec/controllers/google_api/authorizations_controller_spec.rb2
-rw-r--r--spec/controllers/graphql_controller_spec.rb2
-rw-r--r--spec/controllers/groups/avatars_controller_spec.rb2
-rw-r--r--spec/controllers/groups/boards_controller_spec.rb8
-rw-r--r--spec/controllers/groups/children_controller_spec.rb42
-rw-r--r--spec/controllers/groups/clusters/applications_controller_spec.rb2
-rw-r--r--spec/controllers/groups/clusters_controller_spec.rb28
-rw-r--r--spec/controllers/groups/group_members_controller_spec.rb60
-rw-r--r--spec/controllers/groups/labels_controller_spec.rb6
-rw-r--r--spec/controllers/groups/milestones_controller_spec.rb68
-rw-r--r--spec/controllers/groups/runners_controller_spec.rb8
-rw-r--r--spec/controllers/groups/settings/ci_cd_controller_spec.rb4
-rw-r--r--spec/controllers/groups/shared_projects_controller_spec.rb2
-rw-r--r--spec/controllers/groups/uploads_controller_spec.rb2
-rw-r--r--spec/controllers/groups/variables_controller_spec.rb8
-rw-r--r--spec/controllers/groups_controller_spec.rb99
-rw-r--r--spec/controllers/health_check_controller_spec.rb6
-rw-r--r--spec/controllers/health_controller_spec.rb4
-rw-r--r--spec/controllers/help_controller_spec.rb16
-rw-r--r--spec/controllers/import/bitbucket_controller_spec.rb14
-rw-r--r--spec/controllers/import/bitbucket_server_controller_spec.rb14
-rw-r--r--spec/controllers/import/github_controller_spec.rb9
-rw-r--r--spec/controllers/import/gitlab_controller_spec.rb14
-rw-r--r--spec/controllers/import/gitlab_projects_controller_spec.rb6
-rw-r--r--spec/controllers/import/google_code_controller_spec.rb2
-rw-r--r--spec/controllers/invites_controller_spec.rb4
-rw-r--r--spec/controllers/ldap/omniauth_callbacks_controller_spec.rb4
-rw-r--r--spec/controllers/notification_settings_controller_spec.rb60
-rw-r--r--spec/controllers/oauth/applications_controller_spec.rb6
-rw-r--r--spec/controllers/oauth/authorizations_controller_spec.rb6
-rw-r--r--spec/controllers/passwords_controller_spec.rb2
-rw-r--r--spec/controllers/profiles/accounts_controller_spec.rb6
-rw-r--r--spec/controllers/profiles/emails_controller_spec.rb6
-rw-r--r--spec/controllers/profiles/keys_controller_spec.rb22
-rw-r--r--spec/controllers/profiles/notifications_controller_spec.rb4
-rw-r--r--spec/controllers/profiles/personal_access_tokens_controller_spec.rb4
-rw-r--r--spec/controllers/profiles/preferences_controller_spec.rb4
-rw-r--r--spec/controllers/profiles/two_factor_auths_controller_spec.rb2
-rw-r--r--spec/controllers/profiles_controller_spec.rb28
-rw-r--r--spec/controllers/projects/artifacts_controller_spec.rb30
-rw-r--r--spec/controllers/projects/avatars_controller_spec.rb7
-rw-r--r--spec/controllers/projects/badges_controller_spec.rb2
-rw-r--r--spec/controllers/projects/blame_controller_spec.rb8
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb62
-rw-r--r--spec/controllers/projects/boards_controller_spec.rb14
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb175
-rw-r--r--spec/controllers/projects/ci/lints_controller_spec.rb10
-rw-r--r--spec/controllers/projects/clusters/applications_controller_spec.rb2
-rw-r--r--spec/controllers/projects/clusters_controller_spec.rb46
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb106
-rw-r--r--spec/controllers/projects/commits_controller_spec.rb62
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb10
-rw-r--r--spec/controllers/projects/cycle_analytics_controller_spec.rb12
-rw-r--r--spec/controllers/projects/deploy_keys_controller_spec.rb60
-rw-r--r--spec/controllers/projects/deployments_controller_spec.rb18
-rw-r--r--spec/controllers/projects/discussions_controller_spec.rb34
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb64
-rw-r--r--spec/controllers/projects/find_file_controller_spec.rb16
-rw-r--r--spec/controllers/projects/forks_controller_spec.rb20
-rw-r--r--spec/controllers/projects/graphs_controller_spec.rb4
-rw-r--r--spec/controllers/projects/group_links_controller_spec.rb28
-rw-r--r--spec/controllers/projects/hooks_controller_spec.rb4
-rw-r--r--spec/controllers/projects/imports_controller_spec.rb20
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb187
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb159
-rw-r--r--spec/controllers/projects/labels_controller_spec.rb30
-rw-r--r--spec/controllers/projects/mattermosts_controller_spec.rb14
-rw-r--r--spec/controllers/projects/merge_requests/conflicts_controller_spec.rb42
-rw-r--r--spec/controllers/projects/merge_requests/creations_controller_spec.rb32
-rw-r--r--spec/controllers/projects/merge_requests/diffs_controller_spec.rb4
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb220
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb24
-rw-r--r--spec/controllers/projects/mirrors_controller_spec.rb4
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb92
-rw-r--r--spec/controllers/projects/pages_controller_spec.rb16
-rw-r--r--spec/controllers/projects/pages_domains_controller_spec.rb34
-rw-r--r--spec/controllers/projects/pipeline_schedules_controller_spec.rb42
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb56
-rw-r--r--spec/controllers/projects/pipelines_settings_controller_spec.rb2
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb156
-rw-r--r--spec/controllers/projects/prometheus/metrics_controller_spec.rb10
-rw-r--r--spec/controllers/projects/protected_branches_controller_spec.rb14
-rw-r--r--spec/controllers/projects/protected_tags_controller_spec.rb4
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb17
-rw-r--r--spec/controllers/projects/refs_controller_spec.rb24
-rw-r--r--spec/controllers/projects/registry/repositories_controller_spec.rb14
-rw-r--r--spec/controllers/projects/registry/tags_controller_spec.rb18
-rw-r--r--spec/controllers/projects/releases_controller_spec.rb78
-rw-r--r--spec/controllers/projects/repositories_controller_spec.rb16
-rw-r--r--spec/controllers/projects/runners_controller_spec.rb8
-rw-r--r--spec/controllers/projects/serverless/functions_controller_spec.rb72
-rw-r--r--spec/controllers/projects/services_controller_spec.rb66
-rw-r--r--spec/controllers/projects/settings/ci_cd_controller_spec.rb16
-rw-r--r--spec/controllers/projects/settings/integrations_controller_spec.rb2
-rw-r--r--spec/controllers/projects/settings/repository_controller_spec.rb15
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb46
-rw-r--r--spec/controllers/projects/tags/releases_controller_spec.rb58
-rw-r--r--spec/controllers/projects/tags_controller_spec.rb4
-rw-r--r--spec/controllers/projects/templates_controller_spec.rb8
-rw-r--r--spec/controllers/projects/todos_controller_spec.rb20
-rw-r--r--spec/controllers/projects/tree_controller_spec.rb30
-rw-r--r--spec/controllers/projects/uploads_controller_spec.rb4
-rw-r--r--spec/controllers/projects/variables_controller_spec.rb10
-rw-r--r--spec/controllers/projects/wikis_controller_spec.rb33
-rw-r--r--spec/controllers/projects_controller_spec.rb201
-rw-r--r--spec/controllers/registrations_controller_spec.rb22
-rw-r--r--spec/controllers/search_controller_spec.rb12
-rw-r--r--spec/controllers/sent_notifications_controller_spec.rb10
-rw-r--r--spec/controllers/sessions_controller_spec.rb24
-rw-r--r--spec/controllers/snippets/notes_controller_spec.rb28
-rw-r--r--spec/controllers/snippets_controller_spec.rb61
-rw-r--r--spec/controllers/uploads_controller_spec.rb102
-rw-r--r--spec/controllers/user_callouts_controller_spec.rb2
-rw-r--r--spec/controllers/users/terms_controller_spec.rb14
-rw-r--r--spec/controllers/users_controller_spec.rb62
-rw-r--r--spec/db/schema_spec.rb3
-rw-r--r--spec/factories/appearances.rb4
-rw-r--r--spec/factories/ci/bridge.rb17
-rw-r--r--spec/factories/notes.rb15
-rw-r--r--spec/factories/pool_repositories.rb27
-rw-r--r--spec/factories/project_repositories.rb12
-rw-r--r--spec/factories/releases.rb1
-rw-r--r--spec/factories/suggestions.rb20
-rw-r--r--spec/features/admin/admin_disables_git_access_protocol_spec.rb59
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb1
-rw-r--r--spec/features/groups/clusters/user_spec.rb126
-rw-r--r--spec/features/groups/show_spec.rb4
-rw-r--r--spec/features/help_pages_spec.rb18
-rw-r--r--spec/features/ics/project_issues_spec.rb9
-rw-r--r--spec/features/issuables/default_sort_order_spec.rb179
-rw-r--r--spec/features/issuables/sorting_list_spec.rb226
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb2
-rw-r--r--spec/features/issues/user_creates_issue_spec.rb16
-rw-r--r--spec/features/issues/user_sorts_issues_spec.rb8
-rw-r--r--spec/features/merge_request/user_awards_emoji_spec.rb12
-rw-r--r--spec/features/merge_request/user_expands_diff_spec.rb7
-rw-r--r--spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb2
-rw-r--r--spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb18
-rw-r--r--spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb32
-rw-r--r--spec/features/merge_request/user_suggests_changes_on_diff_spec.rb85
-rw-r--r--spec/features/merge_requests/user_sorts_merge_requests_spec.rb12
-rw-r--r--spec/features/projects/clusters/applications_spec.rb38
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb2
-rw-r--r--spec/features/projects/files/user_browses_files_spec.rb1
-rw-r--r--spec/features/projects/jobs_spec.rb87
-rw-r--r--spec/features/projects/labels/issues_sorted_by_priority_spec.rb4
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb15
-rw-r--r--spec/features/projects/labels/user_views_labels_spec.rb8
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb150
-rw-r--r--spec/features/projects/serverless/functions_spec.rb49
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb21
-rw-r--r--spec/features/projects/show/developer_views_empty_project_instructions_spec.rb49
-rw-r--r--spec/features/projects/show/user_manages_notifications_spec.rb15
-rw-r--r--spec/features/projects/show/user_sees_collaboration_links_spec.rb23
-rw-r--r--spec/features/projects/show/user_sees_git_instructions_spec.rb4
-rw-r--r--spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb90
-rw-r--r--spec/features/tags/master_views_tags_spec.rb2
-rw-r--r--spec/features/users/overview_spec.rb6
-rw-r--r--spec/finders/cluster_ancestors_finder_spec.rb77
-rw-r--r--spec/finders/concerns/finder_with_cross_project_access_spec.rb16
-rw-r--r--spec/finders/events_finder_spec.rb17
-rw-r--r--spec/finders/group_members_finder_spec.rb2
-rw-r--r--spec/finders/issues_finder_spec.rb127
-rw-r--r--spec/finders/projects/serverless/functions_finder_spec.rb60
-rw-r--r--spec/finders/user_recent_events_finder_spec.rb3
-rw-r--r--spec/fixtures/api/schemas/cluster_status.json3
-rw-r--r--spec/fixtures/api/schemas/entities/diff_line.json3
-rw-r--r--spec/fixtures/api/schemas/entities/issuable_sidebar_todo.json8
-rw-r--r--spec/fixtures/api/schemas/entities/issue_sidebar.json54
-rw-r--r--spec/fixtures/api/schemas/entities/issue_sidebar_extras.json18
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_basic.json6
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_sidebar.json56
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_sidebar_extras.json21
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_widget.json3
-rw-r--r--spec/fixtures/api/schemas/job/trigger.json3
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/milestone.json22
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/milestones.json21
-rw-r--r--spec/fixtures/bfg_object_map.txt1
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json32
-rw-r--r--spec/fixtures/security-reports/master/gl-dependency-scanning-report.json32
-rw-r--r--spec/fixtures/symlink_export.tar.gzbin0 -> 435 bytes
-rw-r--r--spec/frontend/.eslintrc.yml2
-rw-r--r--spec/frontend/dummy_spec.js1
-rw-r--r--spec/frontend/helpers/test_constants.js2
-rw-r--r--spec/frontend/pages/profiles/show/emoji_menu_spec.js (renamed from spec/javascripts/pages/profiles/show/emoji_menu_spec.js)6
-rw-r--r--spec/frontend/test_setup.js16
-rw-r--r--spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js (renamed from spec/javascripts/vue_shared/components/notes/timeline_entry_item_spec.js)0
-rw-r--r--spec/helpers/emails_helper_spec.rb55
-rw-r--r--spec/helpers/events_helper_spec.rb32
-rw-r--r--spec/helpers/issuables_helper_spec.rb36
-rw-r--r--spec/helpers/projects_helper_spec.rb147
-rw-r--r--spec/helpers/sorting_helper_spec.rb47
-rw-r--r--spec/helpers/storage_helper_spec.rb6
-rw-r--r--spec/helpers/version_check_helper_spec.rb12
-rw-r--r--spec/initializers/lograge_spec.rb38
-rw-r--r--spec/initializers/zz_metrics_spec.rb (renamed from spec/initializers/8_metrics_spec.rb)2
-rw-r--r--spec/javascripts/api_spec.js51
-rw-r--r--spec/javascripts/blob_edit/blob_bundle_spec.js7
-rw-r--r--spec/javascripts/boards/boards_store_spec.js7
-rw-r--r--spec/javascripts/boards/components/issue_due_date_spec.js7
-rw-r--r--spec/javascripts/boards/issue_spec.js18
-rw-r--r--spec/javascripts/boards/mock_data.js69
-rw-r--r--spec/javascripts/ci_variable_list/ci_variable_list_spec.js2
-rw-r--r--spec/javascripts/clusters/components/applications_spec.js111
-rw-r--r--spec/javascripts/clusters/services/mock_data.js2
-rw-r--r--spec/javascripts/clusters/stores/clusters_store_spec.js1
-rw-r--r--spec/javascripts/diffs/components/app_spec.js91
-rw-r--r--spec/javascripts/diffs/components/diff_content_spec.js40
-rw-r--r--spec/javascripts/diffs/components/diff_file_spec.js26
-rw-r--r--spec/javascripts/diffs/components/no_changes_spec.js41
-rw-r--r--spec/javascripts/diffs/mock_data/diff_discussions.js17
-rw-r--r--spec/javascripts/diffs/store/actions_spec.js144
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js150
-rw-r--r--spec/javascripts/diffs/store/utils_spec.js46
-rw-r--r--spec/javascripts/environments/environment_terminal_button_spec.js48
-rw-r--r--spec/javascripts/filtered_search/dropdown_utils_spec.js126
-rw-r--r--spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js43
-rw-r--r--spec/javascripts/fixtures/blob.rb9
-rw-r--r--spec/javascripts/fixtures/boards.rb7
-rw-r--r--spec/javascripts/fixtures/branches.rb3
-rw-r--r--spec/javascripts/fixtures/clusters.rb3
-rw-r--r--spec/javascripts/fixtures/commit.rb2
-rw-r--r--spec/javascripts/fixtures/deploy_keys.rb6
-rw-r--r--spec/javascripts/fixtures/groups.rb6
-rw-r--r--spec/javascripts/fixtures/issues.rb6
-rw-r--r--spec/javascripts/fixtures/jobs.rb13
-rw-r--r--spec/javascripts/fixtures/labels.rb12
-rw-r--r--spec/javascripts/fixtures/merge_requests.rb12
-rw-r--r--spec/javascripts/fixtures/merge_requests_diffs.rb4
-rw-r--r--spec/javascripts/fixtures/pipeline_schedules.rb6
-rw-r--r--spec/javascripts/fixtures/pipelines.rb6
-rw-r--r--spec/javascripts/fixtures/projects.rb15
-rw-r--r--spec/javascripts/fixtures/prometheus_service.rb3
-rw-r--r--spec/javascripts/fixtures/services.rb3
-rw-r--r--spec/javascripts/fixtures/snippet.rb2
-rw-r--r--spec/javascripts/fixtures/todos.rb6
-rw-r--r--spec/javascripts/fixtures/u2f.rb2
-rw-r--r--spec/javascripts/image_diff/helpers/badge_helper_spec.js4
-rw-r--r--spec/javascripts/jobs/components/artifacts_block_spec.js13
-rw-r--r--spec/javascripts/jobs/components/sidebar_spec.js8
-rw-r--r--spec/javascripts/jobs/components/trigger_block_spec.js28
-rw-r--r--spec/javascripts/lib/utils/dom_utils_spec.js54
-rw-r--r--spec/javascripts/lib/utils/file_upload_spec.js36
-rw-r--r--spec/javascripts/lib/utils/users_cache_spec.js110
-rw-r--r--spec/javascripts/notes/components/note_app_spec.js23
-rw-r--r--spec/javascripts/notes/components/note_edited_text_spec.js2
-rw-r--r--spec/javascripts/notes/components/note_header_spec.js3
-rw-r--r--spec/javascripts/notes/components/noteable_discussion_spec.js104
-rw-r--r--spec/javascripts/notes/mock_data.js4
-rw-r--r--spec/javascripts/notes/stores/actions_spec.js2
-rw-r--r--spec/javascripts/notes/stores/mutation_spec.js71
-rw-r--r--spec/javascripts/pipelines/pipeline_url_spec.js7
-rw-r--r--spec/javascripts/registry/components/app_spec.js61
-rw-r--r--spec/javascripts/registry/components/collapsible_container_spec.js43
-rw-r--r--spec/javascripts/registry/stores/actions_spec.js50
-rw-r--r--spec/javascripts/releases/components/app_spec.js79
-rw-r--r--spec/javascripts/releases/components/release_block_spec.js136
-rw-r--r--spec/javascripts/releases/mock_data.js128
-rw-r--r--spec/javascripts/releases/store/actions_spec.js98
-rw-r--r--spec/javascripts/releases/store/helpers.js6
-rw-r--r--spec/javascripts/releases/store/mutations_spec.js47
-rw-r--r--spec/javascripts/sidebar/mock_data.js4
-rw-r--r--spec/javascripts/sidebar/sidebar_mediator_spec.js4
-rw-r--r--spec/javascripts/user_popovers_spec.js66
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js25
-rw-r--r--spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js26
-rw-r--r--spec/javascripts/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js23
-rw-r--r--spec/javascripts/vue_shared/components/issue/issue_assignees_spec.js114
-rw-r--r--spec/javascripts/vue_shared/components/issue/issue_milestone_spec.js234
-rw-r--r--spec/javascripts/vue_shared/components/markdown/field_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/markdown/header_spec.js15
-rw-r--r--spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js69
-rw-r--r--spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js79
-rw-r--r--spec/javascripts/vue_shared/components/markdown/suggestions_spec.js125
-rw-r--r--spec/javascripts/vue_shared/components/user_avatar/user_avatar_image_spec.js13
-rw-r--r--spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js4
-rw-r--r--spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js150
-rw-r--r--spec/lib/backup/repository_spec.rb13
-rw-r--r--spec/lib/banzai/filter/front_matter_filter_spec.rb140
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb77
-rw-r--r--spec/lib/banzai/filter/suggestion_filter_spec.rb26
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb53
-rw-r--r--spec/lib/banzai/suggestions_parser_spec.rb32
-rw-r--r--spec/lib/constraints/feature_constrainer_spec.rb11
-rw-r--r--spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb7
-rw-r--r--spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb7
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb94
-rw-r--r--spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb20
-rw-r--r--spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb32
-rw-r--r--spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_improved_spec.rb57
-rw-r--r--spec/lib/gitlab/bare_repository_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/branch_push_merge_commit_analyzer_spec.rb62
-rw-r--r--spec/lib/gitlab/ci/config/entry/except_policy_spec.rb15
-rw-r--r--spec/lib/gitlab/ci/config/entry/global_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/config/entry/jobs_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/config/entry/only_policy_spec.rb15
-rw-r--r--spec/lib/gitlab/ci/config/entry/policy_spec.rb167
-rw-r--r--spec/lib/gitlab/ci/parsers_spec.rb (renamed from spec/lib/gitlab/ci/parsers/test_spec.rb)8
-rw-r--r--spec/lib/gitlab/correlation_id_spec.rb77
-rw-r--r--spec/lib/gitlab/current_settings_spec.rb80
-rw-r--r--spec/lib/gitlab/database/count/exact_count_strategy_spec.rb6
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb7
-rw-r--r--spec/lib/gitlab/database_spec.rb15
-rw-r--r--spec/lib/gitlab/diff/file_spec.rb89
-rw-r--r--spec/lib/gitlab/discussions_diff/file_collection_spec.rb61
-rw-r--r--spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb102
-rw-r--r--spec/lib/gitlab/encoding_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/git/object_pool_spec.rb79
-rw-r--r--spec/lib/gitlab/git/repository_cleaner_spec.rb62
-rw-r--r--spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb19
-rw-r--r--spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb46
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb82
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb7
-rw-r--r--spec/lib/gitlab/gpg/commit_spec.rb22
-rw-r--r--spec/lib/gitlab/gpg_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml3
-rw-r--r--spec/lib/gitlab/import_export/command_line_util_spec.rb38
-rw-r--r--spec/lib/gitlab/import_export/file_importer_spec.rb13
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml3
-rw-r--r--spec/lib/gitlab/json_cache_spec.rb401
-rw-r--r--spec/lib/gitlab/json_logger_spec.rb6
-rw-r--r--spec/lib/gitlab/lfs_token_spec.rb167
-rw-r--r--spec/lib/gitlab/middleware/read_only_spec.rb1
-rw-r--r--spec/lib/gitlab/object_hierarchy_spec.rb (renamed from spec/lib/gitlab/group_hierarchy_spec.rb)10
-rw-r--r--spec/lib/gitlab/project_authorizations_spec.rb4
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb131
-rw-r--r--spec/lib/gitlab/prometheus/query_variables_spec.rb4
-rw-r--r--spec/lib/gitlab/safe_request_store_spec.rb12
-rw-r--r--spec/lib/gitlab/search/found_blob_spec.rb138
-rw-r--r--spec/lib/gitlab/sentry_spec.rb22
-rw-r--r--spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb3
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/correlation_injector_spec.rb47
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/correlation_logger_spec.rb35
-rw-r--r--spec/lib/gitlab/sql/glob_spec.rb3
-rw-r--r--spec/lib/gitlab/template/finders/global_template_finder_spec.rb35
-rw-r--r--spec/lib/gitlab/template/finders/repo_template_finders_spec.rb4
-rw-r--r--spec/lib/gitlab/upgrader_spec.rb40
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb21
-rw-r--r--spec/lib/gitlab/url_sanitizer_spec.rb1
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb26
-rw-r--r--spec/lib/gitlab/utils/override_spec.rb32
-rw-r--r--spec/lib/gitlab/utils_spec.rb34
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb2
-rw-r--r--spec/lib/mattermost/session_spec.rb12
-rw-r--r--spec/lib/omni_auth/strategies/jwt_spec.rb70
-rw-r--r--spec/mailers/notify_spec.rb22
-rw-r--r--spec/migrations/backfill_releases_name_with_tag_name_spec.rb23
-rw-r--r--spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb68
-rw-r--r--spec/migrations/populate_mr_metrics_with_events_data_spec.rb47
-rw-r--r--spec/models/appearance_spec.rb32
-rw-r--r--spec/models/broadcast_message_spec.rb18
-rw-r--r--spec/models/ci/bridge_spec.rb25
-rw-r--r--spec/models/ci/build_spec.rb6
-rw-r--r--spec/models/ci/pipeline_spec.rb44
-rw-r--r--spec/models/clusters/applications/knative_spec.rb48
-rw-r--r--spec/models/clusters/applications/runner_spec.rb6
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb7
-rw-r--r--spec/models/commit_spec.rb2
-rw-r--r--spec/models/concerns/avatarable_spec.rb37
-rw-r--r--spec/models/concerns/cacheable_attributes_spec.rb8
-rw-r--r--spec/models/concerns/chronic_duration_attribute_spec.rb4
-rw-r--r--spec/models/concerns/discussion_on_diff_spec.rb38
-rw-r--r--spec/models/concerns/token_authenticatable_spec.rb86
-rw-r--r--spec/models/diff_note_spec.rb33
-rw-r--r--spec/models/group_spec.rb6
-rw-r--r--spec/models/member_spec.rb23
-rw-r--r--spec/models/members/group_member_spec.rb22
-rw-r--r--spec/models/members/project_member_spec.rb19
-rw-r--r--spec/models/merge_request_diff_spec.rb2
-rw-r--r--spec/models/merge_request_spec.rb79
-rw-r--r--spec/models/milestone_spec.rb9
-rw-r--r--spec/models/namespace_spec.rb54
-rw-r--r--spec/models/notification_setting_spec.rb7
-rw-r--r--spec/models/pool_repository_spec.rb25
-rw-r--r--spec/models/project_import_data_spec.rb42
-rw-r--r--spec/models/project_spec.rb160
-rw-r--r--spec/models/release_spec.rb1
-rw-r--r--spec/models/remote_mirror_spec.rb39
-rw-r--r--spec/models/suggestion_spec.rb57
-rw-r--r--spec/models/uploads/fog_spec.rb69
-rw-r--r--spec/models/uploads/local_spec.rb45
-rw-r--r--spec/models/user_spec.rb17
-rw-r--r--spec/policies/group_policy_spec.rb8
-rw-r--r--spec/presenters/clusters/cluster_presenter_spec.rb140
-rw-r--r--spec/presenters/group_member_presenter_spec.rb8
-rw-r--r--spec/presenters/project_member_presenter_spec.rb6
-rw-r--r--spec/presenters/project_presenter_spec.rb96
-rw-r--r--spec/requests/api/access_requests_spec.rb2
-rw-r--r--spec/requests/api/applications_spec.rb16
-rw-r--r--spec/requests/api/avatar_spec.rb12
-rw-r--r--spec/requests/api/award_emoji_spec.rb20
-rw-r--r--spec/requests/api/badges_spec.rb20
-rw-r--r--spec/requests/api/boards_spec.rb6
-rw-r--r--spec/requests/api/branches_spec.rb26
-rw-r--r--spec/requests/api/broadcast_messages_spec.rb24
-rw-r--r--spec/requests/api/commit_statuses_spec.rb36
-rw-r--r--spec/requests/api/commits_spec.rb106
-rw-r--r--spec/requests/api/deploy_keys_spec.rb20
-rw-r--r--spec/requests/api/doorkeeper_access_spec.rb10
-rw-r--r--spec/requests/api/environments_spec.rb18
-rw-r--r--spec/requests/api/events_spec.rb66
-rw-r--r--spec/requests/api/features_spec.rb24
-rw-r--r--spec/requests/api/files_spec.rb82
-rw-r--r--spec/requests/api/group_boards_spec.rb2
-rw-r--r--spec/requests/api/group_variables_spec.rb8
-rw-r--r--spec/requests/api/groups_spec.rb70
-rw-r--r--spec/requests/api/helpers_spec.rb32
-rw-r--r--spec/requests/api/internal_spec.rb165
-rw-r--r--spec/requests/api/issues_spec.rb269
-rw-r--r--spec/requests/api/jobs_spec.rb142
-rw-r--r--spec/requests/api/labels_spec.rb152
-rw-r--r--spec/requests/api/lint_spec.rb6
-rw-r--r--spec/requests/api/markdown_spec.rb2
-rw-r--r--spec/requests/api/members_spec.rb65
-rw-r--r--spec/requests/api/merge_requests_spec.rb184
-rw-r--r--spec/requests/api/notes_spec.rb6
-rw-r--r--spec/requests/api/notification_settings_spec.rb10
-rw-r--r--spec/requests/api/oauth_tokens_spec.rb2
-rw-r--r--spec/requests/api/pages_domains_spec.rb28
-rw-r--r--spec/requests/api/pipeline_schedules_spec.rb24
-rw-r--r--spec/requests/api/pipelines_spec.rb52
-rw-r--r--spec/requests/api/project_export_spec.rb6
-rw-r--r--spec/requests/api/project_hooks_spec.rb17
-rw-r--r--spec/requests/api/project_import_spec.rb52
-rw-r--r--spec/requests/api/project_milestones_spec.rb2
-rw-r--r--spec/requests/api/project_snapshots_spec.rb4
-rw-r--r--spec/requests/api/project_snippets_spec.rb16
-rw-r--r--spec/requests/api/project_templates_spec.rb6
-rw-r--r--spec/requests/api/projects_spec.rb182
-rw-r--r--spec/requests/api/protected_branches_spec.rb24
-rw-r--r--spec/requests/api/protected_tags_spec.rb18
-rw-r--r--spec/requests/api/redacted_events_spec.rb68
-rw-r--r--spec/requests/api/repositories_spec.rb20
-rw-r--r--spec/requests/api/runner_spec.rb108
-rw-r--r--spec/requests/api/runners_spec.rb24
-rw-r--r--spec/requests/api/search_spec.rb70
-rw-r--r--spec/requests/api/services_spec.rb16
-rw-r--r--spec/requests/api/settings_spec.rb54
-rw-r--r--spec/requests/api/snippets_spec.rb14
-rw-r--r--spec/requests/api/submodules_spec.rb12
-rw-r--r--spec/requests/api/suggestions_spec.rb83
-rw-r--r--spec/requests/api/system_hooks_spec.rb18
-rw-r--r--spec/requests/api/tags_spec.rb30
-rw-r--r--spec/requests/api/todos_spec.rb12
-rw-r--r--spec/requests/api/triggers_spec.rb30
-rw-r--r--spec/requests/api/users_spec.rb228
-rw-r--r--spec/requests/api/variables_spec.rb8
-rw-r--r--spec/requests/api/wikis_spec.rb52
-rw-r--r--spec/requests/git_http_spec.rb6
-rw-r--r--spec/requests/jwt_controller_spec.rb28
-rw-r--r--spec/requests/lfs_http_spec.rb26
-rw-r--r--spec/requests/lfs_locks_api_spec.rb4
-rw-r--r--spec/requests/oauth_tokens_spec.rb12
-rw-r--r--spec/requests/openid_connect_spec.rb16
-rw-r--r--spec/requests/rack_attack_global_spec.rb4
-rw-r--r--spec/requests/request_profiler_spec.rb2
-rw-r--r--spec/rubocop/cop/inject_enterprise_edition_module_spec.rb133
-rw-r--r--spec/rubocop/cop/migration/add_timestamps_spec.rb6
-rw-r--r--spec/rubocop/cop/migration/datetime_spec.rb8
-rw-r--r--spec/rubocop/cop/migration/timestamps_spec.rb6
-rw-r--r--spec/serializers/diff_file_entity_spec.rb33
-rw-r--r--spec/serializers/discussion_diff_file_entity_spec.rb39
-rw-r--r--spec/serializers/discussion_entity_spec.rb8
-rw-r--r--spec/serializers/entity_date_helper_spec.rb12
-rw-r--r--spec/serializers/environment_entity_spec.rb30
-rw-r--r--spec/serializers/issue_board_entity_spec.rb31
-rw-r--r--spec/serializers/issue_serializer_spec.rb10
-rw-r--r--spec/serializers/merge_request_basic_serializer_spec.rb16
-rw-r--r--spec/serializers/merge_request_serializer_spec.rb12
-rw-r--r--spec/serializers/pipeline_entity_spec.rb2
-rw-r--r--spec/serializers/suggestion_entity_spec.rb23
-rw-r--r--spec/serializers/trigger_variable_entity_spec.rb49
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb89
-rw-r--r--spec/services/ci/retry_build_service_spec.rb6
-rw-r--r--spec/services/clusters/applications/create_service_spec.rb25
-rw-r--r--spec/services/clusters/build_service_spec.rb25
-rw-r--r--spec/services/clusters/gcp/finalize_creation_service_spec.rb6
-rw-r--r--spec/services/clusters/update_service_spec.rb2
-rw-r--r--spec/services/create_release_service_spec.rb5
-rw-r--r--spec/services/groups/create_service_spec.rb4
-rw-r--r--spec/services/groups/nested_create_service_spec.rb2
-rw-r--r--spec/services/groups/transfer_service_spec.rb2
-rw-r--r--spec/services/issuable/common_system_notes_service_spec.rb47
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb73
-rw-r--r--spec/services/notes/update_service_spec.rb23
-rw-r--r--spec/services/notification_service_spec.rb62
-rw-r--r--spec/services/preview_markdown_service_spec.rb25
-rw-r--r--spec/services/projects/after_rename_service_spec.rb20
-rw-r--r--spec/services/projects/cleanup_service_spec.rb44
-rw-r--r--spec/services/projects/fork_service_spec.rb30
-rw-r--r--spec/services/projects/lfs_pointers/lfs_download_service_spec.rb12
-rw-r--r--spec/services/projects/transfer_service_spec.rb20
-rw-r--r--spec/services/suggestions/apply_service_spec.rb229
-rw-r--r--spec/services/suggestions/create_service_spec.rb110
-rw-r--r--spec/spec_helper.rb16
-rw-r--r--spec/support/api/boards_shared_examples.rb15
-rw-r--r--spec/support/api/milestones_shared_examples.rb31
-rw-r--r--spec/support/api/scopes/read_user_shared_examples.rb2
-rw-r--r--spec/support/api/time_tracking_shared_examples.rb22
-rw-r--r--spec/support/carrierwave.rb2
-rw-r--r--spec/support/controllers/githubish_import_controller_shared_examples.rb34
-rw-r--r--spec/support/controllers/sessionless_auth_controller_shared_examples.rb14
-rw-r--r--spec/support/db_cleaner.rb6
-rw-r--r--spec/support/features/discussion_comments_shared_example.rb10
-rw-r--r--spec/support/features/variable_list_shared_examples.rb46
-rw-r--r--spec/support/helpers/email_helpers.rb9
-rw-r--r--spec/support/helpers/fake_migration_classes.rb2
-rw-r--r--spec/support/helpers/features/sorting_helpers.rb4
-rw-r--r--spec/support/helpers/git_http_helpers.rb8
-rw-r--r--spec/support/helpers/graphql_helpers.rb2
-rw-r--r--spec/support/helpers/javascript_fixtures_helpers.rb7
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb66
-rw-r--r--spec/support/helpers/migrations_helpers.rb16
-rw-r--r--spec/support/helpers/test_env.rb3
-rw-r--r--spec/support/helpers/test_request_helpers.rb6
-rw-r--r--spec/support/import_export/export_file_helper.rb2
-rw-r--r--spec/support/issuables_requiring_filter_shared_examples.rb2
-rw-r--r--spec/support/services/migrate_to_ghost_user_service_shared_examples.rb2
-rw-r--r--spec/support/setup_builds_storage.rb2
-rw-r--r--spec/support/shared_examples/common_system_notes_examples.rb4
-rw-r--r--spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb32
-rw-r--r--spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb11
-rw-r--r--spec/support/shared_examples/file_finder.rb13
-rw-r--r--spec/support/shared_examples/issuables_list_metadata_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb44
-rw-r--r--spec/support/shared_examples/milestone_tabs_examples.rb2
-rw-r--r--spec/support/shared_examples/models/member_shared_examples.rb77
-rw-r--r--spec/support/shared_examples/models/with_uploads_shared_examples.rb60
-rw-r--r--spec/support/shared_examples/notify_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/only_except_policy_examples.rb167
-rw-r--r--spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb18
-rw-r--r--spec/support/shared_examples/requests/api/diff_discussions.rb8
-rw-r--r--spec/support/shared_examples/requests/api/discussions.rb16
-rw-r--r--spec/support/shared_examples/requests/api/merge_requests_list.rb16
-rw-r--r--spec/support/shared_examples/requests/api/notes.rb26
-rw-r--r--spec/support/shared_examples/requests/api/resolvable_discussions.rb16
-rw-r--r--spec/support/shared_examples/requests/api/status_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/serializers/diff_file_entity_examples.rb46
-rw-r--r--spec/support/shared_examples/update_invalid_issuable.rb6
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb1
-rw-r--r--spec/tasks/gitlab/storage_rake_spec.rb14
-rw-r--r--spec/tasks/gitlab/web_hook_rake_spec.rb92
-rw-r--r--spec/uploaders/namespace_file_uploader_spec.rb27
-rw-r--r--spec/uploaders/personal_file_uploader_spec.rb27
-rw-r--r--spec/validators/url_validator_spec.rb29
-rw-r--r--spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb26
-rw-r--r--spec/views/notify/changed_milestone_email.html.haml_spec.rb35
-rw-r--r--spec/views/projects/_home_panel.html.haml_spec.rb2
-rw-r--r--spec/views/projects/merge_requests/show.html.haml_spec.rb5
-rw-r--r--spec/workers/cluster_configure_worker_spec.rb (renamed from spec/workers/cluster_platform_configure_worker_spec.rb)2
-rw-r--r--spec/workers/cluster_provision_worker_spec.rb4
-rw-r--r--spec/workers/object_pool/create_worker_spec.rb59
-rw-r--r--spec/workers/object_pool/destroy_worker_spec.rb31
-rw-r--r--spec/workers/object_pool/join_worker_spec.rb35
-rw-r--r--spec/workers/prune_web_hook_logs_worker_spec.rb16
-rw-r--r--spec/workers/rebase_worker_spec.rb2
-rw-r--r--spec/workers/remove_old_web_hook_logs_worker_spec.rb18
-rw-r--r--spec/workers/repository_cleanup_worker_spec.rb55
-rw-r--r--spec/workers/repository_update_remote_mirror_worker_spec.rb13
-rw-r--r--spec/workers/stuck_merge_jobs_worker_spec.rb3
-rw-r--r--vendor/gitignore/CMake.gitignore1
-rw-r--r--vendor/gitignore/Drupal.gitignore77
-rw-r--r--vendor/gitignore/Global/Emacs.gitignore4
-rw-r--r--vendor/gitignore/Global/PuTTY.gitignore2
-rw-r--r--vendor/gitignore/Global/Virtuoso.gitignore18
-rw-r--r--vendor/gitignore/Global/Xcode.gitignore74
-rw-r--r--vendor/gitignore/Node.gitignore5
-rw-r--r--vendor/gitignore/Python.gitignore1
-rw-r--r--vendor/gitignore/Smalltalk.gitignore9
-rw-r--r--vendor/gitignore/TeX.gitignore4
-rw-r--r--vendor/gitignore/Unity.gitignore4
-rw-r--r--vendor/gitignore/VisualStudio.gitignore4
-rw-r--r--vendor/licenses.csv154
-rw-r--r--yarn.lock409
1778 files changed, 29616 insertions, 12168 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml
index ecd9f57b075..b0794bb7434 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -33,3 +33,4 @@ rules:
vue/no-unused-components: off
vue/no-use-v-if-with-v-for: off
vue/no-v-html: off
+ vue/use-v-on-exact: off
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a97414cbba8..3d12f4142ba 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -77,18 +77,6 @@ stages:
- mysql:5.7
- redis:alpine
-.rails4: &rails4
- allow_failure: false
- except:
- variables:
- - $CI_COMMIT_REF_NAME =~ /(^docs[\/-].*|.*-docs$)/
- - $CI_COMMIT_REF_NAME =~ /(^qa[\/-].*|.*-qa$)/
- - $CI_COMMIT_REF_NAME =~ /norails4/
- - $RAILS5_DISABLED
- variables:
- BUNDLE_GEMFILE: "Gemfile.rails4"
- RAILS5: "false"
-
# Skip all jobs except the ones that begin with 'docs/'.
# Used for commits including ONLY documentation changes.
# https://docs.gitlab.com/ce/development/documentation/#testing
@@ -180,18 +168,10 @@ stages:
<<: *rspec-metadata
<<: *use-pg
-.rspec-metadata-pg-rails4: &rspec-metadata-pg-rails4
- <<: *rspec-metadata-pg
- <<: *rails4
-
.rspec-metadata-mysql: &rspec-metadata-mysql
<<: *rspec-metadata
<<: *use-mysql
-.rspec-metadata-mysql-rails4: &rspec-metadata-mysql-rails4
- <<: *rspec-metadata-mysql
- <<: *rails4
-
.only-canonical-masters: &only-canonical-masters
only:
- master@gitlab-org/gitlab-ce
@@ -251,10 +231,16 @@ package-and-qa:
<<: *single-script-job
variables:
<<: *single-script-job-variables
+ API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}"
SCRIPT_NAME: trigger-build
retry: 0
script:
- gem install gitlab --no-document
+ - apk add --update openssl curl jq
+ - wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/review_apps/review-apps.sh
+ - chmod 755 review-apps.sh
+ - source ./review-apps.sh
+ - wait_for_job_to_be_done "gitlab:assets:compile"
- ./$SCRIPT_NAME omnibus
when: manual
only:
@@ -432,7 +418,6 @@ setup-test-env:
script:
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
- scripts/gitaly-test-build # Do not use 'bundle exec' here
- - BUNDLE_GEMFILE=Gemfile.rails4 bundle install $BUNDLE_INSTALL_FLAGS
artifacts:
expire_in: 7d
paths:
@@ -488,7 +473,6 @@ danger-review:
<<: *pull-cache
image: registry.gitlab.com/gitlab-org/gitlab-build-images:danger
stage: test
- allow_failure: true
dependencies: []
before_script: []
only:
@@ -514,14 +498,6 @@ rspec-mysql:
<<: *rspec-metadata-mysql
parallel: 50
-rspec-pg-rails4:
- <<: *rspec-metadata-pg-rails4
- parallel: 50
-
-rspec-mysql-rails4:
- <<: *rspec-metadata-mysql-rails4
- parallel: 50
-
static-analysis:
<<: *dedicated-no-docs-no-db-pull-cache-job
dependencies:
@@ -566,12 +542,6 @@ downtime_check:
- /(^docs[\/-].*|.*-docs$)/
- /(^qa[\/-].*|.*-qa$)/
-rails4_gemfile_lock_check:
- <<: *dedicated-no-docs-no-db-pull-cache-job
- <<: *except-docs-and-qa
- script:
- - scripts/rails4-gemfile-lock-check
-
ee_compat_check:
<<: *rake-exec
dependencies: []
@@ -636,8 +606,8 @@ gitlab:setup-mysql:
# Frontend-related jobs
gitlab:assets:compile:
- <<: *dedicated-no-docs-and-no-qa-pull-cache-job
- image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-git-2.18-chrome-69.0-node-8.x-yarn-1.2-graphicsmagick-1.3.29-docker-18.06.1
+ <<: *dedicated-no-docs-pull-cache-job
+ image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-git-2.18-chrome-69.0-node-8.x-yarn-1.12-graphicsmagick-1.3.29-docker-18.06.1
dependencies: []
services:
- docker:stable-dind
@@ -949,6 +919,8 @@ no_ee_check:
# GitLab Review apps
review-deploy:
<<: *review-base
+ retry: 2
+ allow_failure: true
variables:
GIT_DEPTH: "1"
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
@@ -978,6 +950,8 @@ review-deploy:
.review-qa-base: &review-qa-base
<<: *review-docker
+ retry: 2
+ allow_failure: true
variables:
<<: *review-docker-variables
API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}"
@@ -1005,10 +979,8 @@ review-deploy:
review-qa-smoke:
<<: *review-qa-base
- # retry: 2
script:
- gitlab-qa Test::Instance::Smoke "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
- allow_failure: true
review-qa-all:
<<: *review-qa-base
diff --git a/.gitlab/CODEOWNERS.disabled b/.gitlab/CODEOWNERS.disabled
index a4b773b15a9..b9f886c1d47 100644
--- a/.gitlab/CODEOWNERS.disabled
+++ b/.gitlab/CODEOWNERS.disabled
@@ -1,13 +1,13 @@
# Backend Maintainers are the default for all ruby files
-*.rb @ayufan @DouweM @dzaporozhets @grzesiek @nick.thomas @rspeicher @rymai @smcgivern
-*.rake @ayufan @DouweM @dzaporozhets @grzesiek @nick.thomas @rspeicher @rymai @smcgivern
+*.rb @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @nick.thomas @rspeicher @rymai @smcgivern
+*.rake @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @nick.thomas @rspeicher @rymai @smcgivern
# Technical writing team are the default reviewers for everything in `doc/`
/doc/ @axil @marcia
# Frontend maintainers should see everything in `app/assets/`
-app/assets/ @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann
-*.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann
+app/assets/ @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann @kushalpandya
+*.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann @kushalpandya
# Someone from the database team should review changes in `db/`
db/ @abrandl @NikolayS
diff --git a/.gitlab/issue_templates/Feature proposal.md b/.gitlab/issue_templates/Feature proposal.md
index c4065d3c4ea..639a236631d 100644
--- a/.gitlab/issue_templates/Feature proposal.md
+++ b/.gitlab/issue_templates/Feature proposal.md
@@ -1,14 +1,22 @@
### Problem to solve
+<!--- What problem do we solve? -->
+
+### Target audience
+
+<!--- For whom are we doing this? Include either a persona from https://design.gitlab.com/getting-started/personas or define a specific company role. e.a. "Release Manager" or "Security Analyst" -->
+
### Further details
-(Include use cases, benefits, and/or goals)
+<!--- Include use cases, benefits, and/or goals (contributes to our vision?) -->
### Proposal
+<!--- How are we going to solve the problem? -->
+
### What does success look like, and how can we measure that?
-(If no way to measure success, link to an issue that will implement a way to measure this)
+<!--- If no way to measure success, link to an issue that will implement a way to measure this -->
### Links / references
diff --git a/.rubocop.yml b/.rubocop.yml
index 741403af009..e8e550fdbde 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -8,7 +8,7 @@ require:
- rubocop-rspec
AllCops:
- TargetRailsVersion: 4.2
+ TargetRailsVersion: 5.0
Exclude:
- 'vendor/**/*'
- 'node_modules/**/*'
@@ -174,3 +174,9 @@ GitlabSecurity/PublicSend:
- 'ee/db/**/*'
- 'ee/lib/**/*.rake'
- 'ee/spec/**/*'
+
+Cop/InjectEnterpriseEditionModule:
+ Enabled: true
+ Exclude:
+ - 'spec/**/*'
+ - 'ee/spec/**/*'
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 571df7534cb..847a0f74aa2 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -107,12 +107,6 @@ Lint/UriEscapeUnescape:
Metrics/LineLength:
Max: 1310
-# Offense count: 2
-Naming/ConstantName:
- Exclude:
- - 'lib/gitlab/import_sources.rb'
- - 'lib/gitlab/ssh_public_key.rb'
-
# Offense count: 11
# Configuration parameters: EnforcedStyle.
# SupportedStyles: lowercase, uppercase
@@ -155,17 +149,6 @@ RSpec/ExpectChange:
RSpec/ExpectInHook:
Enabled: false
-# Offense count: 7
-# Configuration parameters: EnforcedStyle.
-# SupportedStyles: implicit, each, example
-RSpec/HookArgument:
- Exclude:
- - 'spec/spec_helper.rb'
- - 'spec/support/carrierwave.rb'
- - 'spec/support/db_cleaner.rb'
- - 'spec/support/gitaly.rb'
- - 'spec/support/setup_builds_storage.rb'
-
# Offense count: 19
# Configuration parameters: EnforcedStyle.
# SupportedStyles: it_behaves_like, it_should_behave_like
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d41e5c8642f..b4fa22ad70e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,297 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.6.0 (2018-12-22)
+
+### Security (24 changes, 1 of them is from the community)
+
+- Fix possible XSS attack in Markdown urls with spaces. !2599
+- Update rack to 2.0.6 (for QA environments). !23171 (Takuya Noguchi)
+- Bump nokogiri, loofah, and rack gems for security updates. !23204
+- Encrypt runners tokens. !23412
+- Encrypt CI/CD builds authentication tokens. !23436
+- Configure mermaid to not render HTML content in diagrams.
+- Fix a possible symlink time of check to time of use race condition in GitLab Pages.
+- Removed ability to see private group names when the group id is entered in the url.
+- Fix stored XSS for Environments.
+- Fix persistent symlink in project import.
+- Fixed ability of guest users to edit/delete comments on locked or confidential issues.
+- Fixed ability to comment on locked/confidential issues.
+- Fix CRLF vulnerability in Project hooks.
+- Fix SSRF in project integrations.
+- Resolve reflected XSS in Ouath authorize window.
+- Restrict Personal Access Tokens to API scope on web requests.
+- Provide email notification when a user changes their email address.
+- Don't expose confidential information in commit message list.
+- Validate LFS hrefs before downloading them.
+- Do not follow redirects in Prometheus service when making http requests to the configured api url.
+- Escape user fullname while rendering autocomplete template to prevent XSS.
+- Redact sensitive information on gitlab-workhorse log.
+- Fix milestone promotion authorization check.
+- Prevent a path traversal attack on global file templates.
+
+### Removed (1 change)
+
+- Remove obsolete gitlab_shell rake tasks. !22417
+
+### Fixed (86 changes, 13 of them are from the community)
+
+- Remove limit of 100 when searching repository code. !8671
+- Show error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)
+- Fix a bug where internal email pattern wasn't respected. !22516
+- Fix project selector consistency in groups issues / MRs / boards pages. !22612 (Heinrich Lee Yu)
+- Add empty state for graphs with no values. !22630
+- Fix navigating by unresolved discussions on Merge Request page. !22789
+- Fix "merged with [commit]" info for merge requests being merged automatically by other actions. !22794
+- Fixing regression issues on pages settings and details. !22821
+- Remove duplicate primary button in dashboard snippets on small viewports. !22902 (George Tsiolis)
+- Fix API::Namespaces routing to accept namepaces with dots. !22912
+- Switch kubernetes:active with checking in Auto-DevOps.gitlab-ci.yml. !22929
+- Avoid Gitaly RPC errors when fetching diff stats. !22995
+- Removes promote to group label for anonymous user. !23042 (Jacopo Beschi @jacopo-beschi)
+- Fix enabling project deploy key for admins. !23043
+- Align issue status label and confidential icon. !23046 (George Tsiolis)
+- Fix default sorting for subgroups and projects list. !23058 (Jacopo Beschi @jacopo-beschi)
+- Hashed Storage: allow migration to be retried in partially migrated projects. !23087
+- Fix line height of numbers in file blame view. !23090 (Johann Hubert Sonntagbauer)
+- Fixes an issue where default values from models would override values set in the interface (e.g. users would be set to external even though their emails matches the internal email address pattern). !23114
+- Remove display of local Sidekiq process in /admin/sidekiq. !23118
+- Fix unrelated deployment status in MR widget. !23175
+- Respect confirmed flag on secondary emails. !23181
+- Restrict member access level to be higher than that of any parent group. !23226
+- Return real deployment status to frontend. !23270
+- Handle force_remove_source_branch when creating merge request. !23281
+- Avoid creating invalid refs using rugged, shelling out for writing refs. !23286
+- Remove needless auto-capitalization on Wiki page titles. !23288
+- Modify the wording for the knative cluster application to match upstream. !23289 (Chris Baumbauer)
+- Change container width for project import. !23318 (George Tsiolis)
+- Validate chunk size when persist. !23341
+- Resolve Main navbar is broken in certain viewport widths. !23348
+- Gracefully handle references with null bytes. !23365
+- Display commit ID for commit diff discussion on merge request. !23370
+- Pass commit when posting diff discussions. !23371
+- Fix flash notice styling for fluid layout. !23382
+- Add monkey patch to unicorn to fix eof? problem. !23385
+- Commits API: Preserve file content in move operations if unspecified. !23387
+- Disable password autocomplete in mirror form fill. !23402
+- Fix "protected branches only" checkbox not set properly at init. !23409
+- Support RSA and ECDSA algorithms in Omniauth JWT provider. !23411 (Michael Tsyganov)
+- Make KUBECONFIG nil if KUBE_TOKEN is nil. !23414
+- Allow search and sort users at same time on admin users page. !23439
+- Fix: Unstar icon button is misaligned. !23444
+- Fix error when searching for group issues with priority or popularity sort. !23445
+- Fix Order By dropdown menu styling in tablet and mobile screens. !23446
+- Fix collapsing discussion replies. !23462
+- Gracefully handle unknown/invalid GPG keys. !23492
+- Fix multiple commits shade overlapping vertical discussion line. !23515
+- Use read_repository scope on read-only files API. !23534
+- Avoid 500's when serializing legacy diff notes. !23544
+- Fix web hook functionality when the database encryption key is too short. !23573
+- Hide Knative from group cluster applications until supported. !23577
+- Add top padding for nested environment items loading icon. !23580 (George Tsiolis)
+- Improve help and validation sections of maximum build timeout inputs. !23586
+- Fix milestone select in issue sidebar of issue boards. !23625
+- Fix gitlab:web_hook tasks. !23635
+- Avoid caching BroadcastMessage as an ActiveRecord object. !23662
+- Only allow strings in URL::Sanitizer.valid?. !23675
+- Fix a frozen string error in app/mailers/notify.rb. !23683
+- Fix a frozen string error in lib/gitlab/utils.rb. !23690
+- Fix MR resolved discussion counts being too low. !23710
+- Fix a potential frozen string error in app/mailers/notify.rb. !23728
+- Remove unnecessary div from MarkdownField to apply list styles correctly. !23733
+- Display reply field if resolved discussion has no replies. !23801
+- Restore kubernetes:active in Auto-DevOps.gitlab-ci.yml (reverts 22929). !23826
+- Fix mergeUrlParams with fragment URL. !54218 (Thomas Holder)
+- Fixed multiple diff line discussions not expanding.
+- Fixed diff files expanding not loading commit content.
+- Fixed styling of image comment badges on commits.
+- Resolve possible cherry pick API race condition.
+- When user clicks linenumber in MR changes, highlight that line.
+- Remove old webhook logs after 90 days, as documented, instead of after 2.
+- Add an external IP address to the knative cluster application page. (Chris Baumbauer)
+- Fixed duplicate discussions getting added to diff lines.
+- Fix deadlock on ChunkedIO.
+- Show tree collapse button for merge request commit diffs.
+- Use approximate count for big tables for usage statistics.
+- Lock writes to trace stream.
+- Ensure that SVG sprite icons are properly rendered in IE11.
+- Make new branch form fields' fonts consistent.
+- Open first 10 merge request files in IDE.
+- Prevent user from navigating away from file edit without commit.
+- Prevent empty button being rendered in empty state.
+- Adds margins between tags when a job is stuck.
+- Fix Image Lazy Loader for some older browsers.
+- Correctly styles tags in sidebar for job page.
+
+### Changed (34 changes, 9 of them are from the community)
+
+- Include new link in breadcrumb for issues, merge requests, milestones, and labels. !18515 (George Tsiolis)
+- Allow sorting issues and MRs in reverse order. !21438
+- Design improvements to project overview page. !22196
+- Remove auto deactivation when failed to create a pipeline via pipeline schedules. !22243
+- Use group clusters when deploying (DeploymentPlatform). !22308
+- Improve initial discussion rendering performance. !22607
+- removes partially matching of No Label filter and makes it case-insensitive. !22622 (Jacopo Beschi @jacopo-beschi)
+- Use search bar for filtering in dashboard issues / MRs. !22641 (Heinrich Lee Yu)
+- Show different empty state for filtered issues and MRs. !22775 (Heinrich Lee Yu)
+- Relocate JSONWebToken::HMACToken from EE. !22906
+- Resolve Add border around the repository file tree. !23018
+- Change breadcrumb title for contribution charts. !23071 (George Tsiolis)
+- Update environments metrics empty state. !23074 (George Tsiolis)
+- Refine cursor positioning in Markdown Editor for wrap tags. !23085 (Johann Hubert Sonntagbauer)
+- Use reports syntax for SAST in Auto DevOps. !23163
+- SystemCheck: Use a more reliable way to detect current Ruby version. !23291
+- Changed frontmatter filtering to support YAML, JSON, TOML, and arbitrary languages. !23331 (Travis Miller)
+- Don't remove failed install pods after installing GitLab managed applications. !23350
+- Expose merge request pipeline variables. !23398
+- Scope default MR search in WebIDE dropdown to current project. !23400
+- Show user contributions in correct timezone within user profile. !23419
+- Redesign of MR header sections (CE). !23465
+- Auto DevOps: Add echo for each branch of the deploy() function where we run helm upgrade. !23499
+- Updates service to update Kubernetes project namespaces and restricted service account if present. !23525
+- Adjust divider margin to comply with design specs. !23548
+- Adjust dropdown item and header padding to comply with design specs. !23552
+- Truncate merge request titles with periods instead of ellipsis. !23558
+- Remove close icon from projects dropdown in issue boards. !23567
+- Change dropdown divider color to gray-200 (#dfdfdf). !23592
+- Define the default value for only/except policies. !23765
+- Don't show Memory Usage for unmerged MRs.
+- reorder notification settings by noisy-ness. (C.J. Jameson)
+- Changed merge request filtering to be by path instead of name.
+- Make diff file headers sticky.
+
+### Performance (22 changes, 6 of them are from the community)
+
+- Upgrade to Ruby 2.5.3. !2806
+- Removes all the irrelevant code and columns that were migrated from the Project table over to the ProjectImportState table. !21497
+- Approximate counting strategy with TABLESAMPLE. !22650
+- Replace tooltip directive with gl-tooltip diretive in badges, cycle analytics, and diffs. !22770 (George Tsiolis)
+- Validate foreign keys being created and indexed for column with _id. !22808
+- Remove monospace extend. !23089 (George Tsiolis)
+- Use Nokogiri as the ActiveSupport XML backend. !23136
+- Improve memory performance by reducing dirty pages after fork(). !23169
+- Add partial index for ci_builds on project_id and status. !23268
+- Reduce Gitaly calls in projects dashboard. !23307
+- Batch load only data from same repository when lazy object is accessed. !23309
+- Add index for events on project_id and created_at. !23354
+- Remove index for notes on updated_at. !23356
+- Improves performance of Project#readme_url by caching the README path. !23357
+- Populate MR metrics with events table information (migration). !23564
+- Remove unused data from discussions endpoint. !23570
+- Speed up issue board lists in groups with many projects.
+- Use cached size when passing artifacts to Runner.
+- Enable even more frozen string for lib/gitlab. (gfyoung)
+- Enable even more frozen string in lib/gitlab/**/*.rb. (gfyoung)
+- Enable even more frozen string in lib/gitlab/**/*.rb. (gfyoung)
+- Enable even more frozen string for lib/gitlab. (gfyoung)
+
+### Added (32 changes, 13 of them are from the community)
+
+- Add ability to create group level clusters and install gitlab managed applications. !22450
+- Creates /create_merge_request quickaction. !22485 (Jacopo Beschi @jacopo-beschi)
+- Filter by None/Any for labels in issues/mrs API. !22622 (Jacopo Beschi @jacopo-beschi)
+- Chat message push notifications now include links back to GitLab branches. !22651 (Tony Castrogiovanni)
+- Added feature flag to signal content headers detection by Workhorse. !22667
+- Add Discord integration. !22684 (@blackst0ne)
+- Upgrade helm to 2.11.0 and upgrade on every install. !22693
+- Add knative client to kubeclient library. !22968 (cab105)
+- Allow SSH public-key authentication for push mirroring. !22982
+- Allow deleting a Pipeline via the API. !22988
+- #40635: Adds support for cert-manager. !23036 (Amit Rathi)
+- WebIDE: Pressing Ctrl-Enter while typing on the commit message now performs the commit action. !23049 (Thomas Pathier)
+- Adds Any option to label filters. !23111 (Jacopo Beschi @jacopo-beschi)
+- Added glob for CI changes detection. !23128 (Kirill Zaitsev)
+- Add model and relation to store repo full path in database. !23143
+- Add ability to render suggestions. !23147
+- Introduce Knative and Serverless Components. !23174 (Chris Baumbauer)
+- Use BFG object maps to clean projects. !23189
+- Merge request pipelines. !23217
+- Extended user centric tooltips on issue and MR page. !23231
+- Add a rebase API endpoint for merge requests. !23296
+- Add config to prohibit impersonation. !23338
+- Merge request pipeline tag, and adds tags to pipeline view. !23364
+- #52753: HTTPS for JupyterHub installation. !23479 (Amit Rathi)
+- Fill project_repositories for hashed storage projects. !23482
+- Ability to override email for cert-manager. !23503 (Amit Rathi)
+- Allow public forks to be deduplicated. !23508
+- Pipeline trigger variable values are hidden in the UI by default. Maintainers have the option to reveal them. !23518 (jhampton)
+- Add new endpoint to download single artifact file for a ref. !23538
+- Log and pass correlation-id between Unicorn, Sidekiq and Gitaly.
+- Allow user to scroll to top of tab on MR page.
+- Adds states to the deployment widget.
+
+### Other (54 changes, 30 of them are from the community)
+
+- Switch to Rails 5. !21492
+- Migration to write fullpath in all repository configs. !22322
+- Rails5: env is deprecated and will be removed from Rails 5.1. !22626 (Jasper Maes)
+- Update haml_lint to 0.28.0. !22660 (Takuya Noguchi)
+- Update ffaker to 2.10.0. !22661 (Takuya Noguchi)
+- Drop gcp_clusters table. !22713
+- Upgrade minimum required Git version to 2.18.0. !22803
+- Adds new icon size to Vue icon component. !22899
+- Make sure there's only one slash as path separator. !22954
+- Show HTTP response code for Kubernetes errors. !22964
+- Update config map for gitlab managed application if already present on install. !22969
+- Drop default value on status column in deployments table. !22971
+- UI improvements to user's profile. !22977
+- Update asana to 0.8.1. !23039 (Takuya Noguchi)
+- Update asciidoctor to 1.5.8. !23047 (Takuya Noguchi)
+- Make auto-generated icons for subgroups in the breadcrumb dropdown display as a circle. !23062 (Thomas Pathier)
+- Make reply shortcut only quote selected discussion text. !23096 (Thomas Pathier)
+- Fix typo in notebook props. !23103 (George Tsiolis)
+- Fix typos in lib. !23106 (George Tsiolis)
+- Rename diffs store variable. !23123 (George Tsiolis)
+- Fix overlapping navbar separator and overflowing navbar dropdown on small displays. !23126 (Thomas Pathier)
+- Show what RPC is called in the performance bar. !23140
+- Updated Gitaly to v0.133.0. !23148
+- Rails5: Passing a class as a value in an Active Record query is deprecated. !23164 (Jasper Maes)
+- Fix project identicon aligning Harry Kiselev. !23166 (Harry Kiselev)
+- Fix horizontal scrollbar overlapping on horizontal scrolling-tabs. !23167 (Harry Kiselev)
+- Fix bottom paddings of profile header and some markup updates of profile. !23168 (Harry Kiselev)
+- Fixes to AWS documentation spelling and grammar. !23198 (Brendan O'Leary)
+- Adds a PHILOSOPHY.md which references GitLab Product Handbook. !23200
+- Externalize strings from `/app/views/invites`. !23205 (Tao Wang)
+- Externalize strings from `/app/views/project/runners`. !23208 (Tao Wang)
+- Fix typo for scheduled pipeline. !23218 (Davy Defaud)
+- Force content disposition attachment to several endpoints. !23223
+- Upgrade kubeclient to 4.0.0. !23261 (Praveen Arimbrathodiyil @pravi)
+- Update used version of Runner Helm Chart to 0.1.38. !23304
+- render :nothing option is deprecated, Use head method to respond with empty response body. !23311 (Jasper Maes)
+- Passing an argument to force an association to reload is now deprecated. !23334 (Jasper Maes)
+- Externalize strings from `/app/views/snippets`. !23351 (Tao Wang)
+- Fix deprecation: You are passing an instance of ActiveRecord::Base to. !23369 (Jasper Maes)
+- Resolve status emoji being replaced by avatar on mobile. !23408
+- Fix deprecation: render :text is deprecated because it does not actually render a text/plain response. !23425 (Jasper Maes)
+- Fix lack of documentation on how to fetch a snippet's content using API. !23448 (Colin Leroy)
+- Upgrade GitLab Workhorse to v7.3.0. !23489
+- Fallback to admin KUBE_TOKEN for project clusters only. !23527
+- Update used version of Runner Helm Chart to 0.1.39. !23633
+- Show primary button when all labels are prioritized. !23648 (George Tsiolis)
+- Upgrade workhorse to 7.6.0. !23694
+- Upgrade Gitaly to v1.7.1 for correlation-id logging. !23732
+- Fix due date test. !23845
+- Remove unused project method. !54103 (George Tsiolis)
+- Uses new gitlab-ui components in Jobs and Pipelines components.
+- Replaces tooltip directive with the new gl-tooltip directive for consistency in some ci/cd code.
+- Bump gpgme gem version from 2.0.13 to 2.0.18. (asaparov)
+- Enable Rubocop on lib/gitlab. (gfyoung)
+
+
+## 11.5.5 (2018-12-20)
+
+### Security (1 change)
+
+- Fix persistent symlink in project import.
+
+
+## 11.5.3 (2018-12-06)
+
+### Security (1 change)
+
+- Prevent a path traversal attack on global file templates.
+
+
## 11.5.2 (2018-12-03)
### Removed (1 change)
@@ -621,6 +912,27 @@ entry.
- Check frozen string in style builds. (gfyoung)
+## 11.3.14 (2018-12-20)
+
+### Security (1 change)
+
+- Fix persistent symlink in project import.
+
+
+## 11.3.13 (2018-12-13)
+
+### Security (1 change)
+
+- Validate LFS hrefs before downloading them.
+
+
+## 11.3.12 (2018-12-06)
+
+### Security (1 change)
+
+- Prevent a path traversal attack on global file templates.
+
+
## 11.3.11 (2018-11-26)
### Security (33 changes)
diff --git a/Dangerfile b/Dangerfile
index 469e77b2514..6a2c5cf2773 100644
--- a/Dangerfile
+++ b/Dangerfile
@@ -8,5 +8,6 @@ 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')
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 26aaba0e866..0eed1a29efd 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.2.0
+1.12.0
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index ba7f754d0c3..ae9a76b9249 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-7.4.0
+8.0.0
diff --git a/Gemfile b/Gemfile
index 022f7e5dbcc..c316f3b0e5f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,22 +1,6 @@
-# --- Special code for migrating to Rails 5.0 ---
-def rails5?
- !%w[0 false].include?(ENV["RAILS5"])
-end
-
-gem_versions = {}
-gem_versions['activerecord_sane_schema_dumper'] = rails5? ? '1.0' : '0.2'
-gem_versions['rails'] = rails5? ? '5.0.7' : '4.2.10'
-gem_versions['rails-i18n'] = rails5? ? '~> 5.1' : '~> 4.0.9'
-
-# The 2.0.6 version of rack requires monkeypatch to be present in
-# `config.ru`. This can be removed once a new update for Rack
-# is available that contains https://github.com/rack/rack/pull/1201.
-gem_versions['rack'] = rails5? ? '2.0.6' : '1.6.11'
-# --- The end of special code for migrating to Rails 5.0 ---
-
source 'https://rubygems.org'
-gem 'rails', gem_versions['rails']
+gem 'rails', '5.0.7'
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Improves copy-on-write performance for MRI
@@ -28,11 +12,7 @@ gem 'responders', '~> 2.0'
gem 'sprockets', '~> 3.7.0'
# Default values for AR models
-if rails5?
- gem 'gitlab-default_value_for', '~> 3.1.1', require: 'default_value_for'
-else
- gem 'default_value_for', '~> 3.0.0'
-end
+gem 'gitlab-default_value_for', '~> 3.1.1', require: 'default_value_for'
# Supported DBs
gem 'mysql2', '~> 0.4.10', group: :mysql
@@ -109,9 +89,7 @@ gem 'kaminari', '~> 1.0'
gem 'hamlit', '~> 2.8.8'
# Files attachments
-# Locked until https://github.com/carrierwaveuploader/carrierwave/pull/2332/files is merged.
-# config/initializers/carrierwave_patch.rb can be removed once that change is released.
-gem 'carrierwave', '= 1.2.3'
+gem 'carrierwave', '~> 1.3'
gem 'mini_magick'
# for backups
@@ -149,7 +127,7 @@ gem 'asciidoctor-plantuml', '0.0.8'
gem 'rouge', '~> 3.1'
gem 'truncato', '~> 0.7.9'
gem 'bootstrap_form', '~> 2.7.0'
-gem 'nokogiri', '~> 1.8.2'
+gem 'nokogiri', '~> 1.8.4'
gem 'escape_utils', '~> 1.1'
# Calendar rendering
@@ -159,7 +137,10 @@ gem 'icalendar'
gem 'diffy', '~> 3.1.0'
# Application server
-gem 'rack', gem_versions['rack']
+# The 2.0.6 version of rack requires monkeypatch to be present in
+# `config.ru`. This can be removed once a new update for Rack
+# is available that contains https://github.com/rack/rack/pull/1201.
+gem 'rack', '2.0.6'
group :unicorn do
gem 'unicorn', '~> 5.1.0'
@@ -181,6 +162,7 @@ gem 'acts-as-taggable-on', '~> 5.0'
gem 'sidekiq', '~> 5.2.1'
gem 'sidekiq-cron', '~> 0.6.0'
gem 'redis-namespace', '~> 1.6.0'
+gem 'gitlab-sidekiq-fetcher', '~> 0.4.0', require: 'sidekiq-reliable-fetch'
# Cron Parser
gem 'rufus-scheduler', '~> 3.4'
@@ -263,6 +245,9 @@ gem 'ace-rails-ap', '~> 4.1.0'
# Detect and convert string character encoding
gem 'charlock_holmes', '~> 0.7.5'
+# Detect mime content type from content
+gem 'mimemagic', '~> 0.3.2'
+
# Faster blank
gem 'fast_blank'
@@ -274,6 +259,7 @@ gem 'webpack-rails', '~> 0.9.10'
gem 'rack-proxy', '~> 0.6.0'
gem 'sass-rails', '~> 5.0.6'
+gem 'sass', '~> 3.5'
gem 'uglifier', '~> 2.7.2'
gem 'addressable', '~> 2.5.2'
@@ -293,7 +279,7 @@ gem 'premailer-rails', '~> 1.9.7'
# I18n
gem 'ruby_parser', '~> 3.8', require: false
-gem 'rails-i18n', gem_versions['rails-i18n']
+gem 'rails-i18n', '~> 5.1'
gem 'gettext_i18n_rails', '~> 1.8.0'
gem 'gettext_i18n_rails_js', '~> 1.3'
gem 'gettext', '~> 3.2.2', require: false, group: :development
@@ -303,11 +289,10 @@ gem 'batch-loader', '~> 1.2.2'
# Perf bar
gem 'peek', '~> 1.0.1'
gem 'peek-gc', '~> 0.0.2'
-gem 'peek-mysql2', '~> 1.1.0', group: :mysql
+gem 'peek-mysql2', '~> 1.2.0', group: :mysql
gem 'peek-pg', '~> 1.3.0', group: :postgres
gem 'peek-rblineprof', '~> 0.2.0'
gem 'peek-redis', '~> 1.2.0'
-gem 'gitlab-sidekiq-fetcher', require: 'sidekiq-reliable-fetch'
# Metrics
group :metrics do
@@ -379,7 +364,7 @@ group :development, :test do
gem 'license_finder', '~> 5.4', require: false
gem 'knapsack', '~> 1.17'
- gem 'activerecord_sane_schema_dumper', gem_versions['activerecord_sane_schema_dumper']
+ gem 'activerecord_sane_schema_dumper', '1.0'
gem 'stackprof', '~> 0.2.10', require: false
@@ -393,8 +378,7 @@ group :test do
gem 'email_spec', '~> 2.2.0'
gem 'json-schema', '~> 2.8.0'
gem 'webmock', '~> 2.3.2'
- gem 'rails-controller-testing' if rails5? # Rails5 only gem.
- gem 'test_after_commit', '~> 1.1' unless rails5? # Remove this gem when migrated to rails 5.0. It's been integrated to rails 5.0.
+ gem 'rails-controller-testing'
gem 'sham_rack', '~> 1.3.6'
gem 'concurrent-ruby', '~> 1.1'
gem 'test-prof', '~> 0.2.5'
@@ -432,7 +416,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 1.2.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 1.5.0', require: 'gitaly'
gem 'grpc', '~> 1.15.0'
gem 'google-protobuf', '~> 3.6'
diff --git a/Gemfile.lock b/Gemfile.lock
index 699d77615aa..942dbac27a6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -82,6 +82,7 @@ GEM
erubi (>= 1.0.0)
rack (>= 0.9.0)
bindata (2.4.3)
+ binding_ninja (0.2.2)
binding_of_caller (0.8.0)
debug_inspector (>= 0.0.1)
bootsnap (1.3.2)
@@ -107,7 +108,7 @@ GEM
capybara-screenshot (1.0.14)
capybara (>= 1.0, < 3)
launchy
- carrierwave (1.2.3)
+ carrierwave (1.3.0)
activemodel (>= 4.0.0)
activesupport (>= 4.0.0)
mime-types (>= 1.16)
@@ -273,13 +274,13 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
- gitaly-proto (1.2.0)
+ gitaly-proto (1.5.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-default_value_for (3.1.1)
activerecord (>= 3.2.0, < 6.0)
gitlab-markup (1.6.5)
- gitlab-sidekiq-fetcher (0.3.0)
+ gitlab-sidekiq-fetcher (0.4.0)
sidekiq (~> 5)
gitlab-styles (2.4.1)
rubocop (~> 0.54.0)
@@ -458,7 +459,7 @@ GEM
mime-types (3.2.2)
mime-types-data (~> 3.2015)
mime-types-data (3.2018.0812)
- mimemagic (0.3.0)
+ mimemagic (0.3.2)
mini_magick (4.8.0)
mini_mime (1.0.1)
mini_portile2 (2.3.0)
@@ -557,8 +558,9 @@ GEM
railties (>= 4.0.0)
peek-gc (0.0.2)
peek
- peek-mysql2 (1.1.0)
- atomic (>= 1.0.0)
+ peek-mysql2 (1.2.0)
+ concurrent-ruby
+ concurrent-ruby-ext
mysql2
peek
peek-pg (1.3.0)
@@ -724,8 +726,8 @@ GEM
rspec-mocks (3.7.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.7.0)
- rspec-parameterized (0.4.0)
- binding_of_caller
+ rspec-parameterized (0.4.1)
+ binding_ninja (>= 0.2.1)
parser
proc_to_ast
rspec (>= 2.13, < 4)
@@ -806,7 +808,7 @@ GEM
selenium-webdriver (3.12.0)
childprocess (~> 0.5)
rubyzip (~> 1.2)
- sentry-raven (2.7.2)
+ sentry-raven (2.7.4)
faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9)
sexp_processor (4.11.0)
@@ -895,7 +897,7 @@ GEM
get_process_mem (~> 0)
unicorn (>= 4, < 6)
uniform_notifier (1.10.0)
- unparser (0.2.7)
+ unparser (0.4.2)
abstract_type (~> 0.0.7)
adamantium (~> 0.2.0)
concord (~> 0.1.5)
@@ -963,7 +965,7 @@ DEPENDENCIES
bundler-audit (~> 0.5.0)
capybara (~> 2.15)
capybara-screenshot (~> 1.0.0)
- carrierwave (= 1.2.3)
+ carrierwave (~> 1.3)
charlock_holmes (~> 0.7.5)
chronic (~> 0.10.2)
chronic_duration (~> 0.10.6)
@@ -1006,11 +1008,11 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 1.2.0)
+ gitaly-proto (~> 1.5.0)
github-markup (~> 1.7.0)
gitlab-default_value_for (~> 3.1.1)
gitlab-markup (~> 1.6.5)
- gitlab-sidekiq-fetcher
+ gitlab-sidekiq-fetcher (~> 0.4.0)
gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4)
gon (~> 6.2)
@@ -1050,13 +1052,14 @@ DEPENDENCIES
loofah (~> 2.2)
mail_room (~> 0.9.1)
method_source (~> 0.8)
+ mimemagic (~> 0.3.2)
mini_magick
minitest (~> 5.7.0)
mysql2 (~> 0.4.10)
nakayoshi_fork (~> 0.0.4)
net-ldap
net-ssh (~> 5.0)
- nokogiri (~> 1.8.2)
+ nokogiri (~> 1.8.4)
oauth2 (~> 1.4)
octokit (~> 4.9)
omniauth (~> 1.8)
@@ -1077,7 +1080,7 @@ DEPENDENCIES
org-ruby (~> 0.9.12)
peek (~> 1.0.1)
peek-gc (~> 0.0.2)
- peek-mysql2 (~> 1.1.0)
+ peek-mysql2 (~> 1.2.0)
peek-pg (~> 1.3.0)
peek-rblineprof (~> 0.2.0)
peek-redis (~> 1.2.0)
@@ -1127,6 +1130,7 @@ DEPENDENCIES
rufus-scheduler (~> 3.4)
rugged (~> 0.27)
sanitize (~> 4.6)
+ sass (~> 3.5)
sass-rails (~> 5.0.6)
scss_lint (~> 0.56.0)
seed-fu (~> 2.3.7)
diff --git a/Gemfile.rails4 b/Gemfile.rails4
deleted file mode 100644
index 0ec00e702aa..00000000000
--- a/Gemfile.rails4
+++ /dev/null
@@ -1,7 +0,0 @@
-# BUNDLE_GEMFILE=Gemfile.rails4 bundle install
-
-ENV["RAILS5"] = "false"
-
-gemfile = File.expand_path("../Gemfile", __FILE__)
-
-eval(File.read(gemfile), nil, gemfile)
diff --git a/Gemfile.rails4.lock b/Gemfile.rails4.lock
deleted file mode 100644
index 15e0b782d5b..00000000000
--- a/Gemfile.rails4.lock
+++ /dev/null
@@ -1,1161 +0,0 @@
-GEM
- remote: https://rubygems.org/
- specs:
- RedCloth (4.3.2)
- abstract_type (0.0.7)
- ace-rails-ap (4.1.2)
- actionmailer (4.2.10)
- actionpack (= 4.2.10)
- actionview (= 4.2.10)
- activejob (= 4.2.10)
- mail (~> 2.5, >= 2.5.4)
- rails-dom-testing (~> 1.0, >= 1.0.5)
- actionpack (4.2.10)
- actionview (= 4.2.10)
- activesupport (= 4.2.10)
- rack (~> 1.6)
- rack-test (~> 0.6.2)
- rails-dom-testing (~> 1.0, >= 1.0.5)
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (4.2.10)
- activesupport (= 4.2.10)
- builder (~> 3.1)
- erubis (~> 2.7.0)
- rails-dom-testing (~> 1.0, >= 1.0.5)
- rails-html-sanitizer (~> 1.0, >= 1.0.3)
- activejob (4.2.10)
- activesupport (= 4.2.10)
- globalid (>= 0.3.0)
- activemodel (4.2.10)
- activesupport (= 4.2.10)
- builder (~> 3.1)
- activerecord (4.2.10)
- activemodel (= 4.2.10)
- activesupport (= 4.2.10)
- arel (~> 6.0)
- activerecord_sane_schema_dumper (0.2)
- rails (>= 4, < 5)
- activesupport (4.2.10)
- i18n (~> 0.7)
- minitest (~> 5.1)
- thread_safe (~> 0.3, >= 0.3.4)
- tzinfo (~> 1.1)
- acts-as-taggable-on (5.0.0)
- activerecord (>= 4.2.8)
- adamantium (0.2.0)
- ice_nine (~> 0.11.0)
- memoizable (~> 0.4.0)
- addressable (2.5.2)
- public_suffix (>= 2.0.2, < 4.0)
- aes_key_wrap (1.0.1)
- akismet (2.0.0)
- arel (6.0.4)
- asana (0.8.1)
- faraday (~> 0.9)
- faraday_middleware (~> 0.9)
- faraday_middleware-multi_json (~> 0.0)
- oauth2 (~> 1.0)
- asciidoctor (1.5.8)
- asciidoctor-plantuml (0.0.8)
- asciidoctor (~> 1.5)
- ast (2.4.0)
- atomic (1.1.99)
- attr_encrypted (3.1.0)
- encryptor (~> 3.0.0)
- attr_required (1.0.0)
- awesome_print (1.8.0)
- axiom-types (0.1.1)
- descendants_tracker (~> 0.0.4)
- ice_nine (~> 0.11.0)
- thread_safe (~> 0.3, >= 0.3.1)
- babosa (1.0.2)
- base32 (0.3.2)
- batch-loader (1.2.2)
- bcrypt (3.1.12)
- bcrypt_pbkdf (1.0.0)
- benchmark-ips (2.3.0)
- better_errors (2.5.0)
- coderay (>= 1.0.0)
- erubi (>= 1.0.0)
- rack (>= 0.9.0)
- bindata (2.4.3)
- binding_of_caller (0.8.0)
- debug_inspector (>= 0.0.1)
- bootsnap (1.3.2)
- msgpack (~> 1.0)
- bootstrap_form (2.7.0)
- brakeman (4.2.1)
- browser (2.5.3)
- builder (3.2.3)
- bullet (5.5.1)
- activesupport (>= 3.0.0)
- uniform_notifier (~> 1.10.0)
- bundler-audit (0.5.0)
- bundler (~> 1.2)
- thor (~> 0.18)
- byebug (9.0.6)
- capybara (2.15.1)
- addressable
- mini_mime (>= 0.1.3)
- nokogiri (>= 1.3.3)
- rack (>= 1.0.0)
- rack-test (>= 0.5.4)
- xpath (~> 2.0)
- capybara-screenshot (1.0.14)
- capybara (>= 1.0, < 3)
- launchy
- carrierwave (1.2.3)
- activemodel (>= 4.0.0)
- activesupport (>= 4.0.0)
- mime-types (>= 1.16)
- cause (0.1)
- charlock_holmes (0.7.6)
- childprocess (0.9.0)
- ffi (~> 1.0, >= 1.0.11)
- chronic (0.10.2)
- chronic_duration (0.10.6)
- numerizer (~> 0.1.1)
- chunky_png (1.3.5)
- citrus (3.0.2)
- coderay (1.1.2)
- coercible (1.0.0)
- descendants_tracker (~> 0.0.1)
- commonmarker (0.17.13)
- ruby-enum (~> 0.5)
- concord (0.1.5)
- adamantium (~> 0.2.0)
- equalizer (~> 0.0.9)
- concurrent-ruby (1.1.3)
- concurrent-ruby-ext (1.1.3)
- concurrent-ruby (= 1.1.3)
- connection_pool (2.2.2)
- crack (0.4.3)
- safe_yaml (~> 1.0.0)
- crass (1.0.4)
- creole (0.5.0)
- css_parser (1.5.0)
- addressable
- daemons (1.2.6)
- database_cleaner (1.5.3)
- debug_inspector (0.0.3)
- debugger-ruby_core_source (1.3.8)
- deckar01-task_list (2.0.0)
- html-pipeline
- declarative (0.0.10)
- declarative-option (0.1.0)
- default_value_for (3.0.2)
- activerecord (>= 3.2.0, < 5.1)
- descendants_tracker (0.0.4)
- thread_safe (~> 0.3, >= 0.3.1)
- device_detector (1.0.0)
- devise (4.4.3)
- bcrypt (~> 3.0)
- orm_adapter (~> 0.1)
- railties (>= 4.1.0, < 6.0)
- responders
- warden (~> 1.2.3)
- devise-two-factor (3.0.0)
- activesupport
- attr_encrypted (>= 1.3, < 4, != 2)
- devise (~> 4.0)
- railties
- rotp (~> 2.0)
- diff-lcs (1.3)
- diffy (3.1.0)
- discordrb-webhooks-blackst0ne (3.3.0)
- rest-client (~> 2.0)
- docile (1.1.5)
- domain_name (0.5.20180417)
- unf (>= 0.0.5, < 1.0.0)
- doorkeeper (4.3.2)
- railties (>= 4.2)
- doorkeeper-openid_connect (1.5.0)
- doorkeeper (~> 4.3)
- json-jwt (~> 1.6)
- ed25519 (1.2.4)
- email_reply_trimmer (0.1.6)
- email_spec (2.2.0)
- htmlentities (~> 4.3.3)
- launchy (~> 2.1)
- mail (~> 2.7)
- encryptor (3.0.0)
- equalizer (0.0.11)
- erubi (1.7.1)
- erubis (2.7.0)
- escape_utils (1.2.1)
- et-orbi (1.0.3)
- tzinfo
- eventmachine (1.2.7)
- excon (0.62.0)
- execjs (2.6.0)
- expression_parser (0.9.0)
- factory_bot (4.8.2)
- activesupport (>= 3.0.0)
- factory_bot_rails (4.8.2)
- factory_bot (~> 4.8.2)
- railties (>= 3.0.0)
- faraday (0.12.2)
- multipart-post (>= 1.2, < 3)
- faraday_middleware (0.12.2)
- faraday (>= 0.7.4, < 1.0)
- faraday_middleware-multi_json (0.0.6)
- faraday_middleware
- multi_json
- fast_blank (1.0.0)
- fast_gettext (1.6.0)
- ffaker (2.10.0)
- ffi (1.9.25)
- flipper (0.13.0)
- flipper-active_record (0.13.0)
- activerecord (>= 3.2, < 6)
- flipper (~> 0.13.0)
- flipper-active_support_cache_store (0.13.0)
- activesupport (>= 3.2, < 6)
- flipper (~> 0.13.0)
- flowdock (0.7.1)
- httparty (~> 0.7)
- multi_json
- fog-aliyun (0.2.0)
- fog-core (~> 1.27)
- fog-json (~> 1.0)
- ipaddress (~> 0.8)
- xml-simple (~> 1.1)
- fog-aws (2.0.1)
- fog-core (~> 1.38)
- fog-json (~> 1.0)
- fog-xml (~> 0.1)
- ipaddress (~> 0.8)
- fog-core (1.45.0)
- builder
- excon (~> 0.58)
- formatador (~> 0.2)
- fog-google (1.7.1)
- fog-core
- fog-json
- fog-xml
- google-api-client (~> 0.23.0)
- fog-json (1.0.2)
- fog-core (~> 1.0)
- multi_json (~> 1.10)
- fog-local (0.3.1)
- fog-core (~> 1.27)
- fog-openstack (0.1.21)
- fog-core (>= 1.40)
- fog-json (>= 1.0)
- ipaddress (>= 0.8)
- fog-rackspace (0.1.1)
- fog-core (>= 1.35)
- fog-json (>= 1.0)
- fog-xml (>= 0.1)
- ipaddress (>= 0.8)
- fog-xml (0.1.3)
- fog-core
- nokogiri (>= 1.5.11, < 2.0.0)
- font-awesome-rails (4.7.0.1)
- railties (>= 3.2, < 5.1)
- foreman (0.84.0)
- thor (~> 0.19.1)
- formatador (0.2.5)
- fuubar (2.2.0)
- rspec-core (~> 3.0)
- ruby-progressbar (~> 1.4)
- gemojione (3.3.0)
- json
- get_process_mem (0.2.0)
- gettext (3.2.9)
- locale (>= 2.0.5)
- text (>= 1.3.0)
- gettext_i18n_rails (1.8.0)
- fast_gettext (>= 0.9.0)
- gettext_i18n_rails_js (1.3.0)
- gettext (>= 3.0.2)
- gettext_i18n_rails (>= 0.7.1)
- po_to_json (>= 1.0.0)
- rails (>= 3.2.0)
- gitaly-proto (1.2.0)
- grpc (~> 1.0)
- github-markup (1.7.0)
- gitlab-markup (1.6.5)
- gitlab-sidekiq-fetcher (0.3.0)
- sidekiq (~> 5)
- gitlab-styles (2.4.1)
- rubocop (~> 0.54.0)
- rubocop-gitlab-security (~> 0.1.0)
- rubocop-rspec (~> 1.19)
- gitlab_omniauth-ldap (2.0.4)
- net-ldap (~> 0.16)
- omniauth (~> 1.3)
- pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
- rubyntlm (~> 0.5)
- globalid (0.4.1)
- activesupport (>= 4.2.0)
- gon (6.2.0)
- actionpack (>= 3.0)
- multi_json
- request_store (>= 1.0)
- google-api-client (0.23.4)
- addressable (~> 2.5, >= 2.5.1)
- googleauth (>= 0.5, < 0.7.0)
- httpclient (>= 2.8.1, < 3.0)
- mime-types (~> 3.0)
- representable (~> 3.0)
- retriable (>= 2.0, < 4.0)
- google-protobuf (3.6.1)
- googleapis-common-protos-types (1.0.2)
- google-protobuf (~> 3.0)
- googleauth (0.6.6)
- faraday (~> 0.12)
- jwt (>= 1.4, < 3.0)
- memoist (~> 0.12)
- multi_json (~> 1.11)
- os (>= 0.9, < 2.0)
- signet (~> 0.7)
- gpgme (2.0.18)
- mini_portile2 (~> 2.3)
- grape (1.1.0)
- activesupport
- builder
- mustermann-grape (~> 1.0.0)
- rack (>= 1.3.0)
- rack-accept
- virtus (>= 1.0.0)
- grape-entity (0.7.1)
- activesupport (>= 4.0)
- multi_json (>= 1.3.2)
- grape-path-helpers (1.0.6)
- activesupport (>= 4, < 5.1)
- grape (~> 1.0)
- rake (~> 12)
- grape_logging (1.7.0)
- grape
- graphiql-rails (1.4.10)
- railties
- sprockets-rails
- graphql (1.8.1)
- grpc (1.15.0)
- google-protobuf (~> 3.1)
- googleapis-common-protos-types (~> 1.0.0)
- haml (5.0.4)
- temple (>= 0.8.0)
- tilt
- haml_lint (0.28.0)
- haml (>= 4.0, < 5.1)
- rainbow
- rake (>= 10, < 13)
- rubocop (>= 0.50.0)
- sysexits (~> 1.1)
- hamlit (2.8.8)
- temple (>= 0.8.0)
- thor
- tilt
- hangouts-chat (0.0.5)
- hashdiff (0.3.4)
- hashie (3.5.7)
- hashie-forbidden_attributes (0.1.1)
- hashie (>= 3.0)
- health_check (2.6.0)
- rails (>= 4.0)
- hipchat (1.5.2)
- httparty
- mimemagic
- html-pipeline (2.8.4)
- activesupport (>= 2)
- nokogiri (>= 1.4)
- html2text (0.2.0)
- nokogiri (~> 1.6)
- htmlentities (4.3.4)
- http (3.3.0)
- addressable (~> 2.3)
- http-cookie (~> 1.0)
- http-form_data (~> 2.0)
- http_parser.rb (~> 0.6.0)
- http-cookie (1.0.3)
- domain_name (~> 0.5)
- http-form_data (2.1.1)
- http_parser.rb (0.6.0)
- httparty (0.13.7)
- json (~> 1.8)
- multi_xml (>= 0.5.2)
- httpclient (2.8.3)
- i18n (0.9.5)
- concurrent-ruby (~> 1.0)
- icalendar (2.4.1)
- ice_nine (0.11.2)
- influxdb (0.2.3)
- cause
- json
- ipaddress (0.8.3)
- jira-ruby (1.4.1)
- activesupport
- multipart-post
- oauth (~> 0.5, >= 0.5.0)
- jquery-atwho-rails (1.3.2)
- js_regex (2.2.1)
- regexp_parser (>= 0.4.11, <= 0.5.0)
- json (1.8.6)
- json-jwt (1.9.4)
- activesupport
- aes_key_wrap
- bindata
- json-schema (2.8.0)
- addressable (>= 2.4)
- jwt (1.5.6)
- kaminari (1.0.1)
- activesupport (>= 4.1.0)
- kaminari-actionview (= 1.0.1)
- kaminari-activerecord (= 1.0.1)
- kaminari-core (= 1.0.1)
- kaminari-actionview (1.0.1)
- actionview
- kaminari-core (= 1.0.1)
- kaminari-activerecord (1.0.1)
- activerecord
- kaminari-core (= 1.0.1)
- kaminari-core (1.0.1)
- kgio (2.10.0)
- knapsack (1.17.0)
- rake
- kubeclient (4.0.0)
- http (~> 3.0)
- recursive-open-struct (~> 1.0, >= 1.0.4)
- rest-client (~> 2.0)
- launchy (2.4.3)
- addressable (~> 2.3)
- letter_opener (1.4.1)
- launchy (~> 2.2)
- letter_opener_web (1.3.0)
- actionmailer (>= 3.2)
- letter_opener (~> 1.0)
- railties (>= 3.2)
- license_finder (5.4.0)
- bundler
- rubyzip
- thor
- toml (= 0.2.0)
- with_env (= 1.1.0)
- xml-simple
- licensee (8.9.2)
- rugged (~> 0.24)
- locale (2.1.2)
- lograge (0.10.0)
- actionpack (>= 4)
- activesupport (>= 4)
- railties (>= 4)
- request_store (~> 1.0)
- loofah (2.2.3)
- crass (~> 1.0.2)
- nokogiri (>= 1.5.9)
- mail (2.7.0)
- mini_mime (>= 0.1.1)
- mail_room (0.9.1)
- memoist (0.16.0)
- memoizable (0.4.2)
- thread_safe (~> 0.3, >= 0.3.1)
- method_source (0.9.0)
- mime-types (3.2.2)
- mime-types-data (~> 3.2015)
- mime-types-data (3.2018.0812)
- mimemagic (0.3.0)
- mini_magick (4.8.0)
- mini_mime (1.0.1)
- mini_portile2 (2.3.0)
- minitest (5.7.0)
- msgpack (1.2.4)
- multi_json (1.13.1)
- multi_xml (0.6.0)
- multipart-post (2.0.0)
- mustermann (1.0.3)
- mustermann-grape (1.0.0)
- mustermann (~> 1.0.0)
- mysql2 (0.4.10)
- nakayoshi_fork (0.0.4)
- net-ldap (0.16.0)
- net-ssh (5.0.1)
- netrc (0.11.0)
- nokogiri (1.8.5)
- mini_portile2 (~> 2.3.0)
- nokogumbo (1.5.0)
- nokogiri
- numerizer (0.1.1)
- oauth (0.5.4)
- oauth2 (1.4.0)
- faraday (>= 0.8, < 0.13)
- jwt (~> 1.0)
- multi_json (~> 1.3)
- multi_xml (~> 0.5)
- rack (>= 1.2, < 3)
- octokit (4.9.0)
- sawyer (~> 0.8.0, >= 0.5.3)
- omniauth (1.8.1)
- hashie (>= 3.4.6, < 3.6.0)
- rack (>= 1.6.2, < 3)
- omniauth-auth0 (2.0.0)
- omniauth-oauth2 (~> 1.4)
- omniauth-authentiq (0.3.3)
- jwt (>= 1.5)
- omniauth-oauth2 (>= 1.5)
- omniauth-azure-oauth2 (0.0.9)
- jwt (~> 1.0)
- omniauth (~> 1.0)
- omniauth-oauth2 (~> 1.4)
- omniauth-cas3 (1.1.4)
- addressable (~> 2.3)
- nokogiri (~> 1.7, >= 1.7.1)
- omniauth (~> 1.2)
- omniauth-facebook (4.0.0)
- omniauth-oauth2 (~> 1.2)
- omniauth-github (1.3.0)
- omniauth (~> 1.5)
- omniauth-oauth2 (>= 1.4.0, < 2.0)
- omniauth-gitlab (1.0.3)
- omniauth (~> 1.0)
- omniauth-oauth2 (~> 1.0)
- omniauth-google-oauth2 (0.5.3)
- jwt (>= 1.5)
- omniauth (>= 1.1.1)
- omniauth-oauth2 (>= 1.5)
- omniauth-kerberos (0.3.0)
- omniauth-multipassword
- timfel-krb5-auth (~> 0.8)
- omniauth-multipassword (0.4.2)
- omniauth (~> 1.0)
- omniauth-oauth (1.1.0)
- oauth
- omniauth (~> 1.0)
- omniauth-oauth2 (1.5.0)
- oauth2 (~> 1.1)
- omniauth (~> 1.2)
- omniauth-oauth2-generic (0.2.2)
- omniauth-oauth2 (~> 1.0)
- omniauth-saml (1.10.0)
- omniauth (~> 1.3, >= 1.3.2)
- ruby-saml (~> 1.7)
- omniauth-shibboleth (1.3.0)
- omniauth (>= 1.0.0)
- omniauth-twitter (1.4.0)
- omniauth-oauth (~> 1.1)
- rack
- omniauth_crowd (2.2.3)
- activesupport
- nokogiri (>= 1.4.4)
- omniauth (~> 1.0)
- org-ruby (0.9.12)
- rubypants (~> 0.2)
- orm_adapter (0.5.0)
- os (1.0.0)
- parallel (1.12.1)
- parser (2.5.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)
- peek-gc (0.0.2)
- peek
- peek-mysql2 (1.1.0)
- atomic (>= 1.0.0)
- mysql2
- peek
- peek-pg (1.3.0)
- concurrent-ruby
- concurrent-ruby-ext
- peek
- pg
- peek-rblineprof (0.2.0)
- peek
- rblineprof
- peek-redis (1.2.0)
- atomic (>= 1.0.0)
- peek
- redis
- pg (0.18.4)
- po_to_json (1.0.1)
- json (>= 1.6.0)
- powerpack (0.1.1)
- premailer (1.10.4)
- addressable
- css_parser (>= 1.4.10)
- htmlentities (>= 4.0.0)
- premailer-rails (1.9.7)
- actionmailer (>= 3, < 6)
- premailer (~> 1.7, >= 1.7.9)
- proc_to_ast (0.1.0)
- coderay
- parser
- unparser
- procto (0.0.3)
- prometheus-client-mmap (0.9.4)
- pry (0.11.3)
- coderay (~> 1.1.0)
- method_source (~> 0.9.0)
- pry-byebug (3.4.3)
- byebug (>= 9.0, < 9.1)
- pry (~> 0.10)
- pry-rails (0.3.6)
- pry (>= 0.10.4)
- public_suffix (3.0.3)
- puma (3.12.0)
- puma_worker_killer (0.1.0)
- get_process_mem (~> 0.2)
- puma (>= 2.7, < 4)
- pyu-ruby-sasl (0.0.3.3)
- rack (1.6.11)
- rack-accept (0.4.5)
- rack (>= 0.4)
- rack-attack (4.4.1)
- rack
- rack-cors (1.0.2)
- rack-oauth2 (1.2.3)
- activesupport (>= 2.3)
- attr_required (>= 0.0.5)
- httpclient (>= 2.4)
- multi_json (>= 1.3.6)
- rack (>= 1.1)
- rack-protection (2.0.4)
- rack
- rack-proxy (0.6.0)
- rack
- rack-test (0.6.3)
- rack (>= 1.0)
- rails (4.2.10)
- actionmailer (= 4.2.10)
- actionpack (= 4.2.10)
- actionview (= 4.2.10)
- activejob (= 4.2.10)
- activemodel (= 4.2.10)
- activerecord (= 4.2.10)
- activesupport (= 4.2.10)
- bundler (>= 1.3.0, < 2.0)
- railties (= 4.2.10)
- sprockets-rails
- rails-deprecated_sanitizer (1.0.3)
- activesupport (>= 4.2.0.alpha)
- rails-dom-testing (1.0.9)
- activesupport (>= 4.2.0, < 5.0)
- nokogiri (~> 1.6)
- rails-deprecated_sanitizer (>= 1.0.1)
- rails-html-sanitizer (1.0.4)
- loofah (~> 2.2, >= 2.2.2)
- rails-i18n (4.0.9)
- i18n (~> 0.7)
- railties (~> 4.0)
- railties (4.2.10)
- actionpack (= 4.2.10)
- activesupport (= 4.2.10)
- rake (>= 0.8.7)
- thor (>= 0.18.1, < 2.0)
- rainbow (3.0.0)
- raindrops (0.18.0)
- rake (12.3.1)
- rb-fsevent (0.10.2)
- rb-inotify (0.9.10)
- ffi (>= 0.5.0, < 2)
- rblineprof (0.3.6)
- debugger-ruby_core_source (~> 1.3)
- rbtrace (0.4.10)
- ffi (>= 1.0.6)
- msgpack (>= 0.4.3)
- trollop (>= 1.16.2)
- rdoc (6.0.4)
- re2 (1.1.1)
- recaptcha (3.0.0)
- json
- recursive-open-struct (1.1.0)
- redcarpet (3.4.0)
- redis (3.3.5)
- redis-actionpack (5.0.2)
- actionpack (>= 4.0, < 6)
- redis-rack (>= 1, < 3)
- redis-store (>= 1.1.0, < 2)
- redis-activesupport (5.0.4)
- activesupport (>= 3, < 6)
- redis-store (>= 1.3, < 2)
- redis-namespace (1.6.0)
- redis (>= 3.0.4)
- redis-rack (2.0.4)
- rack (>= 1.5, < 3)
- redis-store (>= 1.2, < 2)
- redis-rails (5.0.2)
- redis-actionpack (>= 5.0, < 6)
- redis-activesupport (>= 5.0, < 6)
- redis-store (>= 1.2, < 2)
- redis-store (1.6.0)
- redis (>= 2.2, < 5)
- regexp_parser (0.5.0)
- representable (3.0.4)
- declarative (< 0.1.0)
- declarative-option (< 0.2.0)
- uber (< 0.2.0)
- request_store (1.3.1)
- responders (2.4.0)
- actionpack (>= 4.2.0, < 5.3)
- railties (>= 4.2.0, < 5.3)
- rest-client (2.0.2)
- http-cookie (>= 1.0.2, < 2.0)
- mime-types (>= 1.16, < 4.0)
- netrc (~> 0.8)
- retriable (3.1.2)
- rinku (2.0.0)
- rotp (2.1.2)
- rouge (3.3.0)
- rqrcode (0.7.0)
- chunky_png
- rqrcode-rails3 (0.1.7)
- rqrcode (>= 0.4.2)
- rspec (3.7.0)
- rspec-core (~> 3.7.0)
- rspec-expectations (~> 3.7.0)
- rspec-mocks (~> 3.7.0)
- rspec-core (3.7.1)
- rspec-support (~> 3.7.0)
- rspec-expectations (3.7.0)
- diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.7.0)
- rspec-mocks (3.7.0)
- diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.7.0)
- rspec-parameterized (0.4.0)
- binding_of_caller
- parser
- proc_to_ast
- rspec (>= 2.13, < 4)
- unparser
- rspec-rails (3.7.2)
- actionpack (>= 3.0)
- activesupport (>= 3.0)
- railties (>= 3.0)
- rspec-core (~> 3.7.0)
- rspec-expectations (~> 3.7.0)
- rspec-mocks (~> 3.7.0)
- rspec-support (~> 3.7.0)
- rspec-retry (0.4.5)
- rspec-core
- rspec-set (0.1.3)
- rspec-support (3.7.1)
- rspec_junit_formatter (0.2.3)
- builder (< 4)
- rspec-core (>= 2, < 4, != 2.12.0)
- rspec_profiling (0.0.5)
- activerecord
- pg
- rails
- sqlite3
- rubocop (0.54.0)
- parallel (~> 1.10)
- parser (>= 2.5)
- powerpack (~> 0.1)
- rainbow (>= 2.2.2, < 4.0)
- ruby-progressbar (~> 1.7)
- unicode-display_width (~> 1.0, >= 1.0.1)
- rubocop-gitlab-security (0.1.1)
- rubocop (>= 0.51)
- rubocop-rspec (1.22.2)
- rubocop (>= 0.52.1)
- ruby-enum (0.7.2)
- i18n
- ruby-fogbugz (0.2.1)
- crack (~> 0.4)
- ruby-prof (0.17.0)
- ruby-progressbar (1.9.0)
- ruby-saml (1.7.2)
- nokogiri (>= 1.5.10)
- ruby_parser (3.11.0)
- sexp_processor (~> 4.9)
- rubyntlm (0.6.2)
- rubypants (0.2.0)
- rubyzip (1.2.2)
- rufus-scheduler (3.4.0)
- et-orbi (~> 1.0)
- rugged (0.27.5)
- safe_yaml (1.0.4)
- sanitize (4.6.6)
- crass (~> 1.0.2)
- nokogiri (>= 1.4.4)
- nokogumbo (~> 1.4)
- sass (3.5.5)
- sass-listen (~> 4.0.0)
- sass-listen (4.0.0)
- rb-fsevent (~> 0.9, >= 0.9.4)
- rb-inotify (~> 0.9, >= 0.9.7)
- sass-rails (5.0.6)
- railties (>= 4.0.0, < 6)
- sass (~> 3.1)
- sprockets (>= 2.8, < 4.0)
- sprockets-rails (>= 2.0, < 4.0)
- tilt (>= 1.1, < 3)
- sawyer (0.8.1)
- addressable (>= 2.3.5, < 2.6)
- faraday (~> 0.8, < 1.0)
- scss_lint (0.56.0)
- rake (>= 0.9, < 13)
- sass (~> 3.5.3)
- seed-fu (2.3.7)
- activerecord (>= 3.1)
- activesupport (>= 3.1)
- select2-rails (3.5.9.3)
- thor (~> 0.14)
- selenium-webdriver (3.12.0)
- childprocess (~> 0.5)
- rubyzip (~> 1.2)
- sentry-raven (2.7.2)
- faraday (>= 0.7.6, < 1.0)
- settingslogic (2.0.9)
- sexp_processor (4.11.0)
- sham_rack (1.3.6)
- rack
- shoulda-matchers (3.1.2)
- activesupport (>= 4.0.0)
- sidekiq (5.2.3)
- connection_pool (~> 2.2, >= 2.2.2)
- rack-protection (>= 1.5.0)
- redis (>= 3.3.5, < 5)
- sidekiq-cron (0.6.0)
- rufus-scheduler (>= 3.3.0)
- sidekiq (>= 4.2.1)
- signet (0.11.0)
- addressable (~> 2.3)
- faraday (~> 0.9)
- jwt (>= 1.5, < 3.0)
- multi_json (~> 1.10)
- simple_po_parser (1.1.2)
- simplecov (0.14.1)
- docile (~> 1.1.0)
- json (>= 1.8, < 3)
- simplecov-html (~> 0.10.0)
- simplecov-html (0.10.0)
- slack-notifier (1.5.1)
- spring (2.0.2)
- activesupport (>= 4.2)
- spring-commands-rspec (1.0.4)
- spring (>= 0.9.1)
- sprockets (3.7.2)
- concurrent-ruby (~> 1.0)
- rack (> 1, < 3)
- sprockets-rails (3.2.1)
- actionpack (>= 4.0)
- activesupport (>= 4.0)
- sprockets (>= 3.0.0)
- sqlite3 (1.3.13)
- sshkey (1.9.0)
- stackprof (0.2.10)
- state_machines (0.5.0)
- state_machines-activemodel (0.5.1)
- activemodel (>= 4.1, < 6.0)
- state_machines (>= 0.5.0)
- state_machines-activerecord (0.5.1)
- activerecord (>= 4.1, < 6.0)
- state_machines-activemodel (>= 0.5.0)
- sys-filesystem (1.1.6)
- ffi
- sysexits (1.2.0)
- temple (0.8.0)
- test-prof (0.2.5)
- test_after_commit (1.1.0)
- activerecord (>= 3.2)
- text (1.3.1)
- thin (1.7.2)
- daemons (~> 1.0, >= 1.0.9)
- eventmachine (~> 1.0, >= 1.0.4)
- rack (>= 1, < 3)
- thor (0.19.4)
- thread_safe (0.3.6)
- tilt (2.0.8)
- timecop (0.8.1)
- timfel-krb5-auth (0.8.3)
- toml (0.2.0)
- parslet (~> 1.8.0)
- toml-rb (1.0.0)
- citrus (~> 3.0, > 3.0)
- trollop (2.1.3)
- truncato (0.7.10)
- htmlentities (~> 4.3.1)
- nokogiri (~> 1.8.0, >= 1.7.0)
- tzinfo (1.2.5)
- thread_safe (~> 0.1)
- u2f (0.2.1)
- uber (0.1.0)
- uglifier (2.7.2)
- execjs (>= 0.3.0)
- json (>= 1.8.0)
- unf (0.1.4)
- unf_ext
- unf_ext (0.0.7.5)
- unicode-display_width (1.3.2)
- unicorn (5.1.0)
- kgio (~> 2.6)
- raindrops (~> 0.7)
- unicorn-worker-killer (0.4.4)
- get_process_mem (~> 0)
- unicorn (>= 4, < 6)
- uniform_notifier (1.10.0)
- unparser (0.2.7)
- abstract_type (~> 0.0.7)
- adamantium (~> 0.2.0)
- concord (~> 0.1.5)
- diff-lcs (~> 1.3)
- equalizer (~> 0.0.9)
- parser (>= 2.3.1.2, < 2.6)
- procto (~> 0.0.2)
- validates_hostname (1.0.6)
- activerecord (>= 3.0)
- activesupport (>= 3.0)
- version_sorter (2.1.0)
- virtus (1.0.5)
- axiom-types (~> 0.1)
- coercible (~> 1.0)
- descendants_tracker (~> 0.0, >= 0.0.3)
- equalizer (~> 0.0, >= 0.0.9)
- vmstat (2.3.0)
- warden (1.2.7)
- rack (>= 1.0)
- webmock (2.3.2)
- addressable (>= 2.3.6)
- crack (>= 0.3.2)
- hashdiff
- webpack-rails (0.9.11)
- railties (>= 3.2.0)
- wikicloth (0.8.1)
- builder
- expression_parser
- rinku
- with_env (1.1.0)
- xml-simple (1.1.5)
- xpath (2.1.0)
- nokogiri (~> 1.3)
-
-PLATFORMS
- ruby
-
-DEPENDENCIES
- RedCloth (~> 4.3.2)
- ace-rails-ap (~> 4.1.0)
- activerecord_sane_schema_dumper (= 0.2)
- acts-as-taggable-on (~> 5.0)
- addressable (~> 2.5.2)
- akismet (~> 2.0)
- asana (~> 0.8.1)
- asciidoctor (~> 1.5.8)
- asciidoctor-plantuml (= 0.0.8)
- attr_encrypted (~> 3.1.0)
- awesome_print
- babosa (~> 1.0.2)
- base32 (~> 0.3.0)
- batch-loader (~> 1.2.2)
- bcrypt_pbkdf (~> 1.0)
- benchmark-ips (~> 2.3.0)
- better_errors (~> 2.5.0)
- binding_of_caller (~> 0.8.0)
- bootsnap (~> 1.3)
- bootstrap_form (~> 2.7.0)
- brakeman (~> 4.2)
- browser (~> 2.5)
- bullet (~> 5.5.0)
- bundler-audit (~> 0.5.0)
- capybara (~> 2.15)
- capybara-screenshot (~> 1.0.0)
- carrierwave (= 1.2.3)
- charlock_holmes (~> 0.7.5)
- chronic (~> 0.10.2)
- chronic_duration (~> 0.10.6)
- commonmarker (~> 0.17)
- concurrent-ruby (~> 1.1)
- connection_pool (~> 2.0)
- creole (~> 0.5.0)
- database_cleaner (~> 1.5.0)
- deckar01-task_list (= 2.0.0)
- default_value_for (~> 3.0.0)
- device_detector
- devise (~> 4.4)
- devise-two-factor (~> 3.0.0)
- diffy (~> 3.1.0)
- discordrb-webhooks-blackst0ne (~> 3.3)
- doorkeeper (~> 4.3)
- doorkeeper-openid_connect (~> 1.5)
- ed25519 (~> 1.2)
- email_reply_trimmer (~> 0.1)
- email_spec (~> 2.2.0)
- escape_utils (~> 1.1)
- factory_bot_rails (~> 4.8.2)
- faraday (~> 0.12)
- fast_blank
- ffaker (~> 2.10)
- flipper (~> 0.13.0)
- flipper-active_record (~> 0.13.0)
- flipper-active_support_cache_store (~> 0.13.0)
- flowdock (~> 0.7)
- fog-aliyun (~> 0.2.0)
- fog-aws (~> 2.0.1)
- fog-core (~> 1.44)
- fog-google (~> 1.7.1)
- fog-local (~> 0.3)
- fog-openstack (~> 0.1)
- fog-rackspace (~> 0.1.1)
- font-awesome-rails (~> 4.7)
- foreman (~> 0.84.0)
- fuubar (~> 2.2.0)
- gemojione (~> 3.3)
- gettext (~> 3.2.2)
- gettext_i18n_rails (~> 1.8.0)
- gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 1.2.0)
- github-markup (~> 1.7.0)
- gitlab-markup (~> 1.6.5)
- gitlab-sidekiq-fetcher
- gitlab-styles (~> 2.4)
- gitlab_omniauth-ldap (~> 2.0.4)
- gon (~> 6.2)
- google-api-client (~> 0.23)
- google-protobuf (~> 3.6)
- gpgme (~> 2.0.18)
- grape (~> 1.1.0)
- grape-entity (~> 0.7.1)
- grape-path-helpers (~> 1.0)
- grape_logging (~> 1.7)
- graphiql-rails (~> 1.4.10)
- graphql (~> 1.8.0)
- grpc (~> 1.15.0)
- haml_lint (~> 0.28.0)
- hamlit (~> 2.8.8)
- hangouts-chat (~> 0.0.5)
- hashie-forbidden_attributes
- health_check (~> 2.6.0)
- hipchat (~> 1.5.0)
- html-pipeline (~> 2.8)
- html2text
- httparty (~> 0.13.3)
- icalendar
- influxdb (~> 0.2)
- jira-ruby (~> 1.4)
- jquery-atwho-rails (~> 1.3.2)
- js_regex (~> 2.2.1)
- json-schema (~> 2.8.0)
- jwt (~> 1.5.6)
- kaminari (~> 1.0)
- knapsack (~> 1.17)
- kubeclient (~> 4.0.0)
- letter_opener_web (~> 1.3.0)
- license_finder (~> 5.4)
- licensee (~> 8.9)
- lograge (~> 0.5)
- loofah (~> 2.2)
- mail_room (~> 0.9.1)
- method_source (~> 0.8)
- mini_magick
- minitest (~> 5.7.0)
- mysql2 (~> 0.4.10)
- nakayoshi_fork (~> 0.0.4)
- net-ldap
- net-ssh (~> 5.0)
- nokogiri (~> 1.8.2)
- oauth2 (~> 1.4)
- octokit (~> 4.9)
- omniauth (~> 1.8)
- omniauth-auth0 (~> 2.0.0)
- omniauth-authentiq (~> 0.3.3)
- omniauth-azure-oauth2 (~> 0.0.9)
- omniauth-cas3 (~> 1.1.4)
- omniauth-facebook (~> 4.0.0)
- omniauth-github (~> 1.3)
- omniauth-gitlab (~> 1.0.2)
- omniauth-google-oauth2 (~> 0.5.3)
- omniauth-kerberos (~> 0.3.0)
- omniauth-oauth2-generic (~> 0.2.2)
- omniauth-saml (~> 1.10)
- omniauth-shibboleth (~> 1.3.0)
- omniauth-twitter (~> 1.4)
- omniauth_crowd (~> 2.2.0)
- org-ruby (~> 0.9.12)
- peek (~> 1.0.1)
- peek-gc (~> 0.0.2)
- peek-mysql2 (~> 1.1.0)
- peek-pg (~> 1.3.0)
- peek-rblineprof (~> 0.2.0)
- peek-redis (~> 1.2.0)
- pg (~> 0.18.2)
- premailer-rails (~> 1.9.7)
- prometheus-client-mmap (~> 0.9.4)
- pry-byebug (~> 3.4.1)
- pry-rails (~> 0.3.4)
- puma (~> 3.12)
- puma_worker_killer
- rack (= 1.6.11)
- rack-attack (~> 4.4.1)
- rack-cors (~> 1.0.0)
- rack-oauth2 (~> 1.2.1)
- rack-proxy (~> 0.6.0)
- rails (= 4.2.10)
- rails-deprecated_sanitizer (~> 1.0.3)
- rails-i18n (~> 4.0.9)
- rainbow (~> 3.0)
- raindrops (~> 0.18)
- rblineprof (~> 0.3.6)
- rbtrace (~> 0.4)
- rdoc (~> 6.0)
- re2 (~> 1.1.1)
- recaptcha (~> 3.0)
- redcarpet (~> 3.4)
- redis (~> 3.2)
- redis-namespace (~> 1.6.0)
- redis-rails (~> 5.0.2)
- request_store (~> 1.3)
- responders (~> 2.0)
- rouge (~> 3.1)
- rqrcode-rails3 (~> 0.1.7)
- rspec-parameterized
- rspec-rails (~> 3.7.0)
- rspec-retry (~> 0.4.5)
- rspec-set (~> 0.1.3)
- rspec_junit_formatter
- rspec_profiling (~> 0.0.5)
- rubocop (~> 0.54.0)
- rubocop-rspec (~> 1.22.1)
- ruby-fogbugz (~> 0.2.1)
- ruby-prof (~> 0.17.0)
- ruby-progressbar
- ruby_parser (~> 3.8)
- rufus-scheduler (~> 3.4)
- rugged (~> 0.27)
- sanitize (~> 4.6)
- sass-rails (~> 5.0.6)
- scss_lint (~> 0.56.0)
- seed-fu (~> 2.3.7)
- select2-rails (~> 3.5.9)
- selenium-webdriver (~> 3.12)
- sentry-raven (~> 2.7)
- settingslogic (~> 2.0.9)
- sham_rack (~> 1.3.6)
- shoulda-matchers (~> 3.1.2)
- sidekiq (~> 5.2.1)
- sidekiq-cron (~> 0.6.0)
- simple_po_parser (~> 1.1.2)
- simplecov (~> 0.14.0)
- slack-notifier (~> 1.5.1)
- spring (~> 2.0.0)
- spring-commands-rspec (~> 1.0.4)
- sprockets (~> 3.7.0)
- sshkey (~> 1.9.0)
- stackprof (~> 0.2.10)
- state_machines-activerecord (~> 0.5.1)
- sys-filesystem (~> 1.1.6)
- test-prof (~> 0.2.5)
- test_after_commit (~> 1.1)
- thin (~> 1.7.0)
- timecop (~> 0.8.0)
- toml-rb (~> 1.0.0)
- truncato (~> 0.7.9)
- u2f (~> 0.2.1)
- uglifier (~> 2.7.2)
- unf (~> 0.1.4)
- unicorn (~> 5.1.0)
- unicorn-worker-killer (~> 0.4.4)
- validates_hostname (~> 1.0.6)
- version_sorter (~> 2.1.0)
- virtus (~> 1.0.1)
- vmstat (~> 2.3.0)
- webmock (~> 2.3.2)
- webpack-rails (~> 0.9.10)
- wikicloth (= 0.8.1)
-
-BUNDLED WITH
- 1.17.1
diff --git a/PROCESS.md b/PROCESS.md
index aadd6a321f0..5aafbd33daf 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -12,15 +12,18 @@
- [Assigning issues](#assigning-issues)
- [Be kind](#be-kind)
- [Feature freeze on the 7th for the release on the 22nd](#feature-freeze-on-the-7th-for-the-release-on-the-22nd)
+ - [Feature flags](#feature-flags)
- [Between the 1st and the 7th](#between-the-1st-and-the-7th)
+ - [What happens if these deadlines are missed?](#what-happens-if-these-deadlines-are-missed)
- [On the 7th](#on-the-7th)
+ - [Feature merge requests](#feature-merge-requests)
+ - [Documentation merge requests](#documentation-merge-requests)
- [After the 7th](#after-the-7th)
+ - [Asking for an exception](#asking-for-an-exception)
- [Bugs](#bugs)
- [Regressions](#regressions)
- [Managing bugs](#managing-bugs)
- [Release retrospective and kickoff](#release-retrospective-and-kickoff)
- - [Retrospective](#retrospective)
- - [Kickoff](#kickoff)
- [Copy & paste responses](#copy--paste-responses)
- [Improperly formatted issue](#improperly-formatted-issue)
- [Issue report for old version](#issue-report-for-old-version)
@@ -28,11 +31,8 @@
- [Code format](#code-format)
- [Issue fixed in newer version](#issue-fixed-in-newer-version)
- [Improperly formatted merge request](#improperly-formatted-merge-request)
- - [Inactivity close of an issue](#inactivity-close-of-an-issue)
- - [Inactivity close of a merge request](#inactivity-close-of-a-merge-request)
- [Accepting merge requests](#accepting-merge-requests)
- [Only accepting merge requests with green tests](#only-accepting-merge-requests-with-green-tests)
- - [Closing down the issue tracker on GitHub](#closing-down-the-issue-tracker-on-github)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@@ -58,6 +58,18 @@ their contributions accepted by meeting our [Definition of done][done].
What you can expect from them is described at https://about.gitlab.com/roles/merge-request-coach/.
+### Milestones on community contribution issues
+
+The milestone of an issue that is currently being worked on by a community contributor
+should not be set to a named GitLab milestone (e.g. 11.7, 11.8), until the associated
+merge request is very close to being merged, and we will likely know in which named
+GitLab milestone the issue will land. There are many factors that influence when
+a community contributor finishes an issue, or even at all. So we should set this
+milestone only when we have more certainty.
+
+Note this only applies to issues currently assigned to community contributors. For
+issues assigned to GitLabbers, we are [ambitious in assigning milestones to issues](https://about.gitlab.com/direction/#how-we-plan-releases).
+
## Assigning issues
If an issue is complex and needs the attention of a specific person, assignment is a good option but assigning issues might discourage other people from contributing to that issue. We need all the contributions we can get so this should never be discouraged. Also, an assigned person might not have time for a few weeks, so others should feel free to takeover.
@@ -249,7 +261,7 @@ A ~bug is a defect, error, failure which causes the system to behave incorrectly
The level of impact of a ~bug can vary from blocking a whole functionality
or a feature usability bug. A bug should always be linked to a severity level.
-Refer to our [severity levels](../CONTRIBUTING.md#severity-labels)
+Refer to our [severity levels](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#severity-labels)
Whether the bug is also a regression or not, the triage process should start as soon as possible.
Ensure that the Engineering Manager and/or the Product Manager for the relative area is involved to prioritize the work as needed.
@@ -281,10 +293,10 @@ 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](../CONTRIBUTING.md#team-labels),
- the [subject label](../CONTRIBUTING.md#subject-labels)
+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. Notify the respective Engineering Manager to evaluate and apply the [Severity label](../CONTRIBUTING.md#bug-severity-labels) and [Priority label](../CONTRIBUTING.md#bug-priority-labels).
+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:
1. The Engineering Manager decides which milestone the bug will be fixed. The appropriate milestone is applied.
@@ -306,67 +318,77 @@ The counterpart Product Manager is included to weigh-in on prioritization as nee
### Improperly formatted issue
-Thanks for the issue report. Please reformat your issue to conform to the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
+```
+Thanks for the issue report. Please reformat your issue to conform to the
+[contributing guidelines](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#issue-tracker-guidelines).
+```
### Issue report for old version
-Thanks for the issue report but we only support issues for the latest stable version of GitLab. I'm closing this issue but if you still experience this problem in the latest stable version, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
+```
+Thanks for the issue report but we only support issues for the latest stable version of GitLab.
+I'm closing this issue but if you still experience this problem in the latest stable version,
+please open a new issue (but also reference the old issue(s)).
+Make sure to also include the necessary debugging information conforming to the issue tracker
+guidelines found in our [contributing guidelines](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#issue-tracker-guidelines).
+```
### Support requests and configuration questions
+```
Thanks for your interest in GitLab. We don't use the issue tracker for support
requests and configuration questions. Please check our
[getting help](https://about.gitlab.com/getting-help/) page to see all of the available
-support options. Also, have a look at the [contribution guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md)
+support options. Also, have a look at the [contribution guidelines](https://docs.gitlab.com/ee/development/contributing/index.html)
for more information.
+```
### Code format
+```
Please use \`\`\` to format console output, logs, and code as it's very hard to read otherwise.
+```
### Issue fixed in newer version
-Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please [upgrade](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
+```
+Thanks for the issue report. This issue has already been fixed in newer versions of GitLab.
+Due to the size of this project and our limited resources we are only able to support the
+latest stable release as outlined in our [contributing guidelines](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html).
+In order to get this bug fix and enjoy many new features please
+[upgrade](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update).
+If you still experience issues at that time please open a new issue following our issue
+tracker guidelines found in the [contributing guidelines](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#issue-tracker-guidelines).
+```
### Improperly formatted merge request
-Thanks for your interest in improving the GitLab codebase! Please update your merge request according to the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-request-guidelines).
-
-### Inactivity close of an issue
-
-It's been at least 2 weeks (and a new release) since we heard from you. I'm closing this issue but if you still experience this problem, please open a new issue (but also reference the old issue(s)). Make sure to also include the necessary debugging information conforming to the issue tracker guidelines found in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
-
-### Inactivity close of a merge request
-
-This merge request has been closed because a request for more information has not been reacted to for more than 2 weeks. If you respond and conform to the merge request guidelines in our [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#pull-requests) we will reopen this merge request.
+```
+Thanks for your interest in improving the GitLab codebase!
+Please update your merge request according to the [contributing guidelines](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/contributing/merge_request_workflow.md#merge-request-guidelines).
+```
### Accepting merge requests
+```
Is there an issue on the
[issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) that is
similar to this? Could you please link it here?
Please be aware that new functionality that is not marked
-[accepting merge requests](https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_id=&scope=all&sort=created_desc&state=opened&utf8=%E2%9C%93&assignee_id=&author_id=&milestone_title=&label_name=Accepting+Merge+Requests)
+[`Accepting merge requests`](https://docs.gitlab.com/ee/development/contributing/issue_workflow.html#label-for-community-contributors)
might not make it into GitLab.
+```
### Only accepting merge requests with green tests
+```
We can only accept a merge request if all the tests are green. I've just
restarted the build. When the tests are still not passing after this restart and
you're sure that is does not have anything to do with your code changes, please
rebase with master to see if that solves the issue.
-
-### Closing down the issue tracker on GitHub
-
-We are currently in the process of closing down the issue tracker on GitHub, to
-prevent duplication with the GitLab.com issue tracker.
-Since this is an older issue I'll be closing this for now. If you think this is
-still an issue I encourage you to open it on the [GitLab.com issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues).
+```
[team]: https://about.gitlab.com/team/
-[contribution acceptance criteria]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria
-["Implement design & UI elements" guidelines]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#implement-design-ui-elements
-[Thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review
-[done]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#definition-of-done
+[done]: https://docs.gitlab.com/ee/development/contributing/merge_request_workflow.html#definition-of-done
[automatic_ce_ee_merge]: https://docs.gitlab.com/ce/development/automatic_ce_ee_merge.html
[ee_features]: https://docs.gitlab.com/ce/development/ee_features.html
diff --git a/VERSION b/VERSION
index 35167c50c26..74f35bff618 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-11.6.0-pre
+11.7.0-pre
diff --git a/app/assets/images/none-scheme-preview.png b/app/assets/images/none-scheme-preview.png
new file mode 100644
index 00000000000..2eb6bf96671
--- /dev/null
+++ b/app/assets/images/none-scheme-preview.png
Binary files differ
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index f8dbe412f80..d1396b6c4bc 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -14,17 +14,22 @@ const Api = {
projectMergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid',
projectMergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
projectMergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions',
+ projectRunnersPath: '/api/:version/projects/:id/runners',
mergeRequestsPath: '/api/:version/merge_requests',
groupLabelsPath: '/groups/:namespace_path/-/labels',
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
projectTemplatePath: '/api/:version/projects/:id/templates/:type/:key',
projectTemplatesPath: '/api/:version/projects/:id/templates/:type',
usersPath: '/api/:version/users.json',
- userStatusPath: '/api/:version/user/status',
+ userPath: '/api/:version/users/:id',
+ userStatusPath: '/api/:version/users/:id/status',
+ userPostStatusPath: '/api/:version/user/status',
commitPath: '/api/:version/projects/:id/repository/commits',
+ applySuggestionPath: '/api/:version/suggestions/:id/apply',
commitPipelinesPath: '/:project_id/commit/:sha/pipelines',
branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch',
createBranchPath: '/api/:version/projects/:id/repository/branches',
+ releasesPath: '/api/:version/projects/:id/releases',
group(groupId, callback) {
const url = Api.buildUrl(Api.groupPath).replace(':id', groupId);
@@ -124,6 +129,15 @@ const Api = {
return axios.get(url);
},
+ projectRunners(projectPath, config = {}) {
+ const url = Api.buildUrl(Api.projectRunnersPath).replace(
+ ':id',
+ encodeURIComponent(projectPath),
+ );
+
+ return axios.get(url, config);
+ },
+
mergeRequests(params = {}) {
const url = Api.buildUrl(Api.mergeRequestsPath);
@@ -173,6 +187,12 @@ const Api = {
});
},
+ applySuggestion(id) {
+ const url = Api.buildUrl(Api.applySuggestionPath).replace(':id', encodeURIComponent(id));
+
+ return axios.put(url);
+ },
+
commitPipelines(projectId, sha) {
const encodedProjectId = projectId
.split('/')
@@ -244,6 +264,20 @@ const Api = {
});
},
+ user(id, options) {
+ const url = Api.buildUrl(this.userPath).replace(':id', encodeURIComponent(id));
+ return axios.get(url, {
+ params: options,
+ });
+ },
+
+ userStatus(id, options) {
+ const url = Api.buildUrl(this.userStatusPath).replace(':id', encodeURIComponent(id));
+ return axios.get(url, {
+ params: options,
+ });
+ },
+
branches(id, query = '', options = {}) {
const url = Api.buildUrl(this.createBranchPath).replace(':id', encodeURIComponent(id));
@@ -266,7 +300,7 @@ const Api = {
},
postUserStatus({ emoji, message }) {
- const url = Api.buildUrl(this.userStatusPath);
+ const url = Api.buildUrl(this.userPostStatusPath);
return axios.put(url, {
emoji,
@@ -274,6 +308,12 @@ const Api = {
});
},
+ releases(id) {
+ const url = Api.buildUrl(this.releasesPath).replace(':id', encodeURIComponent(id));
+
+ return axios.get(url);
+ },
+
buildUrl(url) {
let urlRoot = '';
if (gon.relative_url_root != null) {
diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js
index a2d4331b6d1..fc9286d15e6 100644
--- a/app/assets/javascripts/behaviors/markdown/render_gfm.js
+++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js
@@ -3,6 +3,7 @@ import syntaxHighlight from '~/syntax_highlight';
import renderMath from './render_math';
import renderMermaid from './render_mermaid';
import highlightCurrentUser from './highlight_current_user';
+import initUserPopovers from '../../user_popovers';
// Render GitLab flavoured Markdown
//
@@ -13,6 +14,7 @@ $.fn.renderGFM = function renderGFM() {
renderMath(this.find('.js-render-math'));
renderMermaid(this.find('.js-render-mermaid'));
highlightCurrentUser(this.find('.gfm-project_member').get());
+ initUserPopovers(this.find('.gfm-project_member').get());
return this;
};
diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js
index c09d9ccddd6..d8056e48d4e 100644
--- a/app/assets/javascripts/behaviors/requires_input.js
+++ b/app/assets/javascripts/behaviors/requires_input.js
@@ -50,10 +50,11 @@ function hideOrShowHelpBlock(form) {
}
$(() => {
- const $form = $('form.js-requires-input');
- if ($form) {
+ $('form.js-requires-input').each((i, el) => {
+ const $form = $(el);
+
$form.requiresInput();
hideOrShowHelpBlock($form);
$('.select2.js-select-namespace').change(() => hideOrShowHelpBlock($form));
- }
+ });
});
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
index fa9b2c9f755..bef1553703b 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
@@ -8,6 +8,7 @@ export default class ShortcutsNavigation extends Shortcuts {
Mousetrap.bind('g p', () => findAndFollowLink('.shortcuts-project'));
Mousetrap.bind('g v', () => findAndFollowLink('.shortcuts-project-activity'));
+ Mousetrap.bind('g r', () => findAndFollowLink('.shortcuts-project-releases'));
Mousetrap.bind('g f', () => findAndFollowLink('.shortcuts-tree'));
Mousetrap.bind('g c', () => findAndFollowLink('.shortcuts-commits'));
Mousetrap.bind('g j', () => findAndFollowLink('.shortcuts-builds'));
diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js
index 9f547471170..b07f951346e 100644
--- a/app/assets/javascripts/blob_edit/blob_bundle.js
+++ b/app/assets/javascripts/blob_edit/blob_bundle.js
@@ -17,6 +17,11 @@ export default () => {
const currentAction = $('.js-file-title').data('currentAction');
const projectId = editBlobForm.data('project-id');
const commitButton = $('.js-commit-button');
+ const cancelLink = $('.btn.btn-cancel');
+
+ cancelLink.on('click', () => {
+ window.onbeforeunload = null;
+ });
commitButton.on('click', () => {
window.onbeforeunload = null;
diff --git a/app/assets/javascripts/boards/components/issue_due_date.vue b/app/assets/javascripts/boards/components/issue_due_date.vue
index 15937b1091a..e038198e6f0 100644
--- a/app/assets/javascripts/boards/components/issue_due_date.vue
+++ b/app/assets/javascripts/boards/components/issue_due_date.vue
@@ -15,6 +15,16 @@ export default {
type: String,
required: true,
},
+ cssClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ tooltipPlacement: {
+ type: String,
+ required: false,
+ default: 'bottom',
+ },
},
computed: {
title() {
@@ -66,15 +76,13 @@ export default {
<template>
<span>
- <span ref="issueDueDate" class="board-card-info card-number">
- <icon
- :class="{ 'text-danger': isPastDue, 'board-card-info-icon': true }"
- name="calendar"
- /><time :class="{ 'text-danger': isPastDue }" datetime="date" class="board-card-info-text">{{
+ <span ref="issueDueDate" :class="cssClass" class="board-card-info card-number">
+ <icon :class="{ 'text-danger': isPastDue, 'board-card-info-icon': true }" name="calendar" />
+ <time :class="{ 'text-danger': isPastDue }" datetime="date" class="board-card-info-text">{{
body
}}</time>
</span>
- <gl-tooltip :target="() => $refs.issueDueDate" placement="bottom">
+ <gl-tooltip :target="() => $refs.issueDueDate" :placement="tooltipPlacement">
<span class="bold">{{ __('Due date') }}</span> <br />
<span :class="{ 'text-danger-muted': isPastDue }">{{ title }}</span>
</gl-tooltip>
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js
index f7016561f93..10577da9305 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js
@@ -37,7 +37,7 @@ export default function initNewListDropdown() {
});
},
renderRow(label) {
- const active = boardsStore.findList('title', label.title);
+ const active = boardsStore.findListByLabelId(label.id);
const $li = $('<li />');
const $a = $('<a />', {
class: active ? `is-active js-board-list-${active.id}` : '',
@@ -63,7 +63,7 @@ export default function initNewListDropdown() {
const label = options.selectedObj;
e.preventDefault();
- if (!boardsStore.findList('title', label.title)) {
+ if (!boardsStore.findListByLabelId(label.id)) {
boardsStore.new({
title: label.title,
position: boardsStore.state.lists.length - 2,
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
index 31651658fe6..d899b7fbd8c 100644
--- a/app/assets/javascripts/boards/components/project_select.vue
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -92,20 +92,7 @@ export default {
{{ selectedProjectName }} <icon name="chevron-down" />
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-full-width">
- <div class="dropdown-title">
- <span>Projects</span>
- <button
- aria-label="Close"
- type="button"
- class="dropdown-title-button dropdown-menu-close"
- >
- <icon
- name="merge-request-close-m"
- data-hidden="true"
- class="dropdown-menu-close-icon"
- />
- </button>
- </div>
+ <div class="dropdown-title">Projects</div>
<div class="dropdown-input">
<input class="dropdown-input-field" type="search" placeholder="Search projects" />
<icon name="search" class="dropdown-input-search" data-hidden="true" />
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index 5e0f0b07247..dd92d3c8552 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -55,12 +55,12 @@ class ListIssue {
}
findLabel(findLabel) {
- return this.labels.filter(label => label.title === findLabel.title)[0];
+ return this.labels.find(label => label.id === findLabel.id);
}
removeLabel(removeLabel) {
if (removeLabel) {
- this.labels = this.labels.filter(label => removeLabel.title !== label.title);
+ this.labels = this.labels.filter(label => removeLabel.id !== label.id);
}
}
@@ -75,7 +75,7 @@ class ListIssue {
}
findAssignee(findAssignee) {
- return this.assignees.filter(assignee => assignee.id === findAssignee.id)[0];
+ return this.assignees.find(assignee => assignee.id === findAssignee.id);
}
removeAssignee(removeAssignee) {
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index cf88a973d33..802796208c2 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -166,6 +166,9 @@ const boardsStore = {
});
return filteredList[0];
},
+ findListByLabelId(id) {
+ return this.state.lists.find(list => list.type === 'label' && list.label.id === id);
+ },
updateFiltersUrl() {
window.history.pushState(null, null, `?${this.filter.path}`);
},
diff --git a/app/assets/javascripts/ci_variable_list/ci_variable_list.js b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
index ee0f7cda189..5b20fa141cd 100644
--- a/app/assets/javascripts/ci_variable_list/ci_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
@@ -36,7 +36,9 @@ export default class VariableList {
},
protected: {
selector: '.js-ci-variable-input-protected',
- default: 'false',
+ // use `attr` instead of `data` as we don't want the value to be
+ // converted. we need the value as a string.
+ default: $('.js-ci-variable-input-protected').attr('data-default'),
},
environment_scope: {
// We can't use a `.js-` class here because
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index aff32d95db1..cf70a48f076 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -1,6 +1,6 @@
import Visibility from 'visibilityjs';
import Vue from 'vue';
-import PersistentUserCallout from '../persistent_user_callout';
+import initDismissableCallout from '~/dismissable_callout';
import { s__, sprintf } from '../locale';
import Flash from '../flash';
import Poll from '../lib/utils/poll';
@@ -67,7 +67,7 @@ export default class Clusters {
this.showTokenButton = document.querySelector('.js-show-cluster-token');
this.tokenField = document.querySelector('.js-cluster-token');
- Clusters.initDismissableCallout();
+ initDismissableCallout('.js-cluster-security-warning');
initSettingsPanels();
setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area'));
this.initApplications(clusterType);
@@ -108,12 +108,6 @@ export default class Clusters {
});
}
- static initDismissableCallout() {
- const callout = document.querySelector('.js-cluster-security-warning');
-
- if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
- }
-
addListeners() {
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
eventHub.$on('installApplication', this.installApplication);
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index 9a96d0fa6d7..489615f1f78 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -84,6 +84,9 @@ export default {
ingressExternalIp() {
return this.applications.ingress.externalIp;
},
+ certManagerInstalled() {
+ return this.applications.cert_manager.status === APPLICATION_STATUS.INSTALLED;
+ },
ingressDescription() {
const extraCostParagraph = sprintf(
_.escape(
@@ -130,9 +133,9 @@ export default {
return sprintf(
_.escape(
s__(
- `ClusterIntegration|cert-manager is a native Kubernetes certificate management controller that helps with issuing certificates.
- Installing cert-manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates
- are valid and up to date.`,
+ `ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates.
+ Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates
+ are valid and up-to-date.`,
),
),
{
@@ -259,6 +262,16 @@ export default {
</span>
</div>
<input v-else type="text" class="form-control js-ip-address" readonly value="?" />
+ <p class="form-text text-muted">
+ {{
+ s__(`ClusterIntegration|Point a wildcard DNS to this
+ generated IP address in order to access
+ your application after it has been deployed.`)
+ }}
+ <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
+ {{ __('More information') }}
+ </a>
+ </p>
</div>
<p v-if="!ingressExternalIp" class="settings-message js-no-ip-message">
@@ -272,17 +285,6 @@ export default {
{{ __('More information') }}
</a>
</p>
-
- <p>
- {{
- s__(`ClusterIntegration|Point a wildcard DNS to this
- generated IP address in order to access
- your application after it has been deployed.`)
- }}
- <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
- {{ __('More information') }}
- </a>
- </p>
</template>
<div v-html="ingressDescription"></div>
</div>
@@ -295,11 +297,41 @@ export default {
:status-reason="applications.cert_manager.statusReason"
:request-status="applications.cert_manager.requestStatus"
:request-reason="applications.cert_manager.requestReason"
+ :install-application-request-params="{ email: applications.cert_manager.email }"
:disabled="!helmInstalled"
- class="hide-bottom-border rounded-bottom"
title-link="https://cert-manager.readthedocs.io/en/latest/#"
>
- <div slot="description" v-html="certManagerDescription"></div>
+ <template>
+ <div slot="description">
+ <p v-html="certManagerDescription"></p>
+ <div class="form-group">
+ <label for="cert-manager-issuer-email">
+ {{ s__('ClusterIntegration|Issuer Email') }}
+ </label>
+ <div class="input-group">
+ <input
+ v-model="applications.cert_manager.email"
+ :readonly="certManagerInstalled"
+ type="text"
+ class="form-control js-email"
+ />
+ </div>
+ <p class="form-text text-muted">
+ {{
+ s__(`ClusterIntegration|Issuers represent a certificate authority.
+ You must provide an email address for your Issuer. `)
+ }}
+ <a
+ href="http://docs.cert-manager.io/en/latest/reference/issuers.html?highlight=email"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ {{ __('More information') }}
+ </a>
+ </p>
+ </div>
+ </div>
+ </template>
</application-row>
<application-row
v-if="isProjectCluster"
@@ -382,20 +414,22 @@ export default {
/>
</span>
</div>
+
+ <p v-if="ingressInstalled" class="form-text text-muted">
+ {{
+ s__(`ClusterIntegration|Replace this with your own hostname if you want.
+ If you do so, point hostname to Ingress IP Address from above.`)
+ }}
+ <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
+ {{ __('More information') }}
+ </a>
+ </p>
</div>
- <p v-if="ingressInstalled">
- {{
- s__(`ClusterIntegration|Replace this with your own hostname if you want.
- If you do so, point hostname to Ingress IP Address from above.`)
- }}
- <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
- {{ __('More information') }}
- </a>
- </p>
</template>
</div>
</application-row>
<application-row
+ v-if="isProjectCluster"
id="knative"
:logo-url="knativeLogo"
:title="applications.knative.title"
@@ -405,7 +439,6 @@ export default {
:request-reason="applications.knative.requestReason"
:install-application-request-params="{ hostname: applications.knative.hostname }"
:disabled="!helmInstalled"
- class="hide-bottom-border rounded-bottom"
title-link="https://github.com/knative/docs"
>
<div slot="description">
@@ -432,7 +465,7 @@ export default {
/>
</div>
</template>
- <template v-else>
+ <template v-else-if="helmInstalled">
<div class="form-group">
<label for="knative-domainname">
{{ s__('ClusterIntegration|Knative Domain Name:') }}
diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js
index 15cf4a56138..e31afadf186 100644
--- a/app/assets/javascripts/clusters/constants.js
+++ b/app/assets/javascripts/clusters/constants.js
@@ -24,3 +24,4 @@ export const REQUEST_FAILURE = 'request-failure';
export const INGRESS = 'ingress';
export const JUPYTER = 'jupyter';
export const KNATIVE = 'knative';
+export const CERT_MANAGER = 'cert_manager';
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index 2d69da8eaec..c750daab112 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -1,5 +1,5 @@
import { s__ } from '../../locale';
-import { INGRESS, JUPYTER, KNATIVE } from '../constants';
+import { INGRESS, JUPYTER, KNATIVE, CERT_MANAGER } from '../constants';
export default class ClusterStore {
constructor() {
@@ -30,6 +30,7 @@ export default class ClusterStore {
statusReason: null,
requestStatus: null,
requestReason: null,
+ email: null,
},
runner: {
title: s__('ClusterIntegration|GitLab Runner'),
@@ -103,6 +104,9 @@ export default class ClusterStore {
if (appId === INGRESS) {
this.state.applications.ingress.externalIp = serverAppEntry.external_ip;
+ } else if (appId === CERT_MANAGER) {
+ this.state.applications.cert_manager.email =
+ this.state.applications.cert_manager.email || serverAppEntry.email;
} else if (appId === JUPYTER) {
this.state.applications.jupyter.hostname =
serverAppEntry.hostname ||
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index bf9244df7f7..d4c1b07093d 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -42,6 +42,16 @@ export default {
type: Object,
required: true,
},
+ helpPagePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ changesEmptyStateIllustration: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
data() {
return {
@@ -63,7 +73,7 @@ export default {
plainDiffPath: state => state.diffs.plainDiffPath,
emailPatchPath: state => state.diffs.emailPatchPath,
}),
- ...mapState('diffs', ['showTreeList', 'isLoading']),
+ ...mapState('diffs', ['showTreeList', 'isLoading', 'startVersion']),
...mapGetters('diffs', ['isParallelView']),
...mapGetters(['isNotesFetched', 'getNoteableData']),
targetBranch() {
@@ -79,6 +89,13 @@ export default {
showCompareVersions() {
return this.mergeRequestDiffs && this.mergeRequestDiff;
},
+ renderDiffFiles() {
+ return (
+ this.diffFiles.length > 0 ||
+ (this.startVersion &&
+ this.startVersion.version_index === this.mergeRequestDiff.version_index)
+ );
+ },
},
watch: {
diffViewType() {
@@ -191,15 +208,16 @@ export default {
<div v-show="showTreeList" class="diff-tree-list"><tree-list /></div>
<div class="diff-files-holder">
<commit-widget v-if="commit" :commit="commit" />
- <template v-if="diffFiles.length > 0">
+ <template v-if="renderDiffFiles">
<diff-file
v-for="file in diffFiles"
:key="file.newPath"
:file="file"
+ :help-page-path="helpPagePath"
:can-current-user-fork="canCurrentUserFork"
/>
</template>
- <no-changes v-else />
+ <no-changes v-else :changes-empty-state-illustration="changesEmptyStateIllustration" />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue
index e405d8b20ae..42d09e44768 100644
--- a/app/assets/javascripts/diffs/components/diff_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_content.vue
@@ -1,6 +1,7 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
+import EmptyFileViewer from '~/vue_shared/components/diff_viewer/viewers/empty_file.vue';
import InlineDiffView from './inline_diff_view.vue';
import ParallelDiffView from './parallel_diff_view.vue';
import NoteForm from '../../notes/components/note_form.vue';
@@ -17,12 +18,18 @@ export default {
NoteForm,
DiffDiscussions,
ImageDiffOverlay,
+ EmptyFileViewer,
},
props: {
diffFile: {
type: Object,
required: true,
},
+ helpPagePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
...mapState({
@@ -70,15 +77,18 @@ export default {
<div class="diff-content">
<div class="diff-viewer">
<template v-if="isTextFile">
+ <empty-file-viewer v-if="diffFile.empty" />
<inline-diff-view
- v-if="isInlineView"
+ v-else-if="isInlineView"
:diff-file="diffFile"
:diff-lines="diffFile.highlighted_diff_lines || []"
+ :help-page-path="helpPagePath"
/>
<parallel-diff-view
- v-if="isParallelView"
+ v-else-if="isParallelView"
:diff-file="diffFile"
:diff-lines="diffFile.parallel_diff_lines || []"
+ :help-page-path="helpPagePath"
/>
</template>
<diff-viewer
@@ -90,6 +100,8 @@ export default {
:old-sha="diffFile.diff_refs.base_sha"
:file-hash="diffFile.file_hash"
:project-path="projectPath"
+ :a-mode="diffFile.a_mode"
+ :b-mode="diffFile.b_mode"
>
<image-diff-overlay
slot="image-overlay"
diff --git a/app/assets/javascripts/diffs/components/diff_discussions.vue b/app/assets/javascripts/diffs/components/diff_discussions.vue
index bee29b04e92..b2021cd6061 100644
--- a/app/assets/javascripts/diffs/components/diff_discussions.vue
+++ b/app/assets/javascripts/diffs/components/diff_discussions.vue
@@ -13,6 +13,11 @@ export default {
type: Array,
required: true,
},
+ line: {
+ type: Object,
+ required: false,
+ default: null,
+ },
shouldCollapseDiscussions: {
type: Boolean,
required: false,
@@ -23,6 +28,11 @@ export default {
required: false,
default: false,
},
+ helpPagePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
methods: {
...mapActions(['toggleDiscussion']),
@@ -72,6 +82,8 @@ export default {
:render-diff-file="false"
:always-expanded="true"
:discussions-by-diff-order="true"
+ :line="line"
+ :help-page-path="helpPagePath"
@noteDeleted="deleteNoteHandler"
>
<span v-if="renderAvatarBadge" slot="avatar-badge" class="badge badge-pill">
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index f7e3655ea40..449f7007077 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -4,6 +4,7 @@ import _ from 'underscore';
import { __, sprintf } from '~/locale';
import createFlash from '~/flash';
import { GlLoadingIcon } from '@gitlab/ui';
+import eventHub from '../../notes/event_hub';
import DiffFileHeader from './diff_file_header.vue';
import DiffContent from './diff_content.vue';
@@ -22,6 +23,11 @@ export default {
type: Boolean,
required: true,
},
+ helpPagePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
data() {
return {
@@ -52,7 +58,9 @@ export default {
(!this.file.highlighted_diff_lines &&
!this.isLoadingCollapsedDiff &&
!this.file.too_large &&
- this.file.text)
+ this.file.text &&
+ !this.file.renamed_file &&
+ !this.file.mode_changed)
);
},
showLoadingIcon() {
@@ -73,6 +81,9 @@ export default {
}
},
},
+ created() {
+ eventHub.$on(`loadCollapsedDiff/${this.file.file_hash}`, this.handleLoadCollapsedDiff);
+ },
methods: {
...mapActions('diffs', ['loadCollapsedDiff', 'assignDiscussionsToDiff']),
handleToggle() {
@@ -143,9 +154,8 @@ export default {
<a
:href="file.fork_path"
class="js-fork-suggestion-button btn btn-grouped btn-inverted btn-success"
+ >Fork</a
>
- Fork
- </a>
<button
class="js-cancel-fork-suggestion-button btn btn-grouped"
type="button"
@@ -159,13 +169,14 @@ export default {
v-if="!isCollapsed && file.renderIt"
:class="{ hidden: isCollapsed || file.too_large }"
:diff-file="file"
+ :help-page-path="helpPagePath"
/>
<gl-loading-icon v-if="showLoadingIcon" class="diff-content loading" />
<div v-else-if="showExpandMessage" class="nothing-here-block diff-collapsed">
{{ __('This diff is collapsed.') }}
- <a class="click-to-expand js-click-to-expand" href="#" @click.prevent="handleToggle">
- {{ __('Click to expand it.') }}
- </a>
+ <a class="click-to-expand js-click-to-expand" href="#" @click.prevent="handleToggle">{{
+ __('Click to expand it.')
+ }}</a>
</div>
<div v-if="file.too_large" class="nothing-here-block diff-collapsed js-too-large-diff">
{{ __('This source diff could not be displayed because it is too large.') }}
diff --git a/app/assets/javascripts/diffs/components/diff_line_note_form.vue b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
index 9fd02acbd6e..e7569ba7b84 100644
--- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
@@ -94,6 +94,7 @@ export default {
ref="noteForm"
:is-editing="true"
:line-code="line.line_code"
+ :line="line"
save-button-title="Comment"
class="diff-comment-form"
@cancelForm="handleCancelCommentForm"
diff --git a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
index aa40b24950a..814ee0b7c02 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
@@ -16,6 +16,11 @@ export default {
type: String,
required: true,
},
+ helpPagePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
className() {
@@ -38,7 +43,12 @@ export default {
<tr v-if="shouldRender" :class="className" class="notes_holder">
<td class="notes_content" colspan="3">
<div class="content">
- <diff-discussions v-if="line.discussions.length" :discussions="line.discussions" />
+ <diff-discussions
+ v-if="line.discussions.length"
+ :line="line"
+ :discussions="line.discussions"
+ :help-page-path="helpPagePath"
+ />
<diff-line-note-form
v-if="line.hasForm"
:diff-file-hash="diffFileHash"
diff --git a/app/assets/javascripts/diffs/components/inline_diff_view.vue b/app/assets/javascripts/diffs/components/inline_diff_view.vue
index 6a0ce760e6d..e781397214d 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_view.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_view.vue
@@ -17,6 +17,11 @@ export default {
type: Array,
required: true,
},
+ helpPagePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
...mapGetters('diffs', ['commitId']),
@@ -44,9 +49,10 @@ export default {
:is-bottom="index + 1 === diffLinesLength"
/>
<inline-diff-comment-row
- :key="`icr-${index}`"
+ :key="`icr-${line.line_code || index}`"
:diff-file-hash="diffFile.file_hash"
:line="line"
+ :help-page-path="helpPagePath"
/>
</template>
</tbody>
diff --git a/app/assets/javascripts/diffs/components/no_changes.vue b/app/assets/javascripts/diffs/components/no_changes.vue
index 25ec157ed25..47e9627a957 100644
--- a/app/assets/javascripts/diffs/components/no_changes.vue
+++ b/app/assets/javascripts/diffs/components/no_changes.vue
@@ -1,34 +1,51 @@
<script>
-import { mapState } from 'vuex';
-import emptyImage from '~/../../views/shared/icons/_mr_widget_empty_state.svg';
+import { mapGetters } from 'vuex';
+import _ from 'underscore';
+import { GlButton } from '@gitlab/ui';
+import { __, sprintf } from '~/locale';
export default {
- data() {
- return {
- emptyImage,
- };
+ components: {
+ GlButton,
+ },
+ props: {
+ changesEmptyStateIllustration: {
+ type: String,
+ required: true,
+ },
},
computed: {
- ...mapState({
- sourceBranch: state => state.notes.noteableData.source_branch,
- targetBranch: state => state.notes.noteableData.target_branch,
- newBlobPath: state => state.notes.noteableData.new_blob_path,
- }),
+ ...mapGetters(['getNoteableData']),
+ emptyStateText() {
+ return sprintf(
+ __(
+ 'No changes between %{ref_start}%{source_branch}%{ref_end} and %{ref_start}%{target_branch}%{ref_end}',
+ ),
+ {
+ ref_start: '<span class="ref-name">',
+ ref_end: '</span>',
+ source_branch: _.escape(this.getNoteableData.source_branch),
+ target_branch: _.escape(this.getNoteableData.target_branch),
+ },
+ false,
+ );
+ },
},
};
</script>
<template>
- <div class="row empty-state nothing-here-block">
- <div class="col-xs-12">
- <div class="svg-content"><span v-html="emptyImage"></span></div>
+ <div class="row empty-state">
+ <div class="col-12">
+ <div class="svg-content svg-250"><img :src="changesEmptyStateIllustration" /></div>
</div>
- <div class="col-xs-12">
+ <div class="col-12">
<div class="text-content text-center">
- No changes between <span class="ref-name">{{ sourceBranch }}</span> and
- <span class="ref-name">{{ targetBranch }}</span>
+ <span v-html="emptyStateText"></span>
<div class="text-center">
- <a :href="newBlobPath" class="btn btn-success"> {{ __('Create commit') }} </a>
+ <gl-button :href="getNoteableData.new_blob_path" variant="success">{{
+ __('Create commit')
+ }}</gl-button>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
index b98463d3dd3..a65cf025cde 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
@@ -20,6 +20,11 @@ export default {
type: Number,
required: true,
},
+ helpPagePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
hasExpandedDiscussionOnLeft() {
@@ -87,6 +92,8 @@ export default {
<diff-discussions
v-if="line.left.discussions.length"
:discussions="line.left.discussions"
+ :line="line.left"
+ :help-page-path="helpPagePath"
/>
</div>
<diff-line-note-form
@@ -102,6 +109,8 @@ export default {
<diff-discussions
v-if="line.right.discussions.length"
:discussions="line.right.discussions"
+ :line="line.right"
+ :help-page-path="helpPagePath"
/>
</div>
<diff-line-note-form
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_view.vue b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
index 9a6e0e82529..1bf693380db 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_view.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
@@ -17,6 +17,11 @@ export default {
type: Array,
required: true,
},
+ helpPagePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
...mapGetters('diffs', ['commitId']),
@@ -38,17 +43,18 @@ export default {
<tbody>
<template v-for="(line, index) in diffLines">
<parallel-diff-table-row
- :key="index"
+ :key="line.line_code"
:file-hash="diffFile.file_hash"
:context-lines-path="diffFile.context_lines_path"
:line="line"
:is-bottom="index + 1 === diffLinesLength"
/>
<parallel-diff-comment-row
- :key="`dcr-${index}`"
+ :key="`dcr-${line.line_code || index}`"
:line="line"
:diff-file-hash="diffFile.file_hash"
:line-index="index"
+ :help-page-path="helpPagePath"
/>
</template>
</tbody>
diff --git a/app/assets/javascripts/diffs/index.js b/app/assets/javascripts/diffs/index.js
index 06ef4207d85..b130cedc24c 100644
--- a/app/assets/javascripts/diffs/index.js
+++ b/app/assets/javascripts/diffs/index.js
@@ -16,7 +16,9 @@ export default function initDiffsApp(store) {
return {
endpoint: dataset.endpoint,
projectPath: dataset.projectPath,
+ helpPagePath: dataset.helpPagePath,
currentUser: JSON.parse(dataset.currentUserData) || {},
+ changesEmptyStateIllustration: dataset.changesEmptyStateIllustration,
};
},
computed: {
@@ -30,7 +32,9 @@ export default function initDiffsApp(store) {
endpoint: this.endpoint,
currentUser: this.currentUser,
projectPath: this.projectPath,
+ helpPagePath: this.helpPagePath,
shouldShow: this.activeTab === 'diffs',
+ changesEmptyStateIllustration: this.changesEmptyStateIllustration,
},
});
},
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index c87e178c8cf..00a4bb6d3a3 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -3,8 +3,9 @@ import axios from '~/lib/utils/axios_utils';
import Cookies from 'js-cookie';
import createFlash from '~/flash';
import { s__ } from '~/locale';
-import { handleLocationHash, historyPushState } from '~/lib/utils/common_utils';
+import { handleLocationHash, historyPushState, scrollToElement } from '~/lib/utils/common_utils';
import { mergeUrlParams, getLocationHash } from '~/lib/utils/url_utility';
+import eventHub from '../../notes/event_hub';
import { getDiffPositionByLineCode, getNoteFormData } from './utils';
import * as types from './mutation_types';
import {
@@ -53,6 +54,10 @@ export const assignDiscussionsToDiff = (
diffPositionByLineCode,
});
});
+
+ Vue.nextTick(() => {
+ eventHub.$emit('scrollToDiscussion');
+ });
};
export const removeDiscussionsFromDiff = ({ commit }, removeDiscussion) => {
@@ -60,6 +65,27 @@ export const removeDiscussionsFromDiff = ({ commit }, removeDiscussion) => {
commit(types.REMOVE_LINE_DISCUSSIONS_FOR_FILE, { fileHash: file_hash, lineCode: line_code, id });
};
+export const renderFileForDiscussionId = ({ commit, rootState, state }, discussionId) => {
+ const discussion = rootState.notes.discussions.find(d => d.id === discussionId);
+
+ if (discussion) {
+ const file = state.diffFiles.find(f => f.file_hash === discussion.diff_file.file_hash);
+
+ if (file) {
+ if (!file.renderIt) {
+ commit(types.RENDER_FILE, file);
+ }
+
+ if (file.collapsed) {
+ eventHub.$emit(`loadCollapsedDiff/${file.file_hash}`);
+ scrollToElement(document.getElementById(file.file_hash));
+ } else {
+ eventHub.$emit('scrollToDiscussion');
+ }
+ }
+ }
+};
+
export const startRenderDiffsQueue = ({ state, commit }) => {
const checkItem = () =>
new Promise(resolve => {
@@ -147,13 +173,19 @@ export const scrollToLineIfNeededParallel = (_, line) => {
}
};
-export const loadCollapsedDiff = ({ commit }, file) =>
- axios.get(file.load_collapsed_diff_url).then(res => {
- commit(types.ADD_COLLAPSED_DIFFS, {
- file,
- data: res.data,
+export const loadCollapsedDiff = ({ commit, getters }, file) =>
+ axios
+ .get(file.load_collapsed_diff_url, {
+ params: {
+ commit_id: getters.commitId,
+ },
+ })
+ .then(res => {
+ commit(types.ADD_COLLAPSED_DIFFS, {
+ file,
+ data: res.data,
+ });
});
- });
export const expandAllFiles = ({ commit }) => {
commit(types.EXPAND_ALL_FILES);
@@ -186,8 +218,9 @@ export const toggleFileDiscussions = ({ getters, dispatch }, diff) => {
});
};
-export const saveDiffDiscussion = ({ dispatch }, { note, formData }) => {
+export const saveDiffDiscussion = ({ state, dispatch }, { note, formData }) => {
const postData = getNoteFormData({
+ commit: state.commit,
note,
...formData,
});
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index 331fb052371..ed4203cf5e0 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -123,22 +123,23 @@ export default {
diffPosition: diffPositionByLineCode[line.line_code],
latestDiff,
});
+ const mapDiscussions = (line, extraCheck = () => true) => ({
+ ...line,
+ discussions: extraCheck()
+ ? line.discussions
+ .filter(() => !line.discussions.some(({ id }) => discussion.id === id))
+ .concat(lineCheck(line) ? discussion : line.discussions)
+ : [],
+ });
state.diffFiles = state.diffFiles.map(diffFile => {
if (diffFile.file_hash === fileHash) {
const file = { ...diffFile };
if (file.highlighted_diff_lines) {
- file.highlighted_diff_lines = file.highlighted_diff_lines.map(line => {
- if (!line.discussions.some(({ id }) => discussion.id === id) && lineCheck(line)) {
- return {
- ...line,
- discussions: line.discussions.concat(discussion),
- };
- }
-
- return line;
- });
+ file.highlighted_diff_lines = file.highlighted_diff_lines.map(line =>
+ lineCheck(line) ? mapDiscussions(line) : line,
+ );
}
if (file.parallel_diff_lines) {
@@ -148,20 +149,8 @@ export default {
if (left || right) {
return {
- left: {
- ...line.left,
- discussions:
- left && !line.left.discussions.some(({ id }) => id === discussion.id)
- ? line.left.discussions.concat(discussion)
- : (line.left && line.left.discussions) || [],
- },
- right: {
- ...line.right,
- discussions:
- right && !left && !line.right.discussions.some(({ id }) => id === discussion.id)
- ? line.right.discussions.concat(discussion)
- : (line.right && line.right.discussions) || [],
- },
+ left: line.left ? mapDiscussions(line.left) : null,
+ right: line.right ? mapDiscussions(line.right, () => !left) : null,
};
}
@@ -170,7 +159,7 @@ export default {
}
if (!file.parallel_diff_lines || !file.highlighted_diff_lines) {
- file.discussions = file.discussions.concat(discussion);
+ file.discussions = (file.discussions || []).concat(discussion);
}
return file;
@@ -180,7 +169,7 @@ export default {
});
},
- [types.REMOVE_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, lineCode, id }) {
+ [types.REMOVE_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, lineCode }) {
const selectedFile = state.diffFiles.find(f => f.file_hash === fileHash);
if (selectedFile) {
if (selectedFile.parallel_diff_lines) {
@@ -193,7 +182,7 @@ export default {
const side = targetLine.left && targetLine.left.line_code === lineCode ? 'left' : 'right';
Object.assign(targetLine[side], {
- discussions: [],
+ discussions: targetLine[side].discussions.filter(discussion => discussion.notes.length),
});
}
}
@@ -205,14 +194,14 @@ export default {
if (targetInlineLine) {
Object.assign(targetInlineLine, {
- discussions: [],
+ discussions: targetInlineLine.discussions.filter(discussion => discussion.notes.length),
});
}
}
if (selectedFile.discussions && selectedFile.discussions.length) {
selectedFile.discussions = selectedFile.discussions.filter(
- discussion => discussion.id !== id,
+ discussion => discussion.notes.length,
);
}
}
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index 54b9ee4d2d6..2fe20551642 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -27,6 +27,7 @@ export const getReversePosition = linePosition => {
export function getFormData(params) {
const {
+ commit,
note,
noteableType,
noteableData,
@@ -66,7 +67,7 @@ export function getFormData(params) {
position,
noteable_type: noteableType,
noteable_id: noteableData.id,
- commit_id: '',
+ commit_id: commit && commit.id,
type:
diffFile.diff_refs.start_sha && diffFile.diff_refs.head_sha
? DIFF_NOTE_TYPE
@@ -195,6 +196,15 @@ export function trimFirstCharOfLineContent(line = {}) {
return parsedLine;
}
+function getLineCode({ left, right }, index) {
+ if (left && left.line_code) {
+ return left.line_code;
+ } else if (right && right.line_code) {
+ return right.line_code;
+ }
+ return index;
+}
+
// This prepares and optimizes the incoming diff data from the server
// by setting up incremental rendering and removing unneeded data
export function prepareDiffData(diffData) {
@@ -207,6 +217,8 @@ export function prepareDiffData(diffData) {
const linesLength = file.parallel_diff_lines.length;
for (let u = 0; u < linesLength; u += 1) {
const line = file.parallel_diff_lines[u];
+
+ line.line_code = getLineCode(line, u);
if (line.left) {
line.left = trimFirstCharOfLineContent(line.left);
line.left.hasForm = false;
@@ -324,5 +336,9 @@ export const generateTreeList = files =>
export const getDiffMode = diffFile => {
const diffModeKey = Object.keys(diffModes).find(key => diffFile[`${key}_file`]);
- return diffModes[diffModeKey] || diffModes.replaced;
+ return (
+ diffModes[diffModeKey] ||
+ (diffFile.mode_changed && diffModes.mode_changed) ||
+ diffModes.replaced
+ );
};
diff --git a/app/assets/javascripts/dismissable_callout.js b/app/assets/javascripts/dismissable_callout.js
new file mode 100644
index 00000000000..5185b019376
--- /dev/null
+++ b/app/assets/javascripts/dismissable_callout.js
@@ -0,0 +1,27 @@
+import $ from 'jquery';
+import axios from '~/lib/utils/axios_utils';
+import { __ } from '~/locale';
+import Flash from '~/flash';
+
+export default function initDismissableCallout(alertSelector) {
+ const alertEl = document.querySelector(alertSelector);
+ if (!alertEl) {
+ return;
+ }
+
+ const closeButtonEl = alertEl.getElementsByClassName('close')[0];
+ const { dismissEndpoint, featureId } = closeButtonEl.dataset;
+
+ closeButtonEl.addEventListener('click', () => {
+ axios
+ .post(dismissEndpoint, {
+ feature_name: featureId,
+ })
+ .then(() => {
+ $(alertEl).alert('close');
+ })
+ .catch(() => {
+ Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.'));
+ });
+ });
+}
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index cd2f46fd07a..f44806d82a6 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -14,6 +14,7 @@ import MonitoringButtonComponent from './environment_monitoring.vue';
import CommitComponent from '../../vue_shared/components/commit.vue';
import eventHub from '../event_hub';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import { CLUSTER_TYPE } from '~/clusters/constants';
/**
* Environment Item Component
@@ -85,6 +86,15 @@ export default {
},
/**
+ * Hide group cluster features which are not currently implemented.
+ *
+ * @returns {Boolean}
+ */
+ disableGroupClusterFeatures() {
+ return this.model && this.model.cluster_type === CLUSTER_TYPE.GROUP;
+ },
+
+ /**
* Returns whether the environment can be stopped.
*
* @returns {Boolean}
@@ -547,6 +557,7 @@ export default {
<terminal-button-component
v-if="model && model.terminal_path"
:terminal-path="model.terminal_path"
+ :disabled="disableGroupClusterFeatures"
/>
<rollback-component
diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.vue b/app/assets/javascripts/environments/components/environment_terminal_button.vue
index 83727caad16..6d74d136a94 100644
--- a/app/assets/javascripts/environments/components/environment_terminal_button.vue
+++ b/app/assets/javascripts/environments/components/environment_terminal_button.vue
@@ -19,6 +19,11 @@ export default {
required: false,
default: '',
},
+ disabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
title() {
@@ -33,6 +38,7 @@ export default {
:title="title"
:aria-label="title"
:href="terminalPath"
+ :class="{ disabled: disabled }"
class="btn terminal-button d-none d-sm-none d-md-block"
>
<icon name="terminal" />
diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue
index 5164d87c5fa..533e90e2222 100644
--- a/app/assets/javascripts/environments/components/environments_table.vue
+++ b/app/assets/javascripts/environments/components/environments_table.vue
@@ -70,7 +70,7 @@ export default {
<template v-if="shouldRenderFolderContent(model)">
<div v-if="model.isLoadingFolderContent" :key="`loading-item-${i}`">
- <gl-loading-icon :size="2" />
+ <gl-loading-icon :size="2" class="prepend-top-16" />
</div>
<template v-else>
diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js
index 1b79a3320c6..8d92af2cf7e 100644
--- a/app/assets/javascripts/filtered_search/dropdown_utils.js
+++ b/app/assets/javascripts/filtered_search/dropdown_utils.js
@@ -54,67 +54,6 @@ export default class DropdownUtils {
return updatedItem;
}
- static mergeDuplicateLabels(dataMap, newLabel) {
- const updatedMap = dataMap;
- const key = newLabel.title;
-
- const hasKeyProperty = Object.prototype.hasOwnProperty.call(updatedMap, key);
-
- if (!hasKeyProperty) {
- updatedMap[key] = newLabel;
- } else {
- const existing = updatedMap[key];
-
- if (!existing.multipleColors) {
- existing.multipleColors = [existing.color];
- }
-
- existing.multipleColors.push(newLabel.color);
- }
-
- return updatedMap;
- }
-
- static duplicateLabelColor(labelColors) {
- const colors = labelColors;
- const spacing = 100 / colors.length;
-
- // Reduce the colors to 4
- colors.length = Math.min(colors.length, 4);
-
- const color = colors
- .map((c, i) => {
- const percentFirst = Math.floor(spacing * i);
- const percentSecond = Math.floor(spacing * (i + 1));
- return `${c} ${percentFirst}%, ${c} ${percentSecond}%`;
- })
- .join(', ');
-
- return `linear-gradient(${color})`;
- }
-
- static duplicateLabelPreprocessing(data) {
- const results = [];
- const dataMap = {};
-
- data.forEach(DropdownUtils.mergeDuplicateLabels.bind(null, dataMap));
-
- Object.keys(dataMap).forEach(key => {
- const label = dataMap[key];
-
- if (label.multipleColors) {
- label.color = DropdownUtils.duplicateLabelColor(label.multipleColors);
- label.text_color = '#000000';
- }
-
- results.push(label);
- });
-
- results.preprocessed = true;
-
- return results;
- }
-
static filterHint(config, item) {
const { input, allowedKeys } = config;
const updatedItem = item;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
index 89dcff74d0e..fba31f16d65 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
@@ -79,11 +79,7 @@ export default class FilteredSearchVisualTokens {
static setTokenStyle(tokenContainer, backgroundColor, textColor) {
const token = tokenContainer;
- // Labels with linear gradient should not override default background color
- if (backgroundColor.indexOf('linear-gradient') === -1) {
- token.style.backgroundColor = backgroundColor;
- }
-
+ token.style.backgroundColor = backgroundColor;
token.style.color = textColor;
if (textColor === '#FFFFFF') {
@@ -94,18 +90,6 @@ export default class FilteredSearchVisualTokens {
return token;
}
- static preprocessLabel(labelsEndpoint, labels) {
- let processed = labels;
-
- if (!labels.preprocessed) {
- processed = DropdownUtils.duplicateLabelPreprocessing(labels);
- AjaxCache.override(labelsEndpoint, processed);
- processed.preprocessed = true;
- }
-
- return processed;
- }
-
static updateLabelTokenColor(tokenValueContainer, tokenValue) {
const filteredSearchInput = FilteredSearchContainer.container.querySelector('.filtered-search');
const { baseEndpoint } = filteredSearchInput.dataset;
@@ -115,7 +99,6 @@ export default class FilteredSearchVisualTokens {
);
return AjaxCache.retrieve(labelsEndpoint)
- .then(FilteredSearchVisualTokens.preprocessLabel.bind(null, labelsEndpoint))
.then(labels => {
const matchingLabel = (labels || []).find(
label => `~${DropdownUtils.getEscapedText(label.title)}` === tokenValue,
diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js
index 3b201f006aa..09245ed0296 100644
--- a/app/assets/javascripts/ide/constants.js
+++ b/app/assets/javascripts/ide/constants.js
@@ -26,6 +26,7 @@ export const diffModes = {
new: 'new',
deleted: 'deleted',
renamed: 'renamed',
+ mode_changed: 'mode_changed',
};
export const rightSidebarViews = {
diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js
index fbf944499d5..6351948f750 100644
--- a/app/assets/javascripts/ide/index.js
+++ b/app/assets/javascripts/ide/index.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import { mapActions } from 'vuex';
+import _ from 'underscore';
import Translate from '~/vue_shared/translate';
import ide from './components/ide.vue';
import store from './stores';
@@ -13,19 +14,19 @@ Vue.use(Translate);
*
* @param {Element} el - The element that will contain the IDE.
* @param {Object} options - Extra options for the IDE (Used by EE).
- * @param {(e:Element) => Object} options.extraInitialData -
- * Function that returns extra properties to seed initial data.
* @param {Component} options.rootComponent -
* Component that overrides the root component.
+ * @param {(store:Vuex.Store, el:Element) => Vuex.Store} options.extendStore -
+ * Function that receives the default store and returns an extended one.
*/
export function initIde(el, options = {}) {
if (!el) return null;
- const { extraInitialData = () => ({}), rootComponent = ide } = options;
+ const { rootComponent = ide, extendStore = _.identity } = options;
return new Vue({
el,
- store,
+ store: extendStore(store, el),
router,
created() {
this.setEmptyStateSvgs({
@@ -41,7 +42,6 @@ export function initIde(el, options = {}) {
});
this.setInitialData({
clientsidePreviewEnabled: parseBoolean(el.dataset.clientsidePreviewEnabled),
- ...extraInitialData(el),
});
},
methods: {
diff --git a/app/assets/javascripts/image_diff/helpers/badge_helper.js b/app/assets/javascripts/image_diff/helpers/badge_helper.js
index eddaeda9578..000157efad0 100644
--- a/app/assets/javascripts/image_diff/helpers/badge_helper.js
+++ b/app/assets/javascripts/image_diff/helpers/badge_helper.js
@@ -12,7 +12,7 @@ export function createImageBadge(noteId, { x, y }, classNames = []) {
}
export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) {
- const buttonEl = createImageBadge(noteId, coordinate, ['badge']);
+ const buttonEl = createImageBadge(noteId, coordinate, ['badge', 'badge-pill']);
buttonEl.innerText = badgeText;
containerEl.appendChild(buttonEl);
diff --git a/app/assets/javascripts/issuable_suggestions/components/app.vue b/app/assets/javascripts/issuable_suggestions/components/app.vue
index eea0701312b..575c860851c 100644
--- a/app/assets/javascripts/issuable_suggestions/components/app.vue
+++ b/app/assets/javascripts/issuable_suggestions/components/app.vue
@@ -27,7 +27,7 @@ export default {
apollo: {
issues: {
query,
- debounce: 250,
+ debounce: 1000,
skip() {
return this.isSearchEmpty;
},
diff --git a/app/assets/javascripts/jobs/components/artifacts_block.vue b/app/assets/javascripts/jobs/components/artifacts_block.vue
index 309b7427b9e..0bce860df91 100644
--- a/app/assets/javascripts/jobs/components/artifacts_block.vue
+++ b/app/assets/javascripts/jobs/components/artifacts_block.vue
@@ -28,27 +28,29 @@ export default {
</script>
<template>
<div class="block">
- <div class="title">{{ s__('Job|Job artifacts') }}</div>
+ <div class="title font-weight-bold">{{ s__('Job|Job artifacts') }}</div>
- <p v-if="isExpired" class="js-artifacts-removed build-detail-row">
- {{ s__('Job|The artifacts were removed') }}
+ <p
+ v-if="isExpired || willExpire"
+ :class="{
+ 'js-artifacts-removed': isExpired,
+ 'js-artifacts-will-be-removed': willExpire,
+ }"
+ class="build-detail-row"
+ >
+ <span v-if="isExpired">{{ s__('Job|The artifacts were removed') }}</span>
+ <span v-if="willExpire">{{ s__('Job|The artifacts will be removed') }}</span>
+ <timeago-tooltip v-if="artifact.expire_at" :time="artifact.expire_at" />
</p>
- <p v-else-if="willExpire" class="js-artifacts-will-be-removed build-detail-row">
- {{ s__('Job|The artifacts will be removed in') }}
- </p>
-
- <timeago-tooltip v-if="artifact.expire_at" :time="artifact.expire_at" />
-
- <div class="btn-group d-flex" role="group">
+ <div class="btn-group d-flex prepend-top-10" role="group">
<gl-link
v-if="artifact.keep_path"
:href="artifact.keep_path"
class="js-keep-artifacts btn btn-sm btn-default"
data-method="post"
+ >{{ s__('Job|Keep') }}</gl-link
>
- {{ s__('Job|Keep') }}
- </gl-link>
<gl-link
v-if="artifact.download_path"
@@ -56,17 +58,15 @@ export default {
class="js-download-artifacts btn btn-sm btn-default"
download
rel="nofollow"
+ >{{ s__('Job|Download') }}</gl-link
>
- {{ s__('Job|Download') }}
- </gl-link>
<gl-link
v-if="artifact.browse_path"
:href="artifact.browse_path"
class="js-browse-artifacts btn btn-sm btn-default"
+ >{{ s__('Job|Browse') }}</gl-link
>
- {{ s__('Job|Browse') }}
- </gl-link>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/jobs/components/commit_block.vue b/app/assets/javascripts/jobs/components/commit_block.vue
index 3b9c61bd48c..e0f55518eef 100644
--- a/app/assets/javascripts/jobs/components/commit_block.vue
+++ b/app/assets/javascripts/jobs/components/commit_block.vue
@@ -31,12 +31,12 @@ export default {
block: !isLastBlock,
}"
>
- <p>
- {{ __('Commit') }}
+ <p class="append-bottom-5">
+ <span class="font-weight-bold">{{ __('Commit') }}</span>
- <gl-link :href="commit.commit_path" class="js-commit-sha commit-sha link-commit">{{
- commit.short_id
- }}</gl-link>
+ <gl-link :href="commit.commit_path" class="js-commit-sha commit-sha link-commit">
+ {{ commit.short_id }}
+ </gl-link>
<clipboard-button
:text="commit.short_id"
@@ -44,11 +44,14 @@ export default {
css-class="btn btn-clipboard btn-transparent"
/>
- <gl-link v-if="mergeRequest" :href="mergeRequest.path" class="js-link-commit link-commit"
- >!{{ mergeRequest.iid }}</gl-link
- >
+ <span v-if="mergeRequest">
+ {{ __('in') }}
+ <gl-link :href="mergeRequest.path" class="js-link-commit link-commit"
+ >!{{ mergeRequest.iid }}</gl-link
+ >
+ </span>
</p>
- <p class="build-light-text append-bottom-0">{{ commit.title }}</p>
+ <p class="append-bottom-0">{{ commit.title }}</p>
</div>
</template>
diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue
index 934ecd0e3ec..ad3e7dabc79 100644
--- a/app/assets/javascripts/jobs/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -110,22 +110,20 @@ export default {
<aside class="right-sidebar build-sidebar" data-offset-top="101" data-spy="affix">
<div class="sidebar-container">
<div class="blocks-container">
- <div class="block">
- <strong class="inline prepend-top-8"> {{ job.name }} </strong>
+ <div class="block d-flex align-items-center">
+ <h4 class="flex-grow-1 prepend-top-8 m-0">{{ job.name }}</h4>
<gl-link
v-if="job.retry_path"
:class="retryButtonClass"
:href="job.retry_path"
data-method="post"
rel="nofollow"
+ >{{ __('Retry') }}</gl-link
>
- {{ __('Retry') }}
- </gl-link>
<gl-link
v-if="job.terminal_path"
:href="job.terminal_path"
- class="js-terminal-link pull-right btn btn-primary
- btn-inverted visible-md-block visible-lg-block"
+ class="js-terminal-link pull-right btn btn-primary btn-inverted visible-md-block visible-lg-block"
target="_blank"
>
{{ __('Debug') }} <icon name="external-link" />
@@ -133,8 +131,7 @@ export default {
<gl-button
:aria-label="__('Toggle Sidebar')"
type="button"
- class="btn btn-blank gutter-toggle
- float-right d-block d-md-none js-sidebar-build-toggle"
+ class="btn btn-blank gutter-toggle float-right d-block d-md-none js-sidebar-build-toggle"
@click="toggleSidebar"
>
<i aria-hidden="true" data-hidden="true" class="fa fa-angle-double-right"></i>
@@ -145,25 +142,18 @@ export default {
v-if="job.new_issue_path"
:href="job.new_issue_path"
class="js-new-issue btn btn-success btn-inverted"
+ >{{ __('New issue') }}</gl-link
>
- {{ __('New issue') }}
- </gl-link>
<gl-link
v-if="job.retry_path"
:href="job.retry_path"
class="js-retry-job btn btn-inverted-secondary"
data-method="post"
rel="nofollow"
+ >{{ __('Retry') }}</gl-link
>
- {{ __('Retry') }}
- </gl-link>
</div>
<div :class="{ block: renderBlock }">
- <p v-if="job.merge_request" class="build-detail-row js-job-mr">
- <span class="build-light-text"> {{ __('Merge Request:') }} </span>
- <gl-link :href="job.merge_request.path"> !{{ job.merge_request.iid }} </gl-link>
- </p>
-
<detail-row
v-if="job.duration"
:value="duration"
@@ -198,10 +188,10 @@ export default {
title="Coverage"
/>
<p v-if="job.tags.length" class="build-detail-row js-job-tags">
- <span class="build-light-text"> {{ __('Tags:') }} </span>
- <span v-for="(tag, i) in job.tags" :key="i" class="badge badge-primary">
- {{ tag }}
- </span>
+ <span class="font-weight-bold">{{ __('Tags:') }}</span>
+ <span v-for="(tag, i) in job.tags" :key="i" class="badge badge-primary mr-1">{{
+ tag
+ }}</span>
</p>
<div v-if="job.cancel_path" class="btn-group prepend-top-5" role="group">
@@ -210,9 +200,8 @@ export default {
class="js-cancel-job btn btn-sm btn-default"
data-method="post"
rel="nofollow"
+ >{{ __('Cancel') }}</gl-link
>
- {{ __('Cancel') }}
- </gl-link>
</div>
</div>
diff --git a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue
index 77be295e802..b826007ec2c 100644
--- a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue
+++ b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue
@@ -34,8 +34,7 @@ export default {
</script>
<template>
<p class="build-detail-row">
- <span v-if="hasTitle" class="build-light-text"> {{ title }}: </span> {{ value }}
-
+ <span v-if="hasTitle" class="font-weight-bold">{{ title }}:</span> {{ value }}
<span v-if="hasHelpURL" class="help-button float-right">
<gl-link :href="helpUrl" target="_blank" rel="noopener noreferrer nofollow">
<i class="fa fa-question-circle" aria-hidden="true"></i>
diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue
index 90482500bbf..7f79e92067f 100644
--- a/app/assets/javascripts/jobs/components/stages_dropdown.vue
+++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue
@@ -38,11 +38,11 @@ export default {
<div class="block-last dropdown">
<ci-icon :status="pipeline.details.status" class="vertical-align-middle" />
- {{ __('Pipeline') }}
- <a :href="pipeline.path" class="js-pipeline-path link-commit"> #{{ pipeline.id }} </a>
+ <span class="font-weight-bold">{{ __('Pipeline') }}</span>
+ <a :href="pipeline.path" class="js-pipeline-path link-commit">#{{ pipeline.id }}</a>
<template v-if="hasRef">
{{ __('from') }}
- <a :href="pipeline.ref.path" class="link-commit ref-name"> {{ pipeline.ref.name }} </a>
+ <a :href="pipeline.ref.path" class="link-commit ref-name">{{ pipeline.ref.name }}</a>
</template>
<button
diff --git a/app/assets/javascripts/jobs/components/trigger_block.vue b/app/assets/javascripts/jobs/components/trigger_block.vue
index 4a9b2903eec..997737b3e23 100644
--- a/app/assets/javascripts/jobs/components/trigger_block.vue
+++ b/app/assets/javascripts/jobs/components/trigger_block.vue
@@ -1,6 +1,9 @@
<script>
+import { __ } from '~/locale';
import { GlButton } from '@gitlab/ui';
+const HIDDEN_VALUE = '••••••';
+
export default {
components: {
GlButton,
@@ -13,17 +16,26 @@ export default {
},
data() {
return {
- areVariablesVisible: false,
+ showVariableValues: false,
};
},
computed: {
hasVariables() {
return this.trigger.variables && this.trigger.variables.length > 0;
},
+ getToggleButtonText() {
+ return this.showVariableValues ? __('Hide values') : __('Reveal values');
+ },
+ hasValues() {
+ return this.trigger.variables.some(v => v.value);
+ },
},
methods: {
- revealVariables() {
- this.areVariablesVisible = true;
+ toggleValues() {
+ this.showVariableValues = !this.showVariableValues;
+ },
+ getDisplayValue(value) {
+ return this.showVariableValues ? value : HIDDEN_VALUE;
},
},
};
@@ -31,33 +43,36 @@ export default {
<template>
<div class="build-widget block">
- <h4 class="title">{{ __('Trigger') }}</h4>
-
- <p v-if="trigger.short_token" class="js-short-token">
- <span class="build-light-text"> {{ __('Token') }} </span> {{ trigger.short_token }}
+ <p
+ v-if="trigger.short_token"
+ class="js-short-token"
+ :class="{ 'append-bottom-5': hasVariables, 'append-bottom-0': !hasVariables }"
+ >
+ <span class="font-weight-bold">{{ __('Trigger token:') }}</span> {{ trigger.short_token }}
</p>
- <p v-if="hasVariables">
- <gl-button
- v-if="!areVariablesVisible"
- type="button"
- class="btn btn-default group js-reveal-variables"
- @click="revealVariables"
- >
- {{ __('Reveal Variables') }}
- </gl-button>
- </p>
+ <template v-if="hasVariables">
+ <p class="trigger-variables-btn-container">
+ <span class="font-weight-bold">{{ __('Trigger variables:') }}</span>
- <dl v-if="areVariablesVisible" class="js-build-variables trigger-build-variables">
- <template v-for="variable in trigger.variables">
- <dt :key="`${variable.key}-variable`" class="js-build-variable trigger-build-variable">
- {{ variable.key }}
- </dt>
+ <gl-button
+ v-if="hasValues"
+ class="btn-sm group js-reveal-variables trigger-variables-btn"
+ @click="toggleValues"
+ >{{ getToggleButtonText }}</gl-button
+ >
+ </p>
- <dd :key="`${variable.key}-value`" class="js-build-value trigger-build-value">
- {{ variable.value }}
- </dd>
- </template>
- </dl>
+ <table class="js-build-variables trigger-build-variables">
+ <tr v-for="(variable, index) in trigger.variables" :key="`${variable.key}-${index}`">
+ <td class="js-build-variable trigger-build-variable trigger-variables-table-cell">
+ {{ variable.key }}
+ </td>
+ <td class="js-build-value trigger-build-value trigger-variables-table-cell">
+ {{ getDisplayValue(variable.value) }}
+ </td>
+ </tr>
+ </table>
+ </template>
</div>
</template>
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index c0a76814102..f7a611fbca0 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -7,7 +7,6 @@ import _ from 'underscore';
import { sprintf, __ } from './locale';
import axios from './lib/utils/axios_utils';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
-import DropdownUtils from './filtered_search/dropdown_utils';
import CreateLabelDropdown from './create_label';
import flash from './flash';
import ModalStore from './boards/stores/modal_store';
@@ -171,23 +170,7 @@ export default class LabelsSelect {
axios
.get(labelUrl)
.then(res => {
- let data = _.chain(res.data)
- .groupBy(function(label) {
- return label.title;
- })
- .map(function(label) {
- var color;
- color = _.map(label, function(dup) {
- return dup.color;
- });
- return {
- id: label[0].id,
- title: label[0].title,
- color: color,
- duplicate: color.length > 1,
- };
- })
- .value();
+ let { data } = res;
if ($dropdown.hasClass('js-extra-options')) {
var extraData = [];
if (showNo) {
@@ -272,15 +255,9 @@ export default class LabelsSelect {
selectedClass.push('dropdown-clear-active');
}
}
- if (label.duplicate) {
- color = DropdownUtils.duplicateLabelColor(label.color);
- } else {
- if (label.color != null) {
- [color] = label.color;
- }
- }
- if (color) {
- colorEl = "<span class='dropdown-label-box' style='background: " + color + "'></span>";
+ if (label.color) {
+ colorEl =
+ "<span class='dropdown-label-box' style='background: " + label.color + "'></span>";
} else {
colorEl = '';
}
@@ -435,7 +412,7 @@ export default class LabelsSelect {
new ListLabel({
id: label.id,
title: label.title,
- color: label.color[0],
+ color: label.color,
textColor: '#fff',
}),
);
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 040d0bc659e..9e22cdc04e9 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -192,8 +192,12 @@ export const contentTop = () => {
const mrTabsHeight = $('.merge-request-tabs').height() || 0;
const headerHeight = $('.navbar-gitlab').height() || 0;
const diffFilesChanged = $('.js-diff-files-changed').height() || 0;
+ const diffFileLargeEnoughScreen =
+ 'matchMedia' in window ? window.matchMedia('min-width: 768') : true;
+ const diffFileTitleBar =
+ (diffFileLargeEnoughScreen && $('.diff-file .file-title-flex-parent:visible').height()) || 0;
- return perfBar + mrTabsHeight + headerHeight + diffFilesChanged;
+ return perfBar + mrTabsHeight + headerHeight + diffFilesChanged + diffFileTitleBar;
};
export const scrollToElement = element => {
diff --git a/app/assets/javascripts/lib/utils/dom_utils.js b/app/assets/javascripts/lib/utils/dom_utils.js
index 6f42382246d..7933c234384 100644
--- a/app/assets/javascripts/lib/utils/dom_utils.js
+++ b/app/assets/javascripts/lib/utils/dom_utils.js
@@ -7,3 +7,8 @@ export const addClassIfElementExists = (element, className) => {
};
export const isInVueNoteablePage = () => isInIssuePage() || isInEpicPage() || isInMRPage();
+
+export const canScrollUp = ({ scrollTop }, margin = 0) => scrollTop > margin;
+
+export const canScrollDown = ({ scrollTop, offsetHeight, scrollHeight }, margin = 0) =>
+ scrollTop + offsetHeight < scrollHeight - margin;
diff --git a/app/assets/javascripts/lib/utils/file_upload.js b/app/assets/javascripts/lib/utils/file_upload.js
new file mode 100644
index 00000000000..b41ffb44971
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/file_upload.js
@@ -0,0 +1,13 @@
+export default (buttonSelector, fileSelector) => {
+ const btn = document.querySelector(buttonSelector);
+ const fileInput = document.querySelector(fileSelector);
+ const form = btn.closest('form');
+
+ btn.addEventListener('click', () => {
+ fileInput.click();
+ });
+
+ fileInput.addEventListener('change', () => {
+ form.querySelector('.js-filename').textContent = fileInput.value.replace(/^.*[\\\/]/, ''); // eslint-disable-line no-useless-escape
+ });
+};
diff --git a/app/assets/javascripts/lib/utils/http_status.js b/app/assets/javascripts/lib/utils/http_status.js
index e4852c85378..14c02218990 100644
--- a/app/assets/javascripts/lib/utils/http_status.js
+++ b/app/assets/javascripts/lib/utils/http_status.js
@@ -16,7 +16,9 @@ const httpStatusCodes = {
IM_USED: 226,
MULTIPLE_CHOICES: 300,
BAD_REQUEST: 400,
+ FORBIDDEN: 403,
NOT_FOUND: 404,
+ UNPROCESSABLE_ENTITY: 422,
};
export const successCodes = [
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index 3618c6af7e2..1254ec798a6 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -39,7 +39,14 @@ function blockTagText(text, textArea, blockTag, selected) {
}
}
-function moveCursor({ textArea, tag, positionBetweenTags, removedLastNewLine, select }) {
+function moveCursor({
+ textArea,
+ tag,
+ cursorOffset,
+ positionBetweenTags,
+ removedLastNewLine,
+ select,
+}) {
var pos;
if (!textArea.setSelectionRange) {
return;
@@ -61,11 +68,24 @@ function moveCursor({ textArea, tag, positionBetweenTags, removedLastNewLine, se
pos -= 1;
}
+ if (cursorOffset) {
+ pos -= cursorOffset;
+ }
+
return textArea.setSelectionRange(pos, pos);
}
}
-export function insertMarkdownText({ textArea, text, tag, blockTag, selected, wrap, select }) {
+export function insertMarkdownText({
+ textArea,
+ text,
+ tag,
+ cursorOffset,
+ blockTag,
+ selected = '',
+ wrap,
+ select,
+}) {
var textToInsert,
selectedSplit,
startChar,
@@ -154,20 +174,30 @@ export function insertMarkdownText({ textArea, text, tag, blockTag, selected, wr
return moveCursor({
textArea,
tag: tag.replace(textPlaceholder, selected),
+ cursorOffset,
positionBetweenTags: wrap && selected.length === 0,
removedLastNewLine,
select,
});
}
-function updateText({ textArea, tag, blockTag, wrap, select }) {
+function updateText({ textArea, tag, cursorOffset, blockTag, wrap, select, tagContent }) {
var $textArea, selected, text;
$textArea = $(textArea);
textArea = $textArea.get(0);
text = $textArea.val();
- selected = selectedText(text, textArea);
+ selected = selectedText(text, textArea) || tagContent;
$textArea.focus();
- return insertMarkdownText({ textArea, text, tag, blockTag, selected, wrap, select });
+ return insertMarkdownText({
+ textArea,
+ text,
+ tag,
+ cursorOffset,
+ blockTag,
+ selected,
+ wrap,
+ select,
+ });
}
export function addMarkdownListeners(form) {
@@ -178,9 +208,11 @@ export function addMarkdownListeners(form) {
return updateText({
textArea: $this.closest('.md-area').find('textarea'),
tag: $this.data('mdTag'),
+ cursorOffset: $this.data('mdCursorOffset'),
blockTag: $this.data('mdBlock'),
wrap: !$this.data('mdPrepend'),
select: $this.data('mdSelect'),
+ tagContent: $this.data('mdTagContent'),
});
});
}
diff --git a/app/assets/javascripts/lib/utils/users_cache.js b/app/assets/javascripts/lib/utils/users_cache.js
index c0d45e017b4..9f980fd4899 100644
--- a/app/assets/javascripts/lib/utils/users_cache.js
+++ b/app/assets/javascripts/lib/utils/users_cache.js
@@ -22,6 +22,34 @@ class UsersCache extends Cache {
});
// missing catch is intentional, error handling depends on use case
}
+
+ retrieveById(userId) {
+ if (this.hasData(userId) && this.get(userId).username) {
+ return Promise.resolve(this.get(userId));
+ }
+
+ return Api.user(userId).then(({ data }) => {
+ this.internalStorage[userId] = data;
+ return data;
+ });
+ // missing catch is intentional, error handling depends on use case
+ }
+
+ retrieveStatusById(userId) {
+ if (this.hasData(userId) && this.get(userId).status) {
+ return Promise.resolve(this.get(userId).status);
+ }
+
+ return Api.userStatus(userId).then(({ data }) => {
+ if (!this.hasData(userId)) {
+ this.internalStorage[userId] = {};
+ }
+ this.internalStorage[userId].status = data;
+
+ return data;
+ });
+ // missing catch is intentional, error handling depends on use case
+ }
}
export default new UsersCache();
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index a88b575ad99..c866e8d180a 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -30,6 +30,7 @@ import initUsagePingConsent from './usage_ping_consent';
import initPerformanceBar from './performance_bar';
import initSearchAutocomplete from './search_autocomplete';
import GlFieldErrors from './gl_field_errors';
+import initUserPopovers from './user_popovers';
// expose jQuery as global (TODO: remove these)
window.jQuery = jQuery;
@@ -78,6 +79,7 @@ document.addEventListener('DOMContentLoaded', () => {
initTodoToggle();
initLogoAnimation();
initUsagePingConsent();
+ initUserPopovers();
if (document.querySelector('.search')) initSearchAutocomplete();
if (document.querySelector('#js-peek')) initPerformanceBar({ container: '#js-peek' });
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index d32f39881dd..75c18a9b6a0 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -155,7 +155,7 @@ export default class MilestoneSelect {
const { $el, e } = clickEvent;
let selected = clickEvent.selectedObj;
- let data, boardsStore;
+ let data, modalStoreFilter;
if (!selected) return;
if (options.handleClick) {
@@ -179,11 +179,11 @@ export default class MilestoneSelect {
}
if ($dropdown.closest('.add-issues-modal').length) {
- boardsStore = ModalStore.store.filter;
+ modalStoreFilter = ModalStore.store.filter;
}
- if (boardsStore) {
- boardsStore[$dropdown.data('fieldName')] = selected.name;
+ if (modalStoreFilter) {
+ modalStoreFilter[$dropdown.data('fieldName')] = selected.name;
e.preventDefault();
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
return Issuable.filterResults($dropdown.closest('form'));
diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue
new file mode 100644
index 00000000000..12224e36ba2
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/charts/area.vue
@@ -0,0 +1,97 @@
+<script>
+import { GlAreaChart } from '@gitlab/ui';
+import dateFormat from 'dateformat';
+
+export default {
+ components: {
+ GlAreaChart,
+ },
+ props: {
+ graphData: {
+ type: Object,
+ required: true,
+ validator(data) {
+ return (
+ data.queries &&
+ Array.isArray(data.queries) &&
+ data.queries.filter(query => {
+ if (Array.isArray(query.result)) {
+ return (
+ query.result.filter(res => Array.isArray(res.values)).length === query.result.length
+ );
+ }
+ return false;
+ }).length === data.queries.length
+ );
+ },
+ },
+ },
+ computed: {
+ chartData() {
+ return this.graphData.queries.reduce((accumulator, query) => {
+ const xLabel = `${query.unit}`;
+ accumulator[xLabel] = {};
+ query.result.forEach(res =>
+ res.values.forEach(v => {
+ accumulator[xLabel][v.time.toISOString()] = v.value;
+ }),
+ );
+ return accumulator;
+ }, {});
+ },
+ chartOptions() {
+ return {
+ xAxis: {
+ name: 'Time',
+ type: 'time',
+ axisLabel: {
+ formatter: date => dateFormat(date, 'h:MMtt'),
+ },
+ nameTextStyle: {
+ padding: [18, 0, 0, 0],
+ },
+ },
+ yAxis: {
+ name: this.graphData.y_label,
+ axisLabel: {
+ formatter: value => value.toFixed(3),
+ },
+ nameTextStyle: {
+ padding: [0, 0, 36, 0],
+ },
+ },
+ legend: {
+ formatter: this.xAxisLabel,
+ },
+ };
+ },
+ xAxisLabel() {
+ return this.graphData.queries.map(query => query.label).join(', ');
+ },
+ },
+ methods: {
+ formatTooltipText(params) {
+ const [date, value] = params;
+ return [dateFormat(date, 'dd mmm yyyy, h:MMtt'), value.toFixed(3)];
+ },
+ onCreated(chart) {
+ this.$emit('created', chart);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="prometheus-graph">
+ <div class="prometheus-graph-header">
+ <h5 class="prometheus-graph-title">{{ graphData.title }}</h5>
+ <div class="prometheus-graph-widgets"><slot></slot></div>
+ </div>
+ <gl-area-chart
+ :data="chartData"
+ :option="chartOptions"
+ :format-tooltip-text="formatTooltipText"
+ @created="onCreated"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 218c508a608..2d9c5050c9b 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -4,6 +4,7 @@ import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import Flash from '../../flash';
import MonitoringService from '../services/monitoring_service';
+import MonitorAreaChart from './charts/area.vue';
import GraphGroup from './graph_group.vue';
import Graph from './graph.vue';
import EmptyState from './empty_state.vue';
@@ -12,6 +13,7 @@ import eventHub from '../event_hub';
export default {
components: {
+ MonitorAreaChart,
Graph,
GraphGroup,
EmptyState,
@@ -102,6 +104,9 @@ export default {
};
},
computed: {
+ graphComponent() {
+ return gon.features && gon.features.areaChart ? MonitorAreaChart : Graph;
+ },
forceRedraw() {
return this.elWidth;
},
@@ -207,7 +212,8 @@ export default {
:name="groupData.group"
:show-panels="showPanels"
>
- <graph
+ <component
+ :is="graphComponent"
v-for="(graphData, graphIndex) in groupData.metrics"
:key="graphIndex"
:graph-data="graphData"
@@ -220,7 +226,7 @@ export default {
>
<!-- EE content -->
{{ null }}
- </graph>
+ </component>
</graph-group>
</div>
<empty-state
diff --git a/app/assets/javascripts/monitoring/utils/multiple_time_series.js b/app/assets/javascripts/monitoring/utils/multiple_time_series.js
index bb24a1acdb3..50ba14dfb2e 100644
--- a/app/assets/javascripts/monitoring/utils/multiple_time_series.js
+++ b/app/assets/javascripts/monitoring/utils/multiple_time_series.js
@@ -92,7 +92,11 @@ function queryTimeSeries(query, graphDrawData, lineStyle) {
if (seriesCustomizationData) {
metricTag = seriesCustomizationData.value || timeSeriesMetricLabel;
[lineColor, areaColor] = pickColor(seriesCustomizationData.color);
- shouldRenderLegend = false;
+ if (timeSeriesParsed.length > 0) {
+ shouldRenderLegend = false;
+ } else {
+ shouldRenderLegend = true;
+ }
} else {
metricTag = timeSeriesMetricLabel || query.label || `series ${timeSeriesNumber + 1}`;
[lineColor, areaColor] = pickColor();
@@ -101,19 +105,6 @@ function queryTimeSeries(query, graphDrawData, lineStyle) {
}
}
- if (!shouldRenderLegend) {
- if (!timeSeriesParsed[0].tracksLegend) {
- timeSeriesParsed[0].tracksLegend = [];
- }
- timeSeriesParsed[0].tracksLegend.push({
- max: maximumValue,
- average: accum / timeSeries.values.length,
- lineStyle,
- lineColor,
- metricTag,
- });
- }
-
const values = datesWithoutGaps.map(time => ({
time,
value: findByDate(timeSeries.values, time),
@@ -135,6 +126,19 @@ function queryTimeSeries(query, graphDrawData, lineStyle) {
shouldRenderLegend,
renderCanary,
});
+
+ if (!shouldRenderLegend) {
+ if (!timeSeriesParsed[0].tracksLegend) {
+ timeSeriesParsed[0].tracksLegend = [];
+ }
+ timeSeriesParsed[0].tracksLegend.push({
+ max: maximumValue,
+ average: accum / timeSeries.values.length,
+ lineStyle,
+ lineColor,
+ metricTag,
+ });
+ }
});
return timeSeriesParsed;
diff --git a/app/assets/javascripts/mr_notes/index.js b/app/assets/javascripts/mr_notes/index.js
index 1c98683c597..e4d72eb8318 100644
--- a/app/assets/javascripts/mr_notes/index.js
+++ b/app/assets/javascripts/mr_notes/index.js
@@ -33,6 +33,7 @@ export default function initMrNotes() {
noteableData,
currentUserData: JSON.parse(notesDataset.currentUserData),
notesData: JSON.parse(notesDataset.notesData),
+ helpPagePath: notesDataset.helpPagePath,
};
},
computed: {
@@ -71,6 +72,7 @@ export default function initMrNotes() {
notesData: this.notesData,
userData: this.currentUserData,
shouldShow: this.activeTab === 'show',
+ helpPagePath: this.helpPagePath,
},
});
},
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 841fcec96e8..ce56beb1e6b 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -247,15 +247,19 @@ Please check your network connection and try again.`;
} else {
this.reopenIssue()
.then(() => this.enableButton())
- .catch(() => {
+ .catch(({ data }) => {
this.enableButton();
this.toggleStateButtonLoading(false);
- Flash(
- sprintf(
- __('Something went wrong while reopening the %{issuable}. Please try again later'),
- { issuable: this.noteableDisplayName },
- ),
+ let errorMessage = sprintf(
+ __('Something went wrong while reopening the %{issuable}. Please try again later'),
+ { issuable: this.noteableDisplayName },
);
+
+ if (data) {
+ errorMessage = Object.values(data).join('\n');
+ }
+
+ Flash(errorMessage);
});
}
},
diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue
index c0bee600181..bcf5d334da4 100644
--- a/app/assets/javascripts/notes/components/note_body.vue
+++ b/app/assets/javascripts/notes/components/note_body.vue
@@ -1,10 +1,12 @@
<script>
+import { mapActions } from 'vuex';
import $ from 'jquery';
import noteEditedText from './note_edited_text.vue';
import noteAwardsList from './note_awards_list.vue';
import noteAttachment from './note_attachment.vue';
import noteForm from './note_form.vue';
import autosave from '../mixins/autosave';
+import Suggestions from '~/vue_shared/components/markdown/suggestions.vue';
export default {
components: {
@@ -12,6 +14,7 @@ export default {
noteAwardsList,
noteAttachment,
noteForm,
+ Suggestions,
},
mixins: [autosave],
props: {
@@ -19,6 +22,11 @@ export default {
type: Object,
required: true,
},
+ line: {
+ type: Object,
+ required: false,
+ default: null,
+ },
canEdit: {
type: Boolean,
required: true,
@@ -28,11 +36,22 @@ export default {
required: false,
default: false,
},
+ helpPagePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
noteBody() {
return this.note.note;
},
+ hasSuggestion() {
+ return this.note.suggestions && this.note.suggestions.length;
+ },
+ lineType() {
+ return this.line ? this.line.type : null;
+ },
},
mounted() {
this.renderGFM();
@@ -53,6 +72,7 @@ export default {
}
},
methods: {
+ ...mapActions(['submitSuggestion']),
renderGFM() {
$(this.$refs['note-body']).renderGFM();
},
@@ -62,19 +82,35 @@ export default {
formCancelHandler(shouldConfirm, isDirty) {
this.$emit('cancelForm', shouldConfirm, isDirty);
},
+ applySuggestion({ suggestionId, flashContainer, callback }) {
+ const { discussion_id: discussionId, id: noteId } = this.note;
+
+ this.submitSuggestion({ discussionId, noteId, suggestionId, flashContainer, callback });
+ },
},
};
</script>
<template>
<div ref="note-body" :class="{ 'js-task-list-container': canEdit }" class="note-body">
- <div class="note-text md" v-html="note.note_html"></div>
+ <suggestions
+ v-if="hasSuggestion && !isEditing"
+ :suggestions="note.suggestions"
+ :note-html="note.note_html"
+ :line-type="lineType"
+ :help-page-path="helpPagePath"
+ @apply="applySuggestion"
+ />
+ <div v-else class="note-text md" v-html="note.note_html"></div>
<note-form
v-if="isEditing"
ref="noteForm"
:is-editing="isEditing"
:note-body="noteBody"
:note-id="note.id"
+ :line="line"
+ :note="note"
+ :help-page-path="helpPagePath"
:markdown-version="note.cached_markdown_version"
@handleFormUpdate="handleFormUpdate"
@cancelForm="formCancelHandler"
diff --git a/app/assets/javascripts/notes/components/note_edited_text.vue b/app/assets/javascripts/notes/components/note_edited_text.vue
index 3d3dbbd7fe1..15ce49d7c31 100644
--- a/app/assets/javascripts/notes/components/note_edited_text.vue
+++ b/app/assets/javascripts/notes/components/note_edited_text.vue
@@ -39,7 +39,10 @@ export default {
<div :class="className">
{{ actionText }}
<template v-if="editedBy">
- by <a :href="editedBy.path" class="js-vue-author author-link"> {{ editedBy.name }} </a>
+ by
+ <a :href="editedBy.path" :data-user-id="editedBy.id" class="js-user-link author-link">
+ {{ editedBy.name }}
+ </a>
</template>
{{ actionDetailText }}
<time-ago-tooltip :time="editedAt" tooltip-placement="bottom" />
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index 95164183ccb..9b7f3d3588d 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -1,4 +1,5 @@
<script>
+import { mergeUrlParams } from '~/lib/utils/url_utility';
import { mapGetters, mapActions } from 'vuex';
import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
@@ -53,6 +54,21 @@ export default {
required: false,
default: false,
},
+ line: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ note: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ helpPagePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
data() {
return {
@@ -79,7 +95,8 @@ export default {
return '#';
},
markdownPreviewPath() {
- return this.getNoteableDataByProp('preview_note_path');
+ const notable = this.getNoteableDataByProp('preview_note_path');
+ return mergeUrlParams({ preview_suggestions: true }, notable);
},
markdownDocsPath() {
return this.getNotesDataByProp('markdownDocsPath');
@@ -93,6 +110,18 @@ export default {
isDisabled() {
return !this.updatedNoteBody.length || this.isSubmitting;
},
+ discussionNote() {
+ const discussionNote = this.discussion.id
+ ? this.getDiscussionLastNote(this.discussion)
+ : this.note;
+ return discussionNote || {};
+ },
+ canSuggest() {
+ return (
+ this.getNoteableData.can_receive_suggestion &&
+ (this.line && this.line.can_receive_suggestion)
+ );
+ },
},
watch: {
noteBody() {
@@ -171,7 +200,11 @@ export default {
:markdown-docs-path="markdownDocsPath"
:markdown-version="markdownVersion"
:quick-actions-docs-path="quickActionsDocsPath"
+ :line="line"
+ :note="discussionNote"
+ :can-suggest="canSuggest"
:add-spacing-classes="false"
+ :help-page-path="helpPagePath"
>
<textarea
id="note_note"
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index e1a58e7cb26..7b39901024d 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -73,7 +73,14 @@ export default {
{{ __('Toggle discussion') }}
</button>
</div>
- <a v-if="hasAuthor" v-once :href="author.path">
+ <a
+ v-if="hasAuthor"
+ v-once
+ :href="author.path"
+ class="js-user-link"
+ :data-user-id="author.id"
+ :data-username="author.username"
+ >
<span class="note-header-author-name">{{ author.name }}</span>
<span v-if="author.status_tooltip_html" v-html="author.status_tooltip_html"></span>
<span class="note-headline-light"> @{{ author.username }} </span>
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index f4991a41325..7c3f5d00308 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -49,6 +49,11 @@ export default {
type: Object,
required: true,
},
+ line: {
+ type: Object,
+ required: false,
+ default: null,
+ },
renderDiffFile: {
type: Boolean,
required: false,
@@ -64,6 +69,11 @@ export default {
required: false,
default: false,
},
+ helpPagePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
data() {
const { diff_discussion: isDiffDiscussion, resolved } = this.discussion;
@@ -81,6 +91,7 @@ export default {
'nextUnresolvedDiscussionId',
'unresolvedDiscussionsCount',
'hasUnresolvedDiscussions',
+ 'showJumpToNextDiscussion',
]),
author() {
return this.initialDiscussion.author;
@@ -121,6 +132,12 @@ export default {
resolvedText() {
return this.discussion.resolved_by_push ? __('Automatically resolved') : __('Resolved');
},
+ shouldShowJumpToNextDiscussion() {
+ return this.showJumpToNextDiscussion(
+ this.discussion.id,
+ this.discussionsByDiffOrder ? 'diff' : 'discussion',
+ );
+ },
shouldRenderDiffs() {
return this.discussion.diff_discussion && this.renderDiffFile;
},
@@ -153,35 +170,47 @@ export default {
return expanded || this.alwaysExpanded || isResolvedNonDiffDiscussion;
},
actionText() {
- const commitId = this.discussion.commit_id ? truncateSha(this.discussion.commit_id) : '';
const linkStart = `<a href="${_.escape(this.discussion.discussion_path)}">`;
const linkEnd = '</a>';
- let text = s__('MergeRequests|started a discussion');
+ let { commit_id: commitId } = this.discussion;
+ if (commitId) {
+ commitId = `<span class="commit-sha">${truncateSha(commitId)}</span>`;
+ }
- if (this.discussion.for_commit) {
+ const {
+ for_commit: isForCommit,
+ diff_discussion: isDiffDiscussion,
+ active: isActive,
+ } = this.discussion;
+
+ let text = s__('MergeRequests|started a discussion');
+ if (isForCommit) {
text = s__(
'MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}',
);
- } else if (this.discussion.diff_discussion) {
- if (this.discussion.active) {
- text = s__('MergeRequests|started a discussion on %{linkStart}the diff%{linkEnd}');
- } else {
- text = s__(
- 'MergeRequests|started a discussion on %{linkStart}an old version of the diff%{linkEnd}',
- );
- }
+ } else if (isDiffDiscussion && commitId) {
+ text = isActive
+ ? s__('MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}')
+ : s__(
+ 'MergeRequests|started a discussion on an outdated change in commit %{linkStart}%{commitId}%{linkEnd}',
+ );
+ } else if (isDiffDiscussion) {
+ text = isActive
+ ? s__('MergeRequests|started a discussion on %{linkStart}the diff%{linkEnd}')
+ : s__(
+ 'MergeRequests|started a discussion on %{linkStart}an old version of the diff%{linkEnd}',
+ );
}
- return sprintf(
- text,
- {
- commitId,
- linkStart,
- linkEnd,
- },
- false,
- );
+ return sprintf(text, { commitId, linkStart, linkEnd }, false);
+ },
+ diffLine() {
+ if (this.discussion.diff_discussion && this.discussion.truncated_diff_lines) {
+ return this.discussion.truncated_diff_lines.slice(-1)[0];
+ }
+
+ return this.line;
},
},
watch: {
@@ -346,8 +375,18 @@ Please check your network connection and try again.`;
<component
:is="componentName(initialDiscussion)"
:note="componentData(initialDiscussion)"
+ :line="line"
+ :help-page-path="helpPagePath"
@handleDeleteNote="deleteNoteHandler"
>
+ <note-edited-text
+ v-if="discussion.resolved"
+ slot="discussion-resolved-text"
+ :edited-at="discussion.resolved_at"
+ :edited-by="discussion.resolved_by"
+ :action-text="resolvedText"
+ class-name="discussion-headline-light js-discussion-headline discussion-resolved-text"
+ />
<slot slot="avatar-badge" name="avatar-badge"></slot>
</component>
<toggle-replies-widget
@@ -362,6 +401,8 @@ Please check your network connection and try again.`;
v-for="note in replies"
:key="note.id"
:note="componentData(note)"
+ :help-page-path="helpPagePath"
+ :line="line"
@handleDeleteNote="deleteNoteHandler"
/>
</template>
@@ -372,6 +413,8 @@ Please check your network connection and try again.`;
v-for="(note, index) in discussion.notes"
:key="note.id"
:note="componentData(note)"
+ :help-page-path="helpPagePath"
+ :line="diffLine"
@handleDeleteNote="deleteNoteHandler"
>
<slot v-if="index === 0" slot="avatar-badge" name="avatar-badge"></slot>
@@ -379,7 +422,7 @@ Please check your network connection and try again.`;
</template>
</ul>
<div
- v-if="!isRepliesCollapsed"
+ v-if="!isRepliesCollapsed || !hasReplies"
:class="{ 'is-replying': isReplying }"
class="discussion-reply-holder"
>
@@ -387,7 +430,7 @@ Please check your network connection and try again.`;
<div class="discussion-with-resolve-btn">
<button
type="button"
- class="js-vue-discussion-reply btn btn-text-field mr-sm-2 qa-discussion-reply"
+ class="js-vue-discussion-reply btn btn-text-field qa-discussion-reply"
title="Add a reply"
@click="showReplyForm"
>
@@ -396,7 +439,7 @@ Please check your network connection and try again.`;
<div v-if="discussion.resolvable">
<button
type="button"
- class="btn btn-default mr-sm-2"
+ class="btn btn-default ml-sm-2"
@click="resolveHandler();"
>
<i v-if="isResolving" aria-hidden="true" class="fa fa-spinner fa-spin"></i>
@@ -418,7 +461,7 @@ Please check your network connection and try again.`;
<icon name="issue-new" />
</a>
</div>
- <div v-if="hasUnresolvedDiscussions" class="btn-group" role="group">
+ <div v-if="shouldShowJumpToNextDiscussion" class="btn-group" role="group">
<button
v-gl-tooltip
class="btn btn-default discussion-next-btn"
@@ -436,6 +479,7 @@ Please check your network connection and try again.`;
ref="noteForm"
:discussion="discussion"
:is-editing="false"
+ :line="diffLine"
save-button-title="Comment"
@handleFormUpdate="saveReply"
@cancelForm="cancelReplyForm"
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index a17be51353e..4c02588127e 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -27,6 +27,16 @@ export default {
type: Object,
required: true,
},
+ line: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ helpPagePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
data() {
return {
@@ -185,7 +195,7 @@ export default {
:img-alt="author.name"
:img-size="40"
>
- <slot slot="avatar-badge" name="avatar-badge"> </slot>
+ <slot slot="avatar-badge" name="avatar-badge"></slot>
</user-avatar-link>
</div>
<div class="timeline-content">
@@ -217,14 +227,19 @@ export default {
@handleResolve="resolveHandler"
/>
</div>
- <note-body
- ref="noteBody"
- :note="note"
- :can-edit="note.current_user.can_edit"
- :is-editing="isEditing"
- @handleFormUpdate="formUpdateHandler"
- @cancelForm="formCancelHandler"
- />
+ <div class="timeline-discussion-body">
+ <slot name="discussion-resolved-text"></slot>
+ <note-body
+ ref="noteBody"
+ :note="note"
+ :line="line"
+ :can-edit="note.current_user.can_edit"
+ :is-editing="isEditing"
+ :help-page-path="helpPagePath"
+ @handleFormUpdate="formUpdateHandler"
+ @cancelForm="formCancelHandler"
+ />
+ </div>
</div>
</timeline-entry-item>
</template>
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index 6e6efb04753..f3fcfdfda05 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -12,6 +12,7 @@ import placeholderNote from '../../vue_shared/components/notes/placeholder_note.
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
import skeletonLoadingContainer from '../../vue_shared/components/notes/skeleton_note.vue';
import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
+import initUserPopovers from '../../user_popovers';
export default {
name: 'NotesApp',
@@ -48,6 +49,11 @@ export default {
required: false,
default: 0,
},
+ helpPagePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
data() {
return {
@@ -101,12 +107,15 @@ export default {
if (parentElement && parentElement.classList.contains('js-vue-notes-event')) {
parentElement.addEventListener('toggleAward', event => {
const { awardName, noteId } = event.detail;
- this.actionToggleAward({ awardName, noteId });
+ this.toggleAward({ awardName, noteId });
});
}
},
updated() {
- this.$nextTick(() => highlightCurrentUser(this.$el.querySelectorAll('.gfm-project_member')));
+ this.$nextTick(() => {
+ highlightCurrentUser(this.$el.querySelectorAll('.gfm-project_member'));
+ initUserPopovers(this.$el.querySelectorAll('.js-user-link'));
+ });
},
methods: {
...mapActions([
@@ -202,6 +211,7 @@ export default {
:key="discussion.id"
:discussion="discussion"
:render-diff-file="true"
+ :help-page-path="helpPagePath"
/>
</template>
</ul>
diff --git a/app/assets/javascripts/notes/mixins/discussion_navigation.js b/app/assets/javascripts/notes/mixins/discussion_navigation.js
index f7c4deee1f8..3d89d907777 100644
--- a/app/assets/javascripts/notes/mixins/discussion_navigation.js
+++ b/app/assets/javascripts/notes/mixins/discussion_navigation.js
@@ -1,29 +1,56 @@
import { scrollToElement } from '~/lib/utils/common_utils';
+import eventHub from '../../notes/event_hub';
export default {
methods: {
- jumpToDiscussion(id) {
- if (id) {
- const activeTab = window.mrTabs.currentAction;
- const selector =
- activeTab === 'diffs'
- ? `ul.notes[data-discussion-id="${id}"]`
- : `div.discussion[data-discussion-id="${id}"]`;
- const el = document.querySelector(selector);
+ diffsJump(id) {
+ const selector = `ul.notes[data-discussion-id="${id}"]`;
- if (activeTab === 'commits' || activeTab === 'pipelines') {
- window.mrTabs.activateTab('show');
- }
+ eventHub.$once('scrollToDiscussion', () => {
+ const el = document.querySelector(selector);
if (el) {
- this.expandDiscussion({ discussionId: id });
-
scrollToElement(el);
+
return true;
}
+
+ return false;
+ });
+
+ this.expandDiscussion({ discussionId: id });
+ },
+ discussionJump(id) {
+ const selector = `div.discussion[data-discussion-id="${id}"]`;
+
+ const el = document.querySelector(selector);
+
+ this.expandDiscussion({ discussionId: id });
+
+ if (el) {
+ scrollToElement(el);
+
+ return true;
}
return false;
},
+ jumpToDiscussion(id) {
+ if (id) {
+ const activeTab = window.mrTabs.currentAction;
+
+ if (activeTab === 'diffs') {
+ this.diffsJump(id);
+ } else if (activeTab === 'commits' || activeTab === 'pipelines') {
+ window.mrTabs.eventHub.$once('MergeRequestTabChange', () => {
+ setTimeout(() => this.discussionJump(id), 0);
+ });
+
+ window.mrTabs.tabShown('show');
+ } else {
+ this.discussionJump(id);
+ }
+ }
+ },
},
};
diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js
index 47a6f07cce2..237e70c0a4c 100644
--- a/app/assets/javascripts/notes/services/notes_service.js
+++ b/app/assets/javascripts/notes/services/notes_service.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import Api from '~/api';
import VueResource from 'vue-resource';
import * as constants from '../constants';
@@ -44,4 +45,7 @@ export default {
toggleIssueState(endpoint, data) {
return Vue.http.put(endpoint, data);
},
+ applySuggestion(id) {
+ return Api.applySuggestion(id);
+ },
};
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index b4befdd6e4a..65f85314fa0 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -17,7 +17,13 @@ import { __ } from '~/locale';
let eTagPoll;
-export const expandDiscussion = ({ commit }, data) => commit(types.EXPAND_DISCUSSION, data);
+export const expandDiscussion = ({ commit, dispatch }, data) => {
+ if (data.discussionId) {
+ dispatch('diffs/renderFileForDiscussionId', data.discussionId, { root: true });
+ }
+
+ commit(types.EXPAND_DISCUSSION, data);
+};
export const collapseDiscussion = ({ commit }, data) => commit(types.COLLAPSE_DISCUSSION, data);
@@ -399,5 +405,25 @@ export const startTaskList = ({ dispatch }) =>
export const updateResolvableDiscussonsCounts = ({ commit }) =>
commit(types.UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS);
+export const submitSuggestion = (
+ { commit },
+ { discussionId, noteId, suggestionId, flashContainer, callback },
+) => {
+ service
+ .applySuggestion(suggestionId)
+ .then(() => {
+ commit(types.APPLY_SUGGESTION, { discussionId, noteId, suggestionId });
+ callback();
+ })
+ .catch(() => {
+ Flash(
+ __('Something went wrong while applying the suggestion. Please try again.'),
+ 'alert',
+ flashContainer,
+ );
+ callback();
+ });
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index 2ed8aac059a..0ffc0cb2593 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -57,6 +57,17 @@ export const unresolvedDiscussionsCount = state => state.unresolvedDiscussionsCo
export const resolvableDiscussionsCount = state => state.resolvableDiscussionsCount;
export const hasUnresolvedDiscussions = state => state.hasUnresolvedDiscussions;
+export const showJumpToNextDiscussion = (state, getters) => (discussionId, mode = 'discussion') => {
+ const orderedDiffs =
+ mode !== 'discussion'
+ ? getters.unresolvedDiscussionsIdsByDiff
+ : getters.unresolvedDiscussionsIdsByDate;
+
+ const indexOf = orderedDiffs.indexOf(discussionId);
+
+ return indexOf !== -1 && indexOf < orderedDiffs.length - 1;
+};
+
export const isDiscussionResolved = (state, getters) => discussionId =>
getters.resolvedDiscussionsById[discussionId] !== undefined;
@@ -104,7 +115,7 @@ export const unresolvedDiscussionsIdsByDate = (state, getters) =>
// line numbers.
export const unresolvedDiscussionsIdsByDiff = (state, getters) =>
getters.allResolvableDiscussions
- .filter(d => !d.resolved)
+ .filter(d => !d.resolved && d.active)
.sort((a, b) => {
if (!a.diff_file || !b.diff_file) {
return 0;
diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js
index b5fe8bdb1d3..887e6d22b06 100644
--- a/app/assets/javascripts/notes/stores/modules/index.js
+++ b/app/assets/javascripts/notes/stores/modules/index.js
@@ -20,6 +20,7 @@ export default () => ({
userData: {},
noteableData: {
current_user: {},
+ preview_note_path: 'path/to/preview',
},
commentsDisabled: false,
resolvableDiscussionsCount: 0,
diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js
index 9c68ab67a8c..df943c155f4 100644
--- a/app/assets/javascripts/notes/stores/mutation_types.js
+++ b/app/assets/javascripts/notes/stores/mutation_types.js
@@ -16,6 +16,7 @@ export const SET_DISCUSSION_DIFF_LINES = 'SET_DISCUSSION_DIFF_LINES';
export const SET_NOTES_FETCHED_STATE = 'SET_NOTES_FETCHED_STATE';
export const SET_NOTES_LOADING_STATE = 'SET_NOTES_LOADING_STATE';
export const DISABLE_COMMENTS = 'DISABLE_COMMENTS';
+export const APPLY_SUGGESTION = 'APPLY_SUGGESTION';
// DISCUSSION
export const COLLAPSE_DISCUSSION = 'COLLAPSE_DISCUSSION';
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index bea396e5bb6..8992454be2e 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -22,6 +22,7 @@ export default {
if (isDiscussion && isInMRPage()) {
noteData.resolvable = note.resolvable;
noteData.resolved = false;
+ noteData.active = true;
noteData.resolve_path = note.resolve_path;
noteData.resolve_with_issue_path = note.resolve_with_issue_path;
noteData.diff_discussion = false;
@@ -196,6 +197,17 @@ export default {
}
},
+ [types.APPLY_SUGGESTION](state, { noteId, discussionId, suggestionId }) {
+ const noteObj = utils.findNoteObjectById(state.discussions, discussionId);
+ const comment = utils.findNoteObjectById(noteObj.notes, noteId);
+
+ comment.suggestions = comment.suggestions.map(suggestion => ({
+ ...suggestion,
+ applied: suggestion.applied || suggestion.id === suggestionId,
+ appliable: false,
+ }));
+ },
+
[types.UPDATE_DISCUSSION](state, noteData) {
const note = noteData;
const selectedDiscussion = state.discussions.find(disc => disc.id === note.id);
@@ -245,7 +257,7 @@ export default {
discussion =>
!discussion.individual_note &&
discussion.resolvable &&
- discussion.notes.some(note => !note.resolved),
+ discussion.notes.some(note => note.resolvable && !note.resolved),
).length;
state.hasUnresolvedDiscussions = state.unresolvedDiscussionsCount > 1;
},
diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js
index c4c8cf86cb0..e7fa05faa8a 100644
--- a/app/assets/javascripts/notifications_dropdown.js
+++ b/app/assets/javascripts/notifications_dropdown.js
@@ -12,6 +12,10 @@ export default function notificationsDropdown() {
const form = $(this).parents('.notification-form:first');
form.find('.js-notification-loading').toggleClass('fa-bell fa-spin fa-spinner');
+ if (form.hasClass('no-label')) {
+ form.find('.js-notification-loading').toggleClass('hidden');
+ form.find('.js-notifications-icon').toggleClass('hidden');
+ }
form.find('#notification_setting_level').val(notificationLevel);
form.submit();
});
diff --git a/app/assets/javascripts/pages/dashboard/projects/index.js b/app/assets/javascripts/pages/dashboard/projects/index.js
index 0c585e162cb..8f98be79640 100644
--- a/app/assets/javascripts/pages/dashboard/projects/index.js
+++ b/app/assets/javascripts/pages/dashboard/projects/index.js
@@ -1,3 +1,7 @@
import ProjectsList from '~/projects_list';
+import Star from '../../../star';
-document.addEventListener('DOMContentLoaded', () => new ProjectsList());
+document.addEventListener('DOMContentLoaded', () => {
+ new ProjectsList(); // eslint-disable-line no-new
+ new Star('.project-row'); // eslint-disable-line no-new
+});
diff --git a/app/assets/javascripts/pages/groups/clusters/index/index.js b/app/assets/javascripts/pages/groups/clusters/index/index.js
index 21efc4f6d00..845a5f7042c 100644
--- a/app/assets/javascripts/pages/groups/clusters/index/index.js
+++ b/app/assets/javascripts/pages/groups/clusters/index/index.js
@@ -1,7 +1,5 @@
-import PersistentUserCallout from '~/persistent_user_callout';
+import initDismissableCallout from '~/dismissable_callout';
document.addEventListener('DOMContentLoaded', () => {
- const callout = document.querySelector('.gcp-signup-offer');
-
- if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
+ initDismissableCallout('.gcp-signup-offer');
});
diff --git a/app/assets/javascripts/pages/groups/index.js b/app/assets/javascripts/pages/groups/index.js
index 00e2d7fc998..bf80d8b8193 100644
--- a/app/assets/javascripts/pages/groups/index.js
+++ b/app/assets/javascripts/pages/groups/index.js
@@ -1,12 +1,6 @@
-import PersistentUserCallout from '~/persistent_user_callout';
+import initDismissableCallout from '~/dismissable_callout';
import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
-function initCallout() {
- const callout = document.querySelector('.gcp-signup-offer');
-
- if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
-}
-
document.addEventListener('DOMContentLoaded', () => {
const { page } = document.body.dataset;
const newClusterViews = [
@@ -16,7 +10,7 @@ document.addEventListener('DOMContentLoaded', () => {
];
if (newClusterViews.indexOf(page) > -1) {
- initCallout();
+ initDismissableCallout('.gcp-signup-offer');
initGkeDropdowns();
}
});
diff --git a/app/assets/javascripts/pages/profiles/show/emoji_menu.js b/app/assets/javascripts/pages/profiles/show/emoji_menu.js
index 094837b40e0..286c1f1e929 100644
--- a/app/assets/javascripts/pages/profiles/show/emoji_menu.js
+++ b/app/assets/javascripts/pages/profiles/show/emoji_menu.js
@@ -1,3 +1,4 @@
+import '~/commons/bootstrap';
import { AwardsHandler } from '~/awards_handler';
class EmojiMenu extends AwardsHandler {
diff --git a/app/assets/javascripts/pages/projects/clusters/index/index.js b/app/assets/javascripts/pages/projects/clusters/index/index.js
index 21efc4f6d00..845a5f7042c 100644
--- a/app/assets/javascripts/pages/projects/clusters/index/index.js
+++ b/app/assets/javascripts/pages/projects/clusters/index/index.js
@@ -1,7 +1,5 @@
-import PersistentUserCallout from '~/persistent_user_callout';
+import initDismissableCallout from '~/dismissable_callout';
document.addEventListener('DOMContentLoaded', () => {
- const callout = document.querySelector('.gcp-signup-offer');
-
- if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
+ initDismissableCallout('.gcp-signup-offer');
});
diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js
index f5b1cf85e68..899d5925956 100644
--- a/app/assets/javascripts/pages/projects/edit/index.js
+++ b/app/assets/javascripts/pages/projects/edit/index.js
@@ -3,8 +3,8 @@ import initSettingsPanels from '~/settings_panels';
import setupProjectEdit from '~/project_edit';
import initConfirmDangerModal from '~/confirm_danger_modal';
import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
+import fileUpload from '~/lib/utils/file_upload';
import initProjectLoadingSpinner from '../shared/save_project_loader';
-import projectAvatar from '../shared/project_avatar';
import initProjectPermissionsSettings from '../shared/permissions';
document.addEventListener('DOMContentLoaded', () => {
@@ -12,7 +12,7 @@ document.addEventListener('DOMContentLoaded', () => {
setupProjectEdit();
// Initialize expandable settings panels
initSettingsPanels();
- projectAvatar();
+ fileUpload('.js-choose-project-avatar-button', '.js-project-avatar-input');
initProjectPermissionsSettings();
initConfirmDangerModal();
mountBadgeSettings(PROJECT_BADGE);
diff --git a/app/assets/javascripts/pages/projects/index.js b/app/assets/javascripts/pages/projects/index.js
index b0345b4e50d..5659e13981a 100644
--- a/app/assets/javascripts/pages/projects/index.js
+++ b/app/assets/javascripts/pages/projects/index.js
@@ -1,5 +1,5 @@
+import initDismissableCallout from '~/dismissable_callout';
import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
-import PersistentUserCallout from '../../persistent_user_callout';
import Project from './project';
import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation';
@@ -12,9 +12,7 @@ document.addEventListener('DOMContentLoaded', () => {
];
if (newClusterViews.indexOf(page) > -1) {
- const callout = document.querySelector('.gcp-signup-offer');
- if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
-
+ initDismissableCallout('.gcp-signup-offer');
initGkeDropdowns();
}
diff --git a/app/assets/javascripts/pages/projects/issues/form.js b/app/assets/javascripts/pages/projects/issues/form.js
index 02a56685a35..f99023ad8e7 100644
--- a/app/assets/javascripts/pages/projects/issues/form.js
+++ b/app/assets/javascripts/pages/projects/issues/form.js
@@ -17,7 +17,7 @@ export default () => {
new MilestoneSelect();
new IssuableTemplateSelectors();
- if (gon.features.issueSuggestions && gon.features.graphql) {
+ if (gon.features.graphql) {
initSuggestions();
}
};
diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js
index a6bee49a6b1..b288989b252 100644
--- a/app/assets/javascripts/pages/projects/project.js
+++ b/app/assets/javascripts/pages/projects/project.js
@@ -13,6 +13,9 @@ export default class Project {
const $cloneOptions = $('ul.clone-options-dropdown');
const $projectCloneField = $('#project_clone');
const $cloneBtnLabel = $('.js-git-clone-holder .js-clone-dropdown-label');
+ const mobileCloneField = document.querySelector(
+ '.js-mobile-git-clone .js-clone-dropdown-label',
+ );
const selectedCloneOption = $cloneBtnLabel.text().trim();
if (selectedCloneOption.length > 0) {
@@ -36,7 +39,11 @@ export default class Project {
$label.text(activeText);
});
- $projectCloneField.val(url);
+ if (mobileCloneField) {
+ mobileCloneField.dataset.clipboardText = url;
+ } else {
+ $projectCloneField.val(url);
+ }
$('.js-git-empty .js-clone').text(url);
});
// Ref switcher
diff --git a/app/assets/javascripts/pages/projects/releases/index/index.js b/app/assets/javascripts/pages/projects/releases/index/index.js
new file mode 100644
index 00000000000..c183fbb9610
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/releases/index/index.js
@@ -0,0 +1,3 @@
+import initReleases from '~/releases';
+
+document.addEventListener('DOMContentLoaded', initReleases);
diff --git a/app/assets/javascripts/pages/projects/serverless/index.js b/app/assets/javascripts/pages/projects/serverless/index.js
new file mode 100644
index 00000000000..7b08620773c
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/serverless/index.js
@@ -0,0 +1,5 @@
+import ServerlessBundle from '~/serverless/serverless_bundle';
+
+document.addEventListener('DOMContentLoaded', () => {
+ new ServerlessBundle(); // eslint-disable-line no-new
+});
diff --git a/app/assets/javascripts/pages/projects/settings/repository/form.js b/app/assets/javascripts/pages/projects/settings/repository/form.js
index a52861c9efa..3e02893f24c 100644
--- a/app/assets/javascripts/pages/projects/settings/repository/form.js
+++ b/app/assets/javascripts/pages/projects/settings/repository/form.js
@@ -7,6 +7,7 @@ import initDeployKeys from '~/deploy_keys';
import ProtectedBranchCreate from '~/protected_branches/protected_branch_create';
import ProtectedBranchEditList from '~/protected_branches/protected_branch_edit_list';
import DueDateSelectors from '~/due_date_select';
+import fileUpload from '~/lib/utils/file_upload';
export default () => {
new ProtectedTagCreate();
@@ -16,4 +17,5 @@ export default () => {
new ProtectedBranchCreate();
new ProtectedBranchEditList();
new DueDateSelectors();
+ fileUpload('.js-choose-file', '.js-object-map-input');
};
diff --git a/app/assets/javascripts/pages/projects/shared/project_avatar.js b/app/assets/javascripts/pages/projects/shared/project_avatar.js
deleted file mode 100644
index 1e69ecb481d..00000000000
--- a/app/assets/javascripts/pages/projects/shared/project_avatar.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import $ from 'jquery';
-
-export default function projectAvatar() {
- $('.js-choose-project-avatar-button').bind('click', function onClickAvatar() {
- const form = $(this).closest('form');
- return form.find('.js-project-avatar-input').click();
- });
-
- $('.js-project-avatar-input').bind('change', function onClickAvatarInput() {
- const form = $(this).closest('form');
- const filename = $(this)
- .val()
- .replace(/^.*[\\\/]/, ''); // eslint-disable-line no-useless-escape
- return form.find('.js-avatar-filename').text(filename);
- });
-}
diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index aa537d4a43e..1c3fd58ca74 100644
--- a/app/assets/javascripts/pages/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -151,8 +151,10 @@ export default class UserTabs {
loadTab(action, endpoint) {
this.toggleLoading(true);
+ const params = action === 'projects' ? { skip_namespace: true } : {};
+
return axios
- .get(endpoint)
+ .get(endpoint, { params })
.then(({ data }) => {
const tabSelector = `div#${action}`;
this.$parentEl.find(tabSelector).html(data.html);
@@ -188,7 +190,7 @@ export default class UserTabs {
requestParams: { limit: 10 },
});
UserTabs.renderMostRecentBlocks('#js-overview .projects-block', {
- requestParams: { limit: 10, skip_pagination: true },
+ requestParams: { limit: 10, skip_pagination: true, skip_namespace: true, compact_mode: true },
});
this.loaded.overview = true;
@@ -206,6 +208,8 @@ export default class UserTabs {
loadActivityCalendar() {
const $calendarWrap = this.$parentEl.find('.tab-pane.active .user-calendar');
+ if (!$calendarWrap.length) return;
+
const calendarPath = $calendarWrap.data('calendarPath');
AjaxCache.retrieve(calendarPath)
diff --git a/app/assets/javascripts/persistent_user_callout.js b/app/assets/javascripts/persistent_user_callout.js
deleted file mode 100644
index 1e34e74a152..00000000000
--- a/app/assets/javascripts/persistent_user_callout.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import axios from './lib/utils/axios_utils';
-import { __ } from './locale';
-import Flash from './flash';
-
-export default class PersistentUserCallout {
- constructor(container) {
- const { dismissEndpoint, featureId } = container.dataset;
- this.container = container;
- this.dismissEndpoint = dismissEndpoint;
- this.featureId = featureId;
-
- this.init();
- }
-
- init() {
- const closeButton = this.container.querySelector('.js-close');
- closeButton.addEventListener('click', event => this.dismiss(event));
- }
-
- dismiss(event) {
- event.preventDefault();
-
- axios
- .post(this.dismissEndpoint, {
- feature_name: this.featureId,
- })
- .then(() => {
- this.container.remove();
- })
- .catch(() => {
- Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.'));
- });
- }
-}
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue
index e5924d3a77e..30a5bbf92ce 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue
@@ -65,7 +65,7 @@ export default {
v-if="pipeline.flags.latest"
v-gl-tooltip
class="js-pipeline-url-latest badge badge-success"
- title="Latest pipeline for this branch"
+ title="__('Latest pipeline for this branch')"
>
latest
</span>
@@ -97,6 +97,14 @@ export default {
<span v-if="pipeline.flags.stuck" class="js-pipeline-url-stuck badge badge-warning">
stuck
</span>
+ <span
+ v-if="pipeline.flags.merge_request"
+ v-gl-tooltip
+ title="__('This pipeline is run in a merge request context')"
+ class="js-pipeline-url-mergerequest badge badge-info"
+ >
+ merge request
+ </span>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/registry/components/app.vue b/app/assets/javascripts/registry/components/app.vue
index 6233fb169e9..9af5660f764 100644
--- a/app/assets/javascripts/registry/components/app.vue
+++ b/app/assets/javascripts/registry/components/app.vue
@@ -1,15 +1,13 @@
<script>
import { mapGetters, mapActions } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
-import Flash from '../../flash';
import store from '../stores';
-import collapsibleContainer from './collapsible_container.vue';
-import { errorMessages, errorMessagesTypes } from '../constants';
+import CollapsibleContainer from './collapsible_container.vue';
export default {
name: 'RegistryListApp',
components: {
- collapsibleContainer,
+ CollapsibleContainer,
GlLoadingIcon,
},
props: {
@@ -26,7 +24,7 @@ export default {
this.setMainEndpoint(this.endpoint);
},
mounted() {
- this.fetchRepos().catch(() => Flash(errorMessages[errorMessagesTypes.FETCH_REPOS]));
+ this.fetchRepos();
},
methods: {
...mapActions(['setMainEndpoint', 'fetchRepos']),
@@ -38,9 +36,9 @@ export default {
<gl-loading-icon v-if="isLoading" :size="3" />
<collapsible-container
- v-for="(item, index) in repos"
+ v-for="item in repos"
v-else-if="!isLoading && repos.length"
- :key="index"
+ :key="item.id"
:repo="item"
/>
diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue
index 6514c05a9c7..5451c61026c 100644
--- a/app/assets/javascripts/registry/components/collapsible_container.vue
+++ b/app/assets/javascripts/registry/components/collapsible_container.vue
@@ -1,22 +1,24 @@
<script>
import { mapActions } from 'vuex';
-import { GlLoadingIcon } from '@gitlab/ui';
-import Flash from '../../flash';
-import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
-import tooltip from '../../vue_shared/directives/tooltip';
-import tableRegistry from './table_registry.vue';
+import { GlLoadingIcon, GlButton, GlTooltipDirective } from '@gitlab/ui';
+import createFlash from '../../flash';
+import ClipboardButton from '../../vue_shared/components/clipboard_button.vue';
+import Icon from '../../vue_shared/components/icon.vue';
+import TableRegistry from './table_registry.vue';
import { errorMessages, errorMessagesTypes } from '../constants';
import { __ } from '../../locale';
export default {
name: 'CollapsibeContainerRegisty',
components: {
- clipboardButton,
- tableRegistry,
+ ClipboardButton,
+ TableRegistry,
GlLoadingIcon,
+ GlButton,
+ Icon,
},
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
props: {
repo: {
@@ -29,30 +31,30 @@ export default {
isOpen: false,
};
},
+ computed: {
+ iconName() {
+ return this.isOpen ? 'angle-up' : 'angle-right';
+ },
+ },
methods: {
...mapActions(['fetchRepos', 'fetchList', 'deleteRepo']),
-
toggleRepo() {
this.isOpen = !this.isOpen;
if (this.isOpen) {
- this.fetchList({ repo: this.repo }).catch(() =>
- this.showError(errorMessagesTypes.FETCH_REGISTRY),
- );
+ this.fetchList({ repo: this.repo });
}
},
-
handleDeleteRepository() {
this.deleteRepo(this.repo)
.then(() => {
- Flash(__('This container registry has been scheduled for deletion.'), 'notice');
+ createFlash(__('This container registry has been scheduled for deletion.'), 'notice');
this.fetchRepos();
})
.catch(() => this.showError(errorMessagesTypes.DELETE_REPO));
},
-
showError(message) {
- Flash(errorMessages[message]);
+ createFlash(errorMessages[message]);
},
},
};
@@ -61,18 +63,9 @@ export default {
<template>
<div class="container-image">
<div class="container-image-head">
- <button type="button" class="js-toggle-repo btn-link" @click="toggleRepo">
- <i
- :class="{
- 'fa-chevron-right': !isOpen,
- 'fa-chevron-up': isOpen,
- }"
- class="fa"
- aria-hidden="true"
- >
- </i>
- {{ repo.name }}
- </button>
+ <gl-button class="js-toggle-repo btn-link align-baseline" @click="toggleRepo">
+ <icon :name="iconName" /> {{ repo.name }}
+ </gl-button>
<clipboard-button
v-if="repo.location"
@@ -82,17 +75,17 @@ export default {
/>
<div class="controls d-none d-sm-block float-right">
- <button
+ <gl-button
v-if="repo.canDelete"
- v-tooltip
+ v-gl-tooltip
:title="s__('ContainerRegistry|Remove repository')"
:aria-label="s__('ContainerRegistry|Remove repository')"
- type="button"
- class="js-remove-repo btn btn-danger"
+ class="js-remove-repo"
+ variant="danger"
@click="handleDeleteRepository"
>
- <i class="fa fa-trash" aria-hidden="true"> </i>
- </button>
+ <icon name="remove" />
+ </gl-button>
</div>
</div>
diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue
index 6735c3ff7cf..78c7671856a 100644
--- a/app/assets/javascripts/registry/components/table_registry.vue
+++ b/app/assets/javascripts/registry/components/table_registry.vue
@@ -1,21 +1,24 @@
<script>
import { mapActions } from 'vuex';
+import { GlButton, GlTooltipDirective } from '@gitlab/ui';
import { n__ } from '../../locale';
-import Flash from '../../flash';
-import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
-import tablePagination from '../../vue_shared/components/table_pagination.vue';
-import tooltip from '../../vue_shared/directives/tooltip';
+import createFlash from '../../flash';
+import ClipboardButton from '../../vue_shared/components/clipboard_button.vue';
+import TablePagination from '../../vue_shared/components/table_pagination.vue';
+import Icon from '../../vue_shared/components/icon.vue';
import timeagoMixin from '../../vue_shared/mixins/timeago';
import { errorMessages, errorMessagesTypes } from '../constants';
import { numberToHumanSize } from '../../lib/utils/number_utils';
export default {
components: {
- clipboardButton,
- tablePagination,
+ ClipboardButton,
+ TablePagination,
+ GlButton,
+ Icon,
},
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
mixins: [timeagoMixin],
props: {
@@ -31,29 +34,24 @@ export default {
},
methods: {
...mapActions(['fetchList', 'deleteRegistry']),
-
layers(item) {
return item.layers ? n__('%d layer', '%d layers', item.layers) : '';
},
-
formatSize(size) {
return numberToHumanSize(size);
},
-
handleDeleteRegistry(registry) {
this.deleteRegistry(registry)
.then(() => this.fetchList({ repo: this.repo }))
.catch(() => this.showError(errorMessagesTypes.DELETE_REGISTRY));
},
-
onPageChange(pageNumber) {
this.fetchList({ repo: this.repo, page: pageNumber }).catch(() =>
this.showError(errorMessagesTypes.FETCH_REGISTRY),
);
},
-
showError(message) {
- Flash(errorMessages[message]);
+ createFlash(errorMessages[message]);
},
},
};
@@ -71,10 +69,9 @@ export default {
</tr>
</thead>
<tbody>
- <tr v-for="(item, i) in repo.list" :key="i">
+ <tr v-for="item in repo.list" :key="item.tag">
<td>
{{ item.tag }}
-
<clipboard-button
v-if="item.location"
:title="item.location"
@@ -83,37 +80,34 @@ export default {
/>
</td>
<td>
- <span v-tooltip :title="item.revision" data-placement="bottom">
- {{ item.shortRevision }}
- </span>
+ <span v-gl-tooltip.bottom :title="item.revision">{{ item.shortRevision }}</span>
</td>
<td>
{{ formatSize(item.size) }}
- <template v-if="item.size && item.layers">
- &middot;
- </template>
+ <template v-if="item.size && item.layers"
+ >&middot;</template
+ >
{{ layers(item) }}
</td>
<td>
- <span v-tooltip :title="tooltipTitle(item.createdAt)" data-placement="bottom">
- {{ timeFormated(item.createdAt) }}
- </span>
+ <span v-gl-tooltip.bottom :title="tooltipTitle(item.createdAt)">{{
+ timeFormated(item.createdAt)
+ }}</span>
</td>
<td class="content">
- <button
+ <gl-button
v-if="item.canDelete"
- v-tooltip
+ v-gl-tooltip
:title="s__('ContainerRegistry|Remove tag')"
:aria-label="s__('ContainerRegistry|Remove tag')"
- type="button"
- class="js-delete-registry btn btn-danger d-none d-sm-block float-right"
- data-container="body"
+ variant="danger"
+ class="js-delete-registry d-none d-sm-block float-right"
@click="handleDeleteRegistry(item);"
>
- <i class="fa fa-trash" aria-hidden="true"> </i>
- </button>
+ <icon name="remove" />
+ </gl-button>
</td>
</tr>
</tbody>
diff --git a/app/assets/javascripts/registry/stores/actions.js b/app/assets/javascripts/registry/stores/actions.js
index a78aa90b7b5..51d057c62c1 100644
--- a/app/assets/javascripts/registry/stores/actions.js
+++ b/app/assets/javascripts/registry/stores/actions.js
@@ -1,39 +1,45 @@
-import Vue from 'vue';
-import VueResource from 'vue-resource';
+import axios from '~/lib/utils/axios_utils';
+import createFlash from '~/flash';
import * as types from './mutation_types';
-
-Vue.use(VueResource);
+import { errorMessages, errorMessagesTypes } from '../constants';
export const fetchRepos = ({ commit, state }) => {
commit(types.TOGGLE_MAIN_LOADING);
- return Vue.http
+ return axios
.get(state.endpoint)
- .then(res => res.json())
- .then(response => {
+ .then(({ data }) => {
+ commit(types.TOGGLE_MAIN_LOADING);
+ commit(types.SET_REPOS_LIST, data);
+ })
+ .catch(() => {
commit(types.TOGGLE_MAIN_LOADING);
- commit(types.SET_REPOS_LIST, response);
+ createFlash(errorMessages[errorMessagesTypes.FETCH_REPOS]);
});
};
export const fetchList = ({ commit }, { repo, page }) => {
commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo);
- return Vue.http.get(repo.tagsPath, { params: { page } }).then(response => {
- const { headers } = response;
+ return axios
+ .get(repo.tagsPath, { params: { page } })
+ .then(response => {
+ const { headers, data } = response;
- return response.json().then(resp => {
commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo);
- commit(types.SET_REGISTRY_LIST, { repo, resp, headers });
+ commit(types.SET_REGISTRY_LIST, { repo, resp: data, headers });
+ })
+ .catch(() => {
+ commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo);
+ createFlash(errorMessages[errorMessagesTypes.FETCH_REGISTRY]);
});
- });
};
// eslint-disable-next-line no-unused-vars
-export const deleteRepo = ({ commit }, repo) => Vue.http.delete(repo.destroyPath);
+export const deleteRepo = ({ commit }, repo) => axios.delete(repo.destroyPath);
// eslint-disable-next-line no-unused-vars
-export const deleteRegistry = ({ commit }, image) => Vue.http.delete(image.destroyPath);
+export const deleteRegistry = ({ commit }, image) => axios.delete(image.destroyPath);
export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data);
export const toggleLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING);
diff --git a/app/assets/javascripts/registry/stores/index.js b/app/assets/javascripts/registry/stores/index.js
index 78b67881210..1bb06bd6e81 100644
--- a/app/assets/javascripts/registry/stores/index.js
+++ b/app/assets/javascripts/registry/stores/index.js
@@ -3,36 +3,12 @@ import Vuex from 'vuex';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
+import createState from './state';
Vue.use(Vuex);
export default new Vuex.Store({
- state: {
- isLoading: false,
- endpoint: '', // initial endpoint to fetch the repos list
- /**
- * Each object in `repos` has the following strucure:
- * {
- * name: String,
- * isLoading: Boolean,
- * tagsPath: String // endpoint to request the list
- * destroyPath: String // endpoit to delete the repo
- * list: Array // List of the registry images
- * }
- *
- * Each registry image inside `list` has the following structure:
- * {
- * tag: String,
- * revision: String
- * shortRevision: String
- * size: Number
- * layers: Number
- * createdAt: String
- * destroyPath: String // endpoit to delete each image
- * }
- */
- repos: [],
- },
+ state: createState(),
actions,
getters,
mutations,
diff --git a/app/assets/javascripts/registry/stores/mutations.js b/app/assets/javascripts/registry/stores/mutations.js
index 69c051cd2d6..1ac699c538f 100644
--- a/app/assets/javascripts/registry/stores/mutations.js
+++ b/app/assets/javascripts/registry/stores/mutations.js
@@ -48,6 +48,7 @@ export default {
[types.TOGGLE_REGISTRY_LIST_LOADING](state, list) {
const listToUpdate = state.repos.find(el => el.id === list.id);
+
listToUpdate.isLoading = !listToUpdate.isLoading;
},
};
diff --git a/app/assets/javascripts/registry/stores/state.js b/app/assets/javascripts/registry/stores/state.js
new file mode 100644
index 00000000000..feeac10cbe1
--- /dev/null
+++ b/app/assets/javascripts/registry/stores/state.js
@@ -0,0 +1,26 @@
+export default () => ({
+ isLoading: false,
+ endpoint: '', // initial endpoint to fetch the repos list
+ /**
+ * Each object in `repos` has the following strucure:
+ * {
+ * name: String,
+ * isLoading: Boolean,
+ * tagsPath: String // endpoint to request the list
+ * destroyPath: String // endpoit to delete the repo
+ * list: Array // List of the registry images
+ * }
+ *
+ * Each registry image inside `list` has the following structure:
+ * {
+ * tag: String,
+ * revision: String
+ * shortRevision: String
+ * size: Number
+ * layers: Number
+ * createdAt: String
+ * destroyPath: String // endpoit to delete each image
+ * }
+ */
+ repos: [],
+});
diff --git a/app/assets/javascripts/releases/components/app.vue b/app/assets/javascripts/releases/components/app.vue
new file mode 100644
index 00000000000..0ad5ee2915c
--- /dev/null
+++ b/app/assets/javascripts/releases/components/app.vue
@@ -0,0 +1,82 @@
+<script>
+import { mapState, mapActions } from 'vuex';
+import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
+import ReleaseBlock from './release_block.vue';
+
+export default {
+ name: 'ReleasesApp',
+ components: {
+ GlLoadingIcon,
+ GlEmptyState,
+ ReleaseBlock,
+ },
+ props: {
+ projectId: {
+ type: String,
+ required: true,
+ },
+ documentationLink: {
+ type: String,
+ required: true,
+ },
+ illustrationPath: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState(['isLoading', 'releases', 'hasError']),
+ shouldRenderEmptyState() {
+ return !this.releases.length && !this.hasError && !this.isLoading;
+ },
+ shouldRenderSuccessState() {
+ return this.releases.length && !this.isLoading && !this.hasError;
+ },
+ },
+ created() {
+ this.fetchReleases(this.projectId);
+ },
+ methods: {
+ ...mapActions(['fetchReleases']),
+ },
+};
+</script>
+<template>
+ <div class="prepend-top-default">
+ <gl-loading-icon v-if="isLoading" :size="2" class="js-loading prepend-top-20" />
+
+ <gl-empty-state
+ v-else-if="shouldRenderEmptyState"
+ class="js-empty-state"
+ :title="__('Getting started with releases')"
+ :svg-path="illustrationPath"
+ :description="
+ __(
+ 'Releases mark specific points in a project\'s development history, communicate information about the type of change, and deliver on prepared, often compiled, versions of the software to be reused elsewhere. Currently, releases can only be created through the API.',
+ )
+ "
+ :primary-button-link="documentationLink"
+ :primary-button-text="__('Open Documentation')"
+ />
+
+ <div v-else-if="shouldRenderSuccessState" class="js-success-state">
+ <release-block
+ v-for="(release, index) in releases"
+ :key="release.tag_name"
+ :release="release"
+ :class="{ 'linked-card': releases.length > 1 && index !== releases.length - 1 }"
+ />
+ </div>
+ </div>
+</template>
+<style>
+.linked-card::after {
+ width: 1px;
+ content: ' ';
+ border: 1px solid #e5e5e5;
+ height: 17px;
+ top: 100%;
+ position: absolute;
+ left: 32px;
+}
+</style>
diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue
new file mode 100644
index 00000000000..34b97826cdb
--- /dev/null
+++ b/app/assets/javascripts/releases/components/release_block.vue
@@ -0,0 +1,129 @@
+<script>
+import _ from 'underscore';
+import { GlTooltipDirective, GlLink } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+import { sprintf } from '../../locale';
+
+export default {
+ name: 'ReleaseBlock',
+ components: {
+ GlLink,
+ Icon,
+ UserAvatarLink,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ release: {
+ type: Object,
+ required: true,
+ default: () => ({}),
+ },
+ },
+ computed: {
+ releasedTimeAgo() {
+ return sprintf('released %{time}', {
+ time: this.timeFormated(this.release.created_at),
+ });
+ },
+ userImageAltDescription() {
+ return this.author && this.author.username
+ ? sprintf("%{username}'s avatar", { username: this.author.username })
+ : null;
+ },
+ commit() {
+ return this.release.commit || {};
+ },
+ assets() {
+ return this.release.assets || {};
+ },
+ author() {
+ return this.release.author || {};
+ },
+ hasAuthor() {
+ return _.isEmpty(this.author);
+ },
+ },
+};
+</script>
+<template>
+ <div class="card">
+ <div class="card-body">
+ <h2 class="card-title mt-0">{{ release.name }}</h2>
+
+ <div class="card-subtitle d-flex flex-wrap text-secondary">
+ <div class="append-right-8">
+ <icon name="commit" class="align-middle" />
+ <span v-gl-tooltip.bottom :title="commit.title">{{ commit.short_id }}</span>
+ </div>
+
+ <div class="append-right-8">
+ <icon name="tag" class="align-middle" />
+ <span v-gl-tooltip.bottom :title="__('Tag')">{{ release.tag_name }}</span>
+ </div>
+
+ <div class="append-right-4">
+ &bull;
+ <span v-gl-tooltip.bottom :title="tooltipTitle(release.created_at)">{{
+ releasedTimeAgo
+ }}</span>
+ </div>
+
+ <div v-if="hasAuthor" class="d-flex">
+ by
+ <user-avatar-link
+ class="prepend-left-4"
+ :link-href="author.path"
+ :img-src="author.avatar_url"
+ :img-alt="userImageAltDescription"
+ :tooltip-text="author.username"
+ />
+ </div>
+ </div>
+
+ <div
+ v-if="assets.links.length || assets.sources.length"
+ Sclass="card-text prepend-top-default"
+ >
+ <b>
+ {{ __('Assets') }}
+ <span class="js-assets-count badge badge-pill">{{ assets.count }}</span>
+ </b>
+
+ <ul v-if="assets.links.length" class="pl-0 mb-0 prepend-top-8 list-unstyled js-assets-list">
+ <li v-for="link in assets.links" :key="link.name" class="append-bottom-8">
+ <gl-link v-gl-tooltip.bottom :title="__('Download asset')" :href="link.url">
+ <icon name="package" class="align-middle append-right-4 align-text-bottom" />
+ {{ link.name }}
+ </gl-link>
+ </li>
+ </ul>
+
+ <div v-if="assets.sources.length" class="dropdown">
+ <button
+ type="button"
+ class="btn btn-link"
+ data-toggle="dropdown"
+ aria-haspopup="true"
+ aria-expanded="false"
+ >
+ <icon name="doc-code" class="align-top append-right-4" /> {{ __('Source code') }}
+ <icon name="arrow-down" />
+ </button>
+
+ <div class="js-sources-dropdown dropdown-menu">
+ <li v-for="asset in assets.sources" :key="asset.url">
+ <gl-link :href="asset.url">{{ __('Download') }} {{ asset.format }}</gl-link>
+ </li>
+ </div>
+ </div>
+ </div>
+
+ <div class="card-text prepend-top-default"><div v-html="release.description_html"></div></div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/releases/index.js b/app/assets/javascripts/releases/index.js
new file mode 100644
index 00000000000..adbed3cb8e2
--- /dev/null
+++ b/app/assets/javascripts/releases/index.js
@@ -0,0 +1,24 @@
+import Vue from 'vue';
+import App from './components/app.vue';
+import createStore from './store';
+
+export default () => {
+ const element = document.getElementById('js-releases-page');
+
+ return new Vue({
+ el: element,
+ store: createStore(),
+ components: {
+ App,
+ },
+ render(createElement) {
+ return createElement('app', {
+ props: {
+ projectId: element.dataset.projectId,
+ documentationLink: element.dataset.documentationPath,
+ illustrationPath: element.dataset.illustrationPath,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/releases/store/actions.js b/app/assets/javascripts/releases/store/actions.js
new file mode 100644
index 00000000000..baa2251403e
--- /dev/null
+++ b/app/assets/javascripts/releases/store/actions.js
@@ -0,0 +1,37 @@
+import * as types from './mutation_types';
+import createFlash from '~/flash';
+import { __ } from '~/locale';
+import api from '~/api';
+
+/**
+ * Commits a mutation to update the state while the main endpoint is being requested.
+ */
+export const requestReleases = ({ commit }) => commit(types.REQUEST_RELEASES);
+
+/**
+ * Fetches the main endpoint.
+ * Will dispatch requestNamespace action before starting the request.
+ * Will dispatch receiveNamespaceSuccess if the request is successfull
+ * Will dispatch receiveNamesapceError if the request returns an error
+ *
+ * @param {String} projectId
+ */
+export const fetchReleases = ({ dispatch }, projectId) => {
+ dispatch('requestReleases');
+
+ api
+ .releases(projectId)
+ .then(({ data }) => dispatch('receiveReleasesSuccess', data))
+ .catch(() => dispatch('receiveReleasesError'));
+};
+
+export const receiveReleasesSuccess = ({ commit }, data) =>
+ commit(types.RECEIVE_RELEASES_SUCCESS, data);
+
+export const receiveReleasesError = ({ commit }) => {
+ commit(types.RECEIVE_RELEASES_ERROR);
+ createFlash(__('An error occured while fetching the releases. Please try again.'));
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/releases/store/index.js b/app/assets/javascripts/releases/store/index.js
new file mode 100644
index 00000000000..968b94f0e0d
--- /dev/null
+++ b/app/assets/javascripts/releases/store/index.js
@@ -0,0 +1,14 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import state from './state';
+import * as actions from './actions';
+import mutations from './mutations';
+
+Vue.use(Vuex);
+
+export default () =>
+ new Vuex.Store({
+ actions,
+ mutations,
+ state: state(),
+ });
diff --git a/app/assets/javascripts/releases/store/mutation_types.js b/app/assets/javascripts/releases/store/mutation_types.js
new file mode 100644
index 00000000000..a74bf15c515
--- /dev/null
+++ b/app/assets/javascripts/releases/store/mutation_types.js
@@ -0,0 +1,3 @@
+export const REQUEST_RELEASES = 'REQUEST_RELEASES';
+export const RECEIVE_RELEASES_SUCCESS = 'RECEIVE_RELEASES_SUCCESS';
+export const RECEIVE_RELEASES_ERROR = 'RECEIVE_RELEASES_ERROR';
diff --git a/app/assets/javascripts/releases/store/mutations.js b/app/assets/javascripts/releases/store/mutations.js
new file mode 100644
index 00000000000..b97dc6cb0ab
--- /dev/null
+++ b/app/assets/javascripts/releases/store/mutations.js
@@ -0,0 +1,37 @@
+import * as types from './mutation_types';
+
+export default {
+ /**
+ * Sets isLoading to true while the request is being made.
+ * @param {Object} state
+ */
+ [types.REQUEST_RELEASES](state) {
+ state.isLoading = true;
+ },
+
+ /**
+ * Sets isLoading to false.
+ * Sets hasError to false.
+ * Sets the received data
+ * @param {Object} state
+ * @param {Object} data
+ */
+ [types.RECEIVE_RELEASES_SUCCESS](state, data) {
+ state.hasError = false;
+ state.isLoading = false;
+ state.releases = data;
+ },
+
+ /**
+ * Sets isLoading to false.
+ * Sets hasError to true.
+ * Resets the data
+ * @param {Object} state
+ * @param {Object} data
+ */
+ [types.RECEIVE_RELEASES_ERROR](state) {
+ state.isLoading = false;
+ state.releases = [];
+ state.hasError = true;
+ },
+};
diff --git a/app/assets/javascripts/releases/store/state.js b/app/assets/javascripts/releases/store/state.js
new file mode 100644
index 00000000000..bf25e651c99
--- /dev/null
+++ b/app/assets/javascripts/releases/store/state.js
@@ -0,0 +1,5 @@
+export default () => ({
+ isLoading: false,
+ hasError: false,
+ releases: [],
+});
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index 225e21ad322..9a0cdc02952 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -79,11 +79,12 @@ Sidebar.prototype.sidebarToggleClicked = function(e, triggered) {
Sidebar.prototype.toggleTodo = function(e) {
var $btnText, $this, $todoLoading, ajaxType, url;
$this = $(e.currentTarget);
- ajaxType = $this.attr('data-delete-path') ? 'delete' : 'post';
- if ($this.attr('data-delete-path')) {
- url = '' + $this.attr('data-delete-path');
+ ajaxType = $this.data('deletePath') ? 'delete' : 'post';
+
+ if ($this.data('deletePath')) {
+ url = '' + $this.data('deletePath');
} else {
- url = '' + $this.data('url');
+ url = '' + $this.data('createPath');
}
$this.tooltip('hide');
@@ -119,14 +120,14 @@ Sidebar.prototype.todoUpdateDone = function(data) {
.removeClass('is-loading')
.enable()
.attr('aria-label', $el.data(`${attrPrefix}Text`))
- .attr('data-delete-path', deletePath)
- .attr('title', $el.data(`${attrPrefix}Text`));
+ .attr('title', $el.data(`${attrPrefix}Text`))
+ .data('deletePath', deletePath);
if ($el.hasClass('has-tooltip')) {
$el.tooltip('_fixTitle');
}
- if ($el.data(`${attrPrefix}Icon`)) {
+ if (typeof $el.data('isCollapsed') !== 'undefined') {
$elText.html($el.data(`${attrPrefix}Icon`));
} else {
$elText.text($el.data(`${attrPrefix}Text`));
diff --git a/app/assets/javascripts/serverless/components/empty_state.vue b/app/assets/javascripts/serverless/components/empty_state.vue
new file mode 100644
index 00000000000..2683805f2f7
--- /dev/null
+++ b/app/assets/javascripts/serverless/components/empty_state.vue
@@ -0,0 +1,40 @@
+<script>
+export default {
+ props: {
+ clustersPath: {
+ type: String,
+ required: true,
+ },
+ helpPath: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="row empty-state js-empty-state">
+ <div class="col-12">
+ <div class="text-content">
+ <h4 class="state-title text-center">
+ {{ s__('Serverless|Getting started with serverless') }}
+ </h4>
+ <p class="state-description">
+ {{
+ s__(`Serverless| In order to start using functions as a service,
+ you must first install Knative on your Kubernetes cluster.`)
+ }}
+
+ <a :href="helpPath"> {{ __('More information') }} </a>
+ </p>
+
+ <div class="text-center">
+ <a :href="clustersPath" class="btn btn-success">
+ {{ s__('Serverless|Install Knative') }}
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/serverless/components/function_row.vue b/app/assets/javascripts/serverless/components/function_row.vue
new file mode 100644
index 00000000000..31f5427c771
--- /dev/null
+++ b/app/assets/javascripts/serverless/components/function_row.vue
@@ -0,0 +1,40 @@
+<script>
+import Timeago from '~/vue_shared/components/time_ago_tooltip.vue';
+
+export default {
+ components: {
+ Timeago,
+ },
+ props: {
+ func: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ name() {
+ return this.func.name;
+ },
+ url() {
+ return this.func.url;
+ },
+ image() {
+ return this.func.image;
+ },
+ timestamp() {
+ return this.func.created_at;
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-responsive-table-row">
+ <div class="table-section section-20">{{ name }}</div>
+ <div class="table-section section-50">
+ <a :href="url">{{ url }}</a>
+ </div>
+ <div class="table-section section-20">{{ image }}</div>
+ <div class="table-section section-10"><timeago :time="timestamp" /></div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/serverless/components/functions.vue b/app/assets/javascripts/serverless/components/functions.vue
new file mode 100644
index 00000000000..349e14670b1
--- /dev/null
+++ b/app/assets/javascripts/serverless/components/functions.vue
@@ -0,0 +1,123 @@
+<script>
+import { GlSkeletonLoading } from '@gitlab/ui';
+import FunctionRow from './function_row.vue';
+import EmptyState from './empty_state.vue';
+
+export default {
+ components: {
+ FunctionRow,
+ EmptyState,
+ GlSkeletonLoading,
+ },
+ props: {
+ functions: {
+ type: Array,
+ required: true,
+ default: () => [],
+ },
+ installed: {
+ type: Boolean,
+ required: true,
+ },
+ clustersPath: {
+ type: String,
+ required: true,
+ },
+ helpPath: {
+ type: String,
+ required: true,
+ },
+ loadingData: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ hasFunctionData: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <section id="serverless-functions">
+ <div v-if="installed">
+ <div v-if="hasFunctionData">
+ <div class="ci-table js-services-list function-element">
+ <div class="gl-responsive-table-row table-row-header" role="row">
+ <div class="table-section section-20" role="rowheader">
+ {{ s__('Serverless|Function') }}
+ </div>
+ <div class="table-section section-50" role="rowheader">
+ {{ s__('Serverless|Domain') }}
+ </div>
+ <div class="table-section section-20" role="rowheader">
+ {{ s__('Serverless|Runtime') }}
+ </div>
+ <div class="table-section section-10" role="rowheader">
+ {{ s__('Serverless|Last Update') }}
+ </div>
+ </div>
+ <template v-if="loadingData">
+ <div v-for="j in 3" :key="j" class="gl-responsive-table-row">
+ <gl-skeleton-loading />
+ </div>
+ </template>
+ <template v-else>
+ <function-row v-for="f in functions" :key="f.name" :func="f" />
+ </template>
+ </div>
+ </div>
+ <div v-else class="empty-state js-empty-state">
+ <div class="text-content">
+ <h4 class="state-title text-center">{{ s__('Serverless|No functions available') }}</h4>
+ <p class="state-description">
+ {{
+ s__(`Serverless|There is currently no function data available from Knative.
+ This could be for a variety of reasons including:`)
+ }}
+ </p>
+ <ul>
+ <li>Your repository does not have a corresponding <code>serverless.yml</code> file.</li>
+ <li>Your <code>.gitlab-ci.yml</code> file is not properly configured.</li>
+ <li>
+ The functions listed in the <code>serverless.yml</code> file don't match the namespace
+ of your cluster.
+ </li>
+ <li>The deploy job has not finished.</li>
+ </ul>
+
+ <p>
+ {{
+ s__(`Serverless|If you believe none of these apply, please check
+ back later as the function data may be in the process of becoming
+ available.`)
+ }}
+ </p>
+ <div class="text-center">
+ <a :href="helpPath" class="btn btn-success">
+ {{ s__('Serverless|Learn more about Serverless') }}
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <empty-state v-else :clusters-path="clustersPath" :help-path="helpPath" />
+ </section>
+</template>
+
+<style>
+.top-area {
+ border-bottom: 0;
+}
+
+.function-element {
+ border-bottom: 1px solid #e5e5e5;
+ border-bottom-color: rgb(229, 229, 229);
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+}
+</style>
diff --git a/app/assets/javascripts/serverless/event_hub.js b/app/assets/javascripts/serverless/event_hub.js
new file mode 100644
index 00000000000..0948c2e5352
--- /dev/null
+++ b/app/assets/javascripts/serverless/event_hub.js
@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();
diff --git a/app/assets/javascripts/serverless/serverless_bundle.js b/app/assets/javascripts/serverless/serverless_bundle.js
new file mode 100644
index 00000000000..3e3b81ba247
--- /dev/null
+++ b/app/assets/javascripts/serverless/serverless_bundle.js
@@ -0,0 +1,106 @@
+import Visibility from 'visibilityjs';
+import Vue from 'vue';
+import { s__ } from '../locale';
+import Flash from '../flash';
+import Poll from '../lib/utils/poll';
+import ServerlessStore from './stores/serverless_store';
+import GetFunctionsService from './services/get_functions_service';
+import Functions from './components/functions.vue';
+
+export default class Serverless {
+ constructor() {
+ const { statusPath, clustersPath, helpPath, installed } = document.querySelector(
+ '.js-serverless-functions-page',
+ ).dataset;
+
+ this.service = new GetFunctionsService(statusPath);
+ this.knativeInstalled = installed !== undefined;
+ this.store = new ServerlessStore(this.knativeInstalled, clustersPath, helpPath);
+ this.initServerless();
+ this.functionLoadCount = 0;
+
+ if (statusPath && this.knativeInstalled) {
+ this.initPolling();
+ }
+ }
+
+ initServerless() {
+ const { store } = this;
+ const el = document.querySelector('#js-serverless-functions');
+
+ this.functions = new Vue({
+ el,
+ data() {
+ return {
+ state: store.state,
+ };
+ },
+ render(createElement) {
+ return createElement(Functions, {
+ props: {
+ functions: this.state.functions,
+ installed: this.state.installed,
+ clustersPath: this.state.clustersPath,
+ helpPath: this.state.helpPath,
+ loadingData: this.state.loadingData,
+ hasFunctionData: this.state.hasFunctionData,
+ },
+ });
+ },
+ });
+ }
+
+ initPolling() {
+ this.poll = new Poll({
+ resource: this.service,
+ method: 'fetchData',
+ successCallback: data => this.handleSuccess(data),
+ errorCallback: () => this.handleError(),
+ });
+
+ if (!Visibility.hidden()) {
+ this.poll.makeRequest();
+ } else {
+ this.service
+ .fetchData()
+ .then(data => this.handleSuccess(data))
+ .catch(() => this.handleError());
+ }
+
+ Visibility.change(() => {
+ if (!Visibility.hidden() && !this.destroyed) {
+ this.poll.restart();
+ } else {
+ this.poll.stop();
+ }
+ });
+ }
+
+ handleSuccess(data) {
+ if (data.status === 200) {
+ this.store.updateFunctionsFromServer(data.data);
+ this.store.updateLoadingState(false);
+ } else if (data.status === 204) {
+ /* Time out after 3 attempts to retrieve data */
+ this.functionLoadCount += 1;
+ if (this.functionLoadCount === 3) {
+ this.poll.stop();
+ this.store.toggleNoFunctionData();
+ }
+ }
+ }
+
+ static handleError() {
+ Flash(s__('Serverless|An error occurred while retrieving serverless components'));
+ }
+
+ destroy() {
+ this.destroyed = true;
+
+ if (this.poll) {
+ this.poll.stop();
+ }
+
+ this.functions.$destroy();
+ }
+}
diff --git a/app/assets/javascripts/serverless/services/get_functions_service.js b/app/assets/javascripts/serverless/services/get_functions_service.js
new file mode 100644
index 00000000000..303b42dc66c
--- /dev/null
+++ b/app/assets/javascripts/serverless/services/get_functions_service.js
@@ -0,0 +1,11 @@
+import axios from '~/lib/utils/axios_utils';
+
+export default class GetFunctionsService {
+ constructor(endpoint) {
+ this.endpoint = endpoint;
+ }
+
+ fetchData() {
+ return axios.get(this.endpoint);
+ }
+}
diff --git a/app/assets/javascripts/serverless/stores/serverless_store.js b/app/assets/javascripts/serverless/stores/serverless_store.js
new file mode 100644
index 00000000000..774c15b5b12
--- /dev/null
+++ b/app/assets/javascripts/serverless/stores/serverless_store.js
@@ -0,0 +1,24 @@
+export default class ServerlessStore {
+ constructor(knativeInstalled = false, clustersPath, helpPath) {
+ this.state = {
+ functions: [],
+ hasFunctionData: true,
+ loadingData: true,
+ installed: knativeInstalled,
+ clustersPath,
+ helpPath,
+ };
+ }
+
+ updateFunctionsFromServer(functions = []) {
+ this.state.functions = functions;
+ }
+
+ updateLoadingState(loadingData) {
+ this.state.loadingData = loadingData;
+ }
+
+ toggleNoFunctionData() {
+ this.state.hasFunctionData = false;
+ }
+}
diff --git a/app/assets/javascripts/sidebar/stores/sidebar_store.js b/app/assets/javascripts/sidebar/stores/sidebar_store.js
index f20cc6d8cca..7b8b4c5d856 100644
--- a/app/assets/javascripts/sidebar/stores/sidebar_store.js
+++ b/app/assets/javascripts/sidebar/stores/sidebar_store.js
@@ -71,7 +71,7 @@ export default class SidebarStore {
}
findAssignee(findAssignee) {
- return this.assignees.filter(assignee => assignee.id === findAssignee.id)[0];
+ return this.assignees.find(assignee => assignee.id === findAssignee.id);
}
removeAssignee(removeAssignee) {
diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js
index 9af5d5b23cb..7404dfbf22a 100644
--- a/app/assets/javascripts/star.js
+++ b/app/assets/javascripts/star.js
@@ -5,11 +5,12 @@ import { spriteIcon } from './lib/utils/common_utils';
import axios from './lib/utils/axios_utils';
export default class Star {
- constructor() {
- $('.project-home-panel .toggle-star').on('click', function toggleStarClickCallback() {
+ constructor(container = '.project-home-panel') {
+ $(`${container} .toggle-star`).on('click', function toggleStarClickCallback() {
const $this = $(this);
const $starSpan = $this.find('span');
- const $startIcon = $this.find('svg');
+ const $starIcon = $this.find('svg');
+ const iconClasses = $starIcon.attr('class').split(' ');
axios
.post($this.data('endpoint'))
@@ -22,12 +23,12 @@ export default class Star {
if (isStarred) {
$starSpan.removeClass('starred').text(s__('StarProject|Star'));
- $startIcon.remove();
- $this.prepend(spriteIcon('star-o', 'icon'));
+ $starIcon.remove();
+ $this.prepend(spriteIcon('star-o', iconClasses));
} else {
$starSpan.addClass('starred').text(__('Unstar'));
- $startIcon.remove();
- $this.prepend(spriteIcon('star', 'icon'));
+ $starIcon.remove();
+ $this.prepend(spriteIcon('star', iconClasses));
}
})
.catch(() => Flash('Star toggle failed. Try again later.'));
diff --git a/app/assets/javascripts/terminal/index.js b/app/assets/javascripts/terminal/index.js
index 49aeb377c74..8faff59fd45 100644
--- a/app/assets/javascripts/terminal/index.js
+++ b/app/assets/javascripts/terminal/index.js
@@ -1,3 +1,3 @@
import Terminal from './terminal';
-export default () => new Terminal({ selector: '#terminal' });
+export default () => new Terminal(document.getElementById('terminal'));
diff --git a/app/assets/javascripts/terminal/terminal.js b/app/assets/javascripts/terminal/terminal.js
index b24aa8a3a34..560f50ebf8f 100644
--- a/app/assets/javascripts/terminal/terminal.js
+++ b/app/assets/javascripts/terminal/terminal.js
@@ -1,9 +1,15 @@
+import _ from 'underscore';
import $ from 'jquery';
import { Terminal } from 'xterm';
import * as fit from 'xterm/lib/addons/fit/fit';
+import { canScrollUp, canScrollDown } from '~/lib/utils/dom_utils';
+
+const SCROLL_MARGIN = 5;
+
+Terminal.applyAddon(fit);
export default class GLTerminal {
- constructor(options = {}) {
+ constructor(element, options = {}) {
this.options = Object.assign(
{},
{
@@ -13,7 +19,8 @@ export default class GLTerminal {
options,
);
- this.container = document.querySelector(options.selector);
+ this.container = element;
+ this.onDispose = [];
this.setSocketUrl();
this.createTerminal();
@@ -34,8 +41,6 @@ export default class GLTerminal {
}
createTerminal() {
- Terminal.applyAddon(fit);
-
this.terminal = new Terminal(this.options);
this.socket = new WebSocket(this.socketUrl, ['terminal.gitlab.com']);
@@ -72,4 +77,48 @@ export default class GLTerminal {
handleSocketFailure() {
this.terminal.write('\r\nConnection failure');
}
+
+ addScrollListener(onScrollLimit) {
+ const viewport = this.container.querySelector('.xterm-viewport');
+ const listener = _.throttle(() => {
+ onScrollLimit({
+ canScrollUp: canScrollUp(viewport, SCROLL_MARGIN),
+ canScrollDown: canScrollDown(viewport, SCROLL_MARGIN),
+ });
+ });
+
+ this.onDispose.push(() => viewport.removeEventListener('scroll', listener));
+ viewport.addEventListener('scroll', listener);
+
+ // don't forget to initialize value before scroll!
+ listener({ target: viewport });
+ }
+
+ disable() {
+ this.terminal.setOption('cursorBlink', false);
+ this.terminal.setOption('theme', { foreground: '#707070' });
+ this.terminal.setOption('disableStdin', true);
+ this.socket.close();
+ }
+
+ dispose() {
+ this.terminal.off('data');
+ this.terminal.dispose();
+ this.socket.close();
+
+ this.onDispose.forEach(fn => fn());
+ this.onDispose.length = 0;
+ }
+
+ scrollToTop() {
+ this.terminal.scrollToTop();
+ }
+
+ scrollToBottom() {
+ this.terminal.scrollToBottom();
+ }
+
+ fit() {
+ this.terminal.fit();
+ }
}
diff --git a/app/assets/javascripts/user_popovers.js b/app/assets/javascripts/user_popovers.js
new file mode 100644
index 00000000000..948f4d5e631
--- /dev/null
+++ b/app/assets/javascripts/user_popovers.js
@@ -0,0 +1,107 @@
+import Vue from 'vue';
+
+import UsersCache from './lib/utils/users_cache';
+import UserPopover from './vue_shared/components/user_popover/user_popover.vue';
+
+let renderedPopover;
+let renderFn;
+
+const handleUserPopoverMouseOut = event => {
+ const { target } = event;
+ target.removeEventListener('mouseleave', handleUserPopoverMouseOut);
+
+ if (renderFn) {
+ clearTimeout(renderFn);
+ }
+ if (renderedPopover) {
+ renderedPopover.$destroy();
+ renderedPopover = null;
+ }
+};
+
+/**
+ * Adds a UserPopover component to the body, hands over as much data as the target element has in data attributes.
+ * loads based on data-user-id more data about a user from the API and sets it on the popover
+ */
+const handleUserPopoverMouseOver = event => {
+ const { target } = event;
+ // Add listener to actually remove it again
+ target.addEventListener('mouseleave', handleUserPopoverMouseOut);
+
+ renderFn = setTimeout(() => {
+ // Helps us to use current markdown setup without maybe breaking or duplicating for now
+ if (target.dataset.user) {
+ target.dataset.userId = target.dataset.user;
+ // Removing titles so its not showing tooltips also
+ target.dataset.originalTitle = '';
+ target.setAttribute('title', '');
+ }
+
+ const { userId, username, name, avatarUrl } = target.dataset;
+ const user = {
+ userId,
+ username,
+ name,
+ avatarUrl,
+ location: null,
+ bio: null,
+ organization: null,
+ status: null,
+ loaded: false,
+ };
+ if (userId || username) {
+ const UserPopoverComponent = Vue.extend(UserPopover);
+ renderedPopover = new UserPopoverComponent({
+ propsData: {
+ target,
+ user,
+ },
+ });
+
+ renderedPopover.$mount();
+
+ UsersCache.retrieveById(userId)
+ .then(userData => {
+ if (!userData) {
+ return;
+ }
+
+ Object.assign(user, {
+ avatarUrl: userData.avatar_url,
+ username: userData.username,
+ name: userData.name,
+ location: userData.location,
+ bio: userData.bio,
+ organization: userData.organization,
+ loaded: true,
+ });
+
+ UsersCache.retrieveStatusById(userId)
+ .then(status => {
+ if (!status) {
+ return;
+ }
+
+ Object.assign(user, {
+ status,
+ });
+ })
+ .catch(() => {
+ throw new Error(`User status for "${userId}" could not be retrieved!`);
+ });
+ })
+ .catch(() => {
+ renderedPopover.$destroy();
+ renderedPopover = null;
+ });
+ }
+ }, 200); // 200ms delay so not every mouseover triggers Popover + API Call
+};
+
+export default elements => {
+ const userLinks = elements || [...document.querySelectorAll('.js-user-link')];
+
+ userLinks.forEach(el => {
+ el.addEventListener('mouseenter', handleUserPopoverMouseOver);
+ });
+};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue
index e3adc7f7af5..4b57693e8f1 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue
@@ -13,5 +13,7 @@ export default {
</script>
<template>
- <div class="circle-icon-container append-right-default"><icon :name="name" /></div>
+ <div class="circle-icon-container append-right-default align-self-start align-self-lg-center">
+ <icon :name="name" />
+ </div>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
index adfbcd18588..0bcccc50eb2 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
@@ -72,7 +72,7 @@ export default {
Flash('Something went wrong. Please try again.');
}
- eventHub.$emit('MRWidgetUpdateRequested');
+ eventHub.$emit('MRWidgetRebaseSuccess');
stopPolling();
}
})
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 3c3e3efcc36..d8a75388e84 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -155,13 +155,13 @@ export default {
};
return new MRWidgetService(endpoints);
},
- checkStatus(cb) {
+ checkStatus(cb, isRebased) {
return this.service
.checkStatus()
.then(res => res.data)
.then(data => {
this.handleNotification(data);
- this.mr.setData(data);
+ this.mr.setData(data, isRebased);
this.setFaviconHelper();
if (cb) {
@@ -263,6 +263,10 @@ export default {
this.checkStatus(cb);
});
+ eventHub.$on('MRWidgetRebaseSuccess', cb => {
+ this.checkStatus(cb, true);
+ });
+
// `params` should be an Array contains a Boolean, like `[true]`
// Passing parameter as Boolean didn't work.
eventHub.$on('SetBranchRemoveFlag', params => {
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js b/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js
index f7f0c1b6cb7..066a3b833d7 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js
@@ -19,7 +19,7 @@ export default function deviseState(data) {
return stateKey.unresolvedDiscussions;
} else if (this.isPipelineBlocked) {
return stateKey.pipelineBlocked;
- } else if (this.hasSHAChanged) {
+ } else if (this.isSHAMismatch) {
return stateKey.shaMismatch;
} else if (this.mergeWhenPipelineSucceeds) {
return this.mergeError ? stateKey.autoMergeFailed : stateKey.mergeWhenPipelineSucceeds;
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 5c9a7133a6e..c777bcca0fa 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
@@ -11,7 +11,11 @@ export default class MergeRequestStore {
this.setData(data);
}
- setData(data) {
+ setData(data, isRebased) {
+ if (isRebased) {
+ this.sha = data.diff_head_sha;
+ }
+
const currentUser = data.current_user;
const pipelineStatus = data.pipeline ? data.pipeline.details.status : null;
@@ -84,7 +88,7 @@ export default class MergeRequestStore {
this.canMerge = !!data.merge_path;
this.canCreateIssue = currentUser.can_create_issue || false;
this.canCancelAutomaticMerge = !!data.cancel_merge_when_pipeline_succeeds_path;
- this.hasSHAChanged = this.sha !== data.diff_head_sha;
+ this.isSHAMismatch = this.sha !== data.diff_head_sha;
this.canBeMerged = data.can_be_merged || false;
this.isMergeAllowed = data.mergeable || false;
this.mergeOngoing = data.merge_ongoing;
diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue
index bb2e0e12c11..75c66ed850b 100644
--- a/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue
@@ -1,7 +1,10 @@
<script>
+import { diffModes } from '~/ide/constants';
import { viewerInformationForPath } from '../content_viewer/lib/viewer_utils';
import ImageDiffViewer from './viewers/image_diff_viewer.vue';
import DownloadDiffViewer from './viewers/download_diff_viewer.vue';
+import RenamedFile from './viewers/renamed.vue';
+import ModeChanged from './viewers/mode_changed.vue';
export default {
props: {
@@ -30,9 +33,25 @@ export default {
required: false,
default: '',
},
+ aMode: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ bMode: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
computed: {
viewer() {
+ if (this.diffMode === diffModes.renamed) {
+ return RenamedFile;
+ } else if (this.diffMode === diffModes.mode_changed) {
+ return ModeChanged;
+ }
+
if (!this.newPath) return null;
const previewInfo = viewerInformationForPath(this.newPath);
@@ -67,8 +86,10 @@ export default {
:new-path="fullNewPath"
:old-path="fullOldPath"
:project-path="projectPath"
+ :a-mode="aMode"
+ :b-mode="bMode"
>
- <slot slot="image-overlay" name="image-overlay"> </slot>
+ <slot slot="image-overlay" name="image-overlay"></slot>
</component>
<slot></slot>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/empty_file.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/empty_file.vue
new file mode 100644
index 00000000000..53210cbcc93
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/empty_file.vue
@@ -0,0 +1,3 @@
+<template>
+ <div class="nothing-here-block">{{ __('Empty file') }}</div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/mode_changed.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/mode_changed.vue
new file mode 100644
index 00000000000..3c7a4ea6183
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/mode_changed.vue
@@ -0,0 +1,30 @@
+<script>
+import { sprintf, __ } from '~/locale';
+
+export default {
+ props: {
+ aMode: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ bMode: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ computed: {
+ outputText() {
+ return sprintf(__('File mode changed from %{a_mode} to %{b_mode}'), {
+ a_mode: this.aMode,
+ b_mode: this.bMode,
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="nothing-here-block">{{ outputText }}</div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/renamed.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/renamed.vue
new file mode 100644
index 00000000000..5c1ea59b471
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/renamed.vue
@@ -0,0 +1,3 @@
+<template>
+ <div class="nothing-here-block">{{ __('File moved') }}</div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue b/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue
new file mode 100644
index 00000000000..7e79e63aa1e
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue
@@ -0,0 +1,94 @@
+<script>
+import { GlTooltipDirective } from '@gitlab/ui';
+import { __, sprintf } from '~/locale';
+
+import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
+
+export default {
+ components: {
+ UserAvatarLink,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ assignees: {
+ type: Array,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ maxVisibleAssignees: 2,
+ maxAssigneeAvatars: 3,
+ maxAssignees: 99,
+ };
+ },
+ computed: {
+ countOverLimit() {
+ return this.assignees.length - this.maxVisibleAssignees;
+ },
+ assigneesToShow() {
+ if (this.assignees.length > this.maxAssigneeAvatars) {
+ return this.assignees.slice(0, this.maxVisibleAssignees);
+ }
+ return this.assignees;
+ },
+ assigneesCounterTooltip() {
+ const { countOverLimit, maxAssignees } = this;
+ const count = countOverLimit > maxAssignees ? maxAssignees : countOverLimit;
+
+ return sprintf(__('%{count} more assignees'), { count });
+ },
+ shouldRenderAssigneesCounter() {
+ const assigneesCount = this.assignees.length;
+ if (assigneesCount <= this.maxAssigneeAvatars) {
+ return false;
+ }
+
+ return assigneesCount > this.countOverLimit;
+ },
+ assigneeCounterLabel() {
+ if (this.countOverLimit > this.maxAssignees) {
+ return `${this.maxAssignees}+`;
+ }
+
+ return `+${this.countOverLimit}`;
+ },
+ },
+ methods: {
+ avatarUrlTitle(assignee) {
+ return sprintf(__('Avatar for %{assigneeName}'), {
+ assigneeName: assignee.name,
+ });
+ },
+ },
+};
+</script>
+<template>
+ <div class="issue-assignees">
+ <user-avatar-link
+ v-for="assignee in assigneesToShow"
+ :key="assignee.id"
+ :link-href="assignee.web_url"
+ :img-alt="avatarUrlTitle(assignee)"
+ :img-src="assignee.avatar_url"
+ :img-size="24"
+ class="js-no-trigger"
+ tooltip-placement="bottom"
+ >
+ <span class="js-assignee-tooltip">
+ <span class="bold d-block">{{ __('Assignee') }}</span> {{ assignee.name }}
+ <span class="text-white-50">@{{ assignee.username }}</span>
+ </span>
+ </user-avatar-link>
+ <span
+ v-if="shouldRenderAssigneesCounter"
+ v-gl-tooltip
+ :title="assigneesCounterTooltip"
+ class="avatar-counter"
+ data-placement="bottom"
+ >{{ assigneeCounterLabel }}</span
+ >
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue b/app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue
new file mode 100644
index 00000000000..d5d967e25bf
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue
@@ -0,0 +1,90 @@
+<script>
+import { GlTooltip } from '@gitlab/ui';
+import { __, sprintf } from '~/locale';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+import { timeFor, parsePikadayDate, dateInWords } from '~/lib/utils/datetime_utility';
+import Icon from '~/vue_shared/components/icon.vue';
+
+export default {
+ components: {
+ Icon,
+ GlTooltip,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ milestone: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ milestoneDue: this.milestone.due_date ? parsePikadayDate(this.milestone.due_date) : null,
+ milestoneStart: this.milestone.start_date
+ ? parsePikadayDate(this.milestone.start_date)
+ : null,
+ };
+ },
+ computed: {
+ isMilestoneStarted() {
+ if (!this.milestoneStart) {
+ return false;
+ }
+ return Date.now() > this.milestoneStart;
+ },
+ isMilestonePastDue() {
+ if (!this.milestoneDue) {
+ return false;
+ }
+ return Date.now() > this.milestoneDue;
+ },
+ milestoneDatesAbsolute() {
+ if (this.milestoneDue) {
+ return `(${dateInWords(this.milestoneDue)})`;
+ } else if (this.milestoneStart) {
+ return `(${dateInWords(this.milestoneStart)})`;
+ }
+ return '';
+ },
+ milestoneDatesHuman() {
+ if (this.milestoneStart || this.milestoneDue) {
+ if (this.milestoneDue) {
+ return timeFor(
+ this.milestoneDue,
+ sprintf(__('Expired %{expiredOn}'), {
+ expiredOn: this.timeFormated(this.milestoneDue),
+ }),
+ );
+ }
+
+ return sprintf(
+ this.isMilestoneStarted ? __('Started %{startsIn}') : __('Starts %{startsIn}'),
+ {
+ startsIn: this.timeFormated(this.milestoneStart),
+ },
+ );
+ }
+ return '';
+ },
+ },
+};
+</script>
+<template>
+ <div ref="milestoneDetails" class="issue-milestone-details">
+ <icon :size="16" class="inline icon" name="clock" />
+ <span class="milestone-title">{{ milestone.title }}</span>
+ <gl-tooltip :target="() => $refs.milestoneDetails" placement="bottom" class="js-item-milestone">
+ <span class="bold">{{ __('Milestone') }}</span> <br />
+ <span>{{ milestone.title }}</span> <br />
+ <span
+ v-if="milestoneStart || milestoneDue"
+ :class="{
+ 'text-danger-muted': isMilestonePastDue,
+ 'text-tertiary': !isMilestonePastDue,
+ }"
+ ><span>{{ milestoneDatesHuman }}</span
+ ><br /><span>{{ milestoneDatesAbsolute }}</span>
+ </span>
+ </gl-tooltip>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 21d6519191f..2f7ed4a982c 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -1,17 +1,21 @@
<script>
import $ from 'jquery';
-import { s__ } from '~/locale';
+import _ from 'underscore';
+import { __ } from '~/locale';
+import { stripHtml } from '~/lib/utils/text_utility';
import Flash from '../../../flash';
import GLForm from '../../../gl_form';
import markdownHeader from './header.vue';
import markdownToolbar from './toolbar.vue';
import icon from '../icon.vue';
+import Suggestions from '~/vue_shared/components/markdown/suggestions.vue';
export default {
components: {
markdownHeader,
markdownToolbar,
icon,
+ Suggestions,
},
props: {
markdownPreviewPath: {
@@ -48,12 +52,33 @@ export default {
required: false,
default: true,
},
+ line: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ note: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ canSuggest: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ helpPagePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
data() {
return {
markdownPreview: '',
referencedCommands: '',
referencedUsers: '',
+ hasSuggestion: false,
markdownPreviewLoading: false,
previewMarkdown: false,
};
@@ -63,6 +88,39 @@ export default {
const referencedUsersThreshold = 10;
return this.referencedUsers.length >= referencedUsersThreshold;
},
+ lineContent() {
+ const FIRST_CHAR_REGEX = /^(\+|-)/;
+ const [firstSuggestion] = this.suggestions;
+ if (firstSuggestion) {
+ return firstSuggestion.from_content;
+ }
+
+ if (this.line) {
+ const { rich_text: richText, text } = this.line;
+
+ if (text) {
+ return text.replace(FIRST_CHAR_REGEX, '');
+ }
+
+ return _.unescape(stripHtml(richText).replace(/\n/g, ''));
+ }
+
+ return '';
+ },
+ lineNumber() {
+ let lineNumber;
+ if (this.line) {
+ const { new_line: newLine, old_line: oldLine } = this.line;
+ lineNumber = newLine || oldLine;
+ }
+ return lineNumber;
+ },
+ suggestions() {
+ return this.note.suggestions || [];
+ },
+ lineType() {
+ return this.line ? this.line.type : '';
+ },
},
mounted() {
/*
@@ -99,11 +157,12 @@ export default {
if (text) {
this.markdownPreviewLoading = true;
+ this.markdownPreview = __('Loading…');
this.$http
.post(this.versionedPreviewPath(), { text })
.then(resp => resp.json())
.then(data => this.renderMarkdown(data))
- .catch(() => new Flash(s__('Error loading markdown preview')));
+ .catch(() => new Flash(__('Error loading markdown preview')));
} else {
this.renderMarkdown();
}
@@ -121,6 +180,7 @@ export default {
if (data.references) {
this.referencedCommands = data.references.commands;
this.referencedUsers = data.references.users;
+ this.hasSuggestion = data.references.suggestions && data.references.suggestions.length;
}
this.$nextTick(() => {
@@ -146,6 +206,8 @@ export default {
>
<markdown-header
:preview-markdown="previewMarkdown"
+ :line-content="lineContent"
+ :can-suggest="canSuggest"
@preview-markdown="showPreviewTab"
@write-markdown="showWriteTab"
/>
@@ -162,17 +224,39 @@ export default {
/>
</div>
</div>
- <div v-show="previewMarkdown" class="md md-preview-holder md-preview js-vue-md-preview">
- <div ref="markdown-preview" v-html="markdownPreview"></div>
- <span v-if="markdownPreviewLoading"> Loading... </span>
- </div>
+ <template v-if="hasSuggestion">
+ <div
+ v-show="previewMarkdown"
+ ref="markdown-preview"
+ class="md-preview js-vue-md-preview md md-preview-holder"
+ >
+ <suggestions
+ v-if="hasSuggestion"
+ :note-html="markdownPreview"
+ :from-line="lineNumber"
+ :from-content="lineContent"
+ :line-type="lineType"
+ :disabled="true"
+ :suggestions="suggestions"
+ :help-page-path="helpPagePath"
+ />
+ </div>
+ </template>
+ <template v-else>
+ <div
+ v-show="previewMarkdown"
+ ref="markdown-preview"
+ class="md-preview js-vue-md-preview md md-preview-holder"
+ v-html="markdownPreview"
+ ></div>
+ </template>
<template v-if="previewMarkdown && !markdownPreviewLoading">
<div v-if="referencedCommands" class="referenced-commands" v-html="referencedCommands"></div>
<div v-if="shouldShowReferencedUsers" class="referenced-users">
<span>
- <i class="fa fa-exclamation-triangle" aria-hidden="true"> </i> You are about to add
+ <i class="fa fa-exclamation-triangle" aria-hidden="true"></i> You are about to add
<strong>
- <span class="js-referenced-users-count"> {{ referencedUsers.length }} </span>
+ <span class="js-referenced-users-count">{{ referencedUsers.length }}</span>
</strong>
people to the discussion. Proceed with caution.
</span>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index 4c4ba537065..bf4d42670ee 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -17,6 +17,16 @@ export default {
type: Boolean,
required: true,
},
+ lineContent: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ canSuggest: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
},
computed: {
mdTable() {
@@ -27,6 +37,9 @@ export default {
'| cell | cell |',
].join('\n');
},
+ mdSuggestion() {
+ return ['```suggestion', `{text}`, '```'].join('\n');
+ },
},
mounted() {
$(document).on('markdown-preview:show.vue', this.previewMarkdownTab);
@@ -119,6 +132,16 @@ export default {
:button-title="__('Add a table')"
icon="table"
/>
+ <toolbar-button
+ v-if="canSuggest"
+ :tag="mdSuggestion"
+ :prepend="true"
+ :button-title="__('Insert suggestion')"
+ :cursor-offset="4"
+ :tag-content="lineContent"
+ icon="doc-code"
+ class="qa-suggestion-btn"
+ />
<button
v-gl-tooltip
aria-label="Go full screen"
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
new file mode 100644
index 00000000000..f98560f7336
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
@@ -0,0 +1,74 @@
+<script>
+import SuggestionDiffHeader from './suggestion_diff_header.vue';
+
+export default {
+ components: {
+ SuggestionDiffHeader,
+ },
+ props: {
+ newLines: {
+ type: Array,
+ required: true,
+ },
+ fromContent: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ fromLine: {
+ type: Number,
+ required: true,
+ },
+ suggestion: {
+ type: Object,
+ required: true,
+ },
+ disabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ helpPagePath: {
+ type: String,
+ required: true,
+ },
+ },
+ methods: {
+ applySuggestion(callback) {
+ this.$emit('apply', { suggestionId: this.suggestion.id, callback });
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <suggestion-diff-header
+ class="qa-suggestion-diff-header"
+ :can-apply="suggestion.appliable && suggestion.current_user.can_apply && !disabled"
+ :is-applied="suggestion.applied"
+ :help-page-path="helpPagePath"
+ @apply="applySuggestion"
+ />
+ <table class="mb-3 md-suggestion-diff">
+ <tbody>
+ <!-- Old Line -->
+ <tr class="line_holder old">
+ <td class="diff-line-num old_line qa-old-diff-line-number old">{{ fromLine }}</td>
+ <td class="diff-line-num new_line old"></td>
+ <td class="line_content old">
+ <span>{{ fromContent }}</span>
+ </td>
+ </tr>
+ <!-- New Line(s) -->
+ <tr v-for="(line, key) of newLines" :key="key" class="line_holder new">
+ <td class="diff-line-num old_line new"></td>
+ <td class="diff-line-num new_line qa-new-diff-line-number new">{{ line.lineNumber }}</td>
+ <td class="line_content new">
+ <span>{{ line.content }}</span>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
new file mode 100644
index 00000000000..563e2f94fcc
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
@@ -0,0 +1,60 @@
+<script>
+import Icon from '~/vue_shared/components/icon.vue';
+
+export default {
+ components: { Icon },
+ props: {
+ canApply: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ isApplied: {
+ type: Boolean,
+ required: true,
+ default: false,
+ },
+ helpPagePath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ isAppliedSuccessfully: false,
+ isApplying: false,
+ };
+ },
+ methods: {
+ applySuggestion() {
+ if (!this.canApply) return;
+ this.isApplying = true;
+ this.$emit('apply', this.applySuggestionCallback);
+ },
+ applySuggestionCallback() {
+ this.isApplying = false;
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="md-suggestion-header border-bottom-0 mt-2">
+ <div class="qa-suggestion-diff-header font-weight-bold">
+ {{ __('Suggested change') }}
+ <a v-if="helpPagePath" :href="helpPagePath" :aria-label="__('Help')">
+ <icon name="question-o" css-classes="link-highlight" />
+ </a>
+ </div>
+ <span v-if="isApplied" class="badge badge-success">{{ __('Applied') }}</span>
+ <button
+ v-if="canApply"
+ type="button"
+ class="btn qa-apply-btn"
+ :disabled="isApplying"
+ @click="applySuggestion"
+ >
+ {{ __('Apply suggestion') }}
+ </button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
new file mode 100644
index 00000000000..7c6dbee3e19
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
@@ -0,0 +1,136 @@
+<script>
+import Vue from 'vue';
+import SuggestionDiff from './suggestion_diff.vue';
+import Flash from '~/flash';
+
+export default {
+ components: { SuggestionDiff },
+ props: {
+ fromLine: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ fromContent: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ lineType: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ suggestions: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ noteHtml: {
+ type: String,
+ required: true,
+ },
+ disabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ helpPagePath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ isRendered: false,
+ };
+ },
+ watch: {
+ suggestions() {
+ this.reset();
+ },
+ noteHtml() {
+ this.reset();
+ },
+ },
+ mounted() {
+ this.renderSuggestions();
+ },
+ methods: {
+ renderSuggestions() {
+ // swaps out suggestion(s) markdown with rich diff components
+ // (while still keeping non-suggestion markdown in place)
+
+ if (!this.noteHtml) return;
+ const { container } = this.$refs;
+ const suggestionElements = container.querySelectorAll('.js-render-suggestion');
+
+ if (this.lineType === 'old') {
+ Flash('Unable to apply suggestions to a deleted line.', 'alert', this.$el);
+ }
+
+ suggestionElements.forEach((suggestionEl, i) => {
+ const suggestionParentEl = suggestionEl.parentElement;
+ const newLines = this.extractNewLines(suggestionParentEl);
+ const diffComponent = this.generateDiff(newLines, i);
+ diffComponent.$mount(suggestionParentEl);
+ });
+
+ this.isRendered = true;
+ },
+ extractNewLines(suggestionEl) {
+ // extracts the suggested lines from the markdown
+ // calculates a line number for each line
+
+ const FIRST_CHAR_REGEX = /^(\+|-)/;
+ const newLines = suggestionEl.querySelectorAll('.line');
+ const fromLine = this.suggestions.length ? this.suggestions[0].from_line : this.fromLine;
+ const lines = [];
+
+ newLines.forEach((line, i) => {
+ const content = `${line.innerText.replace(FIRST_CHAR_REGEX, '')}\n`;
+ const lineNumber = fromLine + i;
+ lines.push({ content, lineNumber });
+ });
+
+ return lines;
+ },
+ generateDiff(newLines, suggestionIndex) {
+ // generates the diff <suggestion-diff /> component
+ // all `suggestion` markdown will be swapped out by this component
+
+ const { suggestions, disabled, helpPagePath } = this;
+ const suggestion =
+ suggestions && suggestions[suggestionIndex] ? suggestions[suggestionIndex] : {};
+ const fromContent = suggestion.from_content || this.fromContent;
+ const fromLine = suggestion.from_line || this.fromLine;
+ const SuggestionDiffComponent = Vue.extend(SuggestionDiff);
+ const suggestionDiff = new SuggestionDiffComponent({
+ propsData: { newLines, fromLine, fromContent, disabled, suggestion, helpPagePath },
+ });
+
+ suggestionDiff.$on('apply', ({ suggestionId, callback }) => {
+ this.$emit('apply', { suggestionId, callback, flashContainer: this.$el });
+ });
+
+ return suggestionDiff;
+ },
+ reset() {
+ // resets the container HTML (replaces it with the updated noteHTML)
+ // calls `renderSuggestions` once the updated noteHTML is added to the DOM
+
+ this.$refs.container.innerHTML = this.noteHtml;
+ this.isRendered = false;
+ this.renderSuggestions();
+ this.$nextTick(() => this.renderSuggestions());
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="flash-container mt-3"></div>
+ <div v-show="isRendered" ref="container" v-html="noteHtml"></div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
index a6d2cecdf7e..4572caa907b 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
@@ -37,6 +37,16 @@ export default {
required: false,
default: false,
},
+ tagContent: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ cursorOffset: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
},
};
</script>
@@ -45,8 +55,10 @@ export default {
<button
v-gl-tooltip
:data-md-tag="tag"
+ :data-md-cursor-offset="cursorOffset"
:data-md-select="tagSelect"
:data-md-block="tagBlock"
+ :data-md-tag-content="tagContent"
:data-md-prepend="prepend"
:title="buttonTitle"
:aria-label="buttonTitle"
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue
index e742900dbcb..373794fb1f2 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue
@@ -44,6 +44,7 @@ export default {
class="sidebar-collapsed-icon"
data-placement="left"
data-container="body"
+ data-boundary="viewport"
@click="handleClick"
>
<i aria-hidden="true" data-hidden="true" class="fa fa-tags"> </i>
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
index 01b8b94f9e3..e833a8e0483 100644
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue
@@ -67,7 +67,7 @@ export default {
// In both cases we should render the defaultAvatarUrl
sanitizedSource() {
let baseSrc = this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc;
- if (baseSrc.indexOf('?') === -1) baseSrc += `?width=${this.size}`;
+ if (!baseSrc.startsWith('data:') && !baseSrc.includes('?')) baseSrc += `?width=${this.size}`;
return baseSrc;
},
resultantSrcAttribute() {
@@ -97,6 +97,7 @@ export default {
class="avatar"
/>
<gl-tooltip
+ v-if="tooltipText || $slots.default"
:target="() => $refs.userAvatarImage"
:placement="tooltipPlacement"
boundary="window"
diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
new file mode 100644
index 00000000000..d24fe1b547e
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
@@ -0,0 +1,91 @@
+<script>
+import { GlPopover, GlSkeletonLoading } from '@gitlab/ui';
+import UserAvatarImage from '../user_avatar/user_avatar_image.vue';
+import { glEmojiTag } from '../../../emoji';
+
+export default {
+ name: 'UserPopover',
+ components: {
+ GlPopover,
+ GlSkeletonLoading,
+ UserAvatarImage,
+ },
+ props: {
+ target: {
+ type: HTMLAnchorElement,
+ required: true,
+ },
+ user: {
+ type: Object,
+ required: true,
+ default: null,
+ },
+ loaded: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ statusHtml() {
+ if (this.user.status.emoji && this.user.status.message) {
+ return `${glEmojiTag(this.user.status.emoji)} ${this.user.status.message}`;
+ } else if (this.user.status.message) {
+ return this.user.status.message;
+ }
+ return '';
+ },
+ nameIsLoading() {
+ return !this.user.name;
+ },
+ jobInfoIsLoading() {
+ return !this.user.loaded && this.user.organization === null;
+ },
+ locationIsLoading() {
+ return !this.user.loaded && this.user.location === null;
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-popover :target="target" boundary="viewport" placement="top" show>
+ <div class="user-popover d-flex">
+ <div class="p-1 flex-shrink-1">
+ <user-avatar-image :img-src="user.avatarUrl" :size="60" css-classes="mr-2" />
+ </div>
+ <div class="p-1 w-100">
+ <h5 class="m-0">
+ {{ user.name }}
+ <gl-skeleton-loading
+ v-if="nameIsLoading"
+ :lines="1"
+ class="animation-container-small mb-1"
+ />
+ </h5>
+ <div class="text-secondary mb-2">
+ <span v-if="user.username">@{{ user.username }}</span>
+ <gl-skeleton-loading v-else :lines="1" class="animation-container-small mb-1" />
+ </div>
+ <div class="text-secondary">
+ <div v-if="user.bio" class="js-bio">{{ user.bio }}</div>
+ <div v-if="user.organization" class="js-organization">{{ user.organization }}</div>
+ <gl-skeleton-loading
+ v-if="jobInfoIsLoading"
+ :lines="1"
+ class="animation-container-small mb-1"
+ />
+ </div>
+ <div class="text-secondary">
+ {{ user.location }}
+ <gl-skeleton-loading
+ v-if="locationIsLoading"
+ :lines="1"
+ class="animation-container-small mb-1"
+ />
+ </div>
+ <div v-if="user.status" class="mt-2"><span v-html="statusHtml"></span></div>
+ </div>
+ </div>
+ </gl-popover>
+</template>
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index bd1cca69c03..bdf20866197 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -35,6 +35,11 @@
@import "pages/**/*";
/*
+ * Component specific styles, will be moved to gitlab-ui
+ */
+@import "components/**/*";
+
+/*
* Code highlight
*/
@import "highlight/dark";
@@ -42,6 +47,7 @@
@import "highlight/solarized_dark";
@import "highlight/solarized_light";
@import "highlight/white";
+@import "highlight/none";
/*
* Styles for JS behaviors.
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index 62024b8c555..f0671e36130 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -18,8 +18,10 @@ $input-border: $border-color;
$padding-base-vertical: $gl-vert-padding;
$padding-base-horizontal: $gl-padding;
-html {
- // Override default font size used in bs4
+body,
+.form-control,
+.search form {
+ // Override default font size used in non-csslab UI
font-size: 14px;
}
diff --git a/app/assets/stylesheets/components/popover.scss b/app/assets/stylesheets/components/popover.scss
new file mode 100644
index 00000000000..2f4d30fe923
--- /dev/null
+++ b/app/assets/stylesheets/components/popover.scss
@@ -0,0 +1,9 @@
+.popover {
+ min-width: 300px;
+
+ .popover-body .user-popover {
+ padding: $gl-padding-8;
+ font-size: $gl-font-size-small;
+ line-height: $gl-line-height;
+ }
+}
diff --git a/app/assets/stylesheets/csslab.scss b/app/assets/stylesheets/csslab.scss
new file mode 100644
index 00000000000..acaa41e2677
--- /dev/null
+++ b/app/assets/stylesheets/csslab.scss
@@ -0,0 +1 @@
+@import "../../../node_modules/@gitlab/csslab/dist/css/csslab-slim";
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 4041f2b4479..834e7ffce81 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -65,3 +65,4 @@
@import 'framework/feature_highlight';
@import 'framework/terms';
@import 'framework/read_more';
+@import 'framework/flex_grid';
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index fcf282a7d7c..e132aa4c216 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -21,6 +21,7 @@
&.s46 { @include avatar-size(46px, 15px); }
&.s48 { @include avatar-size(48px, 10px); }
&.s60 { @include avatar-size(60px, 12px); }
+ &.s64 { @include avatar-size(64px, 14px); }
&.s70 { @include avatar-size(70px, 14px); }
&.s90 { @include avatar-size(90px, 15px); }
&.s100 { @include avatar-size(100px, 15px); }
@@ -80,6 +81,7 @@
&.s40 { font-size: 16px; line-height: 38px; }
&.s48 { font-size: 20px; line-height: 46px; }
&.s60 { font-size: 32px; line-height: 58px; }
+ &.s64 { font-size: 32px; line-height: 64px; }
&.s70 { font-size: 34px; line-height: 70px; }
&.s90 { font-size: 36px; line-height: 88px; }
&.s100 { font-size: 36px; line-height: 98px; }
@@ -106,6 +108,7 @@
width: 100%;
height: 100%;
display: flex;
+ text-decoration: none;
}
.avatar {
@@ -118,6 +121,7 @@
}
&.s40 { min-width: 40px; min-height: 40px; }
+ &.s64 { min-width: 64px; min-height: 64px; }
}
.avatar-counter {
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 219fd99b097..a4a9276c580 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -142,8 +142,14 @@
&.btn-sm {
padding: 4px 10px;
- font-size: 13px;
- line-height: 18px;
+ font-size: $gl-btn-small-font-size;
+ line-height: $gl-btn-small-line-height;
+ }
+
+ &.btn-xs {
+ padding: 2px $gl-btn-padding;
+ font-size: $gl-btn-xs-font-size;
+ line-height: $gl-btn-xs-line-height;
}
&.btn-success,
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 626c8f92d1d..e037b02a30c 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -386,3 +386,18 @@ img.emoji {
.flex-no-shrink { flex-shrink: 0; }
.mw-460 { max-width: 460px; }
.ws-initial { white-space: initial; }
+.min-height-0 { min-height: 0; }
+
+.gl-pl-0 { padding-left: 0; }
+.gl-pl-1 { padding-left: #{0.5 * $grid-size}; }
+.gl-pl-2 { padding-left: $grid-size; }
+.gl-pl-3 { padding-left: #{2 * $grid-size}; }
+.gl-pl-4 { padding-left: #{3 * $grid-size}; }
+.gl-pl-5 { padding-left: #{4 * $grid-size}; }
+
+.gl-pr-0 { padding-right: 0; }
+.gl-pr-1 { padding-right: #{0.5 * $grid-size}; }
+.gl-pr-2 { padding-right: $grid-size; }
+.gl-pr-3 { padding-right: #{2 * $grid-size}; }
+.gl-pr-4 { padding-right: #{3 * $grid-size}; }
+.gl-pr-5 { padding-right: #{4 * $grid-size}; }
diff --git a/app/assets/stylesheets/framework/contextual_sidebar.scss b/app/assets/stylesheets/framework/contextual_sidebar.scss
index 6f103e4e89a..8b6a7017c47 100644
--- a/app/assets/stylesheets/framework/contextual_sidebar.scss
+++ b/app/assets/stylesheets/framework/contextual_sidebar.scss
@@ -261,7 +261,7 @@
height: 1px;
margin: 4px -1px;
padding: 0;
- background-color: $dropdown-divider-color;
+ background-color: $dropdown-divider-bg;
}
> .active {
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index ce5d36a340f..afcb230797a 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -176,9 +176,9 @@
display: block;
font-weight: $gl-font-weight-normal;
position: relative;
- padding: 8px 16px;
+ padding: $dropdown-item-padding-y $dropdown-item-padding-x;
color: $gl-text-color;
- line-height: normal;
+ line-height: $gl-btn-line-height;
white-space: normal;
overflow: hidden;
text-align: left;
@@ -290,14 +290,18 @@
}
}
+ .dropdown-item {
+ @include dropdown-link;
+ }
+
.divider {
height: 1px;
margin: #{$grid-size / 2} 0;
padding: 0;
- background-color: $dropdown-divider-color;
+ background-color: $dropdown-divider-bg;
&:hover {
- background-color: $dropdown-divider-color;
+ background-color: $dropdown-divider-bg;
}
}
@@ -306,7 +310,7 @@
height: 1px;
margin-top: 8px;
margin-bottom: 8px;
- background-color: $dropdown-divider-color;
+ background-color: $dropdown-divider-bg;
}
.dropdown-menu-empty-item a {
@@ -319,8 +323,8 @@
.dropdown-header {
color: $gl-text-color-secondary;
font-size: 13px;
- line-height: 22px;
- padding: 8px 16px;
+ line-height: $gl-line-height;
+ padding: $dropdown-item-padding-y $dropdown-item-padding-x;
}
&.capitalize-header .dropdown-header {
@@ -329,13 +333,8 @@
.dropdown-bold-header {
font-weight: $gl-font-weight-bold;
- line-height: 22px;
- padding: 0 16px;
- }
-
- .separator + .dropdown-header,
- .separator + .dropdown-bold-header {
- padding-top: 10px;
+ line-height: $gl-line-height;
+ padding: $dropdown-item-padding-y $dropdown-item-padding-x;
}
.unclickable {
@@ -535,14 +534,15 @@
.dropdown-title {
position: relative;
- padding: 2px 25px 10px;
- margin: 0 10px 10px;
+ padding: $dropdown-item-padding-y $dropdown-item-padding-x;
+ padding-bottom: #{2 * $dropdown-item-padding-y};
+ margin-bottom: $dropdown-item-padding-y;
font-weight: $gl-font-weight-bold;
line-height: 1;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
- border-bottom: 1px solid $dropdown-divider-color;
+ border-bottom: 1px solid $dropdown-divider-bg;
overflow: hidden;
}
@@ -621,7 +621,7 @@
padding: 0 7px;
color: $gl-gray-700;
line-height: 30px;
- border: 1px solid $dropdown-divider-color;
+ border: 1px solid $dropdown-divider-bg;
border-radius: 2px;
outline: 0;
@@ -656,7 +656,7 @@
padding-top: 10px;
margin-top: 10px;
font-size: 13px;
- border-top: 1px solid $dropdown-divider-color;
+ border-top: 1px solid $dropdown-divider-bg;
}
.dropdown-footer-content {
diff --git a/app/assets/stylesheets/framework/flex_grid.scss b/app/assets/stylesheets/framework/flex_grid.scss
new file mode 100644
index 00000000000..10537fd5549
--- /dev/null
+++ b/app/assets/stylesheets/framework/flex_grid.scss
@@ -0,0 +1,52 @@
+.flex-grid {
+ .grid-row {
+ border-bottom: 1px solid $border-color;
+ padding: 0;
+
+ &:last-child {
+ border-bottom: 0;
+ }
+
+ @include media-breakpoint-down(md) {
+ border-bottom: 0;
+ border-right: 1px solid $border-color;
+
+ &:last-child {
+ border-right: 0;
+ }
+ }
+
+ @include media-breakpoint-down(xs) {
+ border-right: 0;
+ border-bottom: 1px solid $border-color;
+
+ &:last-child {
+ border-bottom: 0;
+ }
+ }
+ }
+
+ .grid-cell {
+ padding: 10px $gl-padding;
+ border-right: 1px solid $border-color;
+
+ &:last-child {
+ border-right: 0;
+ }
+
+ @include media-breakpoint-up(md) {
+ flex: 1;
+ }
+
+ @include media-breakpoint-down(md) {
+ border-right: 0;
+ flex: none;
+ }
+ }
+}
+
+.card {
+ .card-body.flex-grid {
+ padding: 0;
+ }
+}
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index afd888af672..4da2243981e 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -256,7 +256,12 @@ label {
}
}
+.input-md {
+ max-width: $input-md-width;
+ width: 100%;
+}
+
.input-lg {
- max-width: 320px;
+ max-width: $input-lg-width;
width: 100%;
}
diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss
index b8bb9e1e07b..0ef50e139f2 100644
--- a/app/assets/stylesheets/framework/gitlab_theme.scss
+++ b/app/assets/stylesheets/framework/gitlab_theme.scss
@@ -22,6 +22,10 @@
.container-fluid {
.navbar-toggler {
border-left: 1px solid lighten($border-and-box-shadow, 10%);
+
+ svg {
+ fill: $search-and-nav-links;
+ }
}
}
@@ -309,12 +313,14 @@ body {
.navbar-nav {
> li {
> a:hover,
- > a:focus {
+ > a:focus,
+ > button:hover {
color: $theme-gray-900;
}
&.active > a,
- &.active > a:hover {
+ &.active > a:hover,
+ &.active > button {
color: $white-light;
}
}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 39410ac56af..7d283dcfb71 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -33,6 +33,7 @@
.close-icon {
display: block;
+ margin: auto;
}
}
@@ -90,12 +91,6 @@
padding: 2px 8px;
margin: 5px 2px 5px -8px;
border-radius: $border-radius-default;
-
- .tanuki-logo {
- @include media-breakpoint-up(sm) {
- margin-right: 8px;
- }
- }
}
.project-item-select {
@@ -127,12 +122,6 @@
}
}
- li.dropdown-bold-header {
- color: $gl-text-color-secondary;
- font-size: 12px;
- padding: 0 16px;
- }
-
.navbar-collapse {
flex: 0 0 auto;
border-top: 0;
@@ -180,12 +169,6 @@
color: currentColor;
background-color: transparent;
}
-
- .more-icon,
- .close-icon {
- fill: $white-light;
- margin: auto;
- }
}
.navbar-nav {
@@ -383,6 +366,16 @@
top: 1px;
}
}
+
+ .dropdown-menu li a .identicon {
+ width: 17px;
+ height: 17px;
+ font-size: $gl-font-size-xs;
+ vertical-align: middle;
+ text-indent: 0;
+ line-height: $gl-font-size-xs + 2px;
+ display: inline-block;
+ }
}
.breadcrumbs-list {
@@ -531,7 +524,7 @@
left: auto;
li.current-user {
- padding: 5px 18px;
+ padding: $dropdown-item-padding-y $dropdown-item-padding-x;
.user-name {
display: block;
diff --git a/app/assets/stylesheets/framework/highlight.scss b/app/assets/stylesheets/framework/highlight.scss
index 73533571a2f..946f575ac13 100644
--- a/app/assets/stylesheets/framework/highlight.scss
+++ b/app/assets/stylesheets/framework/highlight.scss
@@ -42,7 +42,6 @@
padding: 10px;
text-align: right;
float: left;
- line-height: 1;
a {
font-family: $monospace-font;
@@ -69,3 +68,9 @@
}
}
}
+
+// Vertically aligns <table> line numbers (eg. blame view)
+// see https://gitlab.com/gitlab-org/gitlab-ce/issues/54048
+td.line-numbers {
+ line-height: 1;
+}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 0f6fb16774c..5609a2086e6 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -131,7 +131,7 @@
width: 100%;
}
-.md {
+.md:not(.use-csslab) {
&.md-preview-holder {
// Reset ul style types since we're nested inside a ul already
@include bulleted-list;
@@ -277,6 +277,27 @@
}
}
+.md-suggestion-diff {
+ display: table !important;
+ border: 1px solid $border-color !important;
+}
+
+.md-suggestion-header {
+ height: $suggestion-header-height;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background-color: $gray-light;
+ border: 1px solid $border-color;
+ padding: $gl-padding;
+ border-radius: $border-radius-default $border-radius-default 0 0;
+
+ svg {
+ vertical-align: middle;
+ margin-bottom: 3px;
+ }
+}
+
@include media-breakpoint-down(xs) {
.atwho-view-ul {
width: 350px;
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 6d20c46b99d..3bb046d0e51 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -39,15 +39,6 @@
.git-clone-holder {
display: none;
}
-
- // Display Star and Fork buttons without counters on mobile.
- .project-repo-buttons {
- display: block;
-
- .count-buttons .count-badge {
- margin-top: $gl-padding-8;
- }
- }
}
.group-buttons {
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 7e30747963a..95291b4a9ad 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -25,8 +25,8 @@
&.w-100 {
// after upgrading to Bootstrap 4.2 we can use $modal-header-padding-x here
// https://github.com/twbs/bootstrap/pull/26976
- margin-right: -2rem;
- padding-right: 2rem;
+ margin-right: -28px;
+ padding-right: 28px;
}
}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index 7f0edd88dfb..a68f1e4e570 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -1,6 +1,11 @@
/** Select2 selectbox style override **/
.select2-container {
width: 100% !important;
+
+ &.input-md,
+ &.input-lg {
+ display: block;
+ }
}
.select2-container,
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index b3b99df5790..0c81dc2e156 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -368,11 +368,11 @@ code {
* Apply Markdown typography
*
*/
-.wiki {
+.wiki:not(.use-csslab) {
@include md-typography;
}
-.md {
+.md:not(.use-csslab) {
@include md-typography;
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index bf2868710eb..343c09b4a3e 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -172,6 +172,7 @@ $theme-light-red-700: #a62e21;
$black: #000;
$black-transparent: rgba(0, 0, 0, 0.3);
+$shadow-color: rgba($black, 0.1);
$almost-black: #242424;
$border-white-light: darken($white-light, $darken-border-factor);
@@ -197,6 +198,8 @@ $well-light-text-color: #5b6169;
$gl-font-size: 14px;
$gl-font-size-xs: 11px;
$gl-font-size-small: 12px;
+$gl-font-size-medium: 20px;
+$gl-font-size-large: 16px;
$gl-font-weight-normal: 400;
$gl-font-weight-bold: 600;
$gl-text-color: #2e2e2e;
@@ -249,6 +252,7 @@ $browserScrollbarSize: 10px;
* Misc
*/
$header-height: 40px;
+$suggestion-header-height: 46px;
$ide-statusbar-height: 25px;
$fixed-layout-width: 1280px;
$limited-layout-width: 990px;
@@ -270,9 +274,11 @@ $performance-bar-height: 35px;
$flash-height: 52px;
$context-header-height: 60px;
$breadcrumb-min-height: 48px;
-$project-title-row-height: 24px;
+$project-title-row-height: 64px;
+$project-avatar-mobile-size: 24px;
$gl-line-height: 16px;
$gl-line-height-24: 24px;
+$gl-line-height-14: 14px;
/*
* Common component specific colors
@@ -332,7 +338,6 @@ $dropdown-max-height: 312px;
$dropdown-vertical-offset: 4px;
$dropdown-empty-row-bg: rgba(#000, 0.04);
$dropdown-shadow-color: rgba(#000, 0.1);
-$dropdown-divider-color: rgba(#000, 0.1);
$dropdown-title-btn-color: #bfbfbf;
$dropdown-input-fa-color: #c7c7c7;
$dropdown-input-focus-shadow: rgba($blue-300, 0.4);
@@ -366,6 +371,10 @@ $gl-btn-padding: 10px;
$gl-btn-line-height: 16px;
$gl-btn-vert-padding: 8px;
$gl-btn-horz-padding: 12px;
+$gl-btn-small-font-size: 13px;
+$gl-btn-small-line-height: 18px;
+$gl-btn-xs-font-size: 13px;
+$gl-btn-xs-line-height: 13px;
/*
* Badges
@@ -396,7 +405,7 @@ $award-emoji-positive-add-lines: #bb9c13;
* Search Box
*/
$search-input-border-color: rgba($blue-400, 0.8);
-$search-input-width: 240px;
+$search-input-width: 200px;
$search-input-active-width: 320px;
$location-icon-color: #e7e9ed;
@@ -500,6 +509,8 @@ $gl-field-focus-shadow: rgba(0, 0, 0, 0.075);
$gl-field-focus-shadow-error: rgba($red-500, 0.6);
$input-short-width: 200px;
$input-short-md-width: 280px;
+$input-md-width: 240px;
+$input-lg-width: 320px;
/*
* Help
diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss
index 711de02cd39..069f45bff49 100644
--- a/app/assets/stylesheets/framework/variables_overrides.scss
+++ b/app/assets/stylesheets/framework/variables_overrides.scss
@@ -20,3 +20,17 @@ $warning: $orange-500;
$danger: $red-500;
$zindex-modal-backdrop: 1040;
$nav-divider-margin-y: ($grid-size / 2);
+$dropdown-divider-bg: $theme-gray-200;
+$dropdown-item-padding-y: 8px;
+$dropdown-item-padding-x: 12px;
+$popover-max-width: 300px;
+$popover-border-width: 1px;
+$popover-border-color: $border-color;
+$popover-box-shadow: 0 $border-radius-small $border-radius-default 0 $shadow-color;
+$popover-arrow-outer-color: $shadow-color;
+$h1-font-size: 14px * 2.5;
+$h2-font-size: 14px * 2;
+$h3-font-size: 14px * 1.75;
+$h4-font-size: 14px * 1.5;
+$h5-font-size: 14px * 1.25;
+$h6-font-size: 14px;
diff --git a/app/assets/stylesheets/highlight/none.scss b/app/assets/stylesheets/highlight/none.scss
new file mode 100644
index 00000000000..7d692a87e33
--- /dev/null
+++ b/app/assets/stylesheets/highlight/none.scss
@@ -0,0 +1,242 @@
+/*
+* None Syntax Colors
+*/
+
+
+
+@mixin matchLine {
+ color: $black-transparent;
+ background-color: $white-normal;
+}
+
+.code.none {
+ // Line numbers
+ .line-numbers,
+ .diff-line-num {
+ background-color: $gray-light;
+ }
+
+ .diff-line-num,
+ .diff-line-num a {
+ color: $black-transparent;
+ }
+
+ // Code itself
+ pre.code,
+ .diff-line-num {
+ border-color: $white-normal;
+ }
+
+ &,
+ pre.code,
+ .line_holder .line_content {
+ background-color: $white-light;
+ color: $gl-text-color;
+ }
+
+// Diff line
+
+ $none-over-bg: #ded7fc;
+ $none-expanded-border: #e0e0e0;
+ $none-expanded-bg: #f7f7f7;
+
+ .line_holder {
+
+ &.match .line_content,
+ .new-nonewline.line_content,
+ .old-nonewline.line_content {
+ @include matchLine;
+ }
+
+ .diff-line-num {
+ &.old {
+ background-color: $line-number-old;
+ border-color: $line-removed-dark;
+
+ a {
+ color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
+ }
+ }
+
+ &.new {
+ background-color: $line-number-new;
+ border-color: $line-added-dark;
+
+ a {
+ color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
+ }
+ }
+
+ &.is-over,
+ &.hll:not(.empty-cell).is-over {
+ background-color: $none-over-bg;
+ border-color: darken($none-over-bg, 5%);
+
+ a {
+ color: darken($none-over-bg, 15%);
+ }
+ }
+
+ &.hll:not(.empty-cell) {
+ background-color: $line-number-select;
+ border-color: $line-select-yellow-dark;
+ }
+ }
+
+ &:not(.diff-expanded) + .diff-expanded,
+ &.diff-expanded + .line_holder:not(.diff-expanded) {
+ > .diff-line-num,
+ > .line_content {
+ border-top: 1px solid $none-expanded-border;
+ }
+ }
+
+ &.diff-expanded {
+ > .diff-line-num,
+ > .line_content {
+ background: $none-expanded-bg;
+ border-color: $none-expanded-bg;
+ }
+ }
+
+ .line_content {
+ &.old {
+ background-color: $line-removed;
+
+ &::before {
+ color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
+ }
+
+ span.idiff {
+ background-color: $line-removed-dark;
+ }
+ }
+
+ &.new {
+ background-color: $line-added;
+
+ &::before {
+ color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
+ }
+
+ span.idiff {
+ background-color: $line-added-dark;
+ }
+ }
+
+ &.match {
+ @include matchLine;
+ }
+
+ &.hll:not(.empty-cell) {
+ background-color: $line-select-yellow;
+ }
+ }
+ }
+
+ // highlight line via anchor
+ pre .hll {
+ background-color: $white-normal;
+ }
+
+ // Search result highlight
+ span.highlight_word {
+ background-color: $white-normal;
+ }
+
+ // Links to URLs, emails, or dependencies
+ .line a {
+ color: $gl-text-color;
+ text-decoration: underline;
+ }
+
+ .hll { background-color: $white-light; }
+
+ .gd {
+ color: $gl-text-color;
+ background-color: $white-light;
+
+ .x {
+ color: $gl-text-color;
+ background-color: $white-light;
+ }
+ }
+
+ .gi {
+ color: $gl-text-color;
+ background-color: $white-light;
+
+ .x {
+ color: $gl-text-color;
+ background-color: $white-light;
+ }
+ }
+
+ .c { color: $gl-text-color; } /* Comment */
+ .err { color: $gl-text-color; } /* Error */
+ .g { color: $gl-text-color; } /* Generic */
+ .k { color: $gl-text-color; } /* Keyword */
+ .l { color: $gl-text-color; } /* Literal */
+ .n { color: $gl-text-color; } /* Name */
+ .o { color: $gl-text-color; } /* Operator */
+ .x { color: $gl-text-color; } /* Other */
+ .p { color: $gl-text-color; } /* Punctuation */
+ .cm { color: $gl-text-color; } /* Comment.Multiline */
+ .cp { color: $gl-text-color; } /* Comment.Preproc */
+ .c1 { color: $gl-text-color; } /* Comment.Single */
+ .cs { color: $gl-text-color; } /* Comment.Special */
+ .ge { color: $gl-text-color; } /* Generic.Emph */
+ .gr { color: $gl-text-color; } /* Generic.Error */
+ .gh { color: $gl-text-color; } /* Generic.Heading */
+ .go { color: $gl-text-color; } /* Generic.Output */
+ .gp { color: $gl-text-color; } /* Generic.Prompt */
+ .gs { color: $gl-text-color; } /* Generic.Strong */
+ .gu { color: $gl-text-color; } /* Generic.Subheading */
+ .gt { color: $gl-text-color; } /* Generic.Traceback */
+ .kc { color: $gl-text-color; } /* Keyword.Constant */
+ .kd { color: $gl-text-color; } /* Keyword.Declaration */
+ .kn { color: $gl-text-color; } /* Keyword.Namespace */
+ .kp { color: $gl-text-color; } /* Keyword.Pseudo */
+ .kr { color: $gl-text-color; } /* Keyword.Reserved */
+ .kt { color: $gl-text-color; } /* Keyword.Type */
+ .ld { color: $gl-text-color; } /* Literal.Date */
+ .m { color: $gl-text-color; } /* Literal.Number */
+ .s { color: $gl-text-color; } /* Literal.String */
+ .na { color: $gl-text-color; } /* Name.Attribute */
+ .nb { color: $gl-text-color; } /* Name.Builtin */
+ .nc { color: $gl-text-color; } /* Name.Class */
+ .no { color: $gl-text-color; } /* Name.Constant */
+ .nd { color: $gl-text-color; } /* Name.Decorator */
+ .ni { color: $gl-text-color; } /* Name.Entity */
+ .ne { color: $gl-text-color; } /* Name.Exception */
+ .nf { color: $gl-text-color; } /* Name.Function */
+ .nl { color: $gl-text-color; } /* Name.Label */
+ .nn { color: $gl-text-color; } /* Name.Namespace */
+ .nx { color: $gl-text-color; } /* Name.Other */
+ .py { color: $gl-text-color; } /* Name.Property */
+ .nt { color: $gl-text-color; } /* Name.Tag */
+ .nv { color: $gl-text-color; } /* Name.Variable */
+ .ow { color: $gl-text-color; } /* Operator.Word */
+ .w { color: $gl-text-color; } /* Text.Whitespace */
+ .mf { color: $gl-text-color; } /* Literal.Number.Float */
+ .mh { color: $gl-text-color; } /* Literal.Number.Hex */
+ .mi { color: $gl-text-color; } /* Literal.Number.Integer */
+ .mo { color: $gl-text-color; } /* Literal.Number.Oct */
+ .sb { color: $gl-text-color; } /* Literal.String.Backtick */
+ .sc { color: $gl-text-color; } /* Literal.String.Char */
+ .sd { color: $gl-text-color; } /* Literal.String.Doc */
+ .s2 { color: $gl-text-color; } /* Literal.String.Double */
+ .se { color: $gl-text-color; } /* Literal.String.Escape */
+ .sh { color: $gl-text-color; } /* Literal.String.Heredoc */
+ .si { color: $gl-text-color; } /* Literal.String.Interpol */
+ .sx { color: $gl-text-color; } /* Literal.String.Other */
+ .sr { color: $gl-text-color; } /* Literal.String.Regex */
+ .s1 { color: $gl-text-color; } /* Literal.String.Single */
+ .ss { color: $gl-text-color; } /* Literal.String.Symbol */
+ .bp { color: $gl-text-color; } /* Name.Builtin.Pseudo */
+ .vc { color: $gl-text-color; } /* Name.Variable.Class */
+ .vg { color: $gl-text-color; } /* Name.Variable.Global */
+ .vi { color: $gl-text-color; } /* Name.Variable.Instance */
+ .il { color: $gl-text-color; } /* Literal.Number.Integer.Long */
+
+}
diff --git a/app/assets/stylesheets/page_bundles/_ide_mixins.scss b/app/assets/stylesheets/page_bundles/_ide_mixins.scss
new file mode 100644
index 00000000000..896a3466cb4
--- /dev/null
+++ b/app/assets/stylesheets/page_bundles/_ide_mixins.scss
@@ -0,0 +1,18 @@
+@mixin ide-trace-view {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ margin-top: -$grid-size;
+ margin-bottom: -$grid-size;
+
+ &.build-page .top-bar {
+ top: 0;
+ height: auto;
+ font-size: 12px;
+ border-top-right-radius: $border-radius-default;
+ }
+
+ .top-bar {
+ margin-left: -$gl-padding;
+ }
+}
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index 07d82e984ba..98d0a2d43ea 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -1,5 +1,6 @@
@import 'framework/variables';
@import 'framework/mixins';
+@import './ide_mixins';
$search-list-icon-width: 18px;
$ide-activity-bar-width: 60px;
@@ -1111,11 +1112,7 @@ $ide-commit-header-height: 48px;
}
.ide-pipeline {
- display: flex;
- flex-direction: column;
- height: 100%;
- margin-top: -$grid-size;
- margin-bottom: -$grid-size;
+ @include ide-trace-view();
.empty-state {
margin-top: auto;
@@ -1133,17 +1130,9 @@ $ide-commit-header-height: 48px;
}
}
- .build-trace,
- .top-bar {
+ .build-trace {
margin-left: -$gl-padding;
}
-
- &.build-page .top-bar {
- top: 0;
- height: auto;
- font-size: 12px;
- border-top-right-radius: $border-radius-default;
- }
}
.ide-pipeline-list {
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index c6074eb9df4..37984a8666f 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -41,7 +41,7 @@
.issue-board-dropdown-content {
margin: 0 8px 10px;
padding-bottom: 10px;
- border-bottom: 1px solid $dropdown-divider-color;
+ border-bottom: 1px solid $dropdown-divider-bg;
> p {
margin: 0;
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 81cb519883b..09235661cea 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -135,6 +135,7 @@
.build-loader-animation {
@include build-loader-animation;
float: left;
+ padding-left: $gl-padding-8;
}
}
@@ -228,9 +229,21 @@
padding: 16px 0;
}
+ .trigger-variables-btn-container {
+ @extend .d-flex;
+ justify-content: space-between;
+ align-items: center;
+
+ .trigger-variables-btn {
+ margin-top: -5px;
+ margin-bottom: -5px;
+ }
+ }
+
.trigger-build-variables {
margin: 0;
overflow-x: auto;
+ width: 100%;
-ms-overflow-style: scrollbar;
-webkit-overflow-scrolling: touch;
}
@@ -243,7 +256,15 @@
.trigger-build-value {
padding: 2px 4px;
color: $black;
- background-color: $white-light;
+ }
+
+ .trigger-variables-table-cell {
+ font-size: $gl-font-size-small;
+ line-height: $gl-line-height;
+ border: 1px solid $theme-gray-200;
+ padding: $gl-padding-4 6px;
+ width: 50%;
+ vertical-align: top;
}
.badge.badge-pill {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 5405f20a760..18c62cb4f1e 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -914,6 +914,7 @@
padding: 0;
width: (2px * $image-comment-cursor-left-offset);
height: (2px * $image-comment-cursor-top-offset);
+ color: $blue-400;
// center the indicator to match the top left click region
margin-top: (-1px * $image-comment-cursor-top-offset) + 2;
margin-left: (-1px * $image-comment-cursor-left-offset) + 1;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 5b5f486ea63..a1069aa9783 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -60,6 +60,10 @@
padding: 0;
margin-bottom: $gl-padding;
border-bottom: 0;
+ word-wrap: break-word;
+ overflow-wrap: break-word;
+ min-width: 0;
+ width: 100%;
}
.btn-edit {
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 8ea34f5d19d..bb6b6f84849 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -259,6 +259,16 @@ ul.related-merge-requests > li {
display: block;
}
+.issue-sort-dropdown {
+ .btn-group {
+ width: 100%;
+ }
+
+ .reverse-sort-btn {
+ color: $gl-text-color-secondary;
+ }
+}
+
@include media-breakpoint-up(sm) {
.emoji-block .row {
display: flex;
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index fa0ab1a3bae..67d7a8175ac 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -49,8 +49,8 @@
.login-box,
.omniauth-container {
box-shadow: 0 0 0 1px $border-color;
- border-bottom-right-radius: 2px;
- border-bottom-left-radius: 2px;
+ border-bottom-right-radius: $border-radius-small;
+ border-bottom-left-radius: $border-radius-small;
padding: 15px;
.login-heading h3 {
@@ -95,6 +95,7 @@
}
.omniauth-container {
+ border-radius: $border-radius-small;
font-size: 13px;
p {
diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss
index d26659701e1..e0f7d075fc7 100644
--- a/app/assets/stylesheets/pages/merge_conflicts.scss
+++ b/app/assets/stylesheets/pages/merge_conflicts.scss
@@ -93,8 +93,28 @@ $colors: (
solarized-dark-line-origin-chosen : rgba(#2878c9, .35),
solarized-dark-button-origin-chosen : #0082cc,
- solarized-dark-header-not-chosen : rgba(#839496, .25),
- solarized-dark-line-not-chosen : rgba(#839496, .15)
+ solarized_dark_header_not_chosen : rgba(#839496, .25),
+ solarized_dark_line_not_chosen : rgba(#839496, .15),
+
+ none_header_head_neutral : $gray-normal,
+ none_line_head_neutral : $gray-normal,
+ none_button_head_neutral : $gray-normal,
+
+ none_header_head_chosen : $gray-darker,
+ none_line_head_chosen : $gray-darker,
+ none_button_head_chosen : $gray-darker,
+
+ none_header_origin_neutral : $gray-normal,
+ none_line_origin_neutral : $gray-normal,
+ none_button_origin_neutral : $gray-normal,
+
+ none_header_origin_chosen : $gray-darker,
+ none_line_origin_chosen : $gray-darker,
+ none_button_origin_chosen : $gray-darker,
+
+ none_header_not_chosen : $gray-light,
+ none_line_not_chosen : $gray-light
+
);
// scss-lint:enable ColorVariable
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 97b3f696139..5b30295adf9 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -178,7 +178,7 @@
table {
.discussion-form-container {
- padding: $gl-padding-top $gl-padding $gl-padding;
+ padding: $gl-padding;
}
}
@@ -237,11 +237,12 @@ table {
}
.discussion-body,
-.diff-file {
+.diff-file,
+.commit-diff {
.discussion-reply-holder {
background-color: $white-light;
- padding: 10px 16px;
border-radius: 0 0 3px 3px;
+ padding: $gl-padding;
&.is-replying {
padding-bottom: $gl-padding;
@@ -254,7 +255,6 @@ table {
display: flex;
}
-
.discussion-actions {
display: table;
@@ -275,8 +275,10 @@ table {
}
}
- .btn {
- width: 100%;
+ @include media-breakpoint-down(xs) {
+ .btn {
+ width: 100%;
+ }
}
.btn-text-field {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 39d01c49fd7..a5b1eff3e1d 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -14,7 +14,7 @@ $note-form-margin-left: 72px;
}
@mixin outline-comment() {
- margin: $gl-padding;
+ margin: $gl-padding $gl-padding 0;
border: 1px solid $border-color;
border-radius: $border-radius-default;
}
@@ -27,8 +27,10 @@ $note-form-margin-left: 72px;
}
}
-.main-notes-list {
- @include vertical-line(36px);
+.issuable-discussion {
+ .main-notes-list {
+ @include vertical-line(36px);
+ }
}
.notes {
@@ -76,10 +78,10 @@ $note-form-margin-left: 72px;
.card {
border: 0;
}
+ }
- li.note {
- border-bottom: 1px solid $border-color;
- }
+ li.note {
+ border-bottom: 1px solid $border-color;
}
.replies-toggle {
@@ -150,6 +152,16 @@ $note-form-margin-left: 72px;
display: block;
position: relative;
+ .timeline-discussion-body {
+ margin-top: -8px;
+ overflow-x: auto;
+ overflow-y: hidden;
+
+ .discussion-resolved-text {
+ margin-bottom: 8px;
+ }
+ }
+
.diff-content {
overflow: visible;
padding: 0;
@@ -161,20 +173,6 @@ $note-form-margin-left: 72px;
position: relative;
border-bottom: 0;
- &:target,
- &.target {
- border-bottom: 1px solid $white-normal;
-
- &:not(:first-child) {
- border-top: 1px solid $white-normal;
- margin-top: -1px;
- }
-
- .timeline-entry-inner {
- border-bottom: 0;
- }
- }
-
&.being-posted {
pointer-events: none;
opacity: 0.5;
@@ -462,7 +460,7 @@ $note-form-margin-left: 72px;
font-family: $regular-font;
td {
- border: 1px solid $white-normal;
+ border: 1px solid $border-color;
border-left: 0;
&.notes_content {
@@ -504,8 +502,6 @@ $note-form-margin-left: 72px;
}
.note-wrapper {
- @include outline-comment();
-
&.system-note {
border: 0;
margin-left: 20px;
@@ -514,23 +510,14 @@ $note-form-margin-left: 72px;
.discussion-reply-holder {
border-radius: 0 0 $border-radius-default $border-radius-default;
- border-top: 1px solid $border-color;
position: relative;
}
}
.commit-diff {
- .notes {
- @include vertical-line(52px);
- }
-
.notes_content {
background-color: $white-light;
}
-
- .discussion-reply-holder {
- border-top: 1px solid $border-color;
- }
}
.discussion-header,
@@ -943,12 +930,6 @@ $note-form-margin-left: 72px;
border-bottom: 1px solid $border-color;
}
- .note-wrapper.outlined {
- margin: 0;
- border: 0;
- border-radius: 0;
- }
-
.discussion-form-container {
padding: $gl-padding;
}
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 132f3fea92b..b813eb16dad 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -98,7 +98,6 @@
// Limits the width of the user bio for readability.
max-width: 600px;
margin: 10px auto;
- padding: 0 16px;
}
.user-avatar-button {
@@ -222,7 +221,11 @@
}
.profile-header {
- margin: 0 auto;
+ margin: 0 $gl-padding;
+
+ &.with-no-profile-tabs {
+ margin-bottom: $gl-padding-24;
+ }
.avatar-holder {
width: 90px;
@@ -453,4 +456,15 @@ table.u2f-registrations {
}
}
}
+
+ @include media-breakpoint-down(sm) {
+ .input-md,
+ .input-lg {
+ max-width: 100%;
+ }
+ }
+}
+
+.help-block {
+ color: $gl-text-color-secondary;
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 6cc21072acd..0ce0db038a7 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -144,7 +144,6 @@
.group-home-panel {
padding-top: 24px;
padding-bottom: 24px;
- border-bottom: 1px solid $border-color;
.group-avatar {
float: none;
@@ -155,7 +154,6 @@
}
}
- .project-title,
.group-title {
margin-top: 10px;
margin-bottom: 10px;
@@ -195,25 +193,69 @@
}
.project-home-panel {
- padding-top: $gl-padding-8;
- padding-bottom: $gl-padding-24;
-
- .project-title-row {
- margin-right: $gl-padding-8;
- }
+ padding-top: $gl-padding;
+ padding-bottom: $gl-padding;
.project-avatar {
width: $project-title-row-height;
height: $project-title-row-height;
flex-shrink: 0;
flex-basis: $project-title-row-height;
- margin: 0 $gl-padding-8 0 0;
+ margin: 0 $gl-padding 0 0;
}
.project-title {
+ margin-top: 8px;
+ margin-bottom: 5px;
font-size: 20px;
- line-height: $project-title-row-height;
+ line-height: $gl-line-height-24;
font-weight: bold;
+
+ .icon {
+ font-size: $gl-font-size-large;
+ }
+
+ .project-visibility {
+ color: $gl-text-color-secondary;
+ }
+
+ .project-tag-list {
+ font-size: $gl-font-size;
+ font-weight: $gl-font-weight-normal;
+
+ .icon {
+ position: relative;
+ top: 3px;
+ margin-right: $gl-padding-4;
+ }
+ }
+ }
+
+ .project-title-row {
+ @include media-breakpoint-down(sm) {
+ .project-avatar {
+ width: $project-avatar-mobile-size;
+ height: $project-avatar-mobile-size;
+ flex-basis: $project-avatar-mobile-size;
+
+ .avatar {
+ font-size: 20px;
+ line-height: 46px;
+ }
+ }
+
+ .project-title {
+ margin-top: 4px;
+ margin-bottom: 2px;
+ font-size: $gl-font-size;
+ line-height: $gl-font-size-large;
+ }
+
+ .project-tag-list,
+ .project-metadata {
+ font-size: $gl-font-size-small;
+ }
+ }
}
.project-metadata {
@@ -222,16 +264,6 @@
line-height: $gl-btn-line-height;
color: $gl-text-color-secondary;
- .icon {
- margin-right: $gl-padding-4;
- font-size: 16px;
- }
-
- .project-visibility,
- .project-license,
- .project-tag-list {
- margin-right: $gl-padding-8;
- }
.project-license {
.btn {
@@ -240,12 +272,22 @@
}
}
- .project-tag-list,
- .project-license {
- .icon {
- position: relative;
- top: 2px;
- }
+ .access-request-link,
+ .project-tag-list {
+ padding-left: $gl-padding-8;
+ border-left: 1px solid $gl-text-color-secondary;
+ }
+ }
+
+ .project-description {
+ @include media-breakpoint-up(md) {
+ font-size: $gl-font-size-large;
+ }
+ }
+
+ .notifications-btn {
+ .fa-bell {
+ margin-right: 0;
}
}
}
@@ -298,14 +340,6 @@
vertical-align: top;
margin-top: $gl-padding;
- .count-badge {
- height: $input-height;
-
- .icon {
- top: -1px;
- }
- }
-
.count-badge-count,
.count-badge-button {
border: 1px solid $border-color;
@@ -319,29 +353,25 @@
.count-badge-count {
padding: 0 12px;
- border-right: 0;
- border-radius: $border-radius-base 0 0 $border-radius-base;
background: $gray-light;
+ border-radius: 0 $border-radius-base $border-radius-base 0;
}
.count-badge-button {
- border-radius: 0 $border-radius-base $border-radius-base 0;
+ border-right: 0;
+ border-radius: $border-radius-base 0 0 $border-radius-base;
}
}
.project-clone-holder {
display: inline-block;
- margin: $gl-padding $gl-padding-8 0 0;
+ margin: $gl-padding 0 0;
input {
height: $input-height;
}
}
- .clone-dropdown-btn {
- background-color: $white-light;
- }
-
.clone-options-dropdown {
min-width: 240px;
@@ -355,6 +385,31 @@
}
}
+.project-repo-buttons {
+ .icon {
+ top: 0;
+ }
+
+ .count-badge,
+ .btn-xs {
+ height: 24px;
+ }
+
+ .dropdown-toggle,
+ .clone-dropdown-btn {
+ .fa {
+ color: unset;
+ }
+ }
+
+ .btn {
+ .notifications-icon {
+ top: 1px;
+ margin-right: 0;
+ }
+ }
+}
+
.split-one {
display: inline-table;
margin-right: 12px;
@@ -715,10 +770,10 @@
border-bottom: 1px solid $border-color;
}
-.project-stats {
+.project-stats,
+.project-buttons {
font-size: 0;
text-align: center;
- border-bottom: 1px solid $border-color;
.scrolling-tabs-container {
.scrolling-tabs {
@@ -786,23 +841,43 @@
font-size: $gl-font-size;
line-height: $gl-btn-line-height;
color: $gl-text-color-secondary;
- white-space: nowrap;
+ white-space: pre-wrap;
}
.stat-link {
border-bottom: 0;
+ color: $black;
&:hover,
&:focus {
- color: $gl-text-color;
text-decoration: underline;
border-bottom: 0;
}
+
+ .project-stat-value {
+ color: $gl-text-color;
+ }
+
+ .icon {
+ color: $gl-text-color-secondary;
+ }
+
+ .add-license-link {
+ &,
+ .icon {
+ color: $blue-600;
+ }
+ }
}
.btn {
- padding: $gl-btn-vert-padding $gl-btn-horz-padding;
+ margin-top: $gl-padding;
+ padding: $gl-btn-vert-padding $gl-btn-padding;
line-height: $gl-btn-line-height;
+
+ .icon {
+ top: 0;
+ }
}
.btn-missing {
@@ -811,6 +886,13 @@
}
}
+.project-buttons {
+ .stat-text {
+ @extend .btn;
+ @extend .btn-default;
+ }
+}
+
.repository-languages-bar {
height: 8px;
margin-bottom: $gl-padding-8;
@@ -887,34 +969,73 @@ pre.light-well {
@include basic-list-stats;
display: flex;
align-items: center;
- }
+ color: $gl-text-color-secondary;
+ padding: $gl-padding 0;
- h3 {
- font-size: $gl-font-size;
+ @include media-breakpoint-up(lg) {
+ padding: $gl-padding-24 0;
+ }
+
+ &.no-description {
+ @include media-breakpoint-up(sm) {
+ .avatar-container {
+ align-self: center;
+ }
+
+ .metadata-info {
+ margin-bottom: 0;
+ }
+ }
+ }
}
- .avatar-container,
- .controls {
- flex: 0 0 auto;
+ h2 {
+ font-size: $gl-font-size-medium;
+ font-weight: $gl-font-weight-bold;
+ margin-bottom: 0;
+
+ @include media-breakpoint-up(sm) {
+ .namespace-name {
+ font-weight: $gl-font-weight-normal;
+ }
+ }
}
.avatar-container {
+ flex: 0 0 auto;
align-self: flex-start;
}
.project-details {
min-width: 0;
+ line-height: $gl-line-height;
+
+ .flex-wrapper {
+ min-width: 0;
+ margin-top: -$gl-padding-8; // negative margin required for flex-wrap
+ }
p,
.commit-row-message {
@include str-truncated(100%);
margin-bottom: 0;
}
- }
- .controls {
- margin-left: auto;
- text-align: right;
+ .user-access-role {
+ margin: 0;
+ }
+
+ @include media-breakpoint-up(md) {
+ .description {
+ color: $gl-text-color;
+ }
+ }
+
+ @include media-breakpoint-down(md) {
+ .user-access-role {
+ line-height: $gl-line-height-14;
+ }
+ }
}
.ci-status-link {
@@ -926,6 +1047,149 @@ pre.light-well {
text-decoration: none;
}
}
+
+ .controls {
+ margin-top: $gl-padding;
+
+ @include media-breakpoint-down(md) {
+ margin-top: 0;
+ }
+
+ @include media-breakpoint-down(xs) {
+ margin-top: $gl-padding-8;
+ }
+
+ .icon-wrapper {
+ color: inherit;
+ margin-right: $gl-padding;
+
+ @include media-breakpoint-down(md) {
+ margin-right: 0;
+ margin-left: $gl-padding-8;
+ }
+
+ @include media-breakpoint-down(xs) {
+ &:first-child {
+ margin-left: 0;
+ }
+ }
+ }
+
+ .ci-status-link {
+ display: inline-flex;
+ }
+ }
+
+ .star-button {
+ .icon {
+ top: 0;
+ }
+ }
+
+ .icon-container {
+ @include media-breakpoint-down(xs) {
+ margin-right: $gl-padding-8;
+ }
+ }
+
+ &.compact {
+ .project-row {
+ padding: $gl-padding 0;
+ }
+
+ h2 {
+ font-size: $gl-font-size;
+ }
+
+ .avatar-container {
+ @include avatar-size(40px, 10px);
+ min-height: 40px;
+ min-width: 40px;
+
+ .identicon.s64 {
+ font-size: 16px;
+ }
+ }
+
+ .controls {
+ @include media-breakpoint-up(sm) {
+ margin-top: 0;
+ }
+ }
+
+ .updated-note {
+ @include media-breakpoint-up(sm) {
+ margin-top: $gl-padding-8;
+ }
+ }
+
+ .icon-wrapper {
+ margin-left: $gl-padding-8;
+ margin-right: 0;
+
+ @include media-breakpoint-down(xs) {
+ &:first-child {
+ margin-left: 0;
+ }
+ }
+ }
+
+ .user-access-role {
+ line-height: $gl-line-height-14;
+ }
+ }
+
+ @include media-breakpoint-down(md) {
+ h2 {
+ font-size: $gl-font-size;
+ }
+
+ .avatar-container {
+ @include avatar-size(40px, 10px);
+ min-height: 40px;
+ min-width: 40px;
+
+ .identicon.s64 {
+ font-size: 16px;
+ }
+ }
+ }
+
+ @include media-breakpoint-down(md) {
+ .updated-note {
+ margin-top: $gl-padding-8;
+ text-align: right;
+ }
+ }
+
+ .forks,
+ .pipeline-status,
+ .updated-note {
+ display: flex;
+ }
+
+ @include media-breakpoint-down(md) {
+ &:not(.explore) {
+ .forks {
+ display: none;
+
+ }
+ }
+
+ &.explore {
+ .pipeline-status,
+ .updated-note {
+ display: none !important;
+ }
+ }
+ }
+
+ @include media-breakpoint-down(xs) {
+ .updated-note {
+ margin-top: 0;
+ text-align: left;
+ }
+ }
}
.card .projects-list li {
@@ -934,8 +1198,6 @@ pre.light-well {
}
.git-clone-holder {
- width: 320px;
-
.btn-clipboard {
border: 1px solid $border-color;
}
@@ -958,6 +1220,15 @@ pre.light-well {
}
}
+.git-clone-holder,
+.mobile-git-clone {
+ .btn {
+ .icon {
+ fill: $white;
+ }
+ }
+}
+
.cannot-be-merged,
.cannot-be-merged:hover {
color: $red-500;
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 04151b1cd59..149c3254d84 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -101,8 +101,6 @@ input[type='checkbox']:hover {
.dropdown-header {
// Necessary because glDropdown doesn't support a second style of headers
font-weight: $gl-font-weight-bold;
- // .dropdown-menu li has 1px side padding
- padding: $gl-padding-8 17px;
color: $gl-text-color;
font-size: $gl-font-size;
line-height: 16px;
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index 800f5c68e39..82e887aa62a 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -180,7 +180,7 @@ ul.wiki-pages-list.content-list {
}
}
-.wiki {
+.wiki:not(.use-csslab) {
table {
@include markdown-table;
}
diff --git a/app/controllers/admin/health_check_controller.rb b/app/controllers/admin/health_check_controller.rb
index 25cc241e5b0..7cd80e8b5e1 100644
--- a/app/controllers/admin/health_check_controller.rb
+++ b/app/controllers/admin/health_check_controller.rb
@@ -2,6 +2,12 @@
class Admin::HealthCheckController < Admin::ApplicationController
def show
- @errors = HealthCheck::Utils.process_checks(['standard'])
+ @errors = HealthCheck::Utils.process_checks(checks)
+ end
+
+ private
+
+ def checks
+ ['standard']
end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 65c1576d9d2..140a625d333 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -8,14 +8,10 @@ class ApplicationController < ActionController::Base
include GitlabRoutingHelper
include PageLayoutHelper
include SafeParamsHelper
- include SentryHelper
include WorkhorseHelper
include EnforcesTwoFactorAuthentication
include WithPerformanceBar
include SessionlessAuthentication
- # this can be removed after switching to rails 5
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/51908
- include InvalidUTF8ErrorHandler unless Gitlab.rails5?
before_action :authenticate_user!
before_action :enforce_terms!, if: :should_enforce_terms?
@@ -129,6 +125,7 @@ class ApplicationController < ActionController::Base
payload[:ua] = request.env["HTTP_USER_AGENT"]
payload[:remote_ip] = request.remote_ip
+ payload[Gitlab::CorrelationId::LOG_KEY] = Gitlab::CorrelationId.current_id
logged_user = auth_user
@@ -155,9 +152,9 @@ class ApplicationController < ActionController::Base
end
def log_exception(exception)
- Raven.capture_exception(exception) if sentry_enabled?
+ Gitlab::Sentry.track_acceptable_exception(exception)
- backtrace_cleaner = Gitlab.rails5? ? request.env["action_dispatch.backtrace_cleaner"] : env
+ backtrace_cleaner = request.env["action_dispatch.backtrace_cleaner"]
application_trace = ActionDispatch::ExceptionWrapper.new(backtrace_cleaner, exception).application_trace
application_trace.map! { |t| " #{t}\n" }
logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}"
@@ -406,7 +403,7 @@ class ApplicationController < ActionController::Base
end
def manifest_import_enabled?
- Group.supports_nested_groups? && Gitlab::CurrentSettings.import_sources.include?('manifest')
+ Group.supports_nested_objects? && Gitlab::CurrentSettings.import_sources.include?('manifest')
end
# U2F (universal 2nd factor) devices need a unique identifier for the application
@@ -487,4 +484,8 @@ class ApplicationController < ActionController::Base
def impersonator
@impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id]
end
+
+ def sentry_context
+ Gitlab::Sentry.context(current_user)
+ end
end
diff --git a/app/controllers/clusters/applications_controller.rb b/app/controllers/clusters/applications_controller.rb
index 250f42f3096..c4e7fc950f9 100644
--- a/app/controllers/clusters/applications_controller.rb
+++ b/app/controllers/clusters/applications_controller.rb
@@ -23,6 +23,6 @@ class Clusters::ApplicationsController < Clusters::BaseController
end
def create_cluster_application_params
- params.permit(:application, :hostname)
+ params.permit(:application, :hostname, :email)
end
end
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index 2e9c77ae55c..b9717b97640 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -18,8 +18,20 @@ class Clusters::ClustersController < Clusters::BaseController
STATUS_POLLING_INTERVAL = 10_000
def index
- clusters = ClustersFinder.new(clusterable, current_user, :all).execute
- @clusters = clusters.page(params[:page]).per(20)
+ finder = ClusterAncestorsFinder.new(clusterable.subject, current_user)
+ clusters = finder.execute
+
+ # Note: We are paginating through an array here but this should OK as:
+ #
+ # In CE, we can have a maximum group nesting depth of 21, so including
+ # project cluster, we can have max 22 clusters for a group hierachy.
+ # In EE (Premium) we can have any number, as multiple clusters are
+ # supported, but the number of clusters are fairly low currently.
+ #
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/55260 also.
+ @clusters = Kaminari.paginate_array(clusters).page(params[:page]).per(20)
+
+ @has_ancestor_clusters = finder.has_ancestor_clusters?
end
def new
@@ -181,15 +193,15 @@ class Clusters::ClustersController < Clusters::BaseController
end
def gcp_cluster
- @gcp_cluster = ::Clusters::Cluster.new.tap do |cluster|
- cluster.build_provider_gcp
- end.present(current_user: current_user)
+ cluster = Clusters::BuildService.new(clusterable.subject).execute
+ cluster.build_provider_gcp
+ @gcp_cluster = cluster.present(current_user: current_user)
end
def user_cluster
- @user_cluster = ::Clusters::Cluster.new.tap do |cluster|
- cluster.build_platform_kubernetes
- end.present(current_user: current_user)
+ cluster = Clusters::BuildService.new(clusterable.subject).execute
+ cluster.build_platform_kubernetes
+ @user_cluster = cluster.present(current_user: current_user)
end
def validate_gcp_token
diff --git a/app/controllers/concerns/group_tree.rb b/app/controllers/concerns/group_tree.rb
index 4f56346832c..e9a7d6a3152 100644
--- a/app/controllers/concerns/group_tree.rb
+++ b/app/controllers/concerns/group_tree.rb
@@ -32,14 +32,14 @@ module GroupTree
def filtered_groups_with_ancestors(groups)
filtered_groups = groups.search(params[:filter]).page(params[:page])
- if Group.supports_nested_groups?
+ if Group.supports_nested_objects?
# We find the ancestors by ID of the search results here.
# Otherwise the ancestors would also have filters applied,
# which would cause them not to be preloaded.
#
# Pagination needs to be applied before loading the ancestors to
# make sure ancestors are not cut off by pagination.
- Gitlab::GroupHierarchy.new(Group.where(id: filtered_groups.select(:id)))
+ Gitlab::ObjectHierarchy.new(Group.where(id: filtered_groups.select(:id)))
.base_and_ancestors
else
filtered_groups
diff --git a/app/controllers/concerns/invalid_utf8_error_handler.rb b/app/controllers/concerns/invalid_utf8_error_handler.rb
deleted file mode 100644
index 44c6d6b0da0..00000000000
--- a/app/controllers/concerns/invalid_utf8_error_handler.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-module InvalidUTF8ErrorHandler
- extend ActiveSupport::Concern
-
- included do
- rescue_from ArgumentError, with: :handle_invalid_utf8
- end
-
- private
-
- def handle_invalid_utf8(error)
- if error.message == "invalid byte sequence in UTF-8"
- render_412
- else
- raise(error)
- end
- end
-
- def render_412
- respond_to do |format|
- format.html { render "errors/precondition_failed", layout: "errors", status: 412 }
- format.js { render json: { error: 'Invalid UTF-8' }, status: :precondition_failed, content_type: 'application/json' }
- format.any { head :precondition_failed }
- end
- end
-end
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index ad9cc0925b7..3d64ae8b775 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -5,7 +5,6 @@ module IssuableActions
include Gitlab::Utils::StrongMemoize
included do
- before_action :labels, only: [:show, :new, :edit]
before_action :authorize_destroy_issuable!, only: :destroy
before_action :authorize_admin_issuable!, only: :bulk_update
end
@@ -25,7 +24,10 @@ module IssuableActions
def show
respond_to do |format|
- format.html
+ format.html do
+ @issuable_sidebar = serializer.represent(issuable, serializer: 'sidebar') # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ end
+
format.json do
render json: serializer.represent(issuable, serializer: params[:serializer])
end
@@ -168,10 +170,6 @@ module IssuableActions
end
end
- def labels
- @labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute # rubocop:disable Gitlab/ModuleWithInstanceVariables
- end
-
def authorize_destroy_issuable!
unless can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable)
return access_denied!
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index 0837599977f..789e0dc736e 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -102,7 +102,7 @@ module IssuableCollections
elsif @group
options[:group_id] = @group.id
options[:include_subgroups] = true
- options[:use_cte_for_search] = true
+ options[:attempt_group_search_optimizations] = true
end
params.permit(finder_type.valid_params).merge(options)
@@ -126,6 +126,8 @@ module IssuableCollections
sort_param = params[:sort]
sort_param ||= user_preference[issuable_sorting_field]
+ return sort_param if Gitlab::Database.read_only?
+
if user_preference[issuable_sorting_field] != sort_param
user_preference.update_attribute(issuable_sorting_field, sort_param)
end
@@ -167,12 +169,6 @@ module IssuableCollections
case value
when 'id_asc' then sort_value_oldest_created
when 'id_desc' then sort_value_recently_created
- when 'created_asc' then sort_value_created_date
- when 'created_desc' then sort_value_created_date
- when 'due_date_asc' then sort_value_due_date
- when 'due_date_desc' then sort_value_due_date
- when 'milestone_due_asc' then sort_value_milestone
- when 'milestone_due_desc' then sort_value_milestone
when 'downvotes_asc' then sort_value_popularity
when 'downvotes_desc' then sort_value_popularity
else value
diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb
index 9576eb14fdd..5572c3cee2d 100644
--- a/app/controllers/concerns/lfs_request.rb
+++ b/app/controllers/concerns/lfs_request.rb
@@ -94,6 +94,7 @@ module LfsRequest
def lfs_upload_access?
return false unless project.lfs_enabled?
return false unless has_authentication_ability?(:push_code)
+ return false if limit_exceeded?
lfs_deploy_token? || can?(user, :push_code, project)
end
@@ -121,4 +122,9 @@ module LfsRequest
def has_authentication_ability?(capability)
(authentication_abilities || []).include?(capability)
end
+
+ # Overriden in EE
+ def limit_exceeded?
+ false
+ end
end
diff --git a/app/controllers/concerns/preview_markdown.rb b/app/controllers/concerns/preview_markdown.rb
index c61b9fabe9e..4b0f0b8255c 100644
--- a/app/controllers/concerns/preview_markdown.rb
+++ b/app/controllers/concerns/preview_markdown.rb
@@ -12,7 +12,7 @@ module PreviewMarkdown
when 'wikis' then { pipeline: :wiki, project_wiki: @project_wiki, page_slug: params[:id] }
when 'snippets' then { skip_project_check: true }
when 'groups' then { group: group }
- when 'projects' then { issuable_state_filter_enabled: true }
+ when 'projects' then projects_filter_params
else {}
end
@@ -22,9 +22,17 @@ module PreviewMarkdown
body: view_context.markdown(result[:text], markdown_params),
references: {
users: result[:users],
+ suggestions: result[:suggestions],
commands: view_context.markdown(result[:commands])
}
}
end
+
+ def projects_filter_params
+ {
+ issuable_state_filter_enabled: true,
+ suggestions_filter_enabled: params[:preview_suggestions].present?
+ }
+ end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end
diff --git a/app/controllers/concerns/renders_commits.rb b/app/controllers/concerns/renders_commits.rb
index f48e0586211..ed9b898a2a3 100644
--- a/app/controllers/concerns/renders_commits.rb
+++ b/app/controllers/concerns/renders_commits.rb
@@ -26,4 +26,10 @@ module RendersCommits
commits
end
+
+ def valid_ref?(ref_name)
+ return true unless ref_name.present?
+
+ Gitlab::GitRefValidator.validate(ref_name)
+ end
end
diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb
index 8bd93a349ef..c6ae4fe15bf 100644
--- a/app/controllers/concerns/service_params.rb
+++ b/app/controllers/concerns/service_params.rb
@@ -70,7 +70,7 @@ module ServiceParams
def service_params
dynamic_params = @service.event_channel_names + @service.event_names # rubocop:disable Gitlab/ModuleWithInstanceVariables
- service_params = params.permit(:id, service: ALLOWED_PARAMS_CE + dynamic_params)
+ service_params = params.permit(:id, service: allowed_service_params + dynamic_params)
if service_params[:service].is_a?(Hash)
FILTER_BLANK_PARAMS.each do |param|
@@ -80,4 +80,8 @@ module ServiceParams
service_params
end
+
+ def allowed_service_params
+ ALLOWED_PARAMS_CE
+ end
end
diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb
index 8c22490700c..014232a7d05 100644
--- a/app/controllers/concerns/snippets_actions.rb
+++ b/app/controllers/concerns/snippets_actions.rb
@@ -10,6 +10,8 @@ module SnippetsActions
def raw
disposition = params[:inline] == 'false' ? 'attachment' : 'inline'
+ workhorse_set_content_type!
+
send_data(
convert_line_endings(@snippet.content),
type: 'text/plain; charset=utf-8',
diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb
index 5912fffc058..0eea0cdd50f 100644
--- a/app/controllers/concerns/uploads_actions.rb
+++ b/app/controllers/concerns/uploads_actions.rb
@@ -38,6 +38,7 @@ module UploadsActions
return render_404 unless uploader
+ workhorse_set_content_type!
send_upload(uploader, attachment: uploader.filename, disposition: disposition)
end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 57e612d89d3..f073b6de444 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -56,7 +56,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
projects = ProjectsFinder
.new(params: finder_params, current_user: current_user)
.execute
- .includes(:route, :creator, namespace: [:route, :owner])
+ .includes(:route, :creator, :group, namespace: [:route, :owner])
.page(finder_params[:page])
prepare_projects_for_rendering(projects)
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index 7ecbc32cf4e..778fdda8dbd 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -57,7 +57,7 @@ class Explore::ProjectsController < Explore::ApplicationController
def load_projects
projects = ProjectsFinder.new(current_user: current_user, params: params)
.execute
- .includes(:route, namespace: :route)
+ .includes(:route, :creator, :group, namespace: [:route, :owner])
.page(params[:page])
.without_count
diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb
index 6ea4758ec32..3ef03bc9622 100644
--- a/app/controllers/graphql_controller.rb
+++ b/app/controllers/graphql_controller.rb
@@ -43,6 +43,6 @@ class GraphqlController < ApplicationController
end
def check_graphql_feature_flag!
- render_404 unless Feature.enabled?(:graphql)
+ render_404 unless Gitlab::Graphql.enabled?
end
end
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index 58565aaf8c9..d4c26fa0709 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -7,7 +7,7 @@ class Import::GithubController < Import::BaseController
rescue_from Octokit::Unauthorized, with: :provider_unauthorized
def new
- if logged_in_with_provider?
+ if github_import_configured? && logged_in_with_provider?
go_to_provider_for_permissions
elsif session[access_token_key]
redirect_to status_import_url
diff --git a/app/controllers/notification_settings_controller.rb b/app/controllers/notification_settings_controller.rb
index 84dce74ace8..384f308269a 100644
--- a/app/controllers/notification_settings_controller.rb
+++ b/app/controllers/notification_settings_controller.rb
@@ -16,7 +16,11 @@ class NotificationSettingsController < ApplicationController
@notification_setting = current_user.notification_settings.find(params[:id])
@saved = @notification_setting.update(notification_setting_params_for(@notification_setting.source))
- render_response
+ if params[:hide_label].present?
+ render_response("projects/buttons/_notifications")
+ else
+ render_response
+ end
end
private
@@ -37,9 +41,9 @@ class NotificationSettingsController < ApplicationController
can?(current_user, ability_name, resource)
end
- def render_response
+ def render_response(response_template = "shared/notifications/_button")
render json: {
- html: view_to_html_string("shared/notifications/_button", notification_setting: @notification_setting),
+ html: view_to_html_string(response_template, notification_setting: @notification_setting),
saved: @saved
}
end
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index dcee8eb7e6e..055d900eece 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -40,7 +40,6 @@ class Profiles::KeysController < Profiles::ApplicationController
begin
user = UserFinder.new(params[:username]).find_by_username
if user.present?
- headers['Content-Disposition'] = 'attachment'
render plain: user.all_ssh_keys.join("\n")
else
return render_404
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index e40a1a1d744..2510a31c9b3 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -11,6 +11,7 @@ class Projects::CommitsController < Projects::ApplicationController
before_action :require_non_empty_project
before_action :assign_ref_vars, except: :commits_root
before_action :authorize_download_code!
+ before_action :validate_ref!, except: :commits_root
before_action :set_commits, except: :commits_root
def commits_root
@@ -54,6 +55,10 @@ class Projects::CommitsController < Projects::ApplicationController
private
+ def validate_ref!
+ render_404 unless valid_ref?(@ref)
+ end
+
def set_commits
render_404 unless @path.empty? || request.format == :atom || @repository.blob_at(@commit.id, @path) || @repository.tree(@commit.id, @path).entries.present?
@limit, @offset = (params[:limit] || 40).to_i, (params[:offset] || 0).to_i
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index 2917925947f..5586c2fc631 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -65,12 +65,6 @@ class Projects::CompareController < Projects::ApplicationController
private
- def valid_ref?(ref_name)
- return true unless ref_name.present?
-
- Gitlab::GitRefValidator.validate(ref_name)
- end
-
def validate_refs!
valid = [head_ref, start_ref].map { |ref| valid_ref?(ref) }
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index 0a593bd35b6..6824a07dc76 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -24,7 +24,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
end
def create
- @key = DeployKeys::CreateService.new(current_user, create_params).execute
+ @key = DeployKeys::CreateService.new(current_user, create_params).execute(project: @project)
unless @key.valid?
flash[:alert] = @key.errors.full_messages.join(', ').html_safe
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index e940f382a19..a63eea0ca0e 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -11,6 +11,10 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :verify_api_request!, only: :terminal_websocket_authorize
before_action :expire_etag_cache, only: [:index]
+ before_action do
+ push_frontend_feature_flag(:area_chart, project)
+ end
+
def index
@environments = project.environments
.with_state(params[:scope] || :available)
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index a10e159ea1e..8b33fa85c1e 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -13,7 +13,7 @@ class Projects::ImportsController < Projects::ApplicationController
end
def create
- if @project.update(safe_import_params)
+ if @project.update(import_params)
@project.import_state.reload.schedule
end
@@ -66,11 +66,11 @@ class Projects::ImportsController < Projects::ApplicationController
end
end
- def import_params
- params.require(:project).permit(:import_url)
+ def import_params_attributes
+ [:import_url]
end
- def safe_import_params
- import_params
+ def import_params
+ params.require(:project).permit(import_params_attributes)
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index c6ab6b4642e..5ed46fc0545 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -268,7 +268,6 @@ class Projects::IssuesController < Projects::ApplicationController
end
def set_suggested_issues_feature_flags
- push_frontend_feature_flag(:graphql)
- push_frontend_feature_flag(:issue_suggestions)
+ push_frontend_feature_flag(:graphql, default_enabled: true)
end
end
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index 3ecf94c008e..bfbbcba883f 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -9,7 +9,7 @@ class Projects::JobsController < Projects::ApplicationController
before_action :authorize_update_build!,
except: [:index, :show, :status, :raw, :trace, :cancel_all, :erase]
before_action :authorize_erase_build!, only: [:erase]
- before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_workhorse_authorize]
+ before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_websocket_authorize]
before_action :verify_api_request!, only: :terminal_websocket_authorize
layout 'project'
@@ -140,15 +140,22 @@ class Projects::JobsController < Projects::ApplicationController
def raw
if trace_artifact_file
+ workhorse_set_content_type!
send_upload(trace_artifact_file,
send_params: raw_send_params,
redirect_params: raw_redirect_params)
else
build.trace.read do |stream|
if stream.file?
+ workhorse_set_content_type!
send_file stream.path, type: 'text/plain; charset=utf-8', disposition: 'inline'
else
- send_data stream.raw, type: 'text/plain; charset=utf-8', disposition: 'inline', filename: 'job.log'
+ # In this case we can't use workhorse_set_content_type! and let
+ # Workhorse handle the response because the data is streamed directly
+ # to the user but, because we have the trace content, we can calculate
+ # the proper content type and disposition here.
+ raw_data = stream.raw
+ send_data raw_data, type: 'text/plain; charset=utf-8', disposition: raw_trace_content_disposition(raw_data), filename: 'job.log'
end
end
end
@@ -201,4 +208,13 @@ class Projects::JobsController < Projects::ApplicationController
def build_path(build)
project_job_path(build.project, build)
end
+
+ def raw_trace_content_disposition(raw_data)
+ mime_type = MimeMagic.by_magic(raw_data)
+
+ # if mime_type is nil can also represent 'text/plain'
+ return 'inline' if mime_type.nil? || mime_type.type == 'text/plain'
+
+ 'attachment'
+ end
end
diff --git a/app/controllers/projects/merge_requests/conflicts_controller.rb b/app/controllers/projects/merge_requests/conflicts_controller.rb
index ac1969adc6e..045a4e974fe 100644
--- a/app/controllers/projects/merge_requests/conflicts_controller.rb
+++ b/app/controllers/projects/merge_requests/conflicts_controller.rb
@@ -8,7 +8,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
def show
respond_to do |format|
format.html do
- labels
+ @issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar')
end
format.json do
@@ -60,9 +60,15 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
end
end
+ private
+
def authorize_can_resolve_conflicts!
@conflicts_list = ::MergeRequests::Conflicts::ListService.new(@merge_request)
return render_404 unless @conflicts_list.can_be_resolved_by?(current_user)
end
+
+ def serializer
+ MergeRequestSerializer.new(current_user: current_user, project: project)
+ end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index d521db79f85..162c2636641 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -22,8 +22,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
format.html
format.json do
render json: {
- html: view_to_html_string("projects/merge_requests/_merge_requests"),
- labels: @labels.as_json(methods: :text_color)
+ html: view_to_html_string("projects/merge_requests/_merge_requests")
}
end
end
@@ -43,8 +42,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
@noteable = @merge_request
@commits_count = @merge_request.commits_count
-
- labels
+ @issuable_sidebar = serializer.represent(@merge_request, serializer: 'sidebar')
set_pipeline_variables
@@ -122,17 +120,21 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
respond_to do |format|
format.html do
- if @merge_request.valid?
- redirect_to([@merge_request.target_project.namespace.becomes(Namespace), @merge_request.target_project, @merge_request])
- else
+ if @merge_request.errors.present?
define_edit_vars
render :edit
+ else
+ redirect_to project_merge_request_path(@merge_request.target_project, @merge_request)
end
end
format.json do
- render json: serializer.represent(@merge_request, serializer: 'basic')
+ if merge_request.errors.present?
+ render json: @merge_request.errors, status: :bad_request
+ else
+ render json: serializer.represent(@merge_request, serializer: 'basic')
+ end
end
end
rescue ActiveRecord::StaleObjectError
@@ -216,6 +218,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
head :ok
end
+ def discussions
+ merge_request.preload_discussions_diff_highlight
+
+ super
+ end
+
protected
alias_method :subscribable_resource, :merge_request
diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index a860be83e95..c5454883060 100644
--- a/app/controllers/projects/protected_branches_controller.rb
+++ b/app/controllers/projects/protected_branches_controller.rb
@@ -15,6 +15,10 @@ class Projects::ProtectedBranchesController < Projects::ProtectedRefsController
@protected_ref = @project.protected_branches.find(params[:id])
end
+ def access_levels
+ [:merge_access_levels, :push_access_levels]
+ end
+
def protected_ref_params
params.require(:protected_branch).permit(:name,
merge_access_levels_attributes: access_level_attributes,
diff --git a/app/controllers/projects/protected_refs_controller.rb b/app/controllers/projects/protected_refs_controller.rb
index 3a3a29ddd0d..4e2a9df5576 100644
--- a/app/controllers/projects/protected_refs_controller.rb
+++ b/app/controllers/projects/protected_refs_controller.rb
@@ -32,7 +32,7 @@ class Projects::ProtectedRefsController < Projects::ApplicationController
@protected_ref = update_service_class.new(@project, current_user, protected_ref_params).execute(@protected_ref)
if @protected_ref.valid?
- render json: @protected_ref, status: :ok
+ render json: @protected_ref, status: :ok, include: access_levels
else
render json: @protected_ref.errors, status: :unprocessable_entity
end
@@ -62,6 +62,6 @@ class Projects::ProtectedRefsController < Projects::ApplicationController
end
def access_level_attributes
- %i(access_level id)
+ %i[access_level id]
end
end
diff --git a/app/controllers/projects/protected_tags_controller.rb b/app/controllers/projects/protected_tags_controller.rb
index 01cedba95ac..41191639c2b 100644
--- a/app/controllers/projects/protected_tags_controller.rb
+++ b/app/controllers/projects/protected_tags_controller.rb
@@ -15,6 +15,10 @@ class Projects::ProtectedTagsController < Projects::ProtectedRefsController
@protected_ref = @project.protected_tags.find(params[:id])
end
+ def access_levels
+ [:create_access_levels]
+ end
+
def protected_ref_params
params.require(:protected_tag).permit(:name, create_access_levels_attributes: access_level_attributes)
end
diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb
index 55827075896..58d5ea4762f 100644
--- a/app/controllers/projects/releases_controller.rb
+++ b/app/controllers/projects/releases_controller.rb
@@ -4,39 +4,16 @@ class Projects::ReleasesController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
- before_action :authorize_push_code!
- before_action :tag
- before_action :release
+ before_action :check_releases_page_feature_flag
- def edit
- end
-
- def update
- # Release belongs to Tag which is not active record object,
- # it exists only to save a description to each Tag.
- # If description is empty we should destroy the existing record.
- if release_params[:description].present?
- release.update(release_params)
- else
- release.destroy
- end
-
- redirect_to project_tag_path(@project, @tag.name)
+ def index
end
private
- def tag
- @tag ||= @repository.find_tag(params[:tag_id])
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def release
- @release ||= @project.releases.find_or_initialize_by(tag: @tag.name)
- end
- # rubocop: enable CodeReuse/ActiveRecord
+ def check_releases_page_feature_flag
+ return render_404 unless Feature.enabled?(:releases_page)
- def release_params
- params.require(:release).permit(:description)
+ push_frontend_feature_flag(:releases_page)
end
end
diff --git a/app/controllers/projects/serverless/functions_controller.rb b/app/controllers/projects/serverless/functions_controller.rb
new file mode 100644
index 00000000000..0af2b7ef343
--- /dev/null
+++ b/app/controllers/projects/serverless/functions_controller.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Projects
+ module Serverless
+ class FunctionsController < Projects::ApplicationController
+ include ProjectUnauthorized
+
+ before_action :authorize_read_cluster!
+
+ INDEX_PRIMING_INTERVAL = 10_000
+ INDEX_POLLING_INTERVAL = 30_000
+
+ def index
+ finder = Projects::Serverless::FunctionsFinder.new(project.clusters)
+
+ respond_to do |format|
+ format.json do
+ functions = finder.execute
+
+ if functions.any?
+ Gitlab::PollingInterval.set_header(response, interval: INDEX_POLLING_INTERVAL)
+ render json: Projects::Serverless::ServiceSerializer.new(current_user: @current_user).represent(functions)
+ else
+ Gitlab::PollingInterval.set_header(response, interval: INDEX_PRIMING_INTERVAL)
+ head :no_content
+ end
+ end
+
+ format.html do
+ @installed = finder.installed?
+ render
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb
index 1d76c90d4eb..ac3004d069f 100644
--- a/app/controllers/projects/settings/repository_controller.rb
+++ b/app/controllers/projects/settings/repository_controller.rb
@@ -20,6 +20,20 @@ module Projects
render_show
end
+ def cleanup
+ cleanup_params = params.require(:project).permit(:bfg_object_map)
+ result = Projects::UpdateService.new(project, current_user, cleanup_params).execute
+
+ if result[:status] == :success
+ RepositoryCleanupWorker.perform_async(project.id, current_user.id)
+ flash[:notice] = _('Repository cleanup has started. You will receive an email once the cleanup operation is complete.')
+ else
+ flash[:alert] = _('Failed to upload object map file')
+ end
+
+ redirect_to project_settings_repository_path(project)
+ end
+
private
def render_show
diff --git a/app/controllers/projects/tags/releases_controller.rb b/app/controllers/projects/tags/releases_controller.rb
new file mode 100644
index 00000000000..334e1847cc8
--- /dev/null
+++ b/app/controllers/projects/tags/releases_controller.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+class Projects::Tags::ReleasesController < Projects::ApplicationController
+ # Authorize
+ before_action :require_non_empty_project
+ before_action :authorize_download_code!
+ before_action :authorize_push_code!
+ before_action :tag
+ before_action :release
+
+ def edit
+ end
+
+ def update
+ # Release belongs to Tag which is not active record object,
+ # it exists only to save a description to each Tag.
+ # If description is empty we should destroy the existing record.
+ if release_params[:description].present?
+ release.update(release_params)
+ else
+ release.destroy
+ end
+
+ redirect_to project_tag_path(@project, @tag.name)
+ end
+
+ private
+
+ def tag
+ @tag ||= @repository.find_tag(params[:tag_id])
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def release
+ @release ||= @project.releases.find_or_initialize_by(tag: @tag.name)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def release_params
+ params.require(:release).permit(:description)
+ end
+end
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 686d66b10a3..a50a1475eb2 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -42,7 +42,7 @@ class Projects::TagsController < Projects::ApplicationController
# rubocop: enable CodeReuse/ActiveRecord
def create
- result = Tags::CreateService.new(@project, current_user)
+ result = ::Tags::CreateService.new(@project, current_user)
.execute(params[:tag_name], params[:ref], params[:message], params[:release_description])
if result[:status] == :success
@@ -58,7 +58,7 @@ class Projects::TagsController < Projects::ApplicationController
end
def destroy
- result = Tags::DestroyService.new(project, current_user).execute(params[:id])
+ result = ::Tags::DestroyService.new(project, current_user).execute(params[:id])
respond_to do |format|
if result[:status] == :success
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 8b040dc080e..072d62ddf38 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -58,11 +58,13 @@ class UsersController < ApplicationController
load_projects
skip_pagination = Gitlab::Utils.to_boolean(params[:skip_pagination])
+ skip_namespace = Gitlab::Utils.to_boolean(params[:skip_namespace])
+ compact_mode = Gitlab::Utils.to_boolean(params[:compact_mode])
respond_to do |format|
format.html { render 'show' }
format.json do
- pager_json("shared/projects/_list", @projects.count, projects: @projects, skip_pagination: skip_pagination)
+ pager_json("shared/projects/_list", @projects.count, projects: @projects, skip_pagination: skip_pagination, skip_namespace: skip_namespace, compact_mode: compact_mode)
end
end
end
diff --git a/app/finders/cluster_ancestors_finder.rb b/app/finders/cluster_ancestors_finder.rb
new file mode 100644
index 00000000000..2f9709ee057
--- /dev/null
+++ b/app/finders/cluster_ancestors_finder.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+class ClusterAncestorsFinder
+ include Gitlab::Utils::StrongMemoize
+
+ def initialize(clusterable, current_user)
+ @clusterable = clusterable
+ @current_user = current_user
+ end
+
+ def execute
+ return [] unless can_read_clusters?
+
+ clusterable.clusters + ancestor_clusters
+ end
+
+ def has_ancestor_clusters?
+ ancestor_clusters.any?
+ end
+
+ private
+
+ attr_reader :clusterable, :current_user
+
+ def can_read_clusters?
+ Ability.allowed?(current_user, :read_cluster, clusterable)
+ end
+
+ # This unfortunately returns an Array, not a Relation!
+ def ancestor_clusters
+ strong_memoize(:ancestor_clusters) do
+ Clusters::Cluster.ancestor_clusters_for_clusterable(clusterable)
+ end
+ end
+end
diff --git a/app/finders/concerns/finder_with_cross_project_access.rb b/app/finders/concerns/finder_with_cross_project_access.rb
index 220f62bcc7f..06ebb286086 100644
--- a/app/finders/concerns/finder_with_cross_project_access.rb
+++ b/app/finders/concerns/finder_with_cross_project_access.rb
@@ -5,7 +5,8 @@
#
# This module depends on the finder implementing the following methods:
#
-# - `#execute` should return an `ActiveRecord::Relation`
+# - `#execute` should return an `ActiveRecord::Relation` or the `model` needs to
+# be defined in the call to `requires_cross_project_access`.
# - `#current_user` the user that requires access (or nil)
module FinderWithCrossProjectAccess
extend ActiveSupport::Concern
@@ -13,20 +14,35 @@ module FinderWithCrossProjectAccess
prepended do
extend Gitlab::CrossProjectAccess::ClassMethods
+
+ cattr_accessor :finder_model
+
+ def self.requires_cross_project_access(*args)
+ super
+
+ self.finder_model = extract_model_from_arguments(args)
+ end
+
+ private
+
+ def self.extract_model_from_arguments(args)
+ args.detect { |argument| argument.is_a?(Hash) && argument[:model] }
+ &.fetch(:model)
+ end
end
override :execute
def execute(*args)
check = Gitlab::CrossProjectAccess.find_check(self)
- original = super
+ original = -> { super }
- return original unless check
- return original if should_skip_cross_project_check || can_read_cross_project?
+ return original.call unless check
+ return original.call if should_skip_cross_project_check || can_read_cross_project?
if check.should_run?(self)
- original.model.none
+ finder_model&.none || original.call.model.none
else
- original
+ original.call
end
end
@@ -48,8 +64,6 @@ module FinderWithCrossProjectAccess
skip_cross_project_check { super }
end
- private
-
attr_accessor :should_skip_cross_project_check
def skip_cross_project_check
diff --git a/app/finders/events_finder.rb b/app/finders/events_finder.rb
index 8df01f1dad9..234b7090fd9 100644
--- a/app/finders/events_finder.rb
+++ b/app/finders/events_finder.rb
@@ -3,22 +3,27 @@
class EventsFinder
prepend FinderMethods
prepend FinderWithCrossProjectAccess
+
+ MAX_PER_PAGE = 100
+
attr_reader :source, :params, :current_user
- requires_cross_project_access unless: -> { source.is_a?(Project) }
+ requires_cross_project_access unless: -> { source.is_a?(Project) }, model: Event
# Used to filter Events
#
# Arguments:
# source - which user or project to looks for events on
# current_user - only return events for projects visible to this user
- # WARNING: does not consider project feature visibility!
# params:
# action: string
# target_type: string
# before: datetime
# after: datetime
- #
+ # per_page: integer (max. 100)
+ # page: integer
+ # with_associations: boolean
+ # sort: 'asc' or 'desc'
def initialize(params = {})
@source = params.delete(:source)
@current_user = params.delete(:current_user)
@@ -33,15 +38,18 @@ class EventsFinder
events = by_target_type(events)
events = by_created_at_before(events)
events = by_created_at_after(events)
+ events = sort(events)
+
+ events = events.with_associations if params[:with_associations]
- events
+ paginated_filtered_by_user_visibility(events)
end
private
# rubocop: disable CodeReuse/ActiveRecord
def by_current_user_access(events)
- events.merge(ProjectsFinder.new(current_user: current_user).execute) # rubocop: disable CodeReuse/Finder
+ events.merge(Project.public_or_visible_to_user(current_user))
.joins(:project)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -77,4 +85,31 @@ class EventsFinder
events.where('events.created_at > ?', params[:after].end_of_day)
end
# rubocop: enable CodeReuse/ActiveRecord
+
+ def sort(events)
+ return events unless params[:sort]
+
+ if params[:sort] == 'asc'
+ events.order_id_asc
+ else
+ events.order_id_desc
+ end
+ end
+
+ def paginated_filtered_by_user_visibility(events)
+ limited_events = events.page(page).per(per_page)
+ visible_events = limited_events.select { |event| event.visible_to_user?(current_user) }
+
+ Kaminari.paginate_array(visible_events, total_count: events.count)
+ end
+
+ def per_page
+ return MAX_PER_PAGE unless params[:per_page]
+
+ [params[:per_page], MAX_PER_PAGE].min
+ end
+
+ def page
+ params[:page] || 1
+ end
end
diff --git a/app/finders/group_descendants_finder.rb b/app/finders/group_descendants_finder.rb
index a9ce5be13f3..96a36db7ec8 100644
--- a/app/finders/group_descendants_finder.rb
+++ b/app/finders/group_descendants_finder.rb
@@ -112,7 +112,7 @@ class GroupDescendantsFinder
# rubocop: disable CodeReuse/ActiveRecord
def ancestors_of_groups(base_for_ancestors)
group_ids = base_for_ancestors.except(:select, :sort).select(:id)
- Gitlab::GroupHierarchy.new(Group.where(id: group_ids))
+ Gitlab::ObjectHierarchy.new(Group.where(id: group_ids))
.base_and_ancestors(upto: parent_group.id)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -132,7 +132,7 @@ class GroupDescendantsFinder
end
def subgroups
- return Group.none unless Group.supports_nested_groups?
+ return Group.none unless Group.supports_nested_objects?
# When filtering subgroups, we want to find all matches withing the tree of
# descendants to show to the user
@@ -183,7 +183,7 @@ class GroupDescendantsFinder
# rubocop: disable CodeReuse/ActiveRecord
def hierarchy_for_parent
- @hierarchy ||= Gitlab::GroupHierarchy.new(Group.where(id: parent_group.id))
+ @hierarchy ||= Gitlab::ObjectHierarchy.new(Group.where(id: parent_group.id))
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb
index ea954f98220..0080123407d 100644
--- a/app/finders/groups_finder.rb
+++ b/app/finders/groups_finder.rb
@@ -46,7 +46,7 @@ class GroupsFinder < UnionFinder
return [Group.all] if current_user&.full_private_access? && all_available?
groups = []
- groups << Gitlab::GroupHierarchy.new(groups_for_ancestors, groups_for_descendants).all_groups if current_user
+ groups << Gitlab::ObjectHierarchy.new(groups_for_ancestors, groups_for_descendants).all_objects if current_user
groups << Group.unscoped.public_to_user(current_user) if include_public_groups?
groups << Group.none if groups.empty?
groups
@@ -66,7 +66,7 @@ class GroupsFinder < UnionFinder
.groups
.where('members.access_level >= ?', params[:min_access_level])
- Gitlab::GroupHierarchy
+ Gitlab::ObjectHierarchy
.new(groups)
.base_and_descendants
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index e04e3a2a7e0..b73a3fa6e01 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -27,12 +27,13 @@
# created_before: datetime
# updated_after: datetime
# updated_before: datetime
-# use_cte_for_search: boolean
+# attempt_group_search_optimizations: boolean
#
class IssuableFinder
prepend FinderWithCrossProjectAccess
include FinderMethods
include CreatedAtFilter
+ include Gitlab::Utils::StrongMemoize
requires_cross_project_access unless: -> { project? }
@@ -75,8 +76,9 @@ class IssuableFinder
items = init_collection
items = filter_items(items)
- # This has to be last as we may use a CTE as an optimization fence by
- # passing the use_cte_for_search param
+ # This has to be last as we may use a CTE as an optimization fence
+ # by passing the attempt_group_search_optimizations param and
+ # enabling the use_cte_for_group_issues_search feature flag
# https://www.postgresql.org/docs/current/static/queries-with.html
items = by_search(items)
@@ -85,6 +87,8 @@ class IssuableFinder
def filter_items(items)
items = by_project(items)
+ items = by_group(items)
+ items = by_subquery(items)
items = by_scope(items)
items = by_created_at(items)
items = by_updated_at(items)
@@ -282,12 +286,31 @@ class IssuableFinder
end
# rubocop: enable CodeReuse/ActiveRecord
+ def use_subquery_for_search?
+ strong_memoize(:use_subquery_for_search) do
+ attempt_group_search_optimizations? &&
+ Feature.enabled?(:use_subquery_for_group_issues_search, default_enabled: false)
+ end
+ end
+
+ def use_cte_for_search?
+ strong_memoize(:use_cte_for_search) do
+ attempt_group_search_optimizations? &&
+ !use_subquery_for_search? &&
+ Feature.enabled?(:use_cte_for_group_issues_search, default_enabled: true)
+ end
+ end
+
private
def init_collection
klass.all
end
+ def attempt_group_search_optimizations?
+ search && Gitlab::Database.postgresql? && params[:attempt_group_search_optimizations]
+ end
+
def count_key(value)
Array(value).last.to_sym
end
@@ -351,12 +374,13 @@ class IssuableFinder
end
# rubocop: enable CodeReuse/ActiveRecord
- def use_cte_for_search?
- return false unless search
- return false unless Gitlab::Database.postgresql?
- return false unless Feature.enabled?(:use_cte_for_group_issues_search, default_enabled: true)
-
- params[:use_cte_for_search]
+ # Wrap projects and groups in a subquery if the conditions are met.
+ def by_subquery(items)
+ if use_subquery_for_search?
+ klass.where(id: items.select(:id)) # rubocop: disable CodeReuse/ActiveRecord
+ else
+ items
+ end
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/finders/projects/serverless/functions_finder.rb b/app/finders/projects/serverless/functions_finder.rb
new file mode 100644
index 00000000000..2b5d67e79d7
--- /dev/null
+++ b/app/finders/projects/serverless/functions_finder.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Projects
+ module Serverless
+ class FunctionsFinder
+ def initialize(clusters)
+ @clusters = clusters
+ end
+
+ def execute
+ knative_services.flatten.compact
+ end
+
+ def installed?
+ clusters_with_knative_installed.exists?
+ end
+
+ private
+
+ def knative_services
+ clusters_with_knative_installed.preload_knative.map do |cluster|
+ cluster.application_knative.services_for(ns: cluster.platform_kubernetes&.actual_namespace)
+ end
+ end
+
+ def clusters_with_knative_installed
+ @clusters.with_knative_installed
+ end
+ end
+ end
+end
diff --git a/app/finders/remote_mirror_finder.rb b/app/finders/remote_mirror_finder.rb
new file mode 100644
index 00000000000..420db0077aa
--- /dev/null
+++ b/app/finders/remote_mirror_finder.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class RemoteMirrorFinder
+ attr_accessor :params
+
+ def initialize(params)
+ @params = params
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def execute
+ RemoteMirror.find_by(id: params[:id])
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+end
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index ed13c5cfdd6..473c90c882c 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -2,11 +2,16 @@
module AppearancesHelper
def brand_title
- current_appearance&.title.presence || 'GitLab Community Edition'
+ current_appearance&.title.presence || default_brand_title
+ end
+
+ def default_brand_title
+ # This resides in a separate method so that EE can easily redefine it.
+ 'GitLab Community Edition'
end
def brand_image
- image_tag(current_appearance.logo) if current_appearance&.logo?
+ image_tag(current_appearance.logo_path) if current_appearance&.logo?
end
def brand_text
@@ -23,7 +28,7 @@ module AppearancesHelper
def brand_header_logo
if current_appearance&.header_logo?
- image_tag current_appearance.header_logo
+ image_tag current_appearance.header_logo_path
else
render 'shared/logo.svg'
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 74042f0bae8..82bb2d1a805 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -171,7 +171,6 @@ module ApplicationHelper
def page_filter_path(options = {})
without = options.delete(:without)
- add_label = options.delete(:label)
options = request.query_parameters.merge(options)
@@ -181,11 +180,7 @@ module ApplicationHelper
end
end
- params = options.compact
-
- params.delete(:label_name) unless add_label
-
- "#{request.path}?#{params.to_param}"
+ "#{request.path}?#{options.compact.to_param}"
end
def outdated_browser?
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 086bb38ce9a..5a7c005fd06 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -26,6 +26,18 @@ module ApplicationSettingsHelper
end
end
+ def all_protocols_enabled?
+ Gitlab::CurrentSettings.enabled_git_access_protocol.blank?
+ end
+
+ def ssh_enabled?
+ all_protocols_enabled? || enabled_protocol == 'ssh'
+ end
+
+ def http_enabled?
+ all_protocols_enabled? || enabled_protocol == 'http'
+ end
+
def enabled_project_button(project, protocol)
case protocol
when 'ssh'
@@ -218,7 +230,8 @@ module ApplicationSettingsHelper
:version_check_enabled,
:web_ide_clientside_preview_enabled,
:diff_max_patch_bytes,
- :commit_email_hostname
+ :commit_email_hostname,
+ :protected_ci_variables
]
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 638744a1426..4c8e1b209c0 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -140,32 +140,6 @@ module BlobHelper
Gitlab::Sanitizers::SVG.clean(data)
end
- # If we blindly set the 'real' content type when serving a Git blob we
- # are enabling XSS attacks. An attacker could upload e.g. a Javascript
- # file to a Git repository, trick the browser of a victim into
- # downloading the blob, and then the 'application/javascript' content
- # type would tell the browser to execute the attacker's Javascript. By
- # overriding the content type and setting it to 'text/plain' (in the
- # example of Javascript) we tell the browser of the victim not to
- # execute untrusted data.
- def safe_content_type(blob)
- if blob.extension == 'svg'
- blob.mime_type
- elsif blob.text?
- 'text/plain; charset=utf-8'
- elsif blob.image?
- blob.content_type
- else
- 'application/octet-stream'
- end
- end
-
- def content_disposition(blob, inline)
- return 'attachment' if blob.extension == 'svg'
-
- inline ? 'inline' : 'attachment'
- end
-
def ref_project
@ref_project ||= @target_project || @project
end
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index 7f071d55a6b..494c754e7d5 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -85,13 +85,14 @@ module ButtonHelper
dropdown_item_with_description('SSH', dropdown_description, href: append_url, data: { clone_type: 'ssh' })
end
- def dropdown_item_with_description(title, description, href: nil, data: nil)
+ def dropdown_item_with_description(title, description, href: nil, data: nil, default: false)
+ active_class = "is-active" if default
button_content = content_tag(:strong, title, class: 'dropdown-menu-inner-title')
button_content << content_tag(:span, description, class: 'dropdown-menu-inner-content') if description
content_tag (href ? :a : :span),
(href ? button_content : title),
- class: "#{title.downcase}-selector",
+ class: "#{title.downcase}-selector #{active_class}",
href: (href if href),
data: (data if data)
end
diff --git a/app/helpers/ci_variables_helper.rb b/app/helpers/ci_variables_helper.rb
new file mode 100644
index 00000000000..e3728804c2a
--- /dev/null
+++ b/app/helpers/ci_variables_helper.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module CiVariablesHelper
+ def ci_variable_protected_by_default?
+ Gitlab::CurrentSettings.current_application_settings.protected_ci_variables
+ end
+
+ def ci_variable_protected?(variable, only_key_value)
+ if variable && !only_key_value
+ variable.protected
+ else
+ ci_variable_protected_by_default?
+ end
+ end
+end
diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb
index 4b6c5b215e8..8d8c62f1291 100644
--- a/app/helpers/dropdowns_helper.rb
+++ b/app/helpers/dropdowns_helper.rb
@@ -11,6 +11,10 @@ module DropdownsHelper
dropdown_output = dropdown_toggle(toggle_text, data_attr, options)
+ if options.key?(:toggle_link)
+ dropdown_output = dropdown_toggle_link(toggle_text, data_attr, options)
+ end
+
dropdown_output << content_tag(:div, class: "dropdown-menu dropdown-select #{options[:dropdown_class] if options.key?(:dropdown_class)}") do
output = []
@@ -49,6 +53,11 @@ module DropdownsHelper
end
end
+ def dropdown_toggle_link(toggle_text, data_attr, options = {})
+ output = content_tag(:a, toggle_text, class: "dropdown-toggle-text #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), data: data_attr)
+ output.html_safe
+ end
+
def dropdown_title(title, options: {})
content_tag :div, class: "dropdown-title" do
title_output = []
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
index 2d2e89a2a50..fa5d3ae474a 100644
--- a/app/helpers/emails_helper.rb
+++ b/app/helpers/emails_helper.rb
@@ -58,7 +58,7 @@ module EmailsHelper
def header_logo
if current_appearance&.header_logo?
image_tag(
- current_appearance.header_logo,
+ current_appearance.header_logo_path,
style: 'height: 50px'
)
else
@@ -98,4 +98,29 @@ module EmailsHelper
"#{string} on #{Gitlab.config.gitlab.host}"
end
+
+ def create_list_id_string(project, list_id_max_length = 255)
+ project_path_as_domain = project.full_path.downcase
+ .split('/').reverse.join('/')
+ .gsub(%r{[^a-z0-9\/]}, '-')
+ .gsub(%r{\/+}, '.')
+ .gsub(/(\A\.+|\.+\z)/, '')
+
+ max_domain_length = list_id_max_length - Gitlab.config.gitlab.host.length - project.id.to_s.length - 2
+
+ if max_domain_length < 3
+ return project.id.to_s + "..." + Gitlab.config.gitlab.host
+ end
+
+ if project_path_as_domain.length > max_domain_length
+ project_path_as_domain = project_path_as_domain.slice(0, max_domain_length)
+
+ last_dot_index = project_path_as_domain[0..-2].rindex(".")
+ last_dot_index ||= max_domain_length - 2
+
+ project_path_as_domain = project_path_as_domain.slice(0, last_dot_index).concat("..")
+ end
+
+ project.id.to_s + "." + project_path_as_domain + "." + Gitlab.config.gitlab.host
+ end
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 3ce2398f1de..1371e9993b4 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -161,6 +161,10 @@ module EventsHelper
project_commit_url(event.project, event.note_target, anchor: dom_id(event.target))
elsif event.project_snippet_note?
project_snippet_url(event.project, event.note_target, anchor: dom_id(event.target))
+ elsif event.issue_note?
+ project_issue_url(event.project, id: event.note_target, anchor: dom_id(event.target))
+ elsif event.merge_request_note?
+ project_merge_request_url(event.project, id: event.note_target, anchor: dom_id(event.target))
else
polymorphic_url([event.project.namespace.becomes(Namespace),
event.project, event.note_target],
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 866fc555856..4a9ed123161 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -126,7 +126,7 @@ module GroupsHelper
end
def supports_nested_groups?
- Group.supports_nested_groups?
+ Group.supports_nested_objects?
end
private
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index b0f63de2fb8..4e11772b252 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -42,7 +42,7 @@ module IconsHelper
end
def sprite_icon(icon_name, size: nil, css_class: nil)
- if Gitlab::Sentry.should_raise?
+ if Gitlab::Sentry.should_raise_for_dev?
unless known_sprites.include?(icon_name)
exception = ArgumentError.new("#{icon_name} is not a known icon in @gitlab-org/gitlab-svg")
raise exception
diff --git a/app/helpers/ide_helper.rb b/app/helpers/ide_helper.rb
new file mode 100644
index 00000000000..8e50bbc6c04
--- /dev/null
+++ b/app/helpers/ide_helper.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module IdeHelper
+ def ide_data
+ {
+ "empty-state-svg-path" => image_path('illustrations/multi_file_editor_empty.svg'),
+ "no-changes-state-svg-path" => image_path('illustrations/multi-editor_no_changes_empty.svg'),
+ "committed-state-svg-path" => image_path('illustrations/multi-editor_all_changes_committed_empty.svg'),
+ "pipelines-empty-state-svg-path": image_path('illustrations/pipelines_empty.svg'),
+ "promotion-svg-path": image_path('illustrations/web-ide_promotion.svg'),
+ "ci-help-page-path" => help_page_path('ci/quick_start/README'),
+ "web-ide-help-page-path" => help_page_path('user/project/web_ide/index.html'),
+ "clientside-preview-enabled": Gitlab::CurrentSettings.current_application_settings.web_ide_clientside_preview_enabled.to_s
+ }
+ end
+end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index dfa86f52e40..5f7147508c7 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -23,30 +23,41 @@ module IssuablesHelper
end
end
- def sidebar_due_date_tooltip_label(issuable)
- if issuable.due_date
- "#{_('Due date')}<br />#{due_date_remaining_days(issuable)}"
- else
- _('Due date')
- end
+ def sidebar_milestone_tooltip_label(milestone)
+ return _('Milestone') unless milestone.present?
+
+ [milestone[:title], sidebar_milestone_remaining_days(milestone) || _('Milestone')].join('<br/>')
+ end
+
+ def sidebar_milestone_remaining_days(milestone)
+ due_date_with_remaining_days(milestone[:due_date], milestone[:start_date])
+ end
+
+ def sidebar_due_date_tooltip_label(due_date)
+ [_('Due date'), due_date_with_remaining_days(due_date)].compact.join('<br/>')
end
- def due_date_remaining_days(issuable)
- remaining_days_in_words = remaining_days_in_words(issuable)
+ def due_date_with_remaining_days(due_date, start_date = nil)
+ return unless due_date
- "#{issuable.due_date.to_s(:medium)} (#{remaining_days_in_words})"
+ "#{due_date.to_s(:medium)} (#{remaining_days_in_words(due_date, start_date)})"
+ end
+
+ def sidebar_label_filter_path(base_path, label_name)
+ query_params = { label_name: [label_name] }.to_query
+
+ "#{base_path}?#{query_params}"
end
def multi_label_name(current_labels, default_label)
- if current_labels && current_labels.any?
- title = current_labels.first.try(:title)
- if current_labels.size > 1
- "#{title} +#{current_labels.size - 1} more"
- else
- title
- end
+ return default_label if current_labels.blank?
+
+ title = current_labels.first.try(:title) || current_labels.first[:title]
+
+ if current_labels.size > 1
+ "#{title} +#{current_labels.size - 1} more"
else
- default_label
+ title
end
end
@@ -179,7 +190,7 @@ module IssuablesHelper
output << "Opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe
output << content_tag(:strong) do
- author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline", tooltip: true)
+ author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline")
author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "d-block d-sm-none")
if status = user_status(issuable.author)
@@ -197,19 +208,11 @@ module IssuablesHelper
output.join.html_safe
end
- # rubocop: disable CodeReuse/ActiveRecord
- def issuable_todo(issuable)
- if current_user
- current_user.todos.find_by(target: issuable, state: :pending)
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
def issuable_labels_tooltip(labels, limit: 5)
first, last = labels.partition.with_index { |_, i| i < limit }
if labels && labels.any?
- label_names = first.collect(&:name)
+ label_names = first.collect { |label| label.fetch(:title) }
label_names << "and #{last.size} more" unless last.empty?
label_names.join(', ')
@@ -356,12 +359,6 @@ module IssuablesHelper
issuable.model_name.human.downcase
end
- def selected_labels
- Array(params[:label_name]).map do |label_name|
- Label.new(title: label_name)
- end
- end
-
def has_filter_bar_param?
finder.class.scalar_params.any? { |p| params[p].present? }
end
@@ -386,19 +383,20 @@ module IssuablesHelper
params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] }
end
- def issuable_todo_button_data(issuable, todo, is_collapsed)
+ def issuable_todo_button_data(issuable, is_collapsed)
{
- todo_text: "Add todo",
- mark_text: "Mark todo as done",
- todo_icon: (is_collapsed ? sprite_icon('todo-add') : nil),
- mark_icon: (is_collapsed ? sprite_icon('todo-done', css_class: 'todo-undone') : nil),
- issuable_id: issuable.id,
- issuable_type: issuable.class.name.underscore,
- url: project_todos_path(@project),
- delete_path: (dashboard_todo_path(todo) if todo),
- placement: (is_collapsed ? 'left' : nil),
- container: (is_collapsed ? 'body' : nil),
- boundary: 'viewport'
+ todo_text: _('Add todo'),
+ mark_text: _('Mark todo as done'),
+ todo_icon: sprite_icon('todo-add'),
+ mark_icon: sprite_icon('todo-done', css_class: 'todo-undone'),
+ issuable_id: issuable[:id],
+ issuable_type: issuable[:type],
+ create_path: issuable[:create_todo_path],
+ delete_path: issuable.dig(:current_user, :todo, :delete_path),
+ placement: is_collapsed ? 'left' : nil,
+ container: is_collapsed ? 'body' : nil,
+ boundary: 'viewport',
+ is_collapsed: is_collapsed
}
end
@@ -418,27 +416,20 @@ module IssuablesHelper
end
end
- def issuable_sidebar_options(issuable, can_edit_issuable)
+ def issuable_sidebar_options(issuable)
{
- endpoint: "#{issuable_json_path(issuable)}?serializer=sidebar",
- toggleSubscriptionEndpoint: toggle_subscription_path(issuable),
- moveIssueEndpoint: move_namespace_project_issue_path(namespace_id: issuable.project.namespace.to_param, project_id: issuable.project, id: issuable),
- projectsAutocompleteEndpoint: autocomplete_projects_path(project_id: @project.id),
- editable: can_edit_issuable,
- currentUser: UserSerializer.new.represent(current_user),
+ endpoint: "#{issuable[:issuable_json_path]}?serializer=sidebar_extras",
+ toggleSubscriptionEndpoint: issuable[:toggle_subscription_path],
+ moveIssueEndpoint: issuable[:move_issue_path],
+ projectsAutocompleteEndpoint: issuable[:projects_autocomplete_path],
+ editable: issuable.dig(:current_user, :can_edit),
+ currentUser: issuable[:current_user],
rootPath: root_path,
- fullPath: @project.full_path
+ fullPath: issuable[:project_full_path]
}
end
def parent
@project || @group
end
-
- def issuable_milestone_tooltip_title(issuable)
- if issuable.milestone
- milestone_tooltip = milestone_tooltip_title(issuable.milestone)
- _('Milestone') + (milestone_tooltip ? ': ' + milestone_tooltip : '')
- end
- end
end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index 9666080092b..327b69e5110 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -114,12 +114,6 @@ module MilestonesHelper
end
end
- def milestone_tooltip_title(milestone)
- if milestone
- "#{milestone.title}<br />#{milestone_tooltip_due_date(milestone)}"
- end
- end
-
def milestone_time_for(date, date_type)
title = date_type == :start ? "Start date" : "End date"
@@ -173,7 +167,7 @@ module MilestonesHelper
def milestone_tooltip_due_date(milestone)
if milestone.due_date
- "#{milestone.due_date.to_s(:medium)} (#{remaining_days_in_words(milestone)})"
+ "#{milestone.due_date.to_s(:medium)} (#{remaining_days_in_words(milestone.due_date, milestone.start_date)})"
else
_('Milestone')
end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index a7fe8c3d59c..05da5ebdb22 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -47,8 +47,8 @@ module NavHelper
class_names
end
- def show_separator?
- Gitlab::Sherlock.enabled? || can?(current_user, :read_instance_statistics)
+ def has_extra_nav_icons?
+ Gitlab::Sherlock.enabled? || can?(current_user, :read_instance_statistics) || current_user.admin?
end
def page_has_markdown?
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 0a7f930110a..0cfc2db3285 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -2,7 +2,7 @@
module ProjectsHelper
def link_to_project(project)
- link_to [project.namespace.becomes(Namespace), project], title: h(project.name) do
+ link_to namespace_project_path(namespace_id: project.namespace, id: project), title: h(project.name) do
title = content_tag(:span, project.name, class: 'project-name')
if project.namespace
@@ -50,6 +50,12 @@ module ProjectsHelper
default_opts = { avatar: true, name: true, title: ":name" }
opts = default_opts.merge(opts)
+ data_attrs = {
+ user_id: author.id,
+ username: author.username,
+ name: author.name
+ }
+
return "(deleted)" unless author
author_html = []
@@ -65,7 +71,7 @@ module ProjectsHelper
author_html = author_html.join.html_safe
if opts[:name]
- link_to(author_html, user_path(author), class: "author-link #{"#{opts[:extra_class]}" if opts[:extra_class]} #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe
+ link_to(author_html, user_path(author), class: "author-link js-user-link #{"#{opts[:extra_class]}" if opts[:extra_class]} #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}", data: data_attrs).html_safe
else
title = opts[:title].sub(":name", sanitize(author.name))
link_to(author_html, user_path(author), class: "author-link has-tooltip", title: title, data: { container: 'body' }).html_safe
@@ -257,17 +263,35 @@ module ProjectsHelper
"xcode://clone?repo=#{CGI.escape(default_url_to_repo(project))}"
end
+ def link_to_bfg
+ link_to 'BFG', 'https://rtyley.github.io/bfg-repo-cleaner/', target: '_blank', rel: 'noopener noreferrer'
+ end
+
def legacy_render_context(params)
params[:legacy_render] ? { markdown_engine: :redcarpet } : {}
end
+ def explore_projects_tab?
+ current_page?(explore_projects_path) ||
+ current_page?(trending_explore_projects_path) ||
+ current_page?(starred_explore_projects_path)
+ end
+
+ def show_merge_request_count?(disabled: false, compact_mode: false)
+ !disabled && !compact_mode && Feature.enabled?(:project_list_show_mr_count, default_enabled: true)
+ end
+
+ def show_issue_count?(disabled: false, compact_mode: false)
+ !disabled && !compact_mode && Feature.enabled?(:project_list_show_issue_count, default_enabled: true)
+ end
+
private
def get_project_nav_tabs(project, current_user)
nav_tabs = [:home]
if !project.empty_repo? && can?(current_user, :download_code, project)
- nav_tabs << [:files, :commits, :network, :graphs, :forks]
+ nav_tabs << [:files, :commits, :network, :graphs, :forks, :releases]
end
if project.repo_exists? && can?(current_user, :read_merge_request, project)
@@ -307,6 +331,7 @@ module ProjectsHelper
settings: :admin_project,
builds: :read_build,
clusters: :read_cluster,
+ serverless: :read_cluster,
labels: :read_label,
issues: :read_issue,
project_members: :read_project_member,
@@ -380,6 +405,10 @@ module ProjectsHelper
end
end
+ def sidebar_operations_link_path(project = @project)
+ metrics_project_environments_path(project) if can?(current_user, :read_environment, project)
+ end
+
def project_last_activity(project)
if project.last_activity_at
time_ago_with_tooltip(project.last_activity_at, placement: 'bottom', html_class: 'last_activity_time_ago')
@@ -504,6 +533,7 @@ module ProjectsHelper
%w[
projects#show
projects#activity
+ releases#index
cycle_analytics#show
]
end
@@ -535,7 +565,6 @@ module ProjectsHelper
projects/repositories
tags
branches
- releases
graphs
network
]
@@ -545,6 +574,7 @@ module ProjectsHelper
%w[
environments
clusters
+ functions
user
gcp
]
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index cf60696ef39..2f802e4eab8 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -29,6 +29,11 @@ module SelectsHelper
classes = Array.wrap(opts[:class])
classes << 'ajax-groups-select'
+ # EE requires this line to be present, but there is no easy way of injecting
+ # this into EE without causing merge conflicts. Given this line is very
+ # simple and not really EE specific on its own, we just include it in CE.
+ classes << 'multiselect' if opts[:multiple]
+
opts[:class] = classes.join(' ')
select2_tag(id, opts)
diff --git a/app/helpers/sentry_helper.rb b/app/helpers/sentry_helper.rb
deleted file mode 100644
index d53eaef9952..00000000000
--- a/app/helpers/sentry_helper.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-module SentryHelper
- def sentry_enabled?
- Gitlab::Sentry.enabled?
- end
-
- def sentry_context
- Gitlab::Sentry.context(current_user)
- end
-end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 74113aee89d..6ac1f42c321 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -136,6 +136,54 @@ module SortingHelper
link_to item, path, class: sorted_by == item ? 'is-active' : ''
end
+ def issuable_sort_option_overrides
+ {
+ sort_value_oldest_created => sort_value_created_date,
+ sort_value_oldest_updated => sort_value_recently_updated,
+ sort_value_milestone_later => sort_value_milestone
+ }
+ end
+
+ def issuable_reverse_sort_order_hash
+ {
+ sort_value_created_date => sort_value_oldest_created,
+ sort_value_recently_created => sort_value_oldest_created,
+ sort_value_recently_updated => sort_value_oldest_updated,
+ sort_value_milestone => sort_value_milestone_later
+ }.merge(issuable_sort_option_overrides)
+ end
+
+ def issuable_sort_option_title(sort_value)
+ sort_value = issuable_sort_option_overrides[sort_value] || sort_value
+
+ sort_options_hash[sort_value]
+ end
+
+ def issuable_sort_icon_suffix(sort_value)
+ case sort_value
+ when sort_value_milestone, sort_value_due_date, /_asc\z/
+ 'lowest'
+ else
+ 'highest'
+ end
+ end
+
+ def issuable_sort_direction_button(sort_value)
+ link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort'
+ reverse_sort = issuable_reverse_sort_order_hash[sort_value]
+
+ if reverse_sort
+ reverse_url = page_filter_path(sort: reverse_sort)
+ else
+ reverse_url = '#'
+ link_class += ' disabled'
+ end
+
+ link_to(reverse_url, type: 'button', class: link_class, title: 'Sort direction') do
+ sprite_icon("sort-#{issuable_sort_icon_suffix(sort_value)}", size: 16)
+ end
+ end
+
# Titles.
def sort_title_access_level_asc
s_('SortOptions|Access level, ascending')
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index bde9ca0cbf2..73c1402eae5 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -8,7 +8,7 @@ module UsersHelper
end
def user_email_help_text(user)
- return 'We also use email for avatar detection if no avatar is uploaded.' unless user.unconfirmed_email.present?
+ return 'We also use email for avatar detection if no avatar is uploaded' unless user.unconfirmed_email.present?
confirmation_link = link_to 'Resend confirmation e-mail', user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post
diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb
index ab77b149072..5e519cf5c19 100644
--- a/app/helpers/version_check_helper.rb
+++ b/app/helpers/version_check_helper.rb
@@ -6,8 +6,7 @@ module VersionCheckHelper
return unless Gitlab::CurrentSettings.version_check_enabled
return if User.single_user&.requires_usage_stats_consent?
- image_url = VersionCheck.new.url
- image_tag image_url, class: 'js-version-status-badge'
+ image_tag VersionCheck.url, class: 'js-version-status-badge'
end
def link_to_version
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index e690350a0d1..712f0f808dd 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -140,7 +140,7 @@ module VisibilityLevelHelper
end
def project_visibility_icon_description(level)
- "#{project_visibility_level_description(level)}"
+ "#{visibility_level_label(level)} - #{project_visibility_level_description(level)}"
end
def visibility_level_label(level)
diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb
index 49c08dce96c..bb5b1555dc4 100644
--- a/app/helpers/workhorse_helper.rb
+++ b/app/helpers/workhorse_helper.rb
@@ -6,8 +6,12 @@ module WorkhorseHelper
# Send a Git blob through Workhorse
def send_git_blob(repository, blob, inline: true)
headers.store(*Gitlab::Workhorse.send_git_blob(repository, blob))
- headers['Content-Disposition'] = content_disposition(blob, inline)
- headers['Content-Type'] = safe_content_type(blob)
+
+ headers['Content-Disposition'] = inline ? 'inline' : 'attachment'
+
+ # If enabled, this will override the values set above
+ workhorse_set_content_type!
+
render plain: ""
end
@@ -40,4 +44,8 @@ module WorkhorseHelper
def set_workhorse_internal_api_content_type
headers['Content-Type'] = Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
end
+
+ def workhorse_set_content_type!
+ headers[Gitlab::Workhorse::DETECT_HEADER] = "true"
+ end
end
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 93b51fb1774..370e6d2f90b 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -56,7 +56,9 @@ module Emails
@milestone = milestone
@milestone_url = milestone_url(@milestone)
- mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason))
+ mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason).merge({
+ template_name: 'changed_milestone_email'
+ }))
end
def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id, reason = nil)
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index 6524d0c2087..9ba8f92fcbf 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -51,7 +51,9 @@ module Emails
@milestone = milestone
@milestone_url = milestone_url(@milestone)
- mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
+ mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason).merge({
+ template_name: 'changed_milestone_email'
+ }))
end
def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil)
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index d7e6c2ba7b2..2500622caa7 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -24,6 +24,21 @@ module Emails
subject: subject("Project export error"))
end
+ def repository_cleanup_success_email(project, user)
+ @project = project
+ @user = user
+
+ mail(to: user.notification_email, subject: subject("Project cleanup has completed"))
+ end
+
+ def repository_cleanup_failure_email(project, user, error)
+ @project = project
+ @user = user
+ @error = error
+
+ mail(to: user.notification_email, subject: subject("Project cleanup failure"))
+ end
+
def repository_push_email(project_id, opts = {})
@message =
Gitlab::Email::Message::RepositoryPush.new(self, project_id, opts)
diff --git a/app/mailers/emails/remote_mirrors.rb b/app/mailers/emails/remote_mirrors.rb
new file mode 100644
index 00000000000..2018eb7260b
--- /dev/null
+++ b/app/mailers/emails/remote_mirrors.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Emails
+ module RemoteMirrors
+ def remote_mirror_update_failed_email(remote_mirror_id, recipient_id)
+ @remote_mirror = RemoteMirrorFinder.new(id: remote_mirror_id).execute
+ @project = @remote_mirror.project
+
+ mail(to: recipient(recipient_id), subject: subject('Remote mirror update failed'))
+ end
+ end
+end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 662f3e00047..efa1233b434 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -3,6 +3,7 @@
class Notify < BaseMailer
include ActionDispatch::Routing::PolymorphicRoutes
include GitlabRoutingHelper
+ include EmailsHelper
include Emails::Issues
include Emails::MergeRequests
@@ -13,7 +14,9 @@ class Notify < BaseMailer
include Emails::Pipelines
include Emails::Members
include Emails::AutoDevops
+ include Emails::RemoteMirrors
+ helper MilestonesHelper
helper MergeRequestsHelper
helper DiffHelper
helper BlobHelper
@@ -128,7 +131,7 @@ class Notify < BaseMailer
address.display_name = reply_display_name(model)
end
- fallback_reply_message_id = "<reply-#{reply_key}@#{Gitlab.config.gitlab.host}>".freeze
+ fallback_reply_message_id = "<reply-#{reply_key}@#{Gitlab.config.gitlab.host}>"
headers['References'] ||= []
headers['References'].unshift(fallback_reply_message_id)
@@ -166,7 +169,7 @@ class Notify < BaseMailer
headers['In-Reply-To'] = message_id(model)
headers['References'] = [message_id(model)]
- headers[:subject]&.prepend('Re: ')
+ headers[:subject] = "Re: #{headers[:subject]}" if headers[:subject]
mail_thread(model, headers)
end
@@ -178,7 +181,7 @@ class Notify < BaseMailer
headers['X-GitLab-Discussion-ID'] = note.discussion.id if note.part_of_discussion?
- headers[:subject]&.prepend('Re: ')
+ headers[:subject] = "Re: #{headers[:subject]}" if headers[:subject]
mail_thread(model, headers)
end
@@ -193,6 +196,7 @@ class Notify < BaseMailer
headers['X-GitLab-Project'] = @project.name
headers['X-GitLab-Project-Id'] = @project.id
headers['X-GitLab-Project-Path'] = @project.full_path
+ headers['List-Id'] = "#{@project.full_path} <#{create_list_id_string(@project)}>"
end
def add_unsubscription_headers_and_links
diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb
index e7e8d96eca4..2ac4610967d 100644
--- a/app/mailers/previews/notify_preview.rb
+++ b/app/mailers/previews/notify_preview.rb
@@ -145,6 +145,10 @@ class NotifyPreview < ActionMailer::Preview
Notify.autodevops_disabled_email(pipeline, user.email).message
end
+ def remote_mirror_update_failed_email
+ Notify.remote_mirror_update_failed_email(remote_mirror.id, user.id).message
+ end
+
private
def project
@@ -167,6 +171,10 @@ class NotifyPreview < ActionMailer::Preview
@pipeline = Ci::Pipeline.last
end
+ def remote_mirror
+ @remote_mirror ||= RemoteMirror.last
+ end
+
def user
@user ||= User.last
end
diff --git a/app/models/appearance.rb b/app/models/appearance.rb
index bffba3e13fa..e114c435b67 100644
--- a/app/models/appearance.rb
+++ b/app/models/appearance.rb
@@ -28,4 +28,32 @@ class Appearance < ActiveRecord::Base
errors.add(:single_appearance_row, 'Only 1 appearances row can exist')
end
end
+
+ def logo_path
+ logo_system_path(logo, 'logo')
+ end
+
+ def header_logo_path
+ logo_system_path(header_logo, 'header_logo')
+ end
+
+ def favicon_path
+ logo_system_path(favicon, 'favicon')
+ end
+
+ private
+
+ def logo_system_path(logo, mount_type)
+ return unless logo&.upload
+
+ # If we're using a CDN, we need to use the full URL
+ asset_host = ActionController::Base.asset_host
+ local_path = Gitlab::Routing.url_helpers.appearance_upload_path(
+ filename: logo.filename,
+ id: logo.upload.model_id,
+ model: 'appearance',
+ mounted_as: mount_type)
+
+ Gitlab::Utils.append_path(asset_host, local_path)
+ end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 4319db42019..88746375c67 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -302,7 +302,8 @@ class ApplicationSetting < ActiveRecord::Base
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
+ commit_email_hostname: default_commit_email_hostname,
+ protected_ci_variables: false
}
end
@@ -311,7 +312,7 @@ class ApplicationSetting < ActiveRecord::Base
end
def self.create_from_defaults
- create(defaults)
+ build_from_defaults.tap(&:save)
end
def self.human_attribute_name(attr, _options = {})
@@ -382,7 +383,7 @@ class ApplicationSetting < ActiveRecord::Base
end
def restricted_visibility_levels=(levels)
- super(levels.map { |level| Gitlab::VisibilityLevel.level_value(level) })
+ super(levels&.map { |level| Gitlab::VisibilityLevel.level_value(level) })
end
def strip_sentry_values
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index baf8adb318b..2d237383e60 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -16,20 +16,24 @@ class BroadcastMessage < ActiveRecord::Base
default_value_for :color, '#E75E40'
default_value_for :font, '#FFFFFF'
- CACHE_KEY = 'broadcast_message_current'.freeze
+ CACHE_KEY = 'broadcast_message_current_json'.freeze
+ LEGACY_CACHE_KEY = 'broadcast_message_current'.freeze
after_commit :flush_redis_cache
def self.current
- messages = Rails.cache.fetch(CACHE_KEY, expires_in: cache_expires_in) { current_and_future_messages.to_a }
+ messages = cache.fetch(CACHE_KEY, as: BroadcastMessage, expires_in: cache_expires_in) do
+ remove_legacy_cache_key
+ current_and_future_messages
+ end
- return messages if messages.empty?
+ return [] unless messages&.present?
now_or_future = messages.select(&:now_or_future?)
# If there are cached entries but none are to be displayed we'll purge the
# cache so we don't keep running this code all the time.
- Rails.cache.delete(CACHE_KEY) if now_or_future.empty?
+ cache.expire(CACHE_KEY) if now_or_future.empty?
now_or_future.select(&:now?)
end
@@ -38,10 +42,22 @@ class BroadcastMessage < ActiveRecord::Base
where('ends_at > :now', now: Time.zone.now).order_id_asc
end
+ def self.cache
+ Gitlab::JsonCache.new(cache_key_with_version: false)
+ end
+
def self.cache_expires_in
nil
end
+ # This can be removed in GitLab 12.0+
+ # The old cache key had an indefinite lifetime, and in an HA
+ # environment a one-shot migration would not work because the cache
+ # would be repopulated by a node that has not been upgraded.
+ def self.remove_legacy_cache_key
+ cache.expire(LEGACY_CACHE_KEY)
+ end
+
def active?
started? && !ended?
end
@@ -67,6 +83,7 @@ class BroadcastMessage < ActiveRecord::Base
end
def flush_redis_cache
- Rails.cache.delete(CACHE_KEY)
+ self.class.cache.expire(CACHE_KEY)
+ self.class.remove_legacy_cache_key
end
end
diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb
new file mode 100644
index 00000000000..29aa00a66d9
--- /dev/null
+++ b/app/models/ci/bridge.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Ci
+ class Bridge < CommitStatus
+ include Importable
+ include AfterCommitQueue
+ include Gitlab::Utils::StrongMemoize
+
+ belongs_to :project
+ validates :ref, presence: true
+
+ def self.retry(bridge, current_user)
+ raise NotImplementedError
+ end
+
+ def tags
+ [:bridge]
+ end
+
+ def detailed_status(current_user)
+ Gitlab::Ci::Status::Bridge::Factory
+ .new(self, current_user)
+ .fabricate!
+ end
+
+ def predefined_variables
+ raise NotImplementedError
+ end
+
+ def execute_hooks
+ raise NotImplementedError
+ end
+ end
+end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index d60861dc95f..16a72c680fa 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -120,7 +120,7 @@ module Ci
acts_as_taggable
- add_authentication_token_field :token
+ add_authentication_token_field :token, encrypted: true, fallback: true
before_save :update_artifacts_size, if: :artifacts_file_changed?
before_save :ensure_token
@@ -742,7 +742,7 @@ module Ci
def collect_test_reports!(test_reports)
test_reports.get_suite(group_name).tap do |test_suite|
each_report(Ci::JobArtifact::TEST_REPORT_FILE_TYPES) do |file_type, blob|
- Gitlab::Ci::Parsers::Test.fabricate!(file_type).parse!(blob, test_suite)
+ Gitlab::Ci::Parsers.fabricate!(file_type).parse!(blob, test_suite)
end
end
end
@@ -840,6 +840,7 @@ module Ci
variables.append(key: 'CI_JOB_NAME', value: name)
variables.append(key: 'CI_JOB_STAGE', value: stage)
variables.append(key: 'CI_COMMIT_SHA', value: sha)
+ variables.append(key: 'CI_COMMIT_SHORT_SHA', value: short_sha)
variables.append(key: 'CI_COMMIT_BEFORE_SHA', value: before_sha)
variables.append(key: 'CI_COMMIT_REF_NAME', value: ref)
variables.append(key: 'CI_COMMIT_REF_SLUG', value: ref_slug)
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 60ff2181a95..25937065011 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -56,11 +56,7 @@ module Ci
validates :tag, inclusion: { in: [false], if: :merge_request? }
validates :status, presence: { unless: :importing? }
validate :valid_commit_sha, unless: :importing?
-
- # Replace validator below with
- # `validates :source, presence: { unless: :importing? }, on: :create`
- # when removing Gitlab.rails5? code.
- validate :valid_source, unless: :importing?, on: :create
+ validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create
after_create :keep_around_commits, unless: :importing?
@@ -68,11 +64,7 @@ module Ci
# this `Hash` with new values.
enum_with_nil source: ::Ci::PipelineEnums.sources
- enum_with_nil config_source: {
- unknown_source: nil,
- repository_source: 1,
- auto_devops_source: 2
- }
+ enum_with_nil config_source: ::Ci::PipelineEnums.config_sources
# We use `Ci::PipelineEnums.failure_reasons` here so that EE can more easily
# extend this `Hash` with new values.
@@ -605,13 +597,18 @@ module Ci
end
def predefined_variables
- Gitlab::Ci::Variables::Collection.new
- .append(key: 'CI_PIPELINE_IID', value: iid.to_s)
- .append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path)
- .append(key: 'CI_PIPELINE_SOURCE', value: source.to_s)
- .append(key: 'CI_COMMIT_MESSAGE', value: git_commit_message.to_s)
- .append(key: 'CI_COMMIT_TITLE', value: git_commit_full_title.to_s)
- .append(key: 'CI_COMMIT_DESCRIPTION', value: git_commit_description.to_s)
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ variables.append(key: 'CI_PIPELINE_IID', value: iid.to_s)
+ variables.append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path)
+ variables.append(key: 'CI_PIPELINE_SOURCE', value: source.to_s)
+ variables.append(key: 'CI_COMMIT_MESSAGE', value: git_commit_message.to_s)
+ variables.append(key: 'CI_COMMIT_TITLE', value: git_commit_full_title.to_s)
+ variables.append(key: 'CI_COMMIT_DESCRIPTION', value: git_commit_description.to_s)
+
+ if merge_request? && merge_request
+ variables.concat(merge_request.predefined_variables)
+ end
+ end
end
def queued_duration
@@ -737,11 +734,5 @@ module Ci
project.repository.keep_around(self.sha, self.before_sha)
end
-
- def valid_source
- if source.nil? || source == "unknown"
- errors.add(:source, "invalid source")
- end
- end
end
end
diff --git a/app/models/ci/pipeline_enums.rb b/app/models/ci/pipeline_enums.rb
index c0f16066e0b..2994aaae4aa 100644
--- a/app/models/ci/pipeline_enums.rb
+++ b/app/models/ci/pipeline_enums.rb
@@ -25,5 +25,15 @@ module Ci
merge_request: 10
}
end
+
+ # Returns the `Hash` to use for creating the `config_sources` enum for
+ # `Ci::Pipeline`.
+ def self.config_sources
+ {
+ unknown_source: nil,
+ repository_source: 1,
+ auto_devops_source: 2
+ }
+ end
end
end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 2693386443a..8249199e76f 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -58,8 +58,7 @@ module Ci
# BACKWARD COMPATIBILITY: There are needed to maintain compatibility with `AVAILABLE_SCOPES` used by `lib/api/runners.rb`
scope :deprecated_shared, -> { instance_type }
- # this should get replaced with `project_type.or(group_type)` once using Rails5
- scope :deprecated_specific, -> { where(runner_type: [runner_types[:project_type], runner_types[:group_type]]) }
+ scope :deprecated_specific, -> { project_type.or(group_type) }
scope :belonging_to_project, -> (project_id) {
joins(:runner_projects).where(ci_runner_projects: { project_id: project_id })
@@ -67,7 +66,7 @@ module Ci
scope :belonging_to_parent_group_of_project, -> (project_id) {
project_groups = ::Group.joins(:projects).where(projects: { id: project_id })
- hierarchy_groups = Gitlab::GroupHierarchy.new(project_groups).base_and_ancestors
+ hierarchy_groups = Gitlab::ObjectHierarchy.new(project_groups).base_and_ancestors
joins(:groups).where(namespaces: { id: hierarchy_groups })
}
diff --git a/app/models/clusters/applications/cert_manager.rb b/app/models/clusters/applications/cert_manager.rb
index 077e2bda143..74ef7c7e145 100644
--- a/app/models/clusters/applications/cert_manager.rb
+++ b/app/models/clusters/applications/cert_manager.rb
@@ -14,6 +14,10 @@ module Clusters
default_value_for :version, VERSION
+ default_value_for :email do |cert_manager|
+ cert_manager.cluster&.user&.email
+ end
+
validates :email, presence: true
def chart
diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb
index c0aaa8dce20..0c72d7d8340 100644
--- a/app/models/clusters/applications/knative.rb
+++ b/app/models/clusters/applications/knative.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Knative < ActiveRecord::Base
- VERSION = '0.1.3'.freeze
+ VERSION = '0.2.2'.freeze
REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts'.freeze
FETCH_IP_ADDRESS_DELAY = 30.seconds
@@ -15,6 +15,9 @@ module Clusters
include ::Clusters::Concerns::ApplicationVersion
include ::Clusters::Concerns::ApplicationData
include AfterCommitQueue
+ include ReactiveCaching
+
+ self.reactive_cache_key = ->(knative) { [knative.class.model_name.singular, knative.id] }
state_machine :status do
before_transition any => [:installed] do |application|
@@ -29,6 +32,8 @@ module Clusters
validates :hostname, presence: true, hostname: true
+ scope :for_cluster, -> (cluster) { where(cluster: cluster) }
+
def chart
'knative/knative'
end
@@ -55,12 +60,39 @@ module Clusters
ClusterWaitForIngressIpAddressWorker.perform_async(name, id)
end
+ def client
+ cluster.kubeclient.knative_client
+ end
+
+ def services
+ with_reactive_cache do |data|
+ data[:services]
+ end
+ end
+
+ def calculate_reactive_cache
+ { services: read_services }
+ end
+
def ingress_service
cluster.kubeclient.get_service('knative-ingressgateway', 'istio-system')
end
- def client
- cluster.platform_kubernetes.kubeclient.knative_client
+ def services_for(ns: namespace)
+ return unless services
+ return [] unless ns
+
+ services.select do |service|
+ service.dig('metadata', 'namespace') == ns
+ end
+ end
+
+ private
+
+ def read_services
+ client.get_services.as_json
+ rescue Kubeclient::ResourceNotFoundError
+ []
end
end
end
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index 67746e34913..c931b340b24 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 < ActiveRecord::Base
- VERSION = '0.1.38'.freeze
+ VERSION = '0.1.39'.freeze
self.table_name = 'clusters_applications_runners'
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index c9bd1728dbd..7fe43cd2de0 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -93,6 +93,16 @@ module Clusters
where('NOT EXISTS (?)', subquery)
end
+ scope :with_knative_installed, -> { joins(:application_knative).merge(Clusters::Applications::Knative.installed) }
+
+ scope :preload_knative, -> {
+ preload(
+ :kubernetes_namespace,
+ :platform_kubernetes,
+ :application_knative
+ )
+ }
+
def self.ancestor_clusters_for_clusterable(clusterable, hierarchy_order: :asc)
hierarchy_groups = clusterable.ancestors_upto(hierarchy_order: hierarchy_order).eager_load(:clusters)
hierarchy_groups = hierarchy_groups.merge(current_scope) if current_scope
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 867f0edcb07..0dc0c4f80d6 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -106,7 +106,7 @@ module Clusters
def terminals(environment)
with_reactive_cache do |data|
pods = filter_by_label(data[:pods], app: environment.slug)
- terminals = pods.flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) }
+ terminals = pods.flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) }.compact
terminals.each { |terminal| add_terminal_auth(terminal, terminal_auth) }
end
end
@@ -228,7 +228,7 @@ module Clusters
return unless namespace_changed?
run_after_commit do
- ClusterPlatformConfigureWorker.perform_async(cluster_id)
+ ClusterConfigureWorker.perform_async(cluster_id)
end
end
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 2c89da88b9b..a422a0995ff 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -177,7 +177,9 @@ class Commit
def title
return full_title if full_title.length < 100
- full_title.truncate(81, separator: ' ', omission: '…')
+ # Use three dots instead of the ellipsis Unicode character because
+ # some clients show the raw Unicode value in the merge commit.
+ full_title.truncate(81, separator: ' ', omission: '...')
end
# Returns the full commits title
diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb
index b42236c1fa2..4687ec7d166 100644
--- a/app/models/concerns/avatarable.rb
+++ b/app/models/concerns/avatarable.rb
@@ -43,7 +43,18 @@ module Avatarable
end
def avatar_path(only_path: true, size: nil)
- return unless self[:avatar].present?
+ unless self.try(:id)
+ return uncached_avatar_path(only_path: only_path, size: size)
+ end
+
+ # Cache this avatar path only within the request because avatars in
+ # object storage may be generated with time-limited, signed URLs.
+ key = "#{self.class.name}:#{self.id}:#{only_path}:#{size}"
+ Gitlab::SafeRequestStore[key] ||= uncached_avatar_path(only_path: only_path, size: size)
+ end
+
+ def uncached_avatar_path(only_path: true, size: nil)
+ return unless self.try(:avatar).present?
asset_host = ActionController::Base.asset_host
use_asset_host = asset_host.present?
diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb
index 60b7ec2815c..14bc56f0eee 100644
--- a/app/models/concerns/awardable.rb
+++ b/app/models/concerns/awardable.rb
@@ -43,14 +43,19 @@ module Awardable
end
def order_upvotes_desc
- order_votes_desc(AwardEmoji::UPVOTE_NAME)
+ order_votes(AwardEmoji::UPVOTE_NAME, 'DESC')
+ end
+
+ def order_upvotes_asc
+ order_votes(AwardEmoji::UPVOTE_NAME, 'ASC')
end
def order_downvotes_desc
- order_votes_desc(AwardEmoji::DOWNVOTE_NAME)
+ order_votes(AwardEmoji::DOWNVOTE_NAME, 'DESC')
end
- def order_votes_desc(emoji_name)
+ # Order votes by emoji, optional sort order param `descending` defaults to true
+ def order_votes(emoji_name, direction)
awardable_table = self.arel_table
awards_table = AwardEmoji.arel_table
@@ -62,7 +67,7 @@ module Awardable
)
).join_sources
- joins(join_clause).group(awardable_table[:id]).reorder("COUNT(award_emoji.id) DESC")
+ joins(join_clause).group(awardable_table[:id]).reorder("COUNT(award_emoji.id) #{direction}")
end
end
diff --git a/app/models/concerns/cacheable_attributes.rb b/app/models/concerns/cacheable_attributes.rb
index 75592bb63e2..3d60f6924c1 100644
--- a/app/models/concerns/cacheable_attributes.rb
+++ b/app/models/concerns/cacheable_attributes.rb
@@ -23,7 +23,12 @@ module CacheableAttributes
end
def build_from_defaults(attributes = {})
- new(defaults.merge(attributes))
+ final_attributes = defaults
+ .merge(attributes)
+ .stringify_keys
+ .slice(*column_names)
+
+ new(final_attributes)
end
def cached
diff --git a/app/models/concerns/descendant.rb b/app/models/concerns/descendant.rb
new file mode 100644
index 00000000000..4c436522122
--- /dev/null
+++ b/app/models/concerns/descendant.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Descendant
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def supports_nested_objects?
+ Gitlab::Database.postgresql?
+ end
+ end
+end
diff --git a/app/models/concerns/discussion_on_diff.rb b/app/models/concerns/discussion_on_diff.rb
index c180d7b7c9a..e4e5928f5cf 100644
--- a/app/models/concerns/discussion_on_diff.rb
+++ b/app/models/concerns/discussion_on_diff.rb
@@ -9,7 +9,7 @@ module DiscussionOnDiff
included do
delegate :line_code,
:original_line_code,
- :diff_file,
+ :note_diff_file,
:diff_line,
:active?,
:created_at_diff?,
@@ -38,12 +38,14 @@ module DiscussionOnDiff
end
# Returns an array of at most 16 highlighted lines above a diff note
- def truncated_diff_lines(highlight: true)
+ def truncated_diff_lines(highlight: true, diff_limit: nil)
+ return [] unless on_text?
return [] if diff_line.nil? && first_note.is_a?(LegacyDiffNote)
+ diff_limit = [diff_limit, NUMBER_OF_TRUNCATED_DIFF_LINES].compact.min
lines = highlight ? highlighted_diff_lines : diff_lines
- initial_line_index = [diff_line.index - NUMBER_OF_TRUNCATED_DIFF_LINES + 1, 0].max
+ initial_line_index = [diff_line.index - diff_limit + 1, 0].max
prev_lines = []
@@ -58,6 +60,13 @@ module DiscussionOnDiff
prev_lines
end
+ def diff_file
+ strong_memoize(:diff_file) do
+ # Falling back here is important as `note_diff_files` are created async.
+ fetch_preloaded_diff_file || first_note.diff_file
+ end
+ end
+
def line_code_in_diffs(diff_refs)
if active?(diff_refs)
line_code
@@ -65,4 +74,15 @@ module DiscussionOnDiff
original_line_code
end
end
+
+ private
+
+ def fetch_preloaded_diff_file
+ fetch_preloaded_diff =
+ context_noteable &&
+ context_noteable.preloads_discussion_diff_highlighting? &&
+ note_diff_file
+
+ context_noteable.discussions_diffs.find_by_id(note_diff_file.id) if fetch_preloaded_diff
+ end
end
diff --git a/app/models/concerns/enum_with_nil.rb b/app/models/concerns/enum_with_nil.rb
index 23acfe9a55f..6d0a21cf070 100644
--- a/app/models/concerns/enum_with_nil.rb
+++ b/app/models/concerns/enum_with_nil.rb
@@ -16,7 +16,7 @@ module EnumWithNil
# E.g. for enum_with_nil failure_reason: { unknown_failure: nil }
# this overrides auto-generated method `unknown_failure?`
define_method("#{key_with_nil}?") do
- Gitlab.rails5? ? self[name].nil? : super()
+ self[name].nil?
end
# E.g. for enum_with_nil failure_reason: { unknown_failure: nil }
@@ -24,7 +24,6 @@ module EnumWithNil
define_method(name) do
orig = super()
- return orig unless Gitlab.rails5?
return orig unless orig.nil?
self.class.public_send(name.to_s.pluralize).key(nil) # rubocop:disable GitlabSecurity/PublicSend
diff --git a/app/models/concerns/fast_destroy_all.rb b/app/models/concerns/fast_destroy_all.rb
index 2bfa7da6c1c..1e3afd641ed 100644
--- a/app/models/concerns/fast_destroy_all.rb
+++ b/app/models/concerns/fast_destroy_all.rb
@@ -70,13 +70,14 @@ module FastDestroyAll
module Helpers
extend ActiveSupport::Concern
+ include AfterCommitQueue
class_methods do
##
# This method is to be defined on models which have fast destroyable models as children,
# and let us avoid to use `dependent: :destroy` hook
- def use_fast_destroy(relation)
- before_destroy(prepend: true) do
+ def use_fast_destroy(relation, opts = {})
+ set_callback :destroy, :before, opts.merge(prepend: true) do
perform_fast_destroy(public_send(relation)) # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 5080fe03cc8..0d363ec68b7 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -145,14 +145,16 @@ 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 'milestone' then order_milestone_due_asc
- when 'milestone_due_asc' then order_milestone_due_asc
- when 'milestone_due_desc' then order_milestone_due_desc
- when 'popularity' then order_upvotes_desc
- when 'priority' then order_due_date_and_labels_priority(excluded_labels: excluded_labels)
- when 'upvotes_desc' then order_upvotes_desc
+ 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
else order_by(method)
end
@@ -160,7 +162,7 @@ module Issuable
sorted.with_order_id_desc
end
- def order_due_date_and_labels_priority(excluded_labels: [])
+ def order_due_date_and_labels_priority(direction = 'ASC', excluded_labels: [])
# The order_ methods also modify the query in other ways:
#
# - For milestones, we add a JOIN.
@@ -177,11 +179,11 @@ module Issuable
order_milestone_due_asc
.order_labels_priority(excluded_labels: excluded_labels, extra_select_columns: [milestones_due_date])
- .reorder(Gitlab::Database.nulls_last_order(milestones_due_date, 'ASC'),
- Gitlab::Database.nulls_last_order('highest_priority', 'ASC'))
+ .reorder(Gitlab::Database.nulls_last_order(milestones_due_date, direction),
+ Gitlab::Database.nulls_last_order('highest_priority', direction))
end
- def order_labels_priority(excluded_labels: [], extra_select_columns: [])
+ def order_labels_priority(direction = 'ASC', excluded_labels: [], extra_select_columns: [])
params = {
target_type: name,
target_column: "#{table_name}.id",
@@ -198,7 +200,7 @@ module Issuable
select(select_columns.join(', '))
.group(arel_table[:id])
- .reorder(Gitlab::Database.nulls_last_order('highest_priority', 'ASC'))
+ .reorder(Gitlab::Database.nulls_last_order('highest_priority', direction))
end
def with_label(title, sort = nil)
diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb
index eb315058c3a..29476654bf7 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -26,10 +26,18 @@ module Noteable
DiscussionNote.noteable_types.include?(base_class_name)
end
+ def supports_suggestion?
+ false
+ end
+
def discussions_rendered_on_frontend?
false
end
+ def preloads_discussion_diff_highlighting?
+ false
+ end
+
def discussion_notes
notes
end
diff --git a/app/models/concerns/redis_cacheable.rb b/app/models/concerns/redis_cacheable.rb
index 69554f18ea2..4bb4ffe2a8e 100644
--- a/app/models/concerns/redis_cacheable.rb
+++ b/app/models/concerns/redis_cacheable.rb
@@ -49,10 +49,6 @@ module RedisCacheable
end
def cast_value_from_cache(attribute, value)
- if Gitlab.rails5?
- self.class.type_for_attribute(attribute.to_s).cast(value)
- else
- self.class.column_for_attribute(attribute).type_cast_from_database(value)
- end
+ self.class.type_for_attribute(attribute.to_s).cast(value)
end
end
diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb
index af699eeebce..498996f4f80 100644
--- a/app/models/concerns/storage/legacy_namespace.rb
+++ b/app/models/concerns/storage/legacy_namespace.rb
@@ -4,6 +4,8 @@ module Storage
module LegacyNamespace
extend ActiveSupport::Concern
+ include Gitlab::ShellAdapter
+
def move_dir
proj_with_tags = first_project_with_container_registry_tags
diff --git a/app/models/concerns/with_uploads.rb b/app/models/concerns/with_uploads.rb
index 2bdef2a40e4..d79c0eae77e 100644
--- a/app/models/concerns/with_uploads.rb
+++ b/app/models/concerns/with_uploads.rb
@@ -17,6 +17,8 @@
module WithUploads
extend ActiveSupport::Concern
+ include FastDestroyAll::Helpers
+ include FeatureGate
# Currently there is no simple way how to select only not-mounted
# uploads, it should be all FileUploaders so we select them by
@@ -25,21 +27,40 @@ module WithUploads
included do
has_many :uploads, as: :model
+ has_many :file_uploads, -> { where(uploader: FILE_UPLOADERS) }, class_name: 'Upload', as: :model
- before_destroy :destroy_file_uploads
+ # TODO: when feature flag is removed, we can use just dependent: destroy
+ # option on :file_uploads
+ before_destroy :remove_file_uploads
+
+ use_fast_destroy :file_uploads, if: :fast_destroy_enabled?
+ end
+
+ def retrieve_upload(_identifier, paths)
+ uploads.find_by(path: paths)
end
+ private
+
# mounted uploads are deleted in carrierwave's after_commit hook,
# but FileUploaders which are not mounted must be deleted explicitly and
# it can not be done in after_commit because FileUploader requires loads
# associated model on destroy (which is already deleted in after_commit)
- def destroy_file_uploads
- self.uploads.where(uploader: FILE_UPLOADERS).find_each do |upload|
+ def remove_file_uploads
+ fast_destroy_enabled? ? delete_uploads : destroy_uploads
+ end
+
+ def delete_uploads
+ file_uploads.delete_all(:delete_all)
+ end
+
+ def destroy_uploads
+ file_uploads.find_each do |upload|
upload.destroy
end
end
- def retrieve_upload(_identifier, paths)
- uploads.find_by(path: paths)
+ def fast_destroy_enabled?
+ Feature.enabled?(:fast_destroy_uploads, self)
end
end
diff --git a/app/models/dashboard_group_milestone.rb b/app/models/dashboard_group_milestone.rb
index 32e8104125c..ad0bb55f0a7 100644
--- a/app/models/dashboard_group_milestone.rb
+++ b/app/models/dashboard_group_milestone.rb
@@ -5,7 +5,6 @@ class DashboardGroupMilestone < GlobalMilestone
attr_reader :group_name
- override :initialize
def initialize(milestone)
super(milestone.title, Array(milestone))
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index c32008aa9c7..279603496b0 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -66,10 +66,23 @@ class DiffNote < Note
self.original_position.diff_refs == diff_refs
end
+ def supports_suggestion?
+ return false unless noteable.supports_suggestion? && on_text?
+ # We don't want to trigger side-effects of `diff_file` call.
+ return false unless file = fetch_diff_file
+ return false unless line = file.line_for_position(self.original_position)
+
+ line&.suggestible?
+ end
+
def discussion_first_note?
self == discussion.first_note
end
+ def banzai_render_context(field)
+ super.merge(suggestions_filter_enabled: supports_suggestion?)
+ end
+
private
def enqueue_diff_file_creation_job
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 934828946b9..cdfe3b7c023 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
class Environment < ActiveRecord::Base
+ include Gitlab::Utils::StrongMemoize
# Used to generate random suffixes for the slug
LETTERS = 'a'..'z'
NUMBERS = '0'..'9'
@@ -231,7 +232,9 @@ class Environment < ActiveRecord::Base
end
def deployment_platform
- project.deployment_platform(environment: self.name)
+ strong_memoize(:deployment_platform) do
+ project.deployment_platform(environment: self.name)
+ end
end
private
diff --git a/app/models/event.rb b/app/models/event.rb
index 2e690f8c013..6a35bca72c5 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -87,7 +87,7 @@ class Event < ActiveRecord::Base
scope :with_associations, -> do
# We're using preload for "push_event_payload" as otherwise the association
# is not always available (depending on the query being built).
- includes(:author, :project, project: :namespace)
+ includes(:author, :project, project: [:project_feature, :import_data, :namespace])
.preload(:target, :push_event_payload)
end
@@ -114,19 +114,6 @@ class Event < ActiveRecord::Base
end
end
- # Remove this method when removing Gitlab.rails5? code.
- def subclass_from_attributes(attrs)
- return super if Gitlab.rails5?
-
- # Without this Rails will keep calling this method on the returned class,
- # resulting in an infinite loop.
- return unless self == Event
-
- action = attrs.with_indifferent_access[inheritance_column].to_i
-
- PushEvent if action == PUSHED
- end
-
# Update Gitlab::ContributionsCalendar#activity_dates if this changes
def contributions
where("action = ? OR (target_type IN (?) AND action IN (?)) OR (target_type = ? AND action = ?)",
diff --git a/app/models/group.rb b/app/models/group.rb
index 233747cc2c2..edac2444c4d 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -10,6 +10,7 @@ class Group < Namespace
include Referable
include SelectForProjectAuthorization
include LoadedInGroupList
+ include Descendant
include GroupDescendant
include TokenAuthenticatable
include WithUploads
@@ -63,10 +64,6 @@ class Group < Namespace
after_update :path_changed_hook, if: :path_changed?
class << self
- def supports_nested_groups?
- Gitlab::Database.postgresql?
- end
-
def sort_by_attribute(method)
if method == 'storage_size_desc'
# storage_size is a virtual column so we need to
diff --git a/app/models/member.rb b/app/models/member.rb
index bc8ac14d148..9fc95ea00c3 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -7,6 +7,7 @@ class Member < ActiveRecord::Base
include Expirable
include Gitlab::Access
include Presentable
+ include Gitlab::Utils::StrongMemoize
attr_accessor :raw_invite_token
@@ -22,6 +23,7 @@ class Member < ActiveRecord::Base
message: "already exists in source",
allow_nil: true }
validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true
+ validate :higher_access_level_than_group, unless: :importing?
validates :invite_email,
presence: {
if: :invite?
@@ -364,6 +366,15 @@ class Member < ActiveRecord::Base
end
# rubocop: enable CodeReuse/ServiceClass
+ # Find the user's group member with a highest access level
+ def highest_group_member
+ strong_memoize(:highest_group_member) do
+ next unless user_id && source&.ancestors&.any?
+
+ GroupMember.where(source: source.ancestors, user_id: user_id).order(:access_level).last
+ end
+ end
+
private
def send_invite
@@ -430,4 +441,12 @@ class Member < ActiveRecord::Base
def notifiable_options
{}
end
+
+ def higher_access_level_than_group
+ if highest_group_member && highest_group_member.access_level >= access_level
+ error_parameters = { access: highest_group_member.human_access, group_name: highest_group_member.group.name }
+
+ errors.add(:access_level, s_("should be higher than %{access} inherited membership from group %{group_name}") % error_parameters)
+ end
+ end
end
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index 537f2a3a231..016c18ce6c8 100644
--- a/app/models/members/project_member.rb
+++ b/app/models/members/project_member.rb
@@ -3,8 +3,6 @@
class ProjectMember < Member
SOURCE_TYPE = 'Project'.freeze
- include Gitlab::ShellAdapter
-
belongs_to :project, foreign_key: 'source_id'
# Make sure project member points only to project as it source
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index f40dff7c1bd..b937bef100b 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -48,8 +48,8 @@ class MergeRequest < ActiveRecord::Base
# is the inverse of MergeRequest#merge_request_diff, which means it may not be
# the latest diff, because we could have loaded any diff from this particular
# MR. If we haven't already loaded a diff, then it's fine to load the latest.
- def merge_request_diff(*args)
- fallback = latest_merge_request_diff if args.empty? && !association(:merge_request_diff).loaded?
+ def merge_request_diff
+ fallback = latest_merge_request_diff unless association(:merge_request_diff).loaded?
fallback || super
end
@@ -363,6 +363,10 @@ class MergeRequest < ActiveRecord::Base
end
end
+ def supports_suggestion?
+ true
+ end
+
# Calls `MergeWorker` to proceed with the merge process and
# updates `merge_jid` with the MergeWorker#jid.
# This helps tracking enqueued and ongoing merge jobs.
@@ -404,6 +408,28 @@ class MergeRequest < ActiveRecord::Base
merge_request_diffs.where.not(id: merge_request_diff.id)
end
+ def preloads_discussion_diff_highlighting?
+ true
+ end
+
+ def preload_discussions_diff_highlight
+ preloadable_files = note_diff_files.for_commit_or_unresolved
+
+ discussions_diffs.load_highlight(preloadable_files.pluck(:id))
+ end
+
+ def discussions_diffs
+ strong_memoize(:discussions_diffs) do
+ Gitlab::DiscussionsDiff::FileCollection.new(note_diff_files.to_a)
+ end
+ end
+
+ def note_diff_files
+ NoteDiffFile
+ .where(diff_note: discussion_notes)
+ .includes(diff_note: :project)
+ end
+
def diff_size
# Calling `merge_request_diff.diffs.real_size` will also perform
# highlighting, which we don't need here.
@@ -539,15 +565,26 @@ class MergeRequest < ActiveRecord::Base
def validate_branches
if target_project == source_project && target_branch == source_branch
- errors.add :branch_conflict, "You can not use same project/branch for source and target"
+ errors.add :branch_conflict, "You can't use same project/branch for source and target"
+ return
end
if opened?
- similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.try(:id)).opened
- similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id
- if similar_mrs.any?
- errors.add :validate_branches,
- "Cannot Create: This merge request already exists: #{similar_mrs.pluck(:title)}"
+ similar_mrs = target_project
+ .merge_requests
+ .where(source_branch: source_branch, target_branch: target_branch)
+ .where(source_project_id: source_project&.id)
+ .opened
+
+ similar_mrs = similar_mrs.where.not(id: id) if persisted?
+
+ conflict = similar_mrs.first
+
+ if conflict.present?
+ errors.add(
+ :validate_branches,
+ "Another open merge request already exists for this source branch: #{conflict.to_reference}"
+ )
end
end
end
@@ -604,10 +641,6 @@ class MergeRequest < ActiveRecord::Base
end
end
- def reload_merge_request_diff
- merge_request_diff(true)
- end
-
def viewable_diffs
@viewable_diffs ||= merge_request_diffs.viewable.to_a
end
@@ -967,6 +1000,7 @@ class MergeRequest < ActiveRecord::Base
def mergeable_ci_state?
return true unless project.only_allow_merge_if_pipeline_succeeds?
+ return true unless head_pipeline
actual_head_pipeline&.success? || actual_head_pipeline&.skipped?
end
@@ -1070,14 +1104,53 @@ class MergeRequest < ActiveRecord::Base
actual_head_pipeline&.has_test_reports?
end
- # rubocop: disable CodeReuse/ServiceClass
+ def predefined_variables
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ variables.append(key: 'CI_MERGE_REQUEST_ID', value: id.to_s)
+ variables.append(key: 'CI_MERGE_REQUEST_IID', value: iid.to_s)
+
+ variables.append(key: 'CI_MERGE_REQUEST_REF_PATH',
+ value: ref_path.to_s)
+
+ variables.append(key: 'CI_MERGE_REQUEST_PROJECT_ID',
+ value: project.id.to_s)
+
+ variables.append(key: 'CI_MERGE_REQUEST_PROJECT_PATH',
+ value: project.full_path)
+
+ variables.append(key: 'CI_MERGE_REQUEST_PROJECT_URL',
+ value: project.web_url)
+
+ variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME',
+ value: target_branch.to_s)
+
+ if source_project
+ variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID',
+ value: source_project.id.to_s)
+
+ variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH',
+ value: source_project.full_path)
+
+ variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL',
+ value: source_project.web_url)
+
+ variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME',
+ value: source_branch.to_s)
+ end
+ end
+ end
+
def compare_test_reports
unless has_test_reports?
return { status: :error, status_reason: 'This merge request does not have test reports' }
end
- with_reactive_cache(:compare_test_results) do |data|
- unless Ci::CompareTestReportsService.new(project)
+ compare_reports(Ci::CompareTestReportsService)
+ end
+
+ def compare_reports(service_class)
+ with_reactive_cache(service_class.name) do |data|
+ unless service_class.new(project)
.latest?(base_pipeline, actual_head_pipeline, data)
raise InvalidateReactiveCache
end
@@ -1085,19 +1158,14 @@ class MergeRequest < ActiveRecord::Base
data
end || { status: :parsing }
end
- # rubocop: enable CodeReuse/ServiceClass
- # rubocop: disable CodeReuse/ServiceClass
def calculate_reactive_cache(identifier, *args)
- case identifier.to_sym
- when :compare_test_results
- Ci::CompareTestReportsService.new(project).execute(
- base_pipeline, actual_head_pipeline)
- else
- raise NotImplementedError, "Unknown identifier: #{identifier}"
- end
+ service_class = identifier.constantize
+
+ raise NameError, service_class unless service_class < Ci::CompareReportsBaseService
+
+ service_class.new(project).execute(base_pipeline, actual_head_pipeline)
end
- # rubocop: enable CodeReuse/ServiceClass
def all_commits
# MySQL doesn't support LIMIT in a subquery.
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 3cc8e2c44bb..6dc0fca68e6 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -212,7 +212,7 @@ class Milestone < ActiveRecord::Base
end
def reference_link_text(from = nil)
- self.title
+ self.class.reference_prefix + self.title
end
def milestoneish_ids
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 8865c164b11..a0bebc5e9a2 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -3,7 +3,6 @@
class Namespace < ActiveRecord::Base
include CacheMarkdownField
include Sortable
- include Gitlab::ShellAdapter
include Gitlab::VisibilityLevel
include Routable
include AfterCommitQueue
@@ -176,16 +175,16 @@ class Namespace < ActiveRecord::Base
# Returns all ancestors, self, and descendants of the current namespace.
def self_and_hierarchy
- Gitlab::GroupHierarchy
+ Gitlab::ObjectHierarchy
.new(self.class.where(id: id))
- .all_groups
+ .all_objects
end
# Returns all the ancestors of the current namespaces.
def ancestors
return self.class.none unless parent_id
- Gitlab::GroupHierarchy
+ Gitlab::ObjectHierarchy
.new(self.class.where(id: parent_id))
.base_and_ancestors
end
@@ -193,27 +192,27 @@ class Namespace < ActiveRecord::Base
# returns all ancestors upto but excluding the given namespace
# when no namespace is given, all ancestors upto the top are returned
def ancestors_upto(top = nil, hierarchy_order: nil)
- Gitlab::GroupHierarchy.new(self.class.where(id: id))
+ Gitlab::ObjectHierarchy.new(self.class.where(id: id))
.ancestors(upto: top, hierarchy_order: hierarchy_order)
end
def self_and_ancestors
return self.class.where(id: id) unless parent_id
- Gitlab::GroupHierarchy
+ Gitlab::ObjectHierarchy
.new(self.class.where(id: id))
.base_and_ancestors
end
# Returns all the descendants of the current namespace.
def descendants
- Gitlab::GroupHierarchy
+ Gitlab::ObjectHierarchy
.new(self.class.where(parent_id: id))
.base_and_descendants
end
def self_and_descendants
- Gitlab::GroupHierarchy
+ Gitlab::ObjectHierarchy
.new(self.class.where(id: id))
.base_and_descendants
end
@@ -294,7 +293,7 @@ class Namespace < ActiveRecord::Base
end
def force_share_with_group_lock_on_descendants
- return unless Group.supports_nested_groups?
+ return unless Group.supports_nested_objects?
# We can't use `descendants.update_all` since Rails will throw away the WITH
# RECURSIVE statement. We also can't use WHERE EXISTS since we can't use
@@ -307,6 +306,7 @@ class Namespace < ActiveRecord::Base
def write_projects_repository_config
all_projects.find_each do |project|
project.write_repository_config
+ project.track_project_repository
end
end
end
diff --git a/app/models/note.rb b/app/models/note.rb
index a6ae4f58ac4..becf14e9785 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -69,6 +69,12 @@ class Note < ActiveRecord::Base
belongs_to :last_edited_by, class_name: 'User'
has_many :todos
+
+ # The delete_all definition is required here in order
+ # to generate the correct DELETE sql for
+ # suggestions.delete_all calls
+ has_many :suggestions, -> { order(:relative_order) },
+ inverse_of: :note, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :system_note_metadata
has_one :note_diff_file, inverse_of: :diff_note, foreign_key: :diff_note_id
@@ -110,7 +116,7 @@ class Note < ActiveRecord::Base
scope :inc_author, -> { includes(:author) }
scope :inc_relations_for_view, -> do
includes(:project, { author: :status }, :updated_by, :resolved_by, :award_emoji,
- :system_note_metadata, :note_diff_file)
+ :system_note_metadata, :note_diff_file, :suggestions)
end
scope :with_notes_filter, -> (notes_filter) do
@@ -131,7 +137,7 @@ class Note < ActiveRecord::Base
scope :with_associations, -> do
# FYI noteable cannot be loaded for LegacyDiffNote for commits
includes(:author, :noteable, :updated_by,
- project: [:project_members, { group: [:group_members] }])
+ project: [:project_members, :namespace, { group: [:group_members] }])
end
scope :with_metadata, -> { includes(:system_note_metadata) }
@@ -226,6 +232,10 @@ class Note < ActiveRecord::Base
Gitlab::HookData::NoteBuilder.new(self).build
end
+ def supports_suggestion?
+ false
+ end
+
def for_commit?
noteable_type == "Commit"
end
diff --git a/app/models/note_diff_file.rb b/app/models/note_diff_file.rb
index 27aef7adc48..e369122003e 100644
--- a/app/models/note_diff_file.rb
+++ b/app/models/note_diff_file.rb
@@ -3,7 +3,22 @@
class NoteDiffFile < ActiveRecord::Base
include DiffFile
+ scope :for_commit_or_unresolved, -> do
+ joins(:diff_note).where("resolved_at IS NULL OR noteable_type = 'Commit'")
+ end
+
+ delegate :original_position, :project, to: :diff_note
+
belongs_to :diff_note, inverse_of: :note_diff_file
validates :diff_note, presence: true
+
+ def raw_diff_file
+ raw_diff = Gitlab::Git::Diff.new(to_hash)
+
+ Gitlab::Diff::File.new(raw_diff,
+ repository: project.repository,
+ diff_refs: original_position.diff_refs,
+ unique_identifier: id)
+ end
end
diff --git a/app/models/pool_repository.rb b/app/models/pool_repository.rb
index bad0e30ceb5..ad6a008dee8 100644
--- a/app/models/pool_repository.rb
+++ b/app/models/pool_repository.rb
@@ -1,12 +1,104 @@
# frozen_string_literal: true
+# The PoolRepository model is the database equivalent of an ObjectPool for Gitaly
+# That is; PoolRepository is the record in the database, ObjectPool is the
+# repository on disk
class PoolRepository < ActiveRecord::Base
include Shardable
+ include AfterCommitQueue
+
+ has_one :source_project, class_name: 'Project'
+ validates :source_project, presence: true
has_many :member_projects, class_name: 'Project'
after_create :correct_disk_path
+ state_machine :state, initial: :none do
+ state :scheduled
+ state :ready
+ state :failed
+ state :obsolete
+
+ event :schedule do
+ transition none: :scheduled
+ end
+
+ event :mark_ready do
+ transition [:scheduled, :failed] => :ready
+ end
+
+ event :mark_failed do
+ transition all => :failed
+ end
+
+ event :mark_obsolete do
+ transition all => :obsolete
+ end
+
+ state all - [:ready] do
+ def joinable?
+ false
+ end
+ end
+
+ state :ready do
+ def joinable?
+ true
+ end
+ end
+
+ after_transition none: :scheduled do |pool, _|
+ pool.run_after_commit do
+ ::ObjectPool::CreateWorker.perform_async(pool.id)
+ end
+ end
+
+ after_transition scheduled: :ready do |pool, _|
+ pool.run_after_commit do
+ ::ObjectPool::ScheduleJoinWorker.perform_async(pool.id)
+ end
+ end
+
+ after_transition any => :obsolete do |pool, _|
+ pool.run_after_commit do
+ ::ObjectPool::DestroyWorker.perform_async(pool.id)
+ end
+ end
+ end
+
+ def create_object_pool
+ object_pool.create
+ end
+
+ # The members of the pool should have fetched the missing objects to their own
+ # objects directory. If the caller fails to do so, data loss might occur
+ def delete_object_pool
+ object_pool.delete
+ end
+
+ def link_repository(repository)
+ object_pool.link(repository.raw)
+ end
+
+ # This RPC can cause data loss, as not all objects are present the local repository
+ def unlink_repository(repository)
+ object_pool.unlink_repository(repository.raw)
+
+ mark_obsolete unless member_projects.where.not(id: repository.project.id).exists?
+ end
+
+ def object_pool
+ @object_pool ||= Gitlab::Git::ObjectPool.new(
+ shard.name,
+ disk_path + '.git',
+ source_project.repository.raw)
+ end
+
+ def inspect
+ "#<#{self.class.name} id:#{id} state:#{state} disk_path:#{disk_path} source_project: #{source_project.full_path}>"
+ end
+
private
def correct_disk_path
diff --git a/app/models/project.rb b/app/models/project.rb
index 587bada469e..09e2a6114fe 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -256,7 +256,7 @@ class Project < ActiveRecord::Base
# other pipelines, like webide ones, that we won't retrieve
# if we use this relation.
has_many :ci_pipelines,
- -> { Feature.enabled?(:pipeline_ci_sources_only, default_enabled: true) ? ci_sources : all },
+ -> { ci_sources },
class_name: 'Ci::Pipeline',
inverse_of: :project
has_many :stages, class_name: 'Ci::Stage', inverse_of: :project
@@ -339,6 +339,7 @@ class Project < ActiveRecord::Base
presence: true,
inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } }
validates :variables, variable_duplicates: { scope: :environment_scope }
+ validates :bfg_object_map, file_size: { maximum: :max_attachment_size }
# Scopes
scope :pending_delete, -> { where(pending_delete: true) }
@@ -412,6 +413,9 @@ class Project < ActiveRecord::Base
only_integer: true,
message: 'needs to be beetween 10 minutes and 1 month' }
+ # Used by Projects::CleanupService to hold a map of rewritten object IDs
+ mount_uploader :bfg_object_map, AttachmentUploader
+
# Returns a project, if it is not about to be removed.
#
# id - The ID of the project to retrieve.
@@ -566,10 +570,12 @@ class Project < ActiveRecord::Base
# returns all ancestor-groups upto but excluding the given namespace
# when no namespace is given, all ancestors upto the top are returned
def ancestors_upto(top = nil, hierarchy_order: nil)
- Gitlab::GroupHierarchy.new(Group.where(id: namespace_id))
+ Gitlab::ObjectHierarchy.new(Group.where(id: namespace_id))
.base_and_ancestors(upto: top, hierarchy_order: hierarchy_order)
end
+ alias_method :ancestors, :ancestors_upto
+
def lfs_enabled?
return namespace.lfs_enabled? if self[:lfs_enabled].nil?
@@ -649,6 +655,11 @@ class Project < ActiveRecord::Base
end
end
+ def latest_successful_build_for(job_name, ref = default_branch)
+ builds = latest_successful_builds_for(ref)
+ builds.find_by!(name: job_name)
+ end
+
def merge_base_commit(first_commit_id, second_commit_id)
sha = repository.merge_base(first_commit_id, second_commit_id)
commit_by(oid: sha) if sha
@@ -738,15 +749,9 @@ class Project < ActiveRecord::Base
return if data.nil? && credentials.nil?
project_import_data = import_data || build_import_data
- if data
- project_import_data.data ||= {}
- project_import_data.data = project_import_data.data.merge(data)
- end
- if credentials
- project_import_data.credentials ||= {}
- project_import_data.credentials = project_import_data.credentials.merge(credentials)
- end
+ project_import_data.merge_data(data.to_h)
+ project_import_data.merge_credentials(credentials.to_h)
project_import_data
end
@@ -1239,10 +1244,8 @@ class Project < ActiveRecord::Base
end
def track_project_repository
- return unless hashed_storage?(:repository)
-
- project_repo = project_repository || build_project_repository
- project_repo.update!(shard_name: repository_storage, disk_path: disk_path)
+ repository = project_repository || build_project_repository
+ repository.update!(shard_name: repository_storage, disk_path: disk_path)
end
def create_repository(force: false)
@@ -1579,6 +1582,7 @@ class Project < ActiveRecord::Base
import_state.remove_jid
update_project_counter_caches
after_create_default_branch
+ join_pool_repository
refresh_markdown_cache!
end
@@ -1971,8 +1975,56 @@ class Project < ActiveRecord::Base
Ability.allowed?(user, :read_project_snippet, self)
end
+ def max_attachment_size
+ Gitlab::CurrentSettings.max_attachment_size.megabytes.to_i
+ end
+
+ def object_pool_params
+ return {} unless !forked? && git_objects_poolable?
+
+ {
+ repository_storage: repository_storage,
+ pool_repository: pool_repository || create_new_pool_repository
+ }
+ end
+
+ # Git objects are only poolable when the project is or has:
+ # - Hashed storage -> The object pool will have a remote to its members, using relative paths.
+ # If the repository path changes we would have to update the remote.
+ # - Public -> User will be able to fetch Git objects that might not exist
+ # in their own repository.
+ # - Repository -> Else the disk path will be empty, and there's nothing to pool
+ def git_objects_poolable?
+ hashed_storage?(:repository) &&
+ public? &&
+ repository_exists? &&
+ Gitlab::CurrentSettings.hashed_storage_enabled &&
+ Feature.enabled?(:object_pools, self)
+ end
+
+ def leave_pool_repository
+ pool_repository&.unlink_repository(repository)
+ end
+
private
+ def create_new_pool_repository
+ pool = begin
+ create_pool_repository!(shard: Shard.by_name(repository_storage), source_project: self)
+ rescue ActiveRecord::RecordNotUnique
+ pool_repository(true)
+ end
+
+ pool.schedule unless pool.scheduled?
+ pool
+ end
+
+ def join_pool_repository
+ return unless pool_repository
+
+ ObjectPool::JoinWorker.perform_async(pool_repository.id, self.id)
+ end
+
def use_hashed_storage
if self.new_record? && Gitlab::CurrentSettings.hashed_storage_enabled
self.storage_version = LATEST_STORAGE_VERSION
diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb
index 2c3080c6d8d..525725034a5 100644
--- a/app/models/project_import_data.rb
+++ b/app/models/project_import_data.rb
@@ -22,4 +22,12 @@ class ProjectImportData < ActiveRecord::Base
# bang doesn't work here - attr_encrypted makes it not to work
self.credentials = self.credentials.deep_symbolize_keys unless self.credentials.blank?
end
+
+ def merge_data(hash)
+ self.data = data.to_h.merge(hash) unless hash.empty?
+ end
+
+ def merge_credentials(hash)
+ self.credentials = credentials.to_h.merge(hash) unless hash.empty?
+ end
end
diff --git a/app/models/prometheus_metric.rb b/app/models/prometheus_metric.rb
index ce2db9cb44c..defbade1ed6 100644
--- a/app/models/prometheus_metric.rb
+++ b/app/models/prometheus_metric.rb
@@ -5,11 +5,12 @@ class PrometheusMetric < ActiveRecord::Base
enum group: {
# built-in groups
- nginx_ingress: -1,
+ nginx_ingress_vts: -1,
ha_proxy: -2,
aws_elb: -3,
nginx: -4,
kubernetes: -5,
+ nginx_ingress: -6,
# custom/user groups
business: 0,
@@ -30,6 +31,7 @@ class PrometheusMetric < ActiveRecord::Base
GROUP_TITLES = {
# built-in groups
+ nginx_ingress_vts: _('Response metrics (NGINX Ingress VTS)'),
nginx_ingress: _('Response metrics (NGINX Ingress)'),
ha_proxy: _('Response metrics (HA Proxy)'),
aws_elb: _('Response metrics (AWS ELB)'),
@@ -43,7 +45,8 @@ class PrometheusMetric < ActiveRecord::Base
}.freeze
REQUIRED_METRICS = {
- nginx_ingress: %w(nginx_upstream_responses_total nginx_upstream_response_msecs_avg),
+ nginx_ingress_vts: %w(nginx_upstream_responses_total nginx_upstream_response_msecs_avg),
+ nginx_ingress: %w(nginx_ingress_controller_requests nginx_ingress_controller_ingress_upstream_latency_seconds_sum),
ha_proxy: %w(haproxy_frontend_http_requests_total haproxy_frontend_http_responses_total),
aws_elb: %w(aws_elb_request_count_sum aws_elb_latency_average aws_elb_httpcode_backend_5_xx_sum),
nginx: %w(nginx_server_requests nginx_server_requestMsec),
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
index 6c1073265a1..d075440b147 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
class ProtectedBranch < ActiveRecord::Base
- include Gitlab::ShellAdapter
include ProtectedRef
protected_ref_access_levels :merge, :push
diff --git a/app/models/protected_tag.rb b/app/models/protected_tag.rb
index 94746141945..d28ebabfe49 100644
--- a/app/models/protected_tag.rb
+++ b/app/models/protected_tag.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
class ProtectedTag < ActiveRecord::Base
- include Gitlab::ShellAdapter
include ProtectedRef
validates :name, uniqueness: { scope: :project_id }
diff --git a/app/models/release.rb b/app/models/release.rb
index cba80ad30ca..7a09ee459a6 100644
--- a/app/models/release.rb
+++ b/app/models/release.rb
@@ -6,6 +6,7 @@ class Release < ActiveRecord::Base
cache_markdown_field :description
belongs_to :project
+ belongs_to :author, class_name: 'User'
validates :description, :project, :tag, presence: true
end
diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb
index b7b4d0f1be9..5a6895aefab 100644
--- a/app/models/remote_mirror.rb
+++ b/app/models/remote_mirror.rb
@@ -65,10 +65,14 @@ class RemoteMirror < ActiveRecord::Base
)
end
- after_transition started: :failed do |remote_mirror, _|
+ after_transition started: :failed do |remote_mirror|
Gitlab::Metrics.add_event(:remote_mirrors_failed)
remote_mirror.update(last_update_at: Time.now)
+
+ remote_mirror.run_after_commit do
+ RemoteMirrorNotificationWorker.perform_async(remote_mirror.id)
+ end
end
end
@@ -135,8 +139,8 @@ class RemoteMirror < ActiveRecord::Base
end
def mark_as_failed(error_message)
- update_fail
update_column(:last_error, Gitlab::UrlSanitizer.sanitize(error_message))
+ update_fail
end
def url=(value)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 35dd120856d..015a179f374 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -17,7 +17,6 @@ class Repository
#{REF_ENVIRONMENTS}
].freeze
- include Gitlab::ShellAdapter
include Gitlab::RepositoryCacheAdapter
attr_accessor :full_path, :disk_path, :project, :is_wiki
diff --git a/app/models/service.rb b/app/models/service.rb
index 5b8bf6e7cf0..9dcb0aab0a3 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -210,11 +210,7 @@ class Service < ActiveRecord::Base
class_eval %{
def #{arg}?
# '!!' is used because nil or empty string is converted to nil
- if Gitlab.rails5?
- !!ActiveRecord::Type::Boolean.new.cast(#{arg})
- else
- !!ActiveRecord::Type::Boolean.new.type_cast_from_database(#{arg})
- end
+ !!ActiveRecord::Type::Boolean.new.cast(#{arg})
end
}
end
diff --git a/app/models/shard.rb b/app/models/shard.rb
index 2e75bc91df0..e39d4232486 100644
--- a/app/models/shard.rb
+++ b/app/models/shard.rb
@@ -18,7 +18,9 @@ class Shard < ActiveRecord::Base
end
def self.by_name(name)
- find_or_create_by(name: name)
+ transaction(requires_new: true) do
+ find_or_create_by(name: name)
+ end
rescue ActiveRecord::RecordNotUnique
retry
end
diff --git a/app/models/suggestion.rb b/app/models/suggestion.rb
new file mode 100644
index 00000000000..c76b8e71507
--- /dev/null
+++ b/app/models/suggestion.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+class Suggestion < ApplicationRecord
+ belongs_to :note, inverse_of: :suggestions
+ validates :note, presence: true
+ validates :commit_id, presence: true, if: :applied?
+
+ delegate :original_position, :position, :diff_file,
+ :noteable, to: :note
+
+ def project
+ noteable.source_project
+ end
+
+ def branch
+ noteable.source_branch
+ end
+
+ # For now, suggestions only serve as a way to send patches that
+ # will change a single line (being able to apply multiple in the same place),
+ # which explains `from_line` and `to_line` being the same line.
+ # We'll iterate on that in https://gitlab.com/gitlab-org/gitlab-ce/issues/53310
+ # when allowing multi-line suggestions.
+ def from_line
+ position.new_line
+ end
+ alias_method :to_line, :from_line
+
+ def from_original_line
+ original_position.new_line
+ end
+ alias_method :to_original_line, :from_original_line
+
+ # `from_line_index` and `to_line_index` represents diff/blob line numbers in
+ # index-like way (N-1).
+ def from_line_index
+ from_line - 1
+ end
+ alias_method :to_line_index, :from_line_index
+
+ def appliable?
+ return false unless note.supports_suggestion?
+
+ !applied? &&
+ noteable.opened? &&
+ different_content? &&
+ note.active?
+ end
+
+ private
+
+ def different_content?
+ from_content != to_content
+ end
+end
diff --git a/app/models/upload.rb b/app/models/upload.rb
index e01e9c6a4f0..20860f14b83 100644
--- a/app/models/upload.rb
+++ b/app/models/upload.rb
@@ -25,6 +25,25 @@ class Upload < ActiveRecord::Base
Digest::SHA256.file(path).hexdigest
end
+ class << self
+ ##
+ # FastDestroyAll concerns
+ def begin_fast_destroy
+ {
+ Uploads::Local => Uploads::Local.new.keys(with_files_stored_locally),
+ Uploads::Fog => Uploads::Fog.new.keys(with_files_stored_remotely)
+ }
+ end
+
+ ##
+ # FastDestroyAll concerns
+ def finalize_fast_destroy(keys)
+ keys.each do |store_class, paths|
+ store_class.new.delete_keys_async(paths)
+ end
+ end
+ end
+
def absolute_path
raise ObjectStorage::RemoteStoreError, "Remote object has no absolute path." unless local?
return path unless relative_path?
diff --git a/app/models/uploads/base.rb b/app/models/uploads/base.rb
new file mode 100644
index 00000000000..f9814159958
--- /dev/null
+++ b/app/models/uploads/base.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Uploads
+ class Base
+ BATCH_SIZE = 100
+
+ attr_reader :logger
+
+ def initialize(logger: nil)
+ @logger ||= Rails.logger
+ end
+
+ def delete_keys_async(keys_to_delete)
+ keys_to_delete.each_slice(BATCH_SIZE) do |batch|
+ DeleteStoredFilesWorker.perform_async(self.class, batch)
+ end
+ end
+ end
+end
diff --git a/app/models/uploads/fog.rb b/app/models/uploads/fog.rb
new file mode 100644
index 00000000000..b44e273e9ab
--- /dev/null
+++ b/app/models/uploads/fog.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Uploads
+ class Fog < Base
+ include ::Gitlab::Utils::StrongMemoize
+
+ def available?
+ object_store.enabled
+ end
+
+ def keys(relation)
+ return [] unless available?
+
+ relation.pluck(:path)
+ end
+
+ def delete_keys(keys)
+ keys.each do |key|
+ connection.delete_object(bucket_name, key)
+ end
+ end
+
+ private
+
+ def object_store
+ Gitlab.config.uploads.object_store
+ end
+
+ def bucket_name
+ return unless available?
+
+ object_store.remote_directory
+ end
+
+ def connection
+ return unless available?
+
+ strong_memoize(:connection) do
+ ::Fog::Storage.new(object_store.connection.to_hash.deep_symbolize_keys)
+ end
+ end
+ end
+end
diff --git a/app/models/uploads/local.rb b/app/models/uploads/local.rb
new file mode 100644
index 00000000000..2901c33c359
--- /dev/null
+++ b/app/models/uploads/local.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module Uploads
+ class Local < Base
+ def keys(relation)
+ relation.includes(:model).find_each.map(&:absolute_path)
+ end
+
+ def delete_keys(keys)
+ keys.each do |path|
+ delete_file(path)
+ end
+ end
+
+ private
+
+ def delete_file(path)
+ unless exists?(path)
+ logger.warn("File '#{path}' doesn't exist, skipping")
+ return
+ end
+
+ unless in_uploads?(path)
+ message = "Path '#{path}' is not in uploads dir, skipping"
+ logger.warn(message)
+ Gitlab::Sentry.track_exception(RuntimeError.new(message), extra: { uploads_dir: storage_dir })
+ return
+ end
+
+ FileUtils.rm(path)
+ delete_dir!(File.dirname(path))
+ end
+
+ def exists?(path)
+ path.present? && File.exist?(path)
+ end
+
+ def in_uploads?(path)
+ path.start_with?(storage_dir)
+ end
+
+ def delete_dir!(path)
+ Dir.rmdir(path)
+ rescue Errno::ENOENT
+ # Ignore: path does not exist
+ rescue Errno::ENOTDIR
+ # Ignore: path is not a dir
+ rescue Errno::ENOTEMPTY, Errno::EEXIST
+ # Ignore: dir is not empty
+ end
+
+ def storage_dir
+ @storage_dir ||= File.realpath(Gitlab.config.uploads.storage_path)
+ end
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index dbd754dd25a..26fd2d903a1 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -130,6 +130,7 @@ class User < ActiveRecord::Base
has_many :issues, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
has_many :merge_requests, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
has_many :events, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
+ has_many :releases, dependent: :nullify, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
has_many :subscriptions, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :abuse_report, dependent: :destroy, foreign_key: :user_id # rubocop:disable Cop/ActiveRecordDependent
@@ -708,13 +709,13 @@ class User < ActiveRecord::Base
# Returns the groups a user is a member of, either directly or through a parent group
def membership_groups
- Gitlab::GroupHierarchy.new(groups).base_and_descendants
+ Gitlab::ObjectHierarchy.new(groups).base_and_descendants
end
# Returns a relation of groups the user has access to, including their parent
# and child groups (recursively).
def all_expanded_groups
- Gitlab::GroupHierarchy.new(groups).all_groups
+ Gitlab::ObjectHierarchy.new(groups).all_objects
end
def expanded_groups_requiring_two_factor_authentication
@@ -1152,7 +1153,7 @@ class User < ActiveRecord::Base
end
def manageable_groups
- Gitlab::GroupHierarchy.new(owned_or_maintainers_groups).base_and_descendants
+ Gitlab::ObjectHierarchy.new(owned_or_maintainers_groups).base_and_descendants
end
def namespaces
@@ -1421,6 +1422,10 @@ class User < ActiveRecord::Base
todos.where(id: ids)
end
+ def pending_todo_for(target)
+ todos.find_by(target: target, state: :pending)
+ end
+
# @deprecated
alias_method :owned_or_masters_groups, :owned_or_maintainers_groups
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index 6b4e56ef5e4..f07bb188265 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -16,7 +16,7 @@ class GroupPolicy < BasePolicy
condition(:maintainer) { access_level >= GroupMember::MAINTAINER }
condition(:reporter) { access_level >= GroupMember::REPORTER }
- condition(:nested_groups_supported, scope: :global) { Group.supports_nested_groups? }
+ condition(:nested_groups_supported, scope: :global) { Group.supports_nested_objects? }
condition(:has_parent, scope: :subject) { @subject.has_parent? }
condition(:share_with_group_locked, scope: :subject) { @subject.share_with_group_lock? }
@@ -40,6 +40,7 @@ class GroupPolicy < BasePolicy
rule { guest }.policy do
enable :read_group
+ enable :read_list
enable :upload_file
enable :read_label
end
diff --git a/app/policies/suggestion_policy.rb b/app/policies/suggestion_policy.rb
new file mode 100644
index 00000000000..301b7d965f5
--- /dev/null
+++ b/app/policies/suggestion_policy.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class SuggestionPolicy < BasePolicy
+ delegate { @subject.project }
+
+ condition(:can_push_to_branch) do
+ Gitlab::UserAccess.new(@user, project: @subject.project).can_push_to_branch?(@subject.branch)
+ end
+
+ rule { can_push_to_branch }.enable :apply_suggestion
+end
diff --git a/app/presenters/clusters/cluster_presenter.rb b/app/presenters/clusters/cluster_presenter.rb
index 7e6eccb648c..7a5b68f9a4b 100644
--- a/app/presenters/clusters/cluster_presenter.rb
+++ b/app/presenters/clusters/cluster_presenter.rb
@@ -2,8 +2,22 @@
module Clusters
class ClusterPresenter < Gitlab::View::Presenter::Delegated
+ include ActionView::Helpers::SanitizeHelper
+ include ActionView::Helpers::UrlHelper
+ include IconsHelper
+
presents :cluster
+ # We do not want to show the group path for clusters belonging to the
+ # clusterable, only for the ancestor clusters.
+ def item_link(clusterable_presenter)
+ if cluster.group_type? && clusterable != clusterable_presenter.subject
+ contracted_group_name(cluster.group) + ' / ' + link_to_cluster
+ else
+ link_to_cluster
+ end
+ end
+
def gke_cluster_url
"https://console.cloud.google.com/kubernetes/clusters/details/#{provider.zone}/#{name}" if gcp?
end
@@ -12,6 +26,18 @@ module Clusters
can?(current_user, :update_cluster, cluster) && created?
end
+ def can_read_cluster?
+ can?(current_user, :read_cluster, cluster)
+ end
+
+ def cluster_type_description
+ if cluster.project_type?
+ s_("ClusterIntegration|Project cluster")
+ elsif cluster.group_type?
+ s_("ClusterIntegration|Group cluster")
+ end
+ end
+
def show_path
if cluster.project_type?
project_cluster_path(project, cluster)
@@ -21,5 +47,29 @@ module Clusters
raise NotImplementedError
end
end
+
+ private
+
+ def clusterable
+ if cluster.group_type?
+ cluster.group
+ elsif cluster.project_type?
+ cluster.project
+ end
+ end
+
+ def contracted_group_name(group)
+ sanitize(group.full_name)
+ .sub(%r{\/.*\/}, "/ #{contracted_icon} /")
+ .html_safe
+ end
+
+ def contracted_icon
+ sprite_icon('ellipsis_h', size: 12, css_class: 'vertical-align-middle')
+ end
+
+ def link_to_cluster
+ link_to_if(can_read_cluster?, cluster.name, show_path)
+ end
end
end
diff --git a/app/presenters/group_clusterable_presenter.rb b/app/presenters/group_clusterable_presenter.rb
index d963c188559..ef6bbc0d109 100644
--- a/app/presenters/group_clusterable_presenter.rb
+++ b/app/presenters/group_clusterable_presenter.rb
@@ -31,6 +31,6 @@ class GroupClusterablePresenter < ClusterablePresenter
override :learn_more_link
def learn_more_link
- link_to(s_('ClusterIntegration|Learn more about group Kubernetes clusters'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
+ link_to(s_('ClusterIntegration|Learn more about group Kubernetes clusters'), help_page_path('user/group/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
end
end
diff --git a/app/presenters/member_presenter.rb b/app/presenters/member_presenter.rb
index 2497bea4aff..9e9b6973b8e 100644
--- a/app/presenters/member_presenter.rb
+++ b/app/presenters/member_presenter.rb
@@ -7,6 +7,14 @@ class MemberPresenter < Gitlab::View::Presenter::Delegated
member.class.access_level_roles
end
+ def valid_level_roles
+ return access_level_roles unless member.highest_group_member
+
+ access_level_roles.reject do |_name, level|
+ member.highest_group_member.access_level > level
+ end
+ end
+
def can_resend_invite?
invite? &&
can?(current_user, admin_member_permission, source)
diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb
index d61124fa787..9bd64ea217e 100644
--- a/app/presenters/project_presenter.rb
+++ b/app/presenters/project_presenter.rb
@@ -6,27 +6,27 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
include GitlabRoutingHelper
include StorageHelper
include TreeHelper
+ include IconsHelper
include ChecksCollaboration
include Gitlab::Utils::StrongMemoize
presents :project
- AnchorData = Struct.new(:enabled, :label, :link, :class_modifier)
+ AnchorData = Struct.new(:is_link, :label, :link, :class_modifier, :icon)
MAX_TAGS_TO_SHOW = 3
+ def statistic_icon(icon_name = 'plus-square-o')
+ sprite_icon(icon_name, size: 16, css_class: 'icon append-right-4')
+ end
+
def statistics_anchors(show_auto_devops_callout:)
[
- readme_anchor_data,
- changelog_anchor_data,
- contribution_guide_anchor_data,
- files_anchor_data,
+ license_anchor_data,
commits_anchor_data,
branches_anchor_data,
tags_anchor_data,
- gitlab_ci_anchor_data,
- autodevops_anchor_data(show_auto_devops_callout: show_auto_devops_callout),
- kubernetes_cluster_anchor_data
- ].compact.select { |item| item.enabled }
+ files_anchor_data
+ ].compact.select(&:is_link)
end
def statistics_buttons(show_auto_devops_callout:)
@@ -37,27 +37,28 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
autodevops_anchor_data(show_auto_devops_callout: show_auto_devops_callout),
kubernetes_cluster_anchor_data,
gitlab_ci_anchor_data
- ].compact.reject { |item| item.enabled }
+ ].compact.reject(&:is_link)
end
def empty_repo_statistics_anchors
[
- files_anchor_data,
+ license_anchor_data,
commits_anchor_data,
branches_anchor_data,
tags_anchor_data,
- autodevops_anchor_data,
- kubernetes_cluster_anchor_data
- ].compact.select { |item| item.enabled }
+ files_anchor_data
+ ].compact.select { |item| item.is_link }
end
def empty_repo_statistics_buttons
[
new_file_anchor_data,
readme_anchor_data,
+ changelog_anchor_data,
+ contribution_guide_anchor_data,
autodevops_anchor_data,
kubernetes_cluster_anchor_data
- ].compact.reject { |item| item.enabled }
+ ].compact.reject { |item| item.is_link }
end
def default_view
@@ -113,7 +114,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end
def add_contribution_guide_path
- add_special_file_path(file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide')
+ add_special_file_path(file_name: 'CONTRIBUTING.md', commit_message: 'Add CONTRIBUTING')
end
def add_ci_yml_path
@@ -149,32 +150,52 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
def files_anchor_data
AnchorData.new(true,
- _('Files (%{human_size})') % { human_size: storage_counter(statistics.total_repository_size) },
+ statistic_icon('doc-code') +
+ _('%{strong_start}%{human_size}%{strong_end} Files').html_safe % {
+ human_size: storage_counter(statistics.total_repository_size),
+ strong_start: '<strong class="project-stat-value">'.html_safe,
+ strong_end: '</strong>'.html_safe
+ },
empty_repo? ? nil : project_tree_path(project))
end
def commits_anchor_data
AnchorData.new(true,
- n_('Commit (%{commit_count})', 'Commits (%{commit_count})', statistics.commit_count) % { commit_count: number_with_delimiter(statistics.commit_count) },
+ statistic_icon('commit') +
+ n_('%{strong_start}%{commit_count}%{strong_end} Commit', '%{strong_start}%{commit_count}%{strong_end} Commits', statistics.commit_count).html_safe % {
+ commit_count: number_with_delimiter(statistics.commit_count),
+ strong_start: '<strong class="project-stat-value">'.html_safe,
+ strong_end: '</strong>'.html_safe
+ },
empty_repo? ? nil : project_commits_path(project, repository.root_ref))
end
def branches_anchor_data
AnchorData.new(true,
- n_('Branch (%{branch_count})', 'Branches (%{branch_count})', repository.branch_count) % { branch_count: number_with_delimiter(repository.branch_count) },
+ statistic_icon('branch') +
+ n_('%{strong_start}%{branch_count}%{strong_end} Branch', '%{strong_start}%{branch_count}%{strong_end} Branches', repository.branch_count).html_safe % {
+ branch_count: number_with_delimiter(repository.branch_count),
+ strong_start: '<strong class="project-stat-value">'.html_safe,
+ strong_end: '</strong>'.html_safe
+ },
empty_repo? ? nil : project_branches_path(project))
end
def tags_anchor_data
AnchorData.new(true,
- n_('Tag (%{tag_count})', 'Tags (%{tag_count})', repository.tag_count) % { tag_count: number_with_delimiter(repository.tag_count) },
+ statistic_icon('label') +
+ n_('%{strong_start}%{tag_count}%{strong_end} Tag', '%{strong_start}%{tag_count}%{strong_end} Tags', repository.tag_count).html_safe % {
+ tag_count: number_with_delimiter(repository.tag_count),
+ strong_start: '<strong class="project-stat-value">'.html_safe,
+ strong_end: '</strong>'.html_safe
+ },
empty_repo? ? nil : project_tags_path(project))
end
def new_file_anchor_data
if current_user && can_current_user_push_to_default_branch?
AnchorData.new(false,
- _('New file'),
+ statistic_icon + _('New file'),
project_new_blob_path(project, default_branch || 'master'),
'success')
end
@@ -183,40 +204,45 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
def readme_anchor_data
if current_user && can_current_user_push_to_default_branch? && repository.readme.nil?
AnchorData.new(false,
- _('Add Readme'),
+ statistic_icon + _('Add README'),
add_readme_path)
elsif repository.readme
- AnchorData.new(true,
- _('Readme'),
- default_view != 'readme' ? readme_path : '#readme')
+ AnchorData.new(false,
+ statistic_icon('doc-text') + _('README'),
+ default_view != 'readme' ? readme_path : '#readme',
+ 'default',
+ 'doc-text')
end
end
def changelog_anchor_data
if current_user && can_current_user_push_to_default_branch? && repository.changelog.blank?
AnchorData.new(false,
- _('Add Changelog'),
+ statistic_icon + _('Add CHANGELOG'),
add_changelog_path)
elsif repository.changelog.present?
- AnchorData.new(true,
- _('Changelog'),
- changelog_path)
+ AnchorData.new(false,
+ statistic_icon('doc-text') + _('CHANGELOG'),
+ changelog_path,
+ 'default')
end
end
def license_anchor_data
+ icon = statistic_icon('scale')
+
if repository.license_blob.present?
AnchorData.new(true,
- license_short_name,
+ icon + content_tag(:strong, license_short_name, class: 'project-stat-value'),
license_path)
else
if current_user && can_current_user_push_to_default_branch?
- AnchorData.new(false,
- _('Add license'),
+ AnchorData.new(true,
+ content_tag(:span, icon + _('Add license'), class: 'add-license-link d-flex'),
add_license_path)
else
- AnchorData.new(false,
- _('No license. All rights reserved'),
+ AnchorData.new(true,
+ icon + content_tag(:strong, _('No license. All rights reserved'), class: 'project-stat-value'),
nil)
end
end
@@ -225,22 +251,29 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
def contribution_guide_anchor_data
if current_user && can_current_user_push_to_default_branch? && repository.contribution_guide.blank?
AnchorData.new(false,
- _('Add Contribution guide'),
+ statistic_icon + _('Add CONTRIBUTING'),
add_contribution_guide_path)
elsif repository.contribution_guide.present?
- AnchorData.new(true,
- _('Contribution guide'),
+ AnchorData.new(false,
+ statistic_icon('doc-text') + _('CONTRIBUTING'),
contribution_guide_path)
end
end
def autodevops_anchor_data(show_auto_devops_callout: false)
if current_user && can?(current_user, :admin_pipeline, project) && repository.gitlab_ci_yml.blank? && !show_auto_devops_callout
- AnchorData.new(auto_devops_enabled?,
- auto_devops_enabled? ? _('Auto DevOps enabled') : _('Enable Auto DevOps'),
- project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
+ if auto_devops_enabled?
+ AnchorData.new(false,
+ statistic_icon('doc-text') + _('Auto DevOps enabled'),
+ project_settings_ci_cd_path(project, anchor: 'autodevops-settings'),
+ 'default')
+ else
+ AnchorData.new(false,
+ statistic_icon + _('Enable Auto DevOps'),
+ project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
+ end
elsif auto_devops_enabled?
- AnchorData.new(true,
+ AnchorData.new(false,
_('Auto DevOps enabled'),
nil)
end
@@ -248,27 +281,32 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
def kubernetes_cluster_anchor_data
if current_user && can?(current_user, :create_cluster, project)
- cluster_link = clusters.count == 1 ? project_cluster_path(project, clusters.first) : project_clusters_path(project)
if clusters.empty?
- cluster_link = new_project_cluster_path(project)
- end
+ AnchorData.new(false,
+ statistic_icon + _('Add Kubernetes cluster'),
+ new_project_cluster_path(project))
+ else
+ cluster_link = clusters.count == 1 ? project_cluster_path(project, clusters.first) : project_clusters_path(project)
- AnchorData.new(!clusters.empty?,
- clusters.empty? ? _('Add Kubernetes cluster') : _('Kubernetes configured'),
- cluster_link)
+ AnchorData.new(false,
+ _('Kubernetes configured'),
+ cluster_link,
+ 'default')
+ end
end
end
def gitlab_ci_anchor_data
if current_user && can_current_user_push_code? && repository.gitlab_ci_yml.blank? && !auto_devops_enabled?
AnchorData.new(false,
- _('Set up CI/CD'),
+ statistic_icon + _('Set up CI/CD'),
add_ci_yml_path)
elsif repository.gitlab_ci_yml.present?
- AnchorData.new(true,
- _('CI/CD configuration'),
- ci_configuration_path)
+ AnchorData.new(false,
+ statistic_icon('doc-text') + _('CI/CD configuration'),
+ ci_configuration_path,
+ 'default')
end
end
diff --git a/app/serializers/cluster_application_entity.rb b/app/serializers/cluster_application_entity.rb
index 2bd17e58086..7b1a0be75ca 100644
--- a/app/serializers/cluster_application_entity.rb
+++ b/app/serializers/cluster_application_entity.rb
@@ -6,4 +6,5 @@ class ClusterApplicationEntity < Grape::Entity
expose :status_reason
expose :external_ip, if: -> (e, _) { e.respond_to?(:external_ip) }
expose :hostname, if: -> (e, _) { e.respond_to?(:hostname) }
+ expose :email, if: -> (e, _) { e.respond_to?(:email) }
end
diff --git a/app/serializers/diff_file_base_entity.rb b/app/serializers/diff_file_base_entity.rb
new file mode 100644
index 00000000000..06a8db78476
--- /dev/null
+++ b/app/serializers/diff_file_base_entity.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+class DiffFileBaseEntity < Grape::Entity
+ include RequestAwareEntity
+ include BlobHelper
+ include SubmoduleHelper
+ include DiffHelper
+ include TreeHelper
+ include ChecksCollaboration
+ include Gitlab::Utils::StrongMemoize
+
+ expose :content_sha
+ expose :submodule?, as: :submodule
+
+ expose :submodule_link do |diff_file|
+ memoized_submodule_links(diff_file).first
+ end
+
+ expose :submodule_tree_url do |diff_file|
+ memoized_submodule_links(diff_file).last
+ end
+
+ expose :edit_path, if: -> (_, options) { options[:merge_request] } do |diff_file|
+ merge_request = options[:merge_request]
+
+ options = merge_request.persisted? ? { from_merge_request_iid: merge_request.iid } : {}
+
+ next unless merge_request.source_project
+
+ project_edit_blob_path(merge_request.source_project,
+ tree_join(merge_request.source_branch, diff_file.new_path),
+ options)
+ end
+
+ expose :old_path_html do |diff_file|
+ old_path, _ = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
+ old_path
+ end
+
+ expose :new_path_html do |diff_file|
+ _, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
+ new_path
+ end
+
+ expose :formatted_external_url, if: -> (_, options) { options[:environment] } do |diff_file|
+ options[:environment].formatted_external_url
+ end
+
+ expose :external_url, if: -> (_, options) { options[:environment] } do |diff_file|
+ options[:environment].external_url_for(diff_file.new_path, diff_file.content_sha)
+ end
+
+ expose :blob, using: BlobEntity
+
+ expose :can_modify_blob do |diff_file|
+ merge_request = options[:merge_request]
+
+ next unless diff_file.blob
+
+ if merge_request&.source_project && current_user
+ can_modify_blob?(diff_file.blob, merge_request.source_project, merge_request.source_branch)
+ else
+ false
+ end
+ end
+
+ expose :file_hash do |diff_file|
+ Digest::SHA1.hexdigest(diff_file.file_path)
+ end
+
+ expose :file_path
+ expose :old_path
+ expose :new_path
+ expose :new_file?, as: :new_file
+ expose :collapsed?, as: :collapsed
+ expose :text?, as: :text
+ expose :diff_refs
+ expose :stored_externally?, as: :stored_externally
+ expose :external_storage
+ expose :renamed_file?, as: :renamed_file
+ expose :deleted_file?, as: :deleted_file
+ expose :mode_changed?, as: :mode_changed
+ expose :a_mode
+ expose :b_mode
+
+ private
+
+ def memoized_submodule_links(diff_file)
+ strong_memoize(:submodule_links) do
+ if diff_file.submodule?
+ submodule_links(diff_file.blob, diff_file.content_sha, diff_file.repository)
+ else
+ []
+ end
+ end
+ end
+
+ def current_user
+ request.current_user
+ end
+end
diff --git a/app/serializers/diff_file_entity.rb b/app/serializers/diff_file_entity.rb
index 63ea8e8f95f..b0aaec3326d 100644
--- a/app/serializers/diff_file_entity.rb
+++ b/app/serializers/diff_file_entity.rb
@@ -1,63 +1,13 @@
# frozen_string_literal: true
-class DiffFileEntity < Grape::Entity
- include RequestAwareEntity
+class DiffFileEntity < DiffFileBaseEntity
include CommitsHelper
- include DiffHelper
- include SubmoduleHelper
- include BlobHelper
include IconsHelper
- include TreeHelper
- include ChecksCollaboration
- include Gitlab::Utils::StrongMemoize
- expose :submodule?, as: :submodule
-
- expose :submodule_link do |diff_file|
- memoized_submodule_links(diff_file).first
- end
-
- expose :submodule_tree_url do |diff_file|
- memoized_submodule_links(diff_file).last
- end
-
- expose :blob, using: BlobEntity
-
- expose :can_modify_blob do |diff_file|
- merge_request = options[:merge_request]
-
- next unless diff_file.blob
-
- if merge_request&.source_project && current_user
- can_modify_blob?(diff_file.blob, merge_request.source_project, merge_request.source_branch)
- else
- false
- end
- end
-
- expose :file_hash do |diff_file|
- Digest::SHA1.hexdigest(diff_file.file_path)
- end
-
- expose :file_path
expose :too_large?, as: :too_large
- expose :collapsed?, as: :collapsed
- expose :new_file?, as: :new_file
-
- expose :deleted_file?, as: :deleted_file
- expose :renamed_file?, as: :renamed_file
- expose :old_path
- expose :new_path
- expose :mode_changed?, as: :mode_changed
- expose :a_mode
- expose :b_mode
- expose :text?, as: :text
+ expose :empty?, as: :empty
expose :added_lines
expose :removed_lines
- expose :diff_refs
- expose :content_sha
- expose :stored_externally?, as: :stored_externally
- expose :external_storage
expose :load_collapsed_diff_url, if: -> (diff_file, options) { diff_file.text? && options[:merge_request] } do |diff_file|
merge_request = options[:merge_request]
@@ -75,36 +25,6 @@ class DiffFileEntity < Grape::Entity
)
end
- expose :formatted_external_url, if: -> (_, options) { options[:environment] } do |diff_file|
- options[:environment].formatted_external_url
- end
-
- expose :external_url, if: -> (_, options) { options[:environment] } do |diff_file|
- options[:environment].external_url_for(diff_file.new_path, diff_file.content_sha)
- end
-
- expose :old_path_html do |diff_file|
- old_path, _ = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
- old_path
- end
-
- expose :new_path_html do |diff_file|
- _, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
- new_path
- end
-
- expose :edit_path, if: -> (_, options) { options[:merge_request] } do |diff_file|
- merge_request = options[:merge_request]
-
- options = merge_request.persisted? ? { from_merge_request_iid: merge_request.iid } : {}
-
- next unless merge_request.source_project
-
- project_edit_blob_path(merge_request.source_project,
- tree_join(merge_request.source_branch, diff_file.new_path),
- options)
- end
-
expose :view_path, if: -> (_, options) { options[:merge_request] } do |diff_file|
merge_request = options[:merge_request]
@@ -145,18 +65,4 @@ class DiffFileEntity < Grape::Entity
# Used for parallel diffs
expose :parallel_diff_lines, using: DiffLineParallelEntity, if: -> (diff_file, _) { diff_file.text? }
-
- def current_user
- request.current_user
- end
-
- def memoized_submodule_links(diff_file)
- strong_memoize(:submodule_links) do
- if diff_file.submodule?
- submodule_links(diff_file.blob, diff_file.content_sha, diff_file.repository)
- else
- []
- end
- end
- end
end
diff --git a/app/serializers/diff_line_entity.rb b/app/serializers/diff_line_entity.rb
index 942714b7787..bfef6d3bde8 100644
--- a/app/serializers/diff_line_entity.rb
+++ b/app/serializers/diff_line_entity.rb
@@ -11,4 +11,6 @@ class DiffLineEntity < Grape::Entity
expose :rich_text do |line|
ERB::Util.html_escape(line.rich_text || line.text)
end
+
+ expose :suggestible?, as: :can_receive_suggestion
end
diff --git a/app/serializers/discussion_diff_file_entity.rb b/app/serializers/discussion_diff_file_entity.rb
new file mode 100644
index 00000000000..419e7edf94f
--- /dev/null
+++ b/app/serializers/discussion_diff_file_entity.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+class DiscussionDiffFileEntity < DiffFileBaseEntity
+end
diff --git a/app/serializers/discussion_entity.rb b/app/serializers/discussion_entity.rb
index b6786a0d597..b2d9d52bd22 100644
--- a/app/serializers/discussion_entity.rb
+++ b/app/serializers/discussion_entity.rb
@@ -36,7 +36,7 @@ class DiscussionEntity < Grape::Entity
new_project_issue_path(discussion.project, merge_request_to_resolve_discussions_of: discussion.noteable.iid, discussion_to_resolve: discussion.id)
end
- expose :diff_file, using: DiffFileEntity, if: -> (d, _) { d.diff_discussion? }
+ expose :diff_file, using: DiscussionDiffFileEntity, if: -> (d, _) { d.diff_discussion? }
expose :diff_discussion?, as: :diff_discussion
@@ -46,19 +46,6 @@ class DiscussionEntity < Grape::Entity
expose :truncated_diff_lines, using: DiffLineEntity, if: -> (d, _) { d.diff_discussion? && d.on_text? && (d.expanded? || render_truncated_diff_lines?) }
- expose :image_diff_html, if: -> (d, _) { d.diff_discussion? && d.on_image? } do |discussion|
- diff_file = discussion.diff_file
- partial = diff_file.new_file? || diff_file.deleted_file? ? 'single_image_diff' : 'replaced_image_diff'
- options[:context].render_to_string(
- partial: "projects/diffs/#{partial}",
- locals: { diff_file: diff_file,
- position: discussion.position.to_json,
- click_to_comment: false },
- layout: false,
- formats: [:html]
- )
- end
-
expose :for_commit?, as: :for_commit
expose :commit_id
diff --git a/app/serializers/entity_date_helper.rb b/app/serializers/entity_date_helper.rb
index cc0c2abf863..f515abe5917 100644
--- a/app/serializers/entity_date_helper.rb
+++ b/app/serializers/entity_date_helper.rb
@@ -44,14 +44,14 @@ module EntityDateHelper
# It returns "Upcoming" for upcoming entities
# If due date is provided, it returns "# days|weeks|months remaining|ago"
# If start date is provided and elapsed, with no due date, it returns "# days elapsed"
- def remaining_days_in_words(entity)
- if entity.try(:expired?)
+ def remaining_days_in_words(due_date, start_date = nil)
+ if due_date&.past?
content_tag(:strong, 'Past due')
- elsif entity.try(:upcoming?)
+ elsif start_date&.future?
content_tag(:strong, 'Upcoming')
- elsif entity.due_date
- is_upcoming = (entity.due_date - Date.today).to_i > 0
- time_ago = time_ago_in_words(entity.due_date)
+ elsif due_date
+ is_upcoming = (due_date - Date.today).to_i > 0
+ time_ago = time_ago_in_words(due_date)
# https://gitlab.com/gitlab-org/gitlab-ce/issues/49440
#
@@ -63,8 +63,8 @@ module EntityDateHelper
remaining_or_ago = is_upcoming ? _("remaining") : _("ago")
"#{content} #{remaining_or_ago}".html_safe
- elsif entity.start_date && entity.start_date.past?
- days = entity.elapsed_days
+ elsif start_date&.past?
+ days = (Date.today - start_date).to_i
"#{content_tag(:strong, days)} #{'day'.pluralize(days)} elapsed".html_safe
end
end
diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb
index 07a13c33b89..4a7d13915dd 100644
--- a/app/serializers/environment_entity.rb
+++ b/app/serializers/environment_entity.rb
@@ -23,6 +23,10 @@ class EnvironmentEntity < Grape::Entity
stop_project_environment_path(environment.project, environment)
end
+ expose :cluster_type, if: ->(environment, _) { cluster_platform_kubernetes? } do |environment|
+ cluster.cluster_type
+ end
+
expose :terminal_path, if: ->(*) { environment.has_terminals? && can_access_terminal? } do |environment|
terminal_project_environment_path(environment.project, environment)
end
@@ -48,4 +52,16 @@ class EnvironmentEntity < Grape::Entity
def can_access_terminal?
can?(request.current_user, :create_environment_terminal, environment)
end
+
+ def cluster_platform_kubernetes?
+ deployment_platform && deployment_platform.is_a?(Clusters::Platforms::Kubernetes)
+ end
+
+ def deployment_platform
+ environment.deployment_platform
+ end
+
+ def cluster
+ deployment_platform.cluster
+ end
end
diff --git a/app/serializers/issuable_sidebar_basic_entity.rb b/app/serializers/issuable_sidebar_basic_entity.rb
new file mode 100644
index 00000000000..61de3c93337
--- /dev/null
+++ b/app/serializers/issuable_sidebar_basic_entity.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+class IssuableSidebarBasicEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :id
+ expose :type do |issuable|
+ issuable.to_ability_name
+ end
+ expose :author_id
+ expose :project_id do |issuable|
+ issuable.project.id
+ end
+ expose :discussion_locked
+ expose :reference do |issuable|
+ issuable.to_reference(issuable.project, full: true)
+ end
+
+ expose :milestone, using: ::API::Entities::Milestone
+ expose :labels, using: LabelEntity
+
+ expose :current_user, if: lambda { |_issuable| current_user } do
+ expose :current_user, merge: true, using: API::Entities::UserBasic
+
+ expose :todo, using: IssuableSidebarTodoEntity do |issuable|
+ current_user.pending_todo_for(issuable)
+ end
+
+ expose :can_edit do |issuable|
+ can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
+ end
+
+ expose :can_move do |issuable|
+ issuable.can_move?(current_user)
+ end
+
+ expose :can_admin_label do |issuable|
+ can?(current_user, :admin_label, issuable.project)
+ end
+ end
+
+ expose :issuable_json_path do |issuable|
+ if issuable.is_a?(MergeRequest)
+ project_merge_request_path(issuable.project, issuable.iid, :json)
+ else
+ project_issue_path(issuable.project, issuable.iid, :json)
+ end
+ end
+
+ expose :namespace_path do |issuable|
+ issuable.project.namespace.full_path
+ end
+
+ expose :project_path do |issuable|
+ issuable.project.path
+ end
+
+ expose :project_full_path do |issuable|
+ issuable.project.full_path
+ end
+
+ expose :project_issuables_path do |issuable|
+ project = issuable.project
+ namespace = project.namespace
+
+ if issuable.is_a?(MergeRequest)
+ namespace_project_merge_requests_path(namespace, project)
+ else
+ namespace_project_issues_path(namespace, project)
+ end
+ end
+
+ expose :create_todo_path do |issuable|
+ project_todos_path(issuable.project)
+ end
+
+ expose :project_milestones_path do |issuable|
+ project_milestones_path(issuable.project, :json)
+ end
+
+ expose :project_labels_path do |issuable|
+ project_labels_path(issuable.project, :json, include_ancestor_groups: true)
+ end
+
+ expose :toggle_subscription_path do |issuable|
+ toggle_subscription_path(issuable)
+ end
+
+ expose :move_issue_path do |issuable|
+ move_namespace_project_issue_path(
+ namespace_id: issuable.project.namespace.to_param,
+ project_id: issuable.project,
+ id: issuable
+ )
+ end
+
+ expose :projects_autocomplete_path do |issuable|
+ autocomplete_projects_path(project_id: issuable.project.id)
+ end
+
+ private
+
+ def current_user
+ request.current_user
+ end
+end
diff --git a/app/serializers/issuable_sidebar_entity.rb b/app/serializers/issuable_sidebar_extras_entity.rb
index 773d78d324c..d60253564e1 100644
--- a/app/serializers/issuable_sidebar_entity.rb
+++ b/app/serializers/issuable_sidebar_extras_entity.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-class IssuableSidebarEntity < Grape::Entity
- include TimeTrackableEntity
+class IssuableSidebarExtrasEntity < Grape::Entity
include RequestAwareEntity
+ include TimeTrackableEntity
expose :participants, using: ::API::Entities::UserBasic do |issuable|
issuable.participants(request.current_user)
diff --git a/app/serializers/issuable_sidebar_todo_entity.rb b/app/serializers/issuable_sidebar_todo_entity.rb
new file mode 100644
index 00000000000..b2c98433f05
--- /dev/null
+++ b/app/serializers/issuable_sidebar_todo_entity.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class IssuableSidebarTodoEntity < Grape::Entity
+ include Gitlab::Routing
+
+ expose :id
+
+ expose :delete_path do |todo|
+ dashboard_todo_path(todo) if todo
+ end
+end
diff --git a/app/serializers/issue_board_entity.rb b/app/serializers/issue_board_entity.rb
index 58ab804a3c8..f7719447b92 100644
--- a/app/serializers/issue_board_entity.rb
+++ b/app/serializers/issue_board_entity.rb
@@ -17,7 +17,7 @@ class IssueBoardEntity < Grape::Entity
end
expose :milestone, expose_nil: false do |issue|
- API::Entities::Project.represent issue.milestone, only: [:id, :title]
+ API::Entities::Milestone.represent issue.milestone, only: [:id, :title]
end
expose :assignees do |issue|
@@ -37,7 +37,7 @@ class IssueBoardEntity < Grape::Entity
end
expose :issue_sidebar_endpoint, if: -> (issue) { issue.project } do |issue|
- project_issue_path(issue.project, issue, format: :json, serializer: 'sidebar')
+ project_issue_path(issue.project, issue, format: :json, serializer: 'sidebar_extras')
end
expose :toggle_subscription_endpoint, if: -> (issue) { issue.project } do |issue|
diff --git a/app/serializers/issue_serializer.rb b/app/serializers/issue_serializer.rb
index d66f0a5acb7..0fa76f098cd 100644
--- a/app/serializers/issue_serializer.rb
+++ b/app/serializers/issue_serializer.rb
@@ -2,13 +2,15 @@
class IssueSerializer < BaseSerializer
# This overrided method takes care of which entity should be used
- # to serialize the `issue` based on `basic` key in `opts` param.
+ # to serialize the `issue` based on `serializer` key in `opts` param.
# Hence, `entity` doesn't need to be declared on the class scope.
def represent(issue, opts = {})
entity =
case opts[:serializer]
when 'sidebar'
- IssueSidebarEntity
+ IssueSidebarBasicEntity
+ when 'sidebar_extras'
+ IssueSidebarExtrasEntity
when 'board'
IssueBoardEntity
else
diff --git a/app/serializers/issue_sidebar_basic_entity.rb b/app/serializers/issue_sidebar_basic_entity.rb
new file mode 100644
index 00000000000..723875809ec
--- /dev/null
+++ b/app/serializers/issue_sidebar_basic_entity.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class IssueSidebarBasicEntity < IssuableSidebarBasicEntity
+ expose :due_date
+ expose :confidential
+end
diff --git a/app/serializers/issue_sidebar_entity.rb b/app/serializers/issue_sidebar_extras_entity.rb
index 349ad9d1fef..7b6e860140b 100644
--- a/app/serializers/issue_sidebar_entity.rb
+++ b/app/serializers/issue_sidebar_extras_entity.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-class IssueSidebarEntity < IssuableSidebarEntity
+class IssueSidebarExtrasEntity < IssuableSidebarExtrasEntity
expose :assignees, using: API::Entities::UserBasic
end
diff --git a/app/serializers/merge_request_basic_entity.rb b/app/serializers/merge_request_basic_entity.rb
index f7eb74cf392..084627f9dbe 100644
--- a/app/serializers/merge_request_basic_entity.rb
+++ b/app/serializers/merge_request_basic_entity.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class MergeRequestBasicEntity < IssuableSidebarEntity
+class MergeRequestBasicEntity < Grape::Entity
expose :assignee_id
expose :merge_status
expose :merge_error
diff --git a/app/serializers/merge_request_basic_serializer.rb b/app/serializers/merge_request_basic_serializer.rb
deleted file mode 100644
index a68b48b00db..00000000000
--- a/app/serializers/merge_request_basic_serializer.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# frozen_string_literal: true
-
-class MergeRequestBasicSerializer < BaseSerializer
- entity MergeRequestBasicEntity
-end
diff --git a/app/serializers/merge_request_serializer.rb b/app/serializers/merge_request_serializer.rb
index 1f8c830e1aa..4cf84336aa4 100644
--- a/app/serializers/merge_request_serializer.rb
+++ b/app/serializers/merge_request_serializer.rb
@@ -7,9 +7,14 @@ class MergeRequestSerializer < BaseSerializer
def represent(merge_request, opts = {})
entity =
case opts[:serializer]
- when 'basic', 'sidebar'
+ when 'sidebar'
+ MergeRequestSidebarBasicEntity
+ when 'sidebar_extras'
+ IssuableSidebarExtrasEntity
+ when 'basic'
MergeRequestBasicEntity
- else # It's 'widget'
+ else
+ # fallback to widget for old poll requests without `serializer` set
MergeRequestWidgetEntity
end
diff --git a/app/serializers/merge_request_sidebar_basic_entity.rb b/app/serializers/merge_request_sidebar_basic_entity.rb
new file mode 100644
index 00000000000..0ae7298a7c1
--- /dev/null
+++ b/app/serializers/merge_request_sidebar_basic_entity.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class MergeRequestSidebarBasicEntity < IssuableSidebarBasicEntity
+ expose :assignee, if: lambda { |issuable| issuable.assignee } do
+ expose :assignee, merge: true, using: API::Entities::UserBasic
+
+ expose :can_merge do |issuable|
+ issuable.can_be_merged_by?(issuable.assignee)
+ end
+ end
+end
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index f33a1654d5e..9731b52f1ad 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -238,6 +238,8 @@ class MergeRequestWidgetEntity < IssuableEntity
end
end
+ expose :supports_suggestion?, as: :can_receive_suggestion
+
private
delegate :current_user, to: :request
diff --git a/app/serializers/note_entity.rb b/app/serializers/note_entity.rb
index c6d27817411..1d3b59eb1b7 100644
--- a/app/serializers/note_entity.rb
+++ b/app/serializers/note_entity.rb
@@ -36,6 +36,7 @@ class NoteEntity < API::Entities::Note
end
end
+ expose :suggestions, using: SuggestionEntity
expose :resolved?, as: :resolved
expose :resolvable?, as: :resolvable
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index 477b6710168..c9669e59199 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -23,6 +23,7 @@ class PipelineEntity < Grape::Entity
expose :latest?, as: :latest
expose :stuck?, as: :stuck
expose :auto_devops_source?, as: :auto_devops
+ expose :merge_request?, as: :merge_request
expose :has_yaml_errors?, as: :yaml_errors
expose :can_retry?, as: :retryable
expose :can_cancel?, as: :cancelable
diff --git a/app/serializers/projects/serverless/service_entity.rb b/app/serializers/projects/serverless/service_entity.rb
new file mode 100644
index 00000000000..4f1f62d145b
--- /dev/null
+++ b/app/serializers/projects/serverless/service_entity.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Projects
+ module Serverless
+ class ServiceEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :name do |service|
+ service.dig('metadata', 'name')
+ end
+
+ expose :namespace do |service|
+ service.dig('metadata', 'namespace')
+ end
+
+ expose :created_at do |service|
+ service.dig('metadata', 'creationTimestamp')
+ end
+
+ expose :url do |service|
+ "http://#{service.dig('status', 'domain')}"
+ end
+
+ expose :description do |service|
+ service.dig('spec', 'runLatest', 'configuration', 'revisionTemplate', 'metadata', 'annotations', 'Description')
+ end
+
+ expose :image do |service|
+ service.dig('spec', 'runLatest', 'configuration', 'build', 'template', 'name')
+ end
+ end
+ end
+end
diff --git a/app/serializers/projects/serverless/service_serializer.rb b/app/serializers/projects/serverless/service_serializer.rb
new file mode 100644
index 00000000000..adfd48a8c7d
--- /dev/null
+++ b/app/serializers/projects/serverless/service_serializer.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Projects
+ module Serverless
+ class ServiceSerializer < BaseSerializer
+ entity Projects::Serverless::ServiceEntity
+ end
+ end
+end
diff --git a/app/serializers/suggestion_entity.rb b/app/serializers/suggestion_entity.rb
new file mode 100644
index 00000000000..4d0d4da10be
--- /dev/null
+++ b/app/serializers/suggestion_entity.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class SuggestionEntity < API::Entities::Suggestion
+ include RequestAwareEntity
+
+ expose :current_user do
+ expose :can_apply do |suggestion|
+ Ability.allowed?(current_user, :apply_suggestion, suggestion)
+ end
+ end
+
+ private
+
+ def current_user
+ request.current_user
+ end
+end
diff --git a/app/serializers/trigger_variable_entity.rb b/app/serializers/trigger_variable_entity.rb
index 56203113631..4b28db42e76 100644
--- a/app/serializers/trigger_variable_entity.rb
+++ b/app/serializers/trigger_variable_entity.rb
@@ -3,5 +3,6 @@
class TriggerVariableEntity < Grape::Entity
include RequestAwareEntity
- expose :key, :value, :public
+ expose :key, :public
+ expose :value, if: ->(_, _) { can?(request.current_user, :admin_build, request.project) }
end
diff --git a/app/services/ci/compare_reports_base_service.rb b/app/services/ci/compare_reports_base_service.rb
new file mode 100644
index 00000000000..d5625857599
--- /dev/null
+++ b/app/services/ci/compare_reports_base_service.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Ci
+ class CompareReportsBaseService < ::BaseService
+ def execute(base_pipeline, head_pipeline)
+ comparer = comparer_class.new(get_report(base_pipeline), get_report(head_pipeline))
+ {
+ status: :parsed,
+ key: key(base_pipeline, head_pipeline),
+ data: serializer_class
+ .new(project: project)
+ .represent(comparer).as_json
+ }
+ rescue Gitlab::Ci::Parsers::ParserError => e
+ {
+ status: :error,
+ key: key(base_pipeline, head_pipeline),
+ status_reason: e.message
+ }
+ end
+
+ def latest?(base_pipeline, head_pipeline, data)
+ data&.fetch(:key, nil) == key(base_pipeline, head_pipeline)
+ end
+
+ private
+
+ def key(base_pipeline, head_pipeline)
+ [
+ base_pipeline&.id, base_pipeline&.updated_at,
+ head_pipeline&.id, head_pipeline&.updated_at
+ ]
+ end
+
+ def comparer_class
+ raise NotImplementedError
+ end
+
+ def serializer_class
+ raise NotImplementedError
+ end
+
+ def get_report(pipeline)
+ raise NotImplementedError
+ end
+ end
+end
diff --git a/app/services/ci/compare_test_reports_service.rb b/app/services/ci/compare_test_reports_service.rb
index 2293f95f56b..382d5b8995f 100644
--- a/app/services/ci/compare_test_reports_service.rb
+++ b/app/services/ci/compare_test_reports_service.rb
@@ -1,39 +1,17 @@
# frozen_string_literal: true
module Ci
- class CompareTestReportsService < ::BaseService
- def execute(base_pipeline, head_pipeline)
- # rubocop: disable CodeReuse/Serializer
- comparer = Gitlab::Ci::Reports::TestReportsComparer
- .new(base_pipeline&.test_reports, head_pipeline.test_reports)
-
- {
- status: :parsed,
- key: key(base_pipeline, head_pipeline),
- data: TestReportsComparerSerializer
- .new(project: project)
- .represent(comparer).as_json
- }
- rescue => e
- {
- status: :error,
- key: key(base_pipeline, head_pipeline),
- status_reason: e.message
- }
- # rubocop: enable CodeReuse/Serializer
+ class CompareTestReportsService < CompareReportsBaseService
+ def comparer_class
+ Gitlab::Ci::Reports::TestReportsComparer
end
- def latest?(base_pipeline, head_pipeline, data)
- data&.fetch(:key, nil) == key(base_pipeline, head_pipeline)
+ def serializer_class
+ TestReportsComparerSerializer
end
- private
-
- def key(base_pipeline, head_pipeline)
- [
- base_pipeline&.id, base_pipeline&.updated_at,
- head_pipeline&.id, head_pipeline&.updated_at
- ]
+ def get_report(pipeline)
+ pipeline&.test_reports
end
end
end
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index 13321b2682e..6707a1363d0 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -118,7 +118,7 @@ module Ci
# Workaround for weird Rails bug, that makes `runner.groups.to_sql` to return `runner_id = NULL`
groups = ::Group.joins(:runner_namespaces).merge(runner.runner_namespaces)
- hierarchy_groups = Gitlab::GroupHierarchy.new(groups).base_and_descendants
+ hierarchy_groups = Gitlab::ObjectHierarchy.new(groups).base_and_descendants
projects = Project.where(namespace_id: hierarchy_groups)
.with_group_runners_enabled
.with_builds_enabled
diff --git a/app/services/clusters/applications/create_service.rb b/app/services/clusters/applications/create_service.rb
index a89772e82dc..92c2c1b9834 100644
--- a/app/services/clusters/applications/create_service.rb
+++ b/app/services/clusters/applications/create_service.rb
@@ -20,7 +20,7 @@ module Clusters
end
if application.has_attribute?(:email)
- application.email = current_user.email
+ application.email = params[:email]
end
if application.respond_to?(:oauth_application)
diff --git a/app/services/clusters/build_service.rb b/app/services/clusters/build_service.rb
new file mode 100644
index 00000000000..8de73831164
--- /dev/null
+++ b/app/services/clusters/build_service.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+module Clusters
+ class BuildService
+ def initialize(subject)
+ @subject = subject
+ end
+
+ def execute
+ ::Clusters::Cluster.new.tap do |cluster|
+ case @subject
+ when ::Project
+ cluster.cluster_type = :project_type
+ when ::Group
+ cluster.cluster_type = :group_type
+ else
+ raise NotImplementedError
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/gcp/fetch_operation_service.rb b/app/services/clusters/gcp/fetch_operation_service.rb
index 02c96a1e286..6c648b443a0 100644
--- a/app/services/clusters/gcp/fetch_operation_service.rb
+++ b/app/services/clusters/gcp/fetch_operation_service.rb
@@ -11,8 +11,21 @@ module Clusters
yield(operation) if block_given?
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
+ logger.error(
+ exception: e.class.name,
+ service: self.class.name,
+ provider_id: provider.id,
+ message: e.message
+ )
+
provider.make_errored!("Failed to request to CloudPlatform; #{e.message}")
end
+
+ private
+
+ def logger
+ @logger ||= Gitlab::Kubernetes::Logger.build
+ end
end
end
end
diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb
index e029323774c..5525c1b9b7f 100644
--- a/app/services/clusters/gcp/finalize_creation_service.rb
+++ b/app/services/clusters/gcp/finalize_creation_service.rb
@@ -13,14 +13,17 @@ module Clusters
configure_kubernetes
cluster.save!
- ClusterPlatformConfigureWorker.perform_async(cluster.id)
+ ClusterConfigureWorker.perform_async(cluster.id)
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
- provider.make_errored!("Failed to request to CloudPlatform; #{e.message}")
+ log_service_error(e.class.name, provider.id, e.message)
+ provider.make_errored!(s_('ClusterIntegration|Failed to request to Google Cloud Platform: %{message}') % { message: e.message })
rescue Kubeclient::HttpError => e
- provider.make_errored!("Failed to run Kubeclient: #{e.message}")
+ log_service_error(e.class.name, provider.id, e.message)
+ provider.make_errored!(s_('ClusterIntegration|Failed to run Kubeclient: %{message}') % { message: e.message })
rescue ActiveRecord::RecordInvalid => e
- provider.make_errored!("Failed to configure Google Kubernetes Engine Cluster: #{e.message}")
+ log_service_error(e.class.name, provider.id, e.message)
+ provider.make_errored!(s_('ClusterIntegration|Failed to configure Google Kubernetes Engine Cluster: %{message}') % { message: e.message })
end
private
@@ -105,6 +108,19 @@ module Clusters
def cluster
@cluster ||= provider.cluster
end
+
+ def logger
+ @logger ||= Gitlab::Kubernetes::Logger.build
+ end
+
+ def log_service_error(exception, provider_id, message)
+ logger.error(
+ exception: exception.class.name,
+ service: self.class.name,
+ provider_id: provider_id,
+ message: message
+ )
+ end
end
end
end
diff --git a/app/services/create_release_service.rb b/app/services/create_release_service.rb
index 8d1fdbe11c3..ab2dc5337aa 100644
--- a/app/services/create_release_service.rb
+++ b/app/services/create_release_service.rb
@@ -13,8 +13,13 @@ class CreateReleaseService < BaseService
if release
error('Release already exists', 409)
else
- release = project.releases.new({ tag: tag_name, description: release_description })
- release.save
+ release = project.releases.create!(
+ tag: tag_name,
+ name: tag_name,
+ sha: existing_tag.dereferenced_target.sha,
+ author: current_user,
+ description: release_description
+ )
success(release)
end
diff --git a/app/services/deploy_keys/create_service.rb b/app/services/deploy_keys/create_service.rb
index a25e73666f8..0c935285657 100644
--- a/app/services/deploy_keys/create_service.rb
+++ b/app/services/deploy_keys/create_service.rb
@@ -2,7 +2,7 @@
module DeployKeys
class CreateService < Keys::BaseService
- def execute
+ def execute(project: nil)
DeployKey.create(params.merge(user: user))
end
end
diff --git a/app/services/groups/nested_create_service.rb b/app/services/groups/nested_create_service.rb
index 50d34d8cb91..f01f5656296 100644
--- a/app/services/groups/nested_create_service.rb
+++ b/app/services/groups/nested_create_service.rb
@@ -18,7 +18,7 @@ module Groups
return namespace
end
- if group_path.include?('/') && !Group.supports_nested_groups?
+ if group_path.include?('/') && !Group.supports_nested_objects?
raise 'Nested groups are not supported on MySQL'
end
diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb
index 5efa746dfb9..f64e327416a 100644
--- a/app/services/groups/transfer_service.rb
+++ b/app/services/groups/transfer_service.rb
@@ -40,7 +40,7 @@ module Groups
def ensure_allowed_transfer
raise_transfer_error(:group_is_already_root) if group_is_already_root?
- raise_transfer_error(:database_not_supported) unless Group.supports_nested_groups?
+ raise_transfer_error(:database_not_supported) unless Group.supports_nested_objects?
raise_transfer_error(:same_parent_as_current) if same_parent?
raise_transfer_error(:invalid_policies) unless valid_policies?
raise_transfer_error(:namespace_with_same_path) if namespace_with_same_path?
diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb
index 0bf0e967dcc..31d3c844ad5 100644
--- a/app/services/groups/update_service.rb
+++ b/app/services/groups/update_service.rb
@@ -36,7 +36,7 @@ module Groups
end
def reject_parent_id!
- params.except!(:parent_id)
+ params.delete(:parent_id)
end
def valid_share_with_group_lock_change?
diff --git a/app/services/issuable/common_system_notes_service.rb b/app/services/issuable/common_system_notes_service.rb
index 765de9c66b0..885e14bba8f 100644
--- a/app/services/issuable/common_system_notes_service.rb
+++ b/app/services/issuable/common_system_notes_service.rb
@@ -4,20 +4,23 @@ module Issuable
class CommonSystemNotesService < ::BaseService
attr_reader :issuable
- def execute(issuable, old_labels)
+ def execute(issuable, old_labels: [], is_update: true)
@issuable = issuable
- if issuable.previous_changes.include?('title')
- create_title_change_note(issuable.previous_changes['title'].first)
- end
+ if is_update
+ if issuable.previous_changes.include?('title')
+ create_title_change_note(issuable.previous_changes['title'].first)
+ end
- handle_description_change_note
+ handle_description_change_note
+
+ handle_time_tracking_note if issuable.is_a?(TimeTrackable)
+ create_discussion_lock_note if issuable.previous_changes.include?('discussion_locked')
+ end
- handle_time_tracking_note if issuable.is_a?(TimeTrackable)
- create_labels_note(old_labels) if issuable.labels != old_labels
- create_discussion_lock_note if issuable.previous_changes.include?('discussion_locked')
- create_milestone_note if issuable.previous_changes.include?('milestone_id')
create_due_date_note if issuable.previous_changes.include?('due_date')
+ create_milestone_note if issuable.previous_changes.include?('milestone_id')
+ create_labels_note(old_labels) if issuable.labels != old_labels
end
private
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index e32e262ac31..c7e7bb55e4b 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -152,6 +152,10 @@ class IssuableBaseService < BaseService
before_create(issuable)
if issuable.save
+ ActiveRecord::Base.no_touching do
+ Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, is_update: false)
+ end
+
after_create(issuable)
execute_hooks(issuable)
invalidate_cache_counts(issuable, users: issuable.assignees)
@@ -207,7 +211,7 @@ class IssuableBaseService < BaseService
if issuable.with_transaction_returning_status { issuable.save }
# We do not touch as it will affect a update on updated_at field
ActiveRecord::Base.no_touching do
- Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_associations[:labels])
+ Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels: old_associations[:labels])
end
handle_changes(issuable, old_associations: old_associations)
diff --git a/app/services/labels/promote_service.rb b/app/services/labels/promote_service.rb
index f30ad706c63..3c0e6196d4f 100644
--- a/app/services/labels/promote_service.rb
+++ b/app/services/labels/promote_service.rb
@@ -57,7 +57,7 @@ module Labels
def update_issuables(new_label, label_ids)
LabelLink
.where(label: label_ids)
- .update_all(label_id: new_label)
+ .update_all(label_id: new_label.id)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -65,7 +65,7 @@ module Labels
def update_resource_label_events(new_label, label_ids)
ResourceLabelEvent
.where(label: label_ids)
- .update_all(label_id: new_label)
+ .update_all(label_id: new_label.id)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -73,7 +73,7 @@ module Labels
def update_issue_board_lists(new_label, label_ids)
List
.where(label: label_ids)
- .update_all(label_id: new_label)
+ .update_all(label_id: new_label.id)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -81,7 +81,7 @@ module Labels
def update_priorities(new_label, label_ids)
LabelPriority
.where(label: label_ids)
- .update_all(label_id: new_label)
+ .update_all(label_id: new_label.id)
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 667b5916f38..f712b8863cd 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -58,13 +58,27 @@ module MergeRequests
.preload(:latest_merge_request_diff)
.where(target_branch: @push.branch_name).to_a
.select(&:diff_head_commit)
+ .select do |merge_request|
+ commit_ids.include?(merge_request.diff_head_sha) &&
+ merge_request.merge_request_diff.state != 'empty'
+ end
+ merge_requests = filter_merge_requests(merge_requests)
+
+ return if merge_requests.empty?
- merge_requests = merge_requests.select do |merge_request|
- commit_ids.include?(merge_request.diff_head_sha) &&
- merge_request.merge_request_diff.state != 'empty'
+ commit_analyze_enabled = Feature.enabled?(:branch_push_merge_commit_analyze, @project, default_enabled: true)
+ if commit_analyze_enabled
+ analyzer = Gitlab::BranchPushMergeCommitAnalyzer.new(
+ @commits.reverse,
+ relevant_commit_ids: merge_requests.map(&:diff_head_sha)
+ )
end
- filter_merge_requests(merge_requests).each do |merge_request|
+ merge_requests.each do |merge_request|
+ if commit_analyze_enabled
+ merge_request.merge_commit_sha = analyzer.get_merge_commit(merge_request.diff_head_sha)
+ end
+
MergeRequests::PostMergeService
.new(merge_request.target_project, @current_user)
.execute(merge_request)
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index aacaf10d09c..33d8299c8b6 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -5,14 +5,15 @@ module MergeRequests
def execute(merge_request)
# We don't allow change of source/target projects and source branch
# after merge request was created
- params.except!(:source_project_id)
- params.except!(:target_project_id)
- params.except!(:source_branch)
+ params.delete(:source_project_id)
+ params.delete(:target_project_id)
+ params.delete(:source_branch)
merge_from_quick_action(merge_request) if params[:merge]
if merge_request.closed_without_fork?
- params.except!(:target_branch, :force_remove_source_branch)
+ params.delete(:target_branch)
+ params.delete(:force_remove_source_branch)
end
if params[:force_remove_source_branch].present?
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index e03789e3ca9..c4546f30235 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -36,6 +36,7 @@ module Notes
if !only_commands && note.save
todo_service.new_note(note, current_user)
clear_noteable_diffs_cache(note)
+ Suggestions::CreateService.new(note).execute
end
if command_params.present?
diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb
index 35db409eb27..d2052bed646 100644
--- a/app/services/notes/update_service.rb
+++ b/app/services/notes/update_service.rb
@@ -14,6 +14,17 @@ module Notes
TodoService.new.update_note(note, current_user, old_mentioned_users)
end
+ if 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.reload
+ end
+
note
end
end
diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb
index 9c236d7f41d..68cdc69023a 100644
--- a/app/services/notification_recipient_service.rb
+++ b/app/services/notification_recipient_service.rb
@@ -24,6 +24,10 @@ module NotificationRecipientService
Builder::MergeRequestUnmergeable.new(*args).notification_recipients
end
+ def self.build_project_maintainers_recipients(*args)
+ Builder::ProjectMaintainers.new(*args).notification_recipients
+ end
+
module Builder
class Base
def initialize(*)
@@ -380,5 +384,24 @@ module NotificationRecipientService
nil
end
end
+
+ class ProjectMaintainers < Base
+ attr_reader :target
+
+ def initialize(target, action:)
+ @target = target
+ @action = action
+ end
+
+ def build!
+ return [] unless project
+
+ add_recipients(project.team.maintainers, :watch, nil)
+ end
+
+ def acting_user
+ nil
+ end
+ end
end
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 5904bfbf88d..ff035fea216 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -429,26 +429,26 @@ class NotificationService
end
def pages_domain_verification_succeeded(domain)
- recipients_for_pages_domain(domain).each do |user|
- mailer.pages_domain_verification_succeeded_email(domain, user).deliver_later
+ project_maintainers_recipients(domain, action: 'succeeded').each do |recipient|
+ mailer.pages_domain_verification_succeeded_email(domain, recipient.user).deliver_later
end
end
def pages_domain_verification_failed(domain)
- recipients_for_pages_domain(domain).each do |user|
- mailer.pages_domain_verification_failed_email(domain, user).deliver_later
+ project_maintainers_recipients(domain, action: 'failed').each do |recipient|
+ mailer.pages_domain_verification_failed_email(domain, recipient.user).deliver_later
end
end
def pages_domain_enabled(domain)
- recipients_for_pages_domain(domain).each do |user|
- mailer.pages_domain_enabled_email(domain, user).deliver_later
+ project_maintainers_recipients(domain, action: 'enabled').each do |recipient|
+ mailer.pages_domain_enabled_email(domain, recipient.user).deliver_later
end
end
def pages_domain_disabled(domain)
- recipients_for_pages_domain(domain).each do |user|
- mailer.pages_domain_disabled_email(domain, user).deliver_later
+ project_maintainers_recipients(domain, action: 'disabled').each do |recipient|
+ mailer.pages_domain_disabled_email(domain, recipient.user).deliver_later
end
end
@@ -466,6 +466,22 @@ class NotificationService
end
end
+ def repository_cleanup_success(project, user)
+ mailer.send(:repository_cleanup_success_email, project, user).deliver_later
+ end
+
+ def repository_cleanup_failure(project, user, error)
+ mailer.send(:repository_cleanup_failure_email, project, user, error).deliver_later
+ end
+
+ def remote_mirror_update_failed(remote_mirror)
+ recipients = project_maintainers_recipients(remote_mirror, action: 'update_failed')
+
+ recipients.each do |recipient|
+ mailer.remote_mirror_update_failed_email(remote_mirror.id, recipient.user.id).deliver_later
+ end
+ end
+
protected
def new_resource_email(target, method)
@@ -561,12 +577,8 @@ class NotificationService
private
- def recipients_for_pages_domain(domain)
- project = domain.project
-
- return [] unless project
-
- notifiable_users(project.team.maintainers, :watch, target: project)
+ def project_maintainers_recipients(target, action:)
+ NotificationRecipientService.build_project_maintainers_recipients(target, action: action)
end
def notifiable?(*args)
diff --git a/app/services/preview_markdown_service.rb b/app/services/preview_markdown_service.rb
index de8757006f1..a449a5dc3e9 100644
--- a/app/services/preview_markdown_service.rb
+++ b/app/services/preview_markdown_service.rb
@@ -4,10 +4,12 @@ class PreviewMarkdownService < BaseService
def execute
text, commands = explain_quick_actions(params[:text])
users = find_user_references(text)
+ suggestions = find_suggestions(text)
success(
text: text,
users: users,
+ suggestions: suggestions,
commands: commands.join(' '),
markdown_engine: markdown_engine
)
@@ -28,6 +30,12 @@ class PreviewMarkdownService < BaseService
extractor.users.map(&:username)
end
+ def find_suggestions(text)
+ return [] unless params[:preview_suggestions]
+
+ Banzai::SuggestionsParser.parse(text)
+ end
+
def find_commands_target
QuickActions::TargetService
.new(project, current_user)
diff --git a/app/services/projects/after_rename_service.rb b/app/services/projects/after_rename_service.rb
index 4131da44f5a..aa9b253eb20 100644
--- a/app/services/projects/after_rename_service.rb
+++ b/app/services/projects/after_rename_service.rb
@@ -81,6 +81,7 @@ module Projects
def update_repository_configuration
project.reload_repository!
project.write_repository_config
+ project.track_project_repository
end
def rename_transferred_documents
diff --git a/app/services/projects/cleanup_service.rb b/app/services/projects/cleanup_service.rb
new file mode 100644
index 00000000000..12103ea34b5
--- /dev/null
+++ b/app/services/projects/cleanup_service.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module Projects
+ # The CleanupService removes data from the project repository following a
+ # BFG rewrite: https://rtyley.github.io/bfg-repo-cleaner/
+ #
+ # Before executing this service, all refs rewritten by BFG should have been
+ # pushed to the repository
+ class CleanupService < BaseService
+ NoUploadError = StandardError.new("Couldn't find uploaded object map")
+
+ include Gitlab::Utils::StrongMemoize
+
+ # Attempt to clean up the project following the push. Warning: this is
+ # destructive!
+ #
+ # path is the path of an upload of a BFG object map file. It contains a line
+ # per rewritten object, with the old and new SHAs space-separated. It can be
+ # used to update or remove content that references the objects that BFG has
+ # altered
+ #
+ # Currently, only the project repository is modified by this service, but we
+ # may wish to modify other data sources in the future.
+ def execute
+ apply_bfg_object_map!
+
+ # Remove older objects that are no longer referenced
+ GitGarbageCollectWorker.new.perform(project.id, :gc)
+
+ # The cache may now be inaccurate, and holding onto it could prevent
+ # bugs assuming the presence of some object from manifesting for some
+ # time. Better to feel the pain immediately.
+ project.repository.expire_all_method_caches
+
+ project.bfg_object_map.remove!
+ end
+
+ private
+
+ def apply_bfg_object_map!
+ raise NoUploadError unless project.bfg_object_map.exists?
+
+ project.bfg_object_map.open do |io|
+ repository_cleaner.apply_bfg_object_map(io)
+ end
+ end
+
+ def repository_cleaner
+ @repository_cleaner ||= Gitlab::Git::RepositoryCleaner.new(repository.raw)
+ end
+ end
+end
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 210571b6b4e..336d029d330 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -137,6 +137,8 @@ module Projects
raise_error('Failed to remove some tags in project container registry. Please try again or contact administrator.')
end
+ project.leave_pool_repository
+
Project.transaction do
log_destroy_event
trash_repositories!
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index 8dc0e044875..91091c4393d 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -54,6 +54,8 @@ module Projects
new_params[:avatar] = @project.avatar
end
+ new_params.merge!(@project.object_pool_params)
+
new_project = CreateService.new(current_user, new_params).execute
return new_project unless new_project.persisted?
diff --git a/app/services/projects/lfs_pointers/lfs_download_service.rb b/app/services/projects/lfs_pointers/lfs_download_service.rb
index 1c4a8d05be6..f9b9781ad5f 100644
--- a/app/services/projects/lfs_pointers/lfs_download_service.rb
+++ b/app/services/projects/lfs_pointers/lfs_download_service.rb
@@ -4,6 +4,8 @@
module Projects
module LfsPointers
class LfsDownloadService < BaseService
+ VALID_PROTOCOLS = %w[http https].freeze
+
# rubocop: disable CodeReuse/ActiveRecord
def execute(oid, url)
return unless project&.lfs_enabled? && oid.present? && url.present?
@@ -11,6 +13,7 @@ module Projects
return if LfsObject.exists?(oid: oid)
sanitized_uri = Gitlab::UrlSanitizer.new(url)
+ Gitlab::UrlBlocker.validate!(sanitized_uri.sanitized_url, protocols: VALID_PROTOCOLS)
with_tmp_file(oid) do |file|
size = download_and_save_file(file, sanitized_uri)
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 9db3fd9cf17..5da1e39a1fb 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -81,7 +81,7 @@ module Projects
project.old_path_with_namespace = @old_path
- write_repository_config(@new_path)
+ update_repository_configuration(@new_path)
execute_system_hooks
end
@@ -106,8 +106,9 @@ module Projects
project.save!
end
- def write_repository_config(full_path)
+ def update_repository_configuration(full_path)
project.write_repository_config(gl_full_path: full_path)
+ project.track_project_repository
end
def refresh_permissions
@@ -123,7 +124,7 @@ module Projects
rollback_folder_move
project.reload
update_namespace_and_visibility(@old_namespace)
- write_repository_config(@old_path)
+ update_repository_configuration(@old_path)
end
def rollback_folder_move
diff --git a/app/services/suggestions/apply_service.rb b/app/services/suggestions/apply_service.rb
new file mode 100644
index 00000000000..d931d528c86
--- /dev/null
+++ b/app/services/suggestions/apply_service.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module Suggestions
+ class ApplyService < ::BaseService
+ def initialize(current_user)
+ @current_user = current_user
+ end
+
+ def execute(suggestion)
+ unless suggestion.appliable?
+ return error('Suggestion is not appliable')
+ end
+
+ params = file_update_params(suggestion)
+ result = ::Files::UpdateService.new(suggestion.project, @current_user, params).execute
+
+ if result[:status] == :success
+ suggestion.update(commit_id: result[:result], applied: true)
+ end
+
+ result
+ end
+
+ private
+
+ def file_update_params(suggestion)
+ diff_file = suggestion.diff_file
+
+ file_path = diff_file.file_path
+ branch_name = suggestion.noteable.source_branch
+ file_content = new_file_content(suggestion)
+ commit_message = "Apply suggestion to #{file_path}"
+
+ {
+ file_path: file_path,
+ branch_name: branch_name,
+ start_branch: branch_name,
+ commit_message: commit_message,
+ file_content: file_content
+ }
+ end
+
+ def new_file_content(suggestion)
+ range = suggestion.from_line_index..suggestion.to_line_index
+ blob = suggestion.diff_file.new_blob
+
+ blob.load_all_data!
+ content = blob.data.lines
+ content[range] = suggestion.to_content
+
+ content.join
+ end
+ end
+end
diff --git a/app/services/suggestions/create_service.rb b/app/services/suggestions/create_service.rb
new file mode 100644
index 00000000000..77e958cbe0c
--- /dev/null
+++ b/app/services/suggestions/create_service.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module Suggestions
+ class CreateService
+ def initialize(note)
+ @note = note
+ end
+
+ def execute
+ return unless @note.supports_suggestion?
+
+ suggestions = Banzai::SuggestionsParser.parse(@note.note)
+
+ # For single line suggestion we're only looking forward to
+ # change the line receiving the comment. Though, in
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/53310
+ # we'll introduce a ```suggestion:L<x>-<y>, so this will
+ # slightly change.
+ comment_line = @note.position.new_line
+
+ rows =
+ suggestions.map.with_index do |suggestion, index|
+ from_content = changing_lines(comment_line, comment_line)
+
+ # The parsed suggestion doesn't have information about the correct
+ # ending characters (we may have a line break, or not), so we take
+ # this information from the last line being changed (last
+ # characters).
+ endline_chars = line_break_chars(from_content.lines.last)
+ to_content = "#{suggestion}#{endline_chars}"
+
+ {
+ note_id: @note.id,
+ from_content: from_content,
+ to_content: to_content,
+ relative_order: index
+ }
+ end
+
+ rows.in_groups_of(100, false) do |rows|
+ Gitlab::Database.bulk_insert('suggestions', rows)
+ end
+ end
+
+ private
+
+ def changing_lines(from_line, to_line)
+ @note.diff_file.new_blob_lines_between(from_line, to_line).join
+ end
+
+ def line_break_chars(line)
+ match = /\r\n|\r|\n/.match(line)
+ match[0] if match
+ end
+ end
+end
diff --git a/app/services/tags/create_service.rb b/app/services/tags/create_service.rb
index 35390f5082c..6bb9bb3988e 100644
--- a/app/services/tags/create_service.rb
+++ b/app/services/tags/create_service.rb
@@ -20,7 +20,7 @@ module Tags
end
if new_tag
- if release_description
+ if release_description.present?
CreateReleaseService.new(@project, @current_user)
.execute(tag_name, release_description)
end
diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb
index 23b63aaabdf..fe5a82e23fa 100644
--- a/app/services/users/refresh_authorized_projects_service.rb
+++ b/app/services/users/refresh_authorized_projects_service.rb
@@ -102,7 +102,7 @@ module Users
end
def fresh_authorizations
- klass = if Group.supports_nested_groups?
+ klass = if Group.supports_nested_objects?
Gitlab::ProjectAuthorizations::WithNestedGroups
else
Gitlab::ProjectAuthorizations::WithoutNestedGroups
diff --git a/app/services/users/update_service.rb b/app/services/users/update_service.rb
index a897e4bd56a..af4fe1aebb9 100644
--- a/app/services/users/update_service.rb
+++ b/app/services/users/update_service.rb
@@ -16,7 +16,7 @@ module Users
user_exists = @user.persisted?
- assign_attributes(&block)
+ assign_attributes
if @user.save(validate: validate) && update_status
notify_success(user_exists)
@@ -48,9 +48,11 @@ module Users
success
end
- def assign_attributes(&block)
- if @user.user_synced_attributes_metadata
- params.except!(*@user.user_synced_attributes_metadata.read_only_attributes)
+ def assign_attributes
+ if (metadata = @user.user_synced_attributes_metadata)
+ read_only = metadata.read_only_attributes
+
+ params.reject! { |key, _| read_only.include?(key.to_sym) }
end
@user.assign_attributes(params) if params.any?
diff --git a/app/validators/url_validator.rb b/app/validators/url_validator.rb
index 216acf79cbd..5feb0b0f05b 100644
--- a/app/validators/url_validator.rb
+++ b/app/validators/url_validator.rb
@@ -69,6 +69,7 @@ class UrlValidator < ActiveModel::EachValidator
ports: [],
allow_localhost: true,
allow_local_network: true,
+ ascii_only: false,
enforce_user: false
}
end
diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml
index cb67079853e..544f09048f5 100644
--- a/app/views/admin/appearances/_form.html.haml
+++ b/app/views/admin/appearances/_form.html.haml
@@ -8,7 +8,7 @@
= f.label :header_logo, 'Header logo', class: 'col-sm-2 col-form-label pt-0'
.col-sm-10
- if @appearance.header_logo?
- = image_tag @appearance.header_logo_url, class: 'appearance-light-logo-preview'
+ = image_tag @appearance.header_logo_path, class: 'appearance-light-logo-preview'
- if @appearance.persisted?
%br
= link_to 'Remove header logo', header_logos_admin_appearances_path, data: { confirm: "Header logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-inverted btn-remove btn-sm remove-logo"
@@ -25,7 +25,7 @@
= f.label :favicon, 'Favicon', class: 'col-sm-2 col-form-label pt-0'
.col-sm-10
- if @appearance.favicon?
- = image_tag @appearance.favicon_url, class: 'appearance-light-logo-preview'
+ = image_tag @appearance.favicon_path, class: 'appearance-light-logo-preview'
- if @appearance.persisted?
%br
= link_to 'Remove favicon', favicon_admin_appearances_path, data: { confirm: "Favicon will be removed. Are you sure?"}, method: :delete, class: "btn btn-inverted btn-remove btn-sm remove-logo"
@@ -54,7 +54,7 @@
= f.label :logo, class: 'col-sm-2 col-form-label pt-0'
.col-sm-10
- if @appearance.logo?
- = image_tag @appearance.logo_url, class: 'appearance-logo-preview'
+ = image_tag @appearance.logo_path, class: 'appearance-logo-preview'
- if @appearance.persisted?
%br
= link_to 'Remove logo', logo_admin_appearances_path, data: { confirm: "Logo will be removed. Are you sure?"}, method: :delete, class: "btn btn-inverted btn-remove btn-sm remove-logo"
diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml
index 0d42094fc89..fdaad1cf181 100644
--- a/app/views/admin/application_settings/_ci_cd.html.haml
+++ b/app/views/admin/application_settings/_ci_cd.html.haml
@@ -49,5 +49,12 @@
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>.
+ .form-group
+ .form-check
+ = f.check_box :protected_ci_variables, class: 'form-check-input'
+ = f.label :protected_ci_variables, class: 'form-check-label' do
+ = s_('AdminSettings|Environment variables are protected by default')
+ .form-text.text-muted
+ = s_('AdminSettings|When creating a new environment variable it will be protected by default.')
= f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 7ac79cc77f5..6756299cf43 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -174,7 +174,7 @@
%h4 Latest projects
- @projects.each do |project|
%p
- = link_to project.full_name, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60'
+ = 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
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 5f205d1bcbc..da2ebb08405 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -101,7 +101,7 @@
= _('Add user(s) to the group:')
.card-body.form-holder
%p.light
- - link_to_help = link_to(_("here"), help_page_path("user/permissions"), class: "vlink")
+ - link_to_help = link_to(_("here"), help_page_path("user/permissions"))
= _('Read more about project permissions <strong>%{link_to_help}</strong>').html_safe % { link_to_help: link_to_help }
= form_tag admin_group_members_update_path(@group), id: "new_project_member", class: "bulk_import", method: :put do
diff --git a/app/views/admin/hooks/edit.html.haml b/app/views/admin/hooks/edit.html.haml
index 486d0477f20..9c6c74ed965 100644
--- a/app/views/admin/hooks/edit.html.haml
+++ b/app/views/admin/hooks/edit.html.haml
@@ -4,7 +4,7 @@
Edit System Hook
%p.light
- #{link_to 'System hooks ', help_page_path('system_hooks/system_hooks'), class: 'vlink'} can be
+ #{link_to 'System hooks ', help_page_path('system_hooks/system_hooks')} can be
used for binding events when GitLab creates a User or Project.
%hr
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index 5d462d7b732..b65bf07160a 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -4,7 +4,7 @@
%h4.prepend-top-0
= page_title
%p
- #{link_to 'System hooks ', help_page_path('system_hooks/system_hooks'), class: 'vlink'} can be
+ #{link_to 'System hooks ', help_page_path('system_hooks/system_hooks')} can be
used for binding events when GitLab creates a User or Project.
.col-lg-8.append-bottom-default
diff --git a/app/views/admin/runners/_sort_dropdown.html.haml b/app/views/admin/runners/_sort_dropdown.html.haml
index 19c2a50ebd9..4f4f0a543e0 100644
--- a/app/views/admin/runners/_sort_dropdown.html.haml
+++ b/app/views/admin/runners/_sort_dropdown.html.haml
@@ -6,6 +6,6 @@
= icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
%li
- = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date, label: true), sorted_by)
- = sortable_item(sort_title_contacted_date, page_filter_path(sort: sort_value_contacted_date, label: true), sorted_by)
+ = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date), sorted_by)
+ = sortable_item(sort_title_contacted_date, page_filter_path(sort: sort_value_contacted_date), sorted_by)
diff --git a/app/views/ci/variables/_content.html.haml b/app/views/ci/variables/_content.html.haml
index d355e7799df..fa82611d9c1 100644
--- a/app/views/ci/variables/_content.html.haml
+++ b/app/views/ci/variables/_content.html.haml
@@ -1 +1 @@
-= _('Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want.')
+= _('Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want.')
diff --git a/app/views/ci/variables/_header.html.haml b/app/views/ci/variables/_header.html.haml
new file mode 100644
index 00000000000..cb7779e2175
--- /dev/null
+++ b/app/views/ci/variables/_header.html.haml
@@ -0,0 +1,11 @@
+- expanded = local_assigns.fetch(:expanded)
+
+%h4
+ = _('Environment variables')
+ = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'variables'), target: '_blank', rel: 'noopener noreferrer'
+
+%button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded ? _('Collapse') : _('Expand')
+
+%p.append-bottom-0
+ = render "ci/variables/content"
diff --git a/app/views/ci/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml
index f34305e94fa..dc9ccb6cc39 100644
--- a/app/views/ci/variables/_index.html.haml
+++ b/app/views/ci/variables/_index.html.haml
@@ -1,5 +1,10 @@
- save_endpoint = local_assigns.fetch(:save_endpoint, nil)
+- if ci_variable_protected_by_default?
+ %p.settings-message.text-center
+ - link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('ci/variables/README', anchor: 'protected-variables') }
+ = s_('Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
+
.row
.col-lg-12.js-ci-variable-list-section{ data: { save_endpoint: save_endpoint } }
.hide.alert.alert-danger.js-ci-variable-error-box
diff --git a/app/views/ci/variables/_variable_row.html.haml b/app/views/ci/variables/_variable_row.html.haml
index 6ee55836dd2..16a7527c8ce 100644
--- a/app/views/ci/variables/_variable_row.html.haml
+++ b/app/views/ci/variables/_variable_row.html.haml
@@ -5,7 +5,8 @@
- id = variable&.id
- key = variable&.key
- value = variable&.value
-- is_protected = variable && !only_key_value ? variable.protected : false
+- is_protected_default = ci_variable_protected_by_default?
+- is_protected = ci_variable_protected?(variable, only_key_value)
- id_input_name = "#{form_field}[variables_attributes][][id]"
- destroy_input_name = "#{form_field}[variables_attributes][][_destroy]"
@@ -39,7 +40,8 @@
%input{ type: "hidden",
class: 'js-ci-variable-input-protected js-project-feature-toggle-input',
name: protected_input_name,
- value: is_protected }
+ value: is_protected,
+ data: { default: is_protected_default.to_s } }
%span.toggle-icon
= sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
= sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
diff --git a/app/views/clusters/clusters/_buttons.html.haml b/app/views/clusters/clusters/_buttons.html.haml
index db2e247e341..9238903aa10 100644
--- a/app/views/clusters/clusters/_buttons.html.haml
+++ b/app/views/clusters/clusters/_buttons.html.haml
@@ -1,4 +1,7 @@
-# This partial is overridden in EE
.nav-controls
- %span.btn.btn-add-cluster.disabled.js-add-cluster
- = s_("ClusterIntegration|Add Kubernetes cluster")
+ - if clusterable.can_create_cluster? && clusterable.clusters.empty?
+ = link_to s_('ClusterIntegration|Add Kubernetes cluster'), clusterable.new_path, class: 'btn btn-success js-add-cluster'
+ - else
+ %span.btn.btn-add-cluster.disabled.js-add-cluster
+ = s_("ClusterIntegration|Add Kubernetes cluster")
diff --git a/app/views/clusters/clusters/_cluster.html.haml b/app/views/clusters/clusters/_cluster.html.haml
index adeca013749..b89789e9915 100644
--- a/app/views/clusters/clusters/_cluster.html.haml
+++ b/app/views/clusters/clusters/_cluster.html.haml
@@ -3,7 +3,7 @@
.table-section.section-60
.table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Kubernetes cluster")
.table-mobile-content
- = link_to cluster.name, cluster.show_path
+ = cluster.item_link(clusterable)
- unless cluster.enabled?
%span.badge.badge-danger Connection disabled
.table-section.section-25
@@ -13,4 +13,4 @@
.table-mobile-header{ role: "rowheader" }
.table-mobile-content
%span.badge.badge-light
- = cluster.project_type? ? s_("ClusterIntegration|Project cluster") : s_("ClusterIntegration|Group cluster")
+ = cluster.cluster_type_description
diff --git a/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
index 85d1002243b..73b11d509d3 100644
--- a/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
+++ b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
@@ -1,6 +1,6 @@
- link = link_to(s_('ClusterIntegration|sign up'), 'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer')
-.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert', data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } }
- %button.close.js-close{ type: "button" } &times;
+.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert' }
+ %button.close{ type: "button", data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } } &times;
.gcp-signup-offer--content
.gcp-signup-offer--icon.append-right-8
= sprite_icon("information", size: 16)
diff --git a/app/views/clusters/clusters/index.html.haml b/app/views/clusters/clusters/index.html.haml
index ad6d1d856d6..58d0a304363 100644
--- a/app/views/clusters/clusters/index.html.haml
+++ b/app/views/clusters/clusters/index.html.haml
@@ -11,6 +11,13 @@
.nav-text
= s_("ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project")
= render 'clusters/clusters/buttons'
+
+ - if @has_ancestor_clusters
+ .bs-callout.bs-callout-info
+ = s_("ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters.")
+ %strong
+ = link_to _('More information'), help_page_path('user/group/clusters/', anchor: 'cluster-precedence')
+
.clusters-table.js-clusters-list
.gl-responsive-table-row.table-row-header{ role: "row" }
.table-section.section-60{ role: "rowheader" }
diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml
index 4dbda5c754b..31d4b3da4f1 100644
--- a/app/views/dashboard/activity.html.haml
+++ b/app/views/dashboard/activity.html.haml
@@ -4,9 +4,6 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
-
-= render_if_exists "shared/gold_trial_callout"
-
- page_title "Activity"
- header_title "Activity", activity_dashboard_path
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index 2f7add600e4..50f39f93283 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -1,8 +1,6 @@
- @hide_top_links = true
- page_title "Groups"
- header_title "Groups", dashboard_groups_path
-
-= render_if_exists "shared/gold_trial_callout"
= render 'dashboard/groups_head'
- if params[:filter].blank? && @groups.empty?
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index afd46412fab..fdd5c19d562 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -4,8 +4,6 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues")
-= render_if_exists "shared/gold_trial_callout"
-
.page-title-holder
%h1.page-title= _('Issues')
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index 3e5f13b92e3..77cfa1271df 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -2,8 +2,6 @@
- page_title _("Merge Requests")
- @breadcrumb_link = merge_requests_dashboard_path(assignee_username: current_user.username)
-= render_if_exists "shared/gold_trial_callout"
-
.page-title-holder
%h1.page-title= _('Merge Requests')
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 446b4715b2d..deed774a4a5 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -4,8 +4,6 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
-= render_if_exists "shared/gold_trial_callout"
-
- page_title "Projects"
- header_title "Projects", dashboard_projects_path
diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index ad08409c8fe..8933d9e31ff 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -4,8 +4,6 @@
- page_title "Starred Projects"
- header_title "Projects", dashboard_projects_path
-= render_if_exists "shared/gold_trial_callout"
-
%div{ class: container_class }
= render "projects/last_push"
= render 'dashboard/projects_head'
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 47729321961..d2593179f17 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -2,8 +2,6 @@
- page_title "Todos"
- header_title "Todos", dashboard_todos_path
-= render_if_exists "shared/gold_trial_callout"
-
.page-title-holder
%h1.page-title= _('Todos')
diff --git a/app/views/errors/access_denied.html.haml b/app/views/errors/access_denied.html.haml
index 8ae29b9d337..46931b5932d 100644
--- a/app/views/errors/access_denied.html.haml
+++ b/app/views/errors/access_denied.html.haml
@@ -9,7 +9,7 @@
%p
= message
%p
- = s_('403|Please contact your GitLab administrator to get the permission.')
+ = s_('403|Please contact your GitLab administrator to get permission.')
.action-container.js-go-back{ style: 'display: none' }
%a{ href: 'javascript:history.back()', class: 'btn btn-success' }
= s_('Go Back')
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index 869be4e8581..a3eafc61d0a 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -2,8 +2,6 @@
- page_title _("Groups")
- header_title _("Groups"), dashboard_groups_path
-= render_if_exists "shared/gold_trial_callout"
-
- if current_user
= render 'dashboard/groups_head'
- else
diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml
index d18dec7bd8e..452f390695c 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -2,8 +2,6 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
-= render_if_exists "shared/gold_trial_callout"
-
- if current_user
= render 'dashboard/projects_head'
- else
diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml
index d18dec7bd8e..452f390695c 100644
--- a/app/views/explore/projects/starred.html.haml
+++ b/app/views/explore/projects/starred.html.haml
@@ -2,8 +2,6 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
-= render_if_exists "shared/gold_trial_callout"
-
- if current_user
= render 'dashboard/projects_head'
- else
diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml
index d18dec7bd8e..452f390695c 100644
--- a/app/views/explore/projects/trending.html.haml
+++ b/app/views/explore/projects/trending.html.haml
@@ -2,8 +2,6 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
-= render_if_exists "shared/gold_trial_callout"
-
- if current_user
= render 'dashboard/projects_head'
- else
diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml
index a0760c2073b..6219da2c715 100644
--- a/app/views/groups/_home_panel.html.haml
+++ b/app/views/groups/_home_panel.html.haml
@@ -1,4 +1,4 @@
-.group-home-panel.text-center
+.group-home-panel.text-center.border-bottom
%div{ class: container_class }
.avatar-container.s70.group-avatar
= group_icon(@group, class: "avatar s70 avatar-tile")
diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml
index 04683ec5a9a..c8cdc2cc3e4 100644
--- a/app/views/groups/group_members/_new_group_member.html.haml
+++ b/app/views/groups/group_members/_new_group_member.html.haml
@@ -8,7 +8,7 @@
.col-md-3.col-lg-2
= select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "form-control project-access-select"
.form-text.text-muted.append-bottom-10
- = link_to "Read more", help_page_path("user/permissions"), class: "vlink"
+ = link_to "Read more", help_page_path("user/permissions")
about role permissions
.col-md-3.col-lg-2
diff --git a/app/views/groups/settings/ci_cd/show.html.haml b/app/views/groups/settings/ci_cd/show.html.haml
index a5e6abdba52..d9332e36ef5 100644
--- a/app/views/groups/settings/ci_cd/show.html.haml
+++ b/app/views/groups/settings/ci_cd/show.html.haml
@@ -5,13 +5,7 @@
%section.settings#ci-variables.no-animate{ class: ('expanded' if expanded) }
.settings-header
- %h4
- = _('Variables')
- = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'variables'), target: '_blank', rel: 'noopener noreferrer'
- %button.btn.btn-default.js-settings-toggle{ type: "button" }
- = expanded ? _('Collapse') : _('Expand')
- %p.append-bottom-0
- = render "ci/variables/content"
+ = render 'ci/variables/header', expanded: expanded
.settings-content
= render 'ci/variables/index', save_endpoint: group_variables_path
diff --git a/app/views/ide/_show.html.haml b/app/views/ide/_show.html.haml
new file mode 100644
index 00000000000..b24d6e27536
--- /dev/null
+++ b/app/views/ide/_show.html.haml
@@ -0,0 +1,10 @@
+- @body_class = 'ide-layout'
+- page_title 'IDE'
+
+- content_for :page_specific_javascripts do
+ = stylesheet_link_tag 'page_bundles/ide'
+
+#ide.ide-loading{ data: ide_data() }
+ .text-center
+ = icon('spinner spin 2x')
+ %h2.clgray= _('Loading the GitLab IDE...')
diff --git a/app/views/ide/index.html.haml b/app/views/ide/index.html.haml
index d8bd37fe986..0323f9d093d 100644
--- a/app/views/ide/index.html.haml
+++ b/app/views/ide/index.html.haml
@@ -1,17 +1 @@
-- @body_class = 'ide-layout'
-- page_title 'IDE'
-
-- content_for :page_specific_javascripts do
- = stylesheet_link_tag 'page_bundles/ide'
-
-#ide.ide-loading{ data: {"empty-state-svg-path" => image_path('illustrations/multi_file_editor_empty.svg'),
- "no-changes-state-svg-path" => image_path('illustrations/multi-editor_no_changes_empty.svg'),
- "committed-state-svg-path" => image_path('illustrations/multi-editor_all_changes_committed_empty.svg'),
- "pipelines-empty-state-svg-path": image_path('illustrations/pipelines_empty.svg'),
- "promotion-svg-path": image_path('illustrations/web-ide_promotion.svg'),
- "ci-help-page-path" => help_page_path('ci/quick_start/README'),
- "web-ide-help-page-path" => help_page_path('user/project/web_ide/index.html'),
- "clientside-preview-enabled": Gitlab::CurrentSettings.current_application_settings.web_ide_clientside_preview_enabled.to_s } }
- .text-center
- = icon('spinner spin 2x')
- %h2.clgray= _('Loading the GitLab IDE...')
+= render 'ide/show'
diff --git a/app/views/issues/_issues_calendar.ics.ruby b/app/views/issues/_issues_calendar.ics.ruby
index 73ab8489e0c..94c3099ace2 100644
--- a/app/views/issues/_issues_calendar.ics.ruby
+++ b/app/views/issues/_issues_calendar.ics.ruby
@@ -3,7 +3,7 @@ cal.prodid = '-//GitLab//NONSGML GitLab//EN'
cal.x_wr_calname = 'GitLab Issues'
# rubocop: disable CodeReuse/ActiveRecord
-@issues.includes(project: :namespace).each do |issue|
+@issues.preload(project: :namespace).each do |issue|
cal.event do |event|
event.dtstart = Icalendar::Values::Date.new(issue.due_date)
event.summary = "#{issue.title} (in #{issue.project.full_path})"
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index ac5916d129c..08a6359f777 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -36,6 +36,7 @@
= 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)
= Gon::Base.render_data
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index a86972d8cf3..a6023a1cbb9 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -2,7 +2,7 @@
- group_data_attrs = { group_path: j(@group.path), name: @group.name, issues_path: issues_group_path(j(@group.path)), mr_path: merge_requests_group_path(j(@group.path)) }
- if @project && @project.persisted?
- project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: project_issues_path(@project), mr_path: project_merge_requests_path(@project), issues_disabled: !@project.issues_enabled? }
-.search.search-form
+.search.search-form{ data: { track_label: "navbar_search", track_event: "activate_form_input" } }
= form_tag search_path, method: :get, class: 'form-inline' do |f|
.search-input-container
.search-input-wrap
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 43bd07679ba..6003d973c88 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -1,7 +1,7 @@
!!! 5
%html.devise-layout-html{ class: system_message_class }
= render "layouts/head"
- %body.ui-indigo.login-page.application.navless{ data: { page: body_data_page } }
+ %body.ui-indigo.login-page.application.navless.qa-login-page{ data: { page: body_data_page } }
.page-wrap
= render "layouts/header/empty"
.login-page-broadcast
@@ -9,7 +9,7 @@
.container.navless-container
.content
= render "layouts/flash"
- .row
+ .row.append-bottom-15
.col-sm-7.brand-holder
%h1
= brand_title
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index b7d69539eb7..a9b85889846 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -15,7 +15,7 @@
= brand_header_logo
- logo_text = brand_header_logo_type
- if logo_text.present?
- %span.logo-text.d-none.d-sm-block
+ %span.logo-text.d-none.d-lg-block.prepend-left-8
= logo_text
- if current_user
@@ -60,7 +60,7 @@
.dropdown-menu.dropdown-menu-right
= render 'layouts/header/help_dropdown'
- if header_link?(:user_dropdown)
- %li.nav-item.header-user.dropdown
+ %li.nav-item.header-user.dropdown{ data: { track_label: "profile_dropdown", track_event: "click_dropdown" } }
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
= image_tag avatar_icon_for_user(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar"
= sprite_icon('angle-down', css_class: 'caret-down')
diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml
index 5cb8aebadb3..e42251f9ec8 100644
--- a/app/views/layouts/header/_new_dropdown.haml
+++ b/app/views/layouts/header/_new_dropdown.haml
@@ -1,4 +1,4 @@
-%li.header-new.dropdown
+%li.header-new.dropdown{ data: { track_label: "new_dropdown", track_event: "click_dropdown" } }
= link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip qa-new-menu-toggle", title: _("New..."), ref: 'tooltip', aria: { label: _("New...") }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body', display: 'static' } do
= sprite_icon('plus-square', size: 16)
= sprite_icon('angle-down', css_class: 'caret-down')
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index ea5f2b166b4..ddd30efe062 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,6 +1,8 @@
+-# WAIT! Before adding more items to the nav bar, please see
+-# https://gitlab.com/gitlab-org/gitlab-ce/issues/49713 for more information.
%ul.list-unstyled.navbar-sub-nav
- if dashboard_nav_link?(:projects)
- = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects qa-projects-dropdown" }) do
+ = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects qa-projects-dropdown", data: { track_label: "projects_dropdown", track_event: "click_dropdown" } }) do
%button{ type: 'button', data: { toggle: "dropdown" } }
= _('Projects')
= sprite_icon('angle-down', css_class: 'caret-down')
@@ -8,7 +10,7 @@
= render "layouts/nav/projects_dropdown/show"
- if dashboard_nav_link?(:groups)
- = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { id: 'nav-groups-dropdown', class: "home dropdown header-groups qa-groups-dropdown" }) do
+ = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { id: 'nav-groups-dropdown', class: "home dropdown header-groups qa-groups-dropdown", data: { track_label: "groups_dropdown", track_event: "click_dropdown" } }) do
%button{ type: 'button', data: { toggle: "dropdown" } }
= _('Groups')
= sprite_icon('angle-down', css_class: 'caret-down')
@@ -16,22 +18,22 @@
= render "layouts/nav/groups_dropdown/show"
- if dashboard_nav_link?(:activity)
- = nav_link(path: 'dashboard#activity', html_options: { class: "d-none d-lg-block d-xl-block" }) do
+ = nav_link(path: 'dashboard#activity', html_options: { class: ["d-none d-xl-block", ("d-lg-block" unless has_extra_nav_icons?)] }) do
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: _('Activity') do
= _('Activity')
- if dashboard_nav_link?(:milestones)
- = nav_link(controller: 'dashboard/milestones', html_options: { class: "d-none d-lg-block d-xl-block" }) do
+ = nav_link(controller: 'dashboard/milestones', html_options: { class: ["d-none d-xl-block", ("d-lg-block" unless has_extra_nav_icons?)] }) do
= link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', title: _('Milestones') do
= _('Milestones')
- if dashboard_nav_link?(:snippets)
- = nav_link(controller: 'dashboard/snippets', html_options: { class: "d-none d-lg-block d-xl-block" }) do
+ = nav_link(controller: 'dashboard/snippets', html_options: { class: ["d-none d-xl-block", ("d-lg-block" unless has_extra_nav_icons?)] }) do
= link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: _('Snippets') do
= _('Snippets')
- if any_dashboard_nav_link?([:groups, :milestones, :activity, :snippets])
- %li.header-more.dropdown.d-lg-none.d-xl-none
+ %li.header-more.dropdown.d-xl-none{ class: ('d-lg-none' unless has_extra_nav_icons?) }
%a{ href: "#", data: { toggle: "dropdown" } }
= _('More')
= sprite_icon('angle-down', css_class: 'caret-down')
@@ -52,6 +54,21 @@
= link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: _('Snippets') do
= _('Snippets')
+ = render_if_exists 'dashboard/operations/nav_link'
+ - if can?(current_user, :read_instance_statistics)
+ = nav_link(controller: [:conversational_development_index, :cohorts]) do
+ = link_to instance_statistics_root_path, title: _('Instance Statistics'), aria: { label: _('Instance Statistics') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = _('Instance Statistics')
+ - if current_user.admin?
+ = nav_link(controller: 'admin/dashboard') do
+ = link_to admin_root_path, class: 'admin-icon qa-admin-area-link', title: _('Admin Area'), aria: { label: _('Admin Area') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = _('Admin Area')
+ - if Gitlab::Sherlock.enabled?
+ %li
+ = link_to sherlock_transactions_path, class: 'admin-icon', title: _('Sherlock Transactions'),
+ data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = _('Sherlock Transactions')
+
-# Shortcut to Dashboard > Projects
- if dashboard_nav_link?(:projects)
%li.hidden
@@ -64,19 +81,17 @@
= link_to '#', class: 'dashboard-shortcuts-web-ide', title: _('Web IDE') do
= _('Web IDE')
- - if show_separator?
- %li.line-separator.d-none.d-sm-block
= render_if_exists 'dashboard/operations/nav_link'
- if can?(current_user, :read_instance_statistics)
- = nav_link(controller: [:conversational_development_index, :cohorts]) do
+ = nav_link(controller: [:conversational_development_index, :cohorts], html_options: { class: "d-none d-lg-block d-xl-block"}) do
= link_to instance_statistics_root_path, title: _('Instance Statistics'), aria: { label: _('Instance Statistics') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= sprite_icon('chart', size: 18)
- if current_user.admin?
- = nav_link(controller: 'admin/dashboard') do
- = link_to admin_root_path, class: 'admin-icon qa-admin-area-link', title: _('Admin area'), aria: { label: _('Admin area') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = nav_link(controller: 'admin/dashboard', html_options: { class: "d-none d-lg-block d-xl-block"}) do
+ = link_to admin_root_path, class: 'admin-icon qa-admin-area-link', title: _('Admin Area'), aria: { label: _('Admin Area') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= sprite_icon('admin', size: 18)
- if Gitlab::Sherlock.enabled?
%li
- = link_to sherlock_transactions_path, class: 'admin-icon', title: _('Sherlock Transactions'),
+ = link_to sherlock_transactions_path, class: 'admin-icon d-none d-lg-block d-xl-block', title: _('Sherlock Transactions'),
data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('tachometer fw')
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 477030a20c1..bf475c07711 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -103,19 +103,6 @@
= _('Merge Requests')
%span.badge.badge-pill.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(merge_requests_count)
- - if group_sidebar_link?(:group_members)
- = nav_link(path: 'group_members#index') do
- = link_to group_group_members_path(@group) do
- .nav-icon-container
- = sprite_icon('users')
- %span.nav-item-name.qa-group-members-item
- = _('Members')
- %ul.sidebar-sub-level-items.is-fly-out-only
- = nav_link(path: 'group_members#index', html_options: { class: "fly-out-top-item" } ) do
- = link_to group_group_members_path(@group) do
- %strong.fly-out-top-item-name
- = _('Members')
-
- if group_sidebar_link?(:kubernetes)
= nav_link(controller: [:clusters]) do
= link_to group_clusters_path(@group) do
@@ -129,6 +116,19 @@
%strong.fly-out-top-item-name
= _('Kubernetes')
+ - if group_sidebar_link?(:group_members)
+ = nav_link(path: 'group_members#index') do
+ = link_to group_group_members_path(@group) do
+ .nav-icon-container
+ = sprite_icon('users')
+ %span.nav-item-name.qa-group-members-item
+ = _('Members')
+ %ul.sidebar-sub-level-items.is-fly-out-only
+ = nav_link(path: 'group_members#index', html_options: { class: "fly-out-top-item" } ) do
+ = link_to group_group_members_path(@group) do
+ %strong.fly-out-top-item-name
+ = _('Members')
+
- if group_sidebar_link?(:settings)
= nav_link(path: group_nav_link_paths) do
= link_to edit_group_path(@group) do
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index ab15889a465..59557c70904 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -29,6 +29,11 @@
= link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity' do
%span= _('Activity')
+ - if project_nav_tab?(:releases) && Feature.enabled?(:releases_page)
+ = nav_link(controller: :releases) do
+ = link_to project_releases_path(@project), title: _('Releases'), class: 'shortcuts-project-releases' do
+ %span= _('Releases')
+
= render_if_exists 'projects/sidebar/security_dashboard'
- if can?(current_user, :read_cycle_analytics, @project)
@@ -62,7 +67,7 @@
= link_to project_branches_path(@project) do
= _('Branches')
- = nav_link(controller: [:tags, :releases]) do
+ = nav_link(controller: [:tags]) do
= link_to project_tags_path(@project) do
= _('Tags')
@@ -196,7 +201,7 @@
- if project_nav_tab? :operations
= nav_link(controller: sidebar_operations_paths) do
- = link_to metrics_project_environments_path(@project), class: 'shortcuts-operations' do
+ = link_to sidebar_operations_link_path, class: 'shortcuts-operations' do
.nav-icon-container
= sprite_icon('cloud-gear')
%span.nav-item-name
@@ -204,7 +209,7 @@
%ul.sidebar-sub-level-items
= nav_link(controller: sidebar_operations_paths, html_options: { class: "fly-out-top-item" } ) do
- = link_to metrics_project_environments_path(@project) do
+ = link_to sidebar_operations_link_path do
%strong.fly-out-top-item-name
= _('Operations')
%li.divider.fly-out-top-item
@@ -222,6 +227,12 @@
%span
= _('Environments')
+ - if project_nav_tab? :serverless
+ = nav_link(controller: :functions) do
+ = link_to project_serverless_functions_path(@project), title: _('Serverless') do
+ %span
+ = _('Serverless')
+
- if project_nav_tab? :clusters
- show_cluster_hint = show_gke_cluster_integration_callout?(@project)
= nav_link(controller: [:clusters, :user, :gcp]) do
diff --git a/app/views/notify/_note_email.html.haml b/app/views/notify/_note_email.html.haml
index 94bd6f96dbc..83c7f548975 100644
--- a/app/views/notify/_note_email.html.haml
+++ b/app/views/notify/_note_email.html.haml
@@ -1,42 +1,43 @@
-- discussion = @note.discussion if @note.part_of_discussion?
-- diff_discussion = discussion&.diff_discussion?
-- on_image = discussion.on_image? if diff_discussion
+- note = local_assigns.fetch(:note, @note)
+- diff_limit = local_assigns.fetch(:diff_limit, nil)
+- target_url = local_assigns.fetch(:target_url, @target_url)
+- note_style = local_assigns.fetch(:note_style, "")
-- if discussion
- - phrase_end_char = on_image ? "." : ":"
+- discussion = note.discussion if note.part_of_discussion?
- %p.details
- = succeed phrase_end_char do
- = link_to @note.author_name, user_url(@note.author)
+- if discussion
+ %p{ style: "color: #777777;" }
+ = succeed ':' do
+ = link_to note.author_name, user_url(note.author)
- - if diff_discussion
+ - if discussion&.diff_discussion?
- if discussion.new_discussion?
started a new discussion
- else
commented on a discussion
- on #{link_to discussion.file_path, @target_url}
+ on #{link_to discussion.file_path, target_url}
- else
- if discussion.new_discussion?
started a new discussion
- else
- commented on a #{link_to 'discussion', @target_url}
+ commented on a #{link_to 'discussion', target_url}
- elsif Gitlab::CurrentSettings.email_author_in_body
%p.details
- #{link_to @note.author_name, user_url(@note.author)} commented:
+ #{link_to note.author_name, user_url(note.author)} commented:
-- if diff_discussion && !on_image
+- if discussion&.diff_discussion? && discussion.on_text?
= content_for :head do
= stylesheet_link_tag 'mailers/highlighted_diff_email'
%table
= render partial: "projects/diffs/line",
- collection: discussion.truncated_diff_lines,
+ collection: discussion.truncated_diff_lines(diff_limit: diff_limit),
as: :line,
locals: { diff_file: discussion.diff_file,
plain: true,
email: true }
-%div
- = markdown(@note.note, pipeline: :email, author: @note.author)
+%div{ style: note_style }
+ = markdown(note.note, pipeline: :email, author: note.author)
diff --git a/app/views/notify/_note_email.text.erb b/app/views/notify/_note_email.text.erb
index c319cb55e87..50209c46ed1 100644
--- a/app/views/notify/_note_email.text.erb
+++ b/app/views/notify/_note_email.text.erb
@@ -1,6 +1,9 @@
-<% discussion = @note.discussion if @note.part_of_discussion? -%>
+<% note = local_assigns.fetch(:note, @note) -%>
+<% diff_limit = local_assigns.fetch(:diff_limit, nil) -%>
+
+<% discussion = note.discussion if note.part_of_discussion? -%>
<% if discussion && !discussion.individual_note? -%>
-<%= @note.author_name -%>
+<%= note.author_name -%>
<% if discussion.new_discussion? -%>
<%= " started a new discussion" -%>
<% else -%>
@@ -13,14 +16,14 @@
<% elsif Gitlab::CurrentSettings.email_author_in_body -%>
-<%= "#{@note.author_name} commented:" -%>
+<%= "#{note.author_name} commented:" -%>
<% end -%>
-<% if discussion&.diff_discussion? -%>
-<% discussion.truncated_diff_lines(highlight: false).each do |line| -%>
+<% if discussion&.diff_discussion? && discussion.on_text? -%>
+<% discussion.truncated_diff_lines(highlight: false, diff_limit: diff_limit).each do |line| -%>
<%= "> #{line.text}\n" -%>
<% end -%>
<% end -%>
-<%= @note.note -%>
+<%= note.note -%>
diff --git a/app/views/notify/changed_milestone_issue_email.html.haml b/app/views/notify/changed_milestone_email.html.haml
index 7d5425fc72d..01d27cac36b 100644
--- a/app/views/notify/changed_milestone_issue_email.html.haml
+++ b/app/views/notify/changed_milestone_email.html.haml
@@ -1,3 +1,5 @@
%p
Milestone changed to
%strong= link_to(@milestone.name, @milestone_url)
+ - if date_range = milestone_date_range(@milestone)
+ = "(#{date_range})"
diff --git a/app/views/notify/changed_milestone_email.text.erb b/app/views/notify/changed_milestone_email.text.erb
new file mode 100644
index 00000000000..a466da4eb19
--- /dev/null
+++ b/app/views/notify/changed_milestone_email.text.erb
@@ -0,0 +1 @@
+Milestone changed to <%= @milestone.name %><% if date_range = milestone_date_range(@milestone) %> (<%= date_range %>)<% end %> ( <%= @milestone_url %> )
diff --git a/app/views/notify/changed_milestone_issue_email.text.erb b/app/views/notify/changed_milestone_issue_email.text.erb
deleted file mode 100644
index c5fc0b61518..00000000000
--- a/app/views/notify/changed_milestone_issue_email.text.erb
+++ /dev/null
@@ -1 +0,0 @@
-Milestone changed to <%= @milestone.name %> ( <%= @milestone_url %> )
diff --git a/app/views/notify/changed_milestone_merge_request_email.html.haml b/app/views/notify/changed_milestone_merge_request_email.html.haml
deleted file mode 100644
index 7d5425fc72d..00000000000
--- a/app/views/notify/changed_milestone_merge_request_email.html.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-%p
- Milestone changed to
- %strong= link_to(@milestone.name, @milestone_url)
diff --git a/app/views/notify/changed_milestone_merge_request_email.text.erb b/app/views/notify/changed_milestone_merge_request_email.text.erb
deleted file mode 100644
index c5fc0b61518..00000000000
--- a/app/views/notify/changed_milestone_merge_request_email.text.erb
+++ /dev/null
@@ -1 +0,0 @@
-Milestone changed to <%= @milestone.name %> ( <%= @milestone_url %> )
diff --git a/app/views/notify/remote_mirror_update_failed_email.html.haml b/app/views/notify/remote_mirror_update_failed_email.html.haml
new file mode 100644
index 00000000000..4fb0a4c5a8a
--- /dev/null
+++ b/app/views/notify/remote_mirror_update_failed_email.html.haml
@@ -0,0 +1,46 @@
+%tr.alert{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif;" }
+ %td{ style: "padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;background-color:#d22f57;color:#ffffff;" }
+ %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" }
+ %tbody
+ %tr
+ %td{ style: "vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;line-height:1;" }
+ %img{ alt: "✖", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif'), style: "display:block;", width: "13" }/
+ %td{ style: "vertical-align:middle;color:#ffffff;text-align:center;" }
+ A remote mirror update has failed.
+%tr.spacer{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif;" }
+ %td{ style: "height:18px;font-size:18px;line-height:18px;" }
+ &nbsp;
+%tr.section{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif;" }
+ %td{ style: "padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
+ %table.table-info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" }
+ %tbody{ style: "font-size:15px;line-height:1.4;color:#8c8c8c;" }
+ %tr
+ %td{ style: "font-weight:300;padding:14px 0;margin:0;" } Project
+ %td{ style: "font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;" }
+ - namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner)
+ %a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" }
+ = @project.owner_name
+ \/
+ %a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" }
+ = @project.name
+ %tr
+ %td{ style: "font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Remote mirror
+ %td{ style: "font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
+ = @remote_mirror.safe_url
+ %tr
+ %td{ style: "font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Last update at
+ %td{ style: "font-weight:500;padding:14px 0;margin:0;color:#333333;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
+ = @remote_mirror.last_update_at
+
+%tr.table-warning{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif;" }
+ %td{ style: "border: 1px solid #ededed; border-bottom: 0; border-radius: 4px 4px 0 0; overflow: hidden; background-color: #fdf4f6; color: #d22852; font-size: 14px; line-height: 1.4; text-align: center; padding: 8px 16px;" }
+ Logs may contain sensitive data. Please consider before forwarding this email.
+%tr.section{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif;" }
+ %td{ style: "padding: 0 16px; border: 1px solid #ededed; border-radius: 4px; overflow: hidden; border-top: 0; border-radius: 0 0 4px 4px;" }
+ %table.builds{ border: "0", cellpadding: "0", cellspacing: "0", style: "width: 100%; border-collapse: collapse;" }
+ %tbody
+ %tr.build-log
+ %td{ colspan: "2", style: "padding: 0 0 16px;" }
+ %pre{ style: "font-family: Monaco,'Lucida Console','Courier New',Courier,monospace; background-color: #fafafa; border-radius: 4px; overflow: hidden; white-space: pre-wrap; word-break: break-all; font-size:13px; line-height: 1.4; padding: 16px 8px; color: #333333; margin: 0;" }
+ = @remote_mirror.last_error
+
diff --git a/app/views/notify/remote_mirror_update_failed_email.text.erb b/app/views/notify/remote_mirror_update_failed_email.text.erb
new file mode 100644
index 00000000000..c6f29f0ad1c
--- /dev/null
+++ b/app/views/notify/remote_mirror_update_failed_email.text.erb
@@ -0,0 +1,7 @@
+A remote mirror update has failed.
+
+Project: <%= @project.human_name %> ( <%= project_url(@project) %> )
+Remote mirror: <%= @remote_mirror.safe_url %>
+Last update at: <%= @remote_mirror.last_update_at %>
+Last error:
+<%= @remote_mirror.last_error %>
diff --git a/app/views/notify/repository_cleanup_failure_email.text.erb b/app/views/notify/repository_cleanup_failure_email.text.erb
new file mode 100644
index 00000000000..f5a426a51d1
--- /dev/null
+++ b/app/views/notify/repository_cleanup_failure_email.text.erb
@@ -0,0 +1,3 @@
+Repository cleanup failed on <%= @project.web_url %>
+
+<%= @error %>
diff --git a/app/views/notify/repository_cleanup_success_email.text.erb b/app/views/notify/repository_cleanup_success_email.text.erb
new file mode 100644
index 00000000000..e6e95da2fcc
--- /dev/null
+++ b/app/views/notify/repository_cleanup_success_email.text.erb
@@ -0,0 +1,3 @@
+Repository cleanup succeeded on <%= @project.web_url %>
+
+Repository size is now <%= "%.1f" % (@project.repository.size || 0) %> MiB
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 2603c558c0f..2629b374e7c 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -71,43 +71,43 @@
%h4.prepend-top-0
= s_("Profiles|Main settings")
%p
- = s_("Profiles|This information will appear on your profile.")
+ = s_("Profiles|This information will appear on your profile")
- if current_user.ldap_user?
= s_("Profiles|Some options are unavailable for LDAP accounts")
.col-lg-8
.row
- if @user.read_only_attribute?(:name)
= f.text_field :name, required: true, readonly: true, wrapper: { class: 'col-md-9' },
- help: s_("Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you.") % { provider_label: attribute_provider_label(:name) }
+ help: s_("Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you") % { provider_label: attribute_provider_label(:name) }
- else
- = f.text_field :name, label: 'Full name', required: true, wrapper: { class: 'col-md-9' }, help: "Enter your name, so people you know can recognize you."
+ = f.text_field :name, label: 'Full name', required: true, wrapper: { class: 'col-md-9' }, help: s_("Profiles|Enter your name, so people you know can recognize you")
= f.text_field :id, readonly: true, label: 'User ID', wrapper: { class: 'col-md-3' }
- if @user.read_only_attribute?(:email)
- = f.text_field :email, required: true, readonly: true, help: s_("Profiles|Your email address was automatically set based on your %{provider_label} account.") % { provider_label: attribute_provider_label(:email) }
+ = f.text_field :email, required: true, class: 'input-lg', readonly: true, help: s_("Profiles|Your email address was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:email) }
- else
- = f.text_field :email, required: true, value: (@user.email unless @user.temp_oauth_email?),
+ = f.text_field :email, required: true, class: 'input-lg', value: (@user.email unless @user.temp_oauth_email?),
help: user_email_help_text(@user)
= f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email),
- { help: s_("Profiles|This email will be displayed on your public profile."), include_blank: s_("Profiles|Do not show on profile") },
- control_class: 'select2'
+ { help: s_("Profiles|This email will be displayed on your public profile"), include_blank: s_("Profiles|Do not show on profile") },
+ control_class: 'select2 input-lg'
- commit_email_docs_link = link_to s_('Profiles|Learn more'), help_page_path('user/profile/index', anchor: 'commit-email', target: '_blank')
= f.select :commit_email, options_for_select(commit_email_select_options(@user), selected: selected_commit_email(@user)),
{ help: s_("Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}").html_safe % { learn_more: commit_email_docs_link } },
- control_class: 'select2'
+ control_class: 'select2 input-lg'
= f.select :preferred_language, Gitlab::I18n::AVAILABLE_LANGUAGES.map { |value, label| [label, value] },
- { help: s_("Profiles|This feature is experimental and translations are not complete yet.") },
- control_class: 'select2'
- = f.text_field :skype
- = f.text_field :linkedin
- = f.text_field :twitter
- = f.text_field :website_url, label: s_("Profiles|Website")
+ { help: s_("Profiles|This feature is experimental and translations are not complete yet") },
+ control_class: 'select2 input-lg'
+ = f.text_field :skype, class: 'input-md', placeholder: s_("Profiles|username")
+ = f.text_field :linkedin, class: 'input-md', help: s_("Profiles|Your LinkedIn profile name from linkedin.com/in/profilename")
+ = f.text_field :twitter, class: 'input-md', placeholder: s_("Profiles|@username")
+ = f.text_field :website_url, class: 'input-lg', placeholder: s_("Profiles|website.com")
- if @user.read_only_attribute?(:location)
- = f.text_field :location, readonly: true, help: s_("Profiles|Your location was automatically set based on your %{provider_label} account.") % { provider_label: attribute_provider_label(:location) }
+ = f.text_field :location, readonly: true, help: s_("Profiles|Your location was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:location) }
- else
- = f.text_field :location
- = f.text_field :organization
- = f.text_area :bio, rows: 4, maxlength: 250, help: s_("Profiles|Tell us about yourself in fewer than 250 characters.")
+ = f.text_field :location, class: 'input-lg', placeholder: s_("Profiles|City, country")
+ = f.text_field :organization, class: 'input-md', help: s_("Profiles|Who you represent or work for")
+ = f.text_area :bio, rows: 4, maxlength: 250, help: s_("Profiles|Tell us about yourself in fewer than 250 characters")
%hr
%h5= ("Private profile")
.checkbox-icon-inline-wrapper
@@ -118,7 +118,7 @@
%h5= s_("Profiles|Private contributions")
= f.check_box :include_private_contributions, label: 'Include private contributions on my profile'
.help-block
- = s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information.")
+ = s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information")
.prepend-top-default.append-bottom-default
= f.submit s_("Profiles|Update profile settings"), class: 'btn btn-success'
= link_to _("Cancel"), user_path(current_user), class: 'btn btn-cancel'
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index 94ec0cc5db8..d986c566928 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -25,7 +25,8 @@
- else
%p
- Download the Google Authenticator application from App Store or Google Play Store and scan this code.
+ Install a soft token authenticator like <a href="https://freeotp.github.io/">FreeOTP</a>
+ or Google Authenticator from your application repository and scan this QR code.
More information is available in the #{link_to('documentation', help_page_path('user/profile/account/two_factor_authentication'))}.
.row.append-bottom-10
.col-md-4
diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml
index 79530e78154..22a721ee9ad 100644
--- a/app/views/projects/_files.html.haml
+++ b/app/views/projects/_files.html.haml
@@ -1,7 +1,9 @@
+- is_project_overview = local_assigns.fetch(:is_project_overview, false)
- commit = local_assigns.fetch(:commit) { @repository.commit }
- ref = local_assigns.fetch(:ref) { current_ref }
- project = local_assigns.fetch(:project) { @project }
- content_url = local_assigns.fetch(:content_url) { @tree.readme ? project_blob_path(@project, tree_join(@ref, @tree.readme.path)) : project_tree_path(@project, @ref) }
+- show_auto_devops_callout = show_auto_devops_callout?(@project)
#tree-holder.tree-holder.clearfix
.nav-block
@@ -10,4 +12,8 @@
- if commit
= render 'shared/commit_well', commit: commit, ref: ref, project: project
+ - if is_project_overview
+ .project-buttons.append-bottom-default
+ = render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout)
+
= render 'projects/tree/tree_content', tree: @tree, content_url: content_url
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index dcef4dd5b69..82b2ab64a5d 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,83 +1,75 @@
- empty_repo = @project.empty_repo?
-- license = @project.license_anchor_data
+- show_auto_devops_callout = show_auto_devops_callout?(@project)
.project-home-panel{ class: ("empty-project" if empty_repo) }
- .limit-container-width{ class: container_class }
- .project-header.d-flex.flex-row.flex-wrap.align-items-center.append-bottom-8
- .project-title-row.d-flex.align-items-center
- .avatar-container.project-avatar.float-none
- = project_icon(@project, alt: @project.name, class: 'avatar avatar-tile s24', width: 24, height: 24)
- %h1.project-title.d-flex.align-items-baseline.qa-project-name
- = @project.name
- .project-metadata.d-flex.flex-row.flex-wrap.align-items-baseline
- .project-visibility.d-inline-flex.align-items-baseline.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) }
- = visibility_level_icon(@project.visibility_level, fw: false, options: {class: 'icon'})
- = visibility_level_label(@project.visibility_level)
- - if license.present?
- .project-license.d-inline-flex.align-items-baseline
- = link_to_if license.link, sprite_icon('scale', size: 16, css_class: 'icon') + license.label, license.link, class: license.enabled ? 'btn btn-link btn-secondary-hover-link' : 'btn btn-link'
- - if @project.tag_list.present?
- .project-tag-list.d-inline-flex.align-items-baseline.has-tooltip{ data: { container: 'body' }, title: @project.has_extra_tags? ? @project.tag_list.join(', ') : nil }
- = sprite_icon('tag', size: 16, css_class: 'icon')
- = @project.tags_to_show
- - if @project.has_extra_tags?
- = _("+ %{count} more") % { count: @project.count_of_extra_tags_not_shown }
+ .project-header.row.append-bottom-8
+ .project-title-row.col-md-12.col-lg-6.d-flex
+ .avatar-container.project-avatar.float-none
+ = project_icon(@project, alt: @project.name, class: 'avatar avatar-tile s64', width: 64, height: 64)
+ .d-flex.flex-column.flex-wrap.align-items-baseline
+ .d-inline-flex.align-items-baseline
+ %h1.project-title.qa-project-name
+ = @project.name
+ %span.project-visibility.prepend-left-8.d-inline-flex.align-items-baseline.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) }
+ = visibility_level_icon(@project.visibility_level, fw: false, options: {class: 'icon'})
+ .project-metadata.d-flex.align-items-center
+ - if can?(current_user, :read_project, @project)
+ %span.text-secondary
+ = s_('ProjectPage|Project ID: %{project_id}') % { project_id: @project.id }
+ - if current_user
+ %span.access-request-links.prepend-left-8
+ = render 'shared/members/access_request_links', source: @project
+ - if @project.tag_list.present?
+ %span.project-tag-list.d-inline-flex.prepend-left-8.has-tooltip{ data: { container: 'body' }, title: @project.has_extra_tags? ? @project.tag_list.join(', ') : nil }
+ = sprite_icon('tag', size: 16, css_class: 'icon append-right-4')
+ = @project.tags_to_show
+ - if @project.has_extra_tags?
+ = _("+ %{count} more") % { count: @project.count_of_extra_tags_not_shown }
- .project-home-desc
- - if @project.description.present?
- .project-description
- .project-description-markdown.read-more-container
- = markdown_field(@project, :description)
- %button.btn.btn-blank.btn-link.text-secondary.js-read-more-trigger.text-secondary.d-lg-none{ type: "button" }
- = _("Read more")
-
- - if can?(current_user, :read_project, @project)
- .text-secondary.prepend-top-8
- = s_('ProjectPage|Project ID: %{project_id}') % { project_id: @project.id }
-
- - if @project.forked?
- %p
- - if @project.fork_source
- #{ s_('ForkedFromProjectPath|Forked from') }
- = link_to project_path(@project.fork_source) do
- = fork_source_name(@project)
- - else
- - deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)')
- = deleted_message % { project_name: fork_source_name(@project) }
-
- - if @project.badges.present?
- .project-badges.prepend-top-default.append-bottom-default
- - @project.badges.each do |badge|
- %a.append-right-8{ href: badge.rendered_link_url(@project),
- target: '_blank',
- rel: 'noopener noreferrer' }>
- %img.project-badge{ src: badge.rendered_image_url(@project),
- 'aria-hidden': true,
- alt: 'Project badge' }>
+ .project-repo-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end
+ - if current_user
+ .d-inline-flex
+ = render 'projects/buttons/notifications', notification_setting: @notification_setting, btn_class: 'btn-xs'
- .project-repo-buttons.d-inline-flex.flex-wrap
.count-buttons.d-inline-flex
= render 'projects/buttons/star'
= render 'projects/buttons/fork'
- if can?(current_user, :download_code, @project)
- .project-clone-holder.d-inline-flex.d-sm-none
+ .project-clone-holder.d-inline-flex.d-md-none.btn-block
= render "shared/mobile_clone_panel"
- .project-clone-holder.d-none.d-sm-inline-flex
- = render "shared/clone_panel"
+ .project-clone-holder.d-none.d-md-inline-flex
+ = render "projects/buttons/clone"
- - if show_xcode_link?(@project)
- .project-action-button.project-xcode.inline
- = render "projects/buttons/xcode_link"
+ - if can?(current_user, :download_code, @project)
+ %nav.project-stats
+ .nav-links.quick-links.mt-3
+ = render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout)
- - if current_user
- - if can?(current_user, :download_code, @project)
- .d-none.d-sm-inline-flex
- = render 'projects/buttons/download', project: @project, ref: @ref
- .d-none.d-sm-inline-flex
- = render 'projects/buttons/dropdown'
+ .project-home-desc.mt-1
+ - if @project.description.present?
+ .project-description
+ .project-description-markdown.read-more-container
+ = markdown_field(@project, :description)
+ %button.btn.btn-blank.btn-link.js-read-more-trigger.d-lg-none{ type: "button" }
+ = _("Read more")
+
+ - if @project.forked?
+ %p
+ - if @project.fork_source
+ #{ s_('ForkedFromProjectPath|Forked from') }
+ = link_to project_path(@project.fork_source) do
+ = fork_source_name(@project)
+ - else
+ - deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)')
+ = deleted_message % { project_name: fork_source_name(@project) }
- .d-none.d-sm-inline-flex
- = render 'shared/notifications/button', notification_setting: @notification_setting
- .d-none.d-sm-inline-flex
- = render 'shared/members/access_request_buttons', source: @project
+ - if @project.badges.present?
+ .project-badges.mb-2
+ - @project.badges.each do |badge|
+ %a.append-right-8{ href: badge.rendered_link_url(@project),
+ target: '_blank',
+ rel: 'noopener noreferrer' }>
+ %img.project-badge{ src: badge.rendered_image_url(@project),
+ 'aria-hidden': true,
+ alt: 'Project badge' }>
diff --git a/app/views/projects/_stat_anchor_list.html.haml b/app/views/projects/_stat_anchor_list.html.haml
index 4cf49f3cf62..8e3d759b683 100644
--- a/app/views/projects/_stat_anchor_list.html.haml
+++ b/app/views/projects/_stat_anchor_list.html.haml
@@ -4,5 +4,5 @@
%ul.nav
- anchors.each do |anchor|
%li.nav-item
- = link_to_if anchor.link, anchor.label, anchor.link, class: anchor.enabled ? 'nav-link stat-link' : "nav-link btn btn-#{anchor.class_modifier || 'missing'}" do
- .stat-text= anchor.label
+ = link_to_if anchor.link, anchor.label, anchor.link, class: anchor.is_link ? 'nav-link stat-link d-flex align-items-center' : "nav-link btn btn-#{anchor.class_modifier || 'missing'} d-flex align-items-center" do
+ .stat-text.d-flex.align-items-center= anchor.label
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index cf273aab108..95c5eb32c7f 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
+ %article.file-holder{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
= render 'projects/blob/header', blob: blob
= render 'projects/blob/content', blob: blob
diff --git a/app/views/projects/blob/preview.html.haml b/app/views/projects/blob/preview.html.haml
index eb65cd90ea8..ff460a3831c 100644
--- a/app/views/projects/blob/preview.html.haml
+++ b/app/views/projects/blob/preview.html.haml
@@ -1,7 +1,7 @@
.diff-file.file-holder
.diff-content
- if markup?(@blob.name)
- .file-content.wiki
+ .file-content.wiki.md{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
= markup(@blob.name, @content, legacy_render_context(params))
- else
.file-content.code.js-syntax-highlight
diff --git a/app/views/projects/blob/viewers/_markup.html.haml b/app/views/projects/blob/viewers/_markup.html.haml
index bd12cadf240..6edbfd91b21 100644
--- a/app/views/projects/blob/viewers/_markup.html.haml
+++ b/app/views/projects/blob/viewers/_markup.html.haml
@@ -2,5 +2,5 @@
- context = legacy_render_context(params)
- unless context[:markdown_engine] == :redcarpet
- context[:rendered] = blob.rendered_markup if blob.respond_to?(:rendered_markup)
-.file-content.wiki
+.file-content.wiki.md{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
= markup(blob.name, blob.data, context)
diff --git a/app/views/projects/buttons/_clone.html.haml b/app/views/projects/buttons/_clone.html.haml
new file mode 100644
index 00000000000..159d9e44e17
--- /dev/null
+++ b/app/views/projects/buttons/_clone.html.haml
@@ -0,0 +1,28 @@
+- project = project || @project
+
+.git-clone-holder.js-git-clone-holder.input-group
+ %a#clone-dropdown.input-group-text.btn.btn-primary.btn-xs.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } }
+ %span.append-right-4.js-clone-dropdown-label
+ = _('Clone')
+ = sprite_icon("arrow-down", css_class: "icon")
+ %ul.p-3.dropdown-menu.dropdown-menu-right.dropdown-menu-large.dropdown-menu-selectable.clone-options-dropdown.qa-clone-options
+ - if ssh_enabled?
+ %li.pb-2
+ %label.label-bold
+ = _('Clone with SSH')
+ .input-group
+ = text_field_tag :ssh_project_clone, project.ssh_url_to_repo, class: "js-select-on-focus form-control qa-ssh-clone-url", readonly: true, aria: { label: 'Project clone URL' }
+ .input-group-append
+ = clipboard_button(target: '#ssh_project_clone', title: _("Copy URL to clipboard"), class: "input-group-text btn-default btn-clipboard")
+ = render_if_exists 'projects/buttons/geo'
+ - if http_enabled?
+ %li
+ %label.label-bold
+ = _('Clone with %{http_label}') % { http_label: gitlab_config.protocol.upcase }
+ .input-group
+ = text_field_tag :http_project_clone, project.http_url_to_repo, class: "js-select-on-focus form-control qa-http-clone-url", readonly: true, aria: { label: 'Project clone URL' }
+ .input-group-append
+ = clipboard_button(target: '#http_project_clone', title: _("Copy URL to clipboard"), class: "input-group-text btn-default btn-clipboard")
+ = render_if_exists 'projects/buttons/geo'
+
+= render_if_exists 'shared/geo_info_modal', project: project
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index f7551434d47..4eb53faa6ff 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -5,8 +5,8 @@
.project-action-button.dropdown.inline>
%button.btn.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download'), 'data-display' => 'static' }
= sprite_icon('download')
- = icon("caret-down")
%span.sr-only= _('Select Archive Format')
+ = sprite_icon("arrow-down")
%ul.dropdown-menu.dropdown-menu-right{ role: 'menu' }
%li.dropdown-header
#{ _('Source code') }
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index 8da27ca7cb3..bc0a89bea62 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -1,9 +1,6 @@
- unless @project.empty_repo?
- if current_user && can?(current_user, :fork_project, @project)
.count-badge.d-inline-flex.align-item-stretch.append-right-8
- %span.fork-count.count-badge-count.d-flex.align-items-center
- = link_to project_forks_path(@project), title: n_(s_('ProjectOverview|Fork'), s_('ProjectOverview|Forks'), @project.forks_count), class: 'count' do
- = @project.forks_count
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: s_('ProjectOverview|Go to your fork'), class: 'btn btn-default has-tooltip count-badge-button d-flex align-items-center fork-btn' do
= sprite_icon('fork', { css_class: 'icon' })
@@ -15,3 +12,6 @@
title: (s_('ProjectOverview|You have reached your project limit') unless can_create_fork) do
= sprite_icon('fork', { css_class: 'icon' })
%span= s_('ProjectOverview|Fork')
+ %span.fork-count.count-badge-count.d-flex.align-items-center
+ = link_to project_forks_path(@project), title: n_(s_('ProjectOverview|Fork'), s_('ProjectOverview|Forks'), @project.forks_count), class: 'count' do
+ = @project.forks_count
diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml
new file mode 100644
index 00000000000..745983ace7e
--- /dev/null
+++ b/app/views/projects/buttons/_notifications.html.haml
@@ -0,0 +1,27 @@
+- btn_class = local_assigns.fetch(:btn_class, "btn-xs")
+
+- if notification_setting
+ .js-notification-dropdown.notification-dropdown.project-action-button.dropdown.inline
+ = form_for notification_setting, remote: true, html: { class: "inline notification-form no-label" } do |f|
+ = hidden_setting_source_input(notification_setting)
+ = hidden_field_tag "hide_label", true
+ = f.hidden_field :level, class: "notification_setting_level"
+ .js-notification-toggle-btns
+ %div{ class: ("btn-group" if notification_setting.custom?) }
+ - if notification_setting.custom?
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
+ = sprite_icon("notifications", css_class: "icon notifications-icon js-notifications-icon")
+ %span.js-notification-loading.fa.hidden
+ %button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
+ = sprite_icon("arrow-down", css_class: "icon")
+ .sr-only Toggle dropdown
+ - else
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting - #{notification_title(notification_setting.level)}", class: "#{btn_class}", "aria-label" => "Notification setting: #{notification_title(notification_setting.level)}", data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
+ = sprite_icon("notifications", css_class: "icon notifications-icon js-notifications-icon")
+ %span.js-notification-loading.fa.hidden
+ = sprite_icon("arrow-down", css_class: "icon")
+
+ = render "shared/notifications/notification_dropdown", notification_setting: notification_setting
+
+ = content_for :scripts_body do
+ = render "shared/notifications/custom_notifications", notification_setting: notification_setting
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
index 0d04ecb3a58..090d1549aa7 100644
--- a/app/views/projects/buttons/_star.html.haml
+++ b/app/views/projects/buttons/_star.html.haml
@@ -1,19 +1,19 @@
- if current_user
.count-badge.d-inline-flex.align-item-stretch.append-right-8
- %span.star-count.count-badge-count.d-flex.align-items-center
- = @project.star_count
- %button.count-badge-button.btn.btn-default.d-flex.align-items-center.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(@project, :json) } }
+ %button.count-badge-button.btn.btn-default.btn-xs.d-flex.align-items-center.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(@project, :json) } }
- if current_user.starred?(@project)
= sprite_icon('star', { css_class: 'icon' })
%span.starred= s_('ProjectOverview|Unstar')
- else
= sprite_icon('star-o', { css_class: 'icon' })
%span= s_('ProjectOverview|Star')
+ %span.star-count.count-badge-count.d-flex.align-items-center
+ = @project.star_count
- else
.count-badge.d-inline-flex.align-item-stretch.append-right-8
- %span.star-count.count-badge-count.d-flex.align-items-center
- = @project.star_count
- = link_to new_user_session_path, class: 'btn btn-default has-tooltip count-badge-button d-flex align-items-center star-btn', title: s_('ProjectOverview|You must sign in to star a project') do
+ = link_to new_user_session_path, class: 'btn btn-default btn-xs has-tooltip count-badge-button d-flex align-items-center star-btn', title: s_('ProjectOverview|You must sign in to star a project') do
= sprite_icon('star-o', { css_class: 'icon' })
%span= s_('ProjectOverview|Star')
+ %span.star-count.count-badge-count.d-flex.align-items-center
+ = @project.star_count
diff --git a/app/views/projects/cleanup/_show.html.haml b/app/views/projects/cleanup/_show.html.haml
new file mode 100644
index 00000000000..cecc139b183
--- /dev/null
+++ b/app/views/projects/cleanup/_show.html.haml
@@ -0,0 +1,29 @@
+- expanded = Rails.env.test?
+
+%section.settings.no-animate#cleanup{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4= _('Repository cleanup')
+ %button.btn.js-settings-toggle
+ = expanded ? _('Collapse') : _('Expand')
+ %p
+ = _("Clean up after running %{bfg} on the repository" % { bfg: link_to_bfg }).html_safe
+ = link_to icon('question-circle'),
+ help_page_path('user/project/repository/reducing_the_repo_size_using_git.md'),
+ target: '_blank', rel: 'noopener noreferrer'
+
+ .settings-content
+ - url = cleanup_namespace_project_settings_repository_path(@project.namespace, @project)
+ = form_for @project, url: url, method: :post, authenticity_token: true, html: { class: 'js-requires-input' } do |f|
+ %fieldset.prepend-top-0.append-bottom-10
+ .append-bottom-10
+ %h5.prepend-top-0
+ = _("Upload object map")
+ %button.btn.btn-default.js-choose-file{ type: "button" }
+ = _("Choose a file")
+ %span.prepend-left-default.js-filename
+ = _("No file selected")
+ = f.file_field :bfg_object_map, accept: 'text/plain', class: "hidden js-object-map-input", required: true
+ .form-text.text-muted
+ = _("The maximum file size allowed is %{max_attachment_size}mb") % { max_attachment_size: Gitlab::CurrentSettings.max_attachment_size }
+ = f.submit _('Start cleanup'), class: 'btn btn-success'
+
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index aab5712d197..2a919a767c0 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -28,7 +28,7 @@
= link_to project_tree_path(@project, @commit), class: "btn btn-default append-right-10 d-none d-sm-none d-md-inline" do
#{ _('Browse files') }
.dropdown.inline
- %a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } }
+ %a.btn.btn-default.dropdown-toggle.qa-options-button{ data: { toggle: "dropdown" } }
%span= _('Options')
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-right
@@ -48,8 +48,8 @@
%li.dropdown-header
#{ _('Download') }
- unless @commit.parents.length > 1
- %li= link_to s_("DownloadCommit|Email Patches"), project_commit_path(@project, @commit, format: :patch)
- %li= link_to s_("DownloadCommit|Plain Diff"), project_commit_path(@project, @commit, format: :diff)
+ %li= link_to s_("DownloadCommit|Email Patches"), project_commit_path(@project, @commit, format: :patch), class: "qa-email-patches"
+ %li= link_to s_("DownloadCommit|Plain Diff"), project_commit_path(@project, @commit, format: :diff), class: "qa-plain-diff"
.commit-box{ data: { project_path: project_path(@project) } }
%h3.commit-title
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index f376df29878..1b52821af15 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -53,7 +53,7 @@
= _("Project avatar in repository: %{link}").html_safe % { link: @project.avatar_in_git }
.prepend-top-5.append-bottom-10
%button.btn.js-choose-project-avatar-button{ type: 'button' }= _("Choose file...")
- %span.file_name.prepend-left-default.js-avatar-filename= _("No file chosen")
+ %span.file_name.prepend-left-default.js-filename= _("No file chosen")
= f.file_field :avatar, class: "js-project-avatar-input hidden"
.form-text.text-muted= _("The maximum file size allowed is 200KB.")
- if @project.avatar?
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 936900a0087..081990ac9b7 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -4,11 +4,10 @@
= render partial: 'flash_messages', locals: { project: @project }
-= render "home_panel"
+%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
+ = render "home_panel"
-.project-empty-note-panel
- %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
- .prepend-top-20
+ .project-empty-note-panel
%h4.append-bottom-20
= _('The repository for this project is empty')
@@ -32,66 +31,65 @@
= _('Otherwise it is recommended you start with one of the options below.')
.prepend-top-20
-%nav.project-stats{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
- .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
- .fade-left= icon('angle-left')
- .fade-right= icon('angle-right')
- .nav-links.scrolling-tabs.quick-links
- = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_anchors
- = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons
+ %nav.project-buttons
+ .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller.qa-quick-actions
+ .fade-left= icon('angle-left')
+ .fade-right= icon('angle-right')
+ .nav-links.scrolling-tabs.quick-links
+ = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons
-- if can?(current_user, :push_code, @project)
- %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
- .prepend-top-20
- .empty_wrapper
- %h3#repo-command-line-instructions.page-title-empty
- Command line instructions
- .git-empty.js-git-empty
- %fieldset
- %h5 Git global setup
- %pre.bg-light
- :preserve
- git config --global user.name "#{h git_user_name}"
- git config --global user.email "#{h git_user_email}"
+ - if can?(current_user, :push_code, @project)
+ %div
+ .prepend-top-20
+ .empty_wrapper
+ %h3#repo-command-line-instructions.page-title-empty
+ = _('Command line instructions')
+ .git-empty.js-git-empty
+ %fieldset
+ %h5= _('Git global setup')
+ %pre.bg-light
+ :preserve
+ git config --global user.name "#{h git_user_name}"
+ git config --global user.email "#{h git_user_email}"
- %fieldset
- %h5 Create a new repository
- %pre.bg-light
- :preserve
- git clone #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
- cd #{h @project.path}
- touch README.md
- git add README.md
- git commit -m "add README"
- - if @project.can_current_user_push_to_default_branch?
- %span><
- git push -u origin master
+ %fieldset
+ %h5= _('Create a new repository')
+ %pre.bg-light
+ :preserve
+ git clone #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
+ cd #{h @project.path}
+ touch README.md
+ git add README.md
+ git commit -m "add README"
+ - if @project.can_current_user_push_to_default_branch?
+ %span><
+ git push -u origin master
- %fieldset
- %h5 Existing folder
- %pre.bg-light
- :preserve
- cd existing_folder
- git init
- git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
- git add .
- git commit -m "Initial commit"
- - if @project.can_current_user_push_to_default_branch?
- %span><
- git push -u origin master
+ %fieldset
+ %h5= _('Existing folder')
+ %pre.bg-light
+ :preserve
+ cd existing_folder
+ git init
+ git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
+ git add .
+ git commit -m "Initial commit"
+ - if @project.can_current_user_push_to_default_branch?
+ %span><
+ git push -u origin master
- %fieldset
- %h5 Existing Git repository
- %pre.bg-light
- :preserve
- cd existing_repo
- git remote rename origin old-origin
- git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
- - if @project.can_current_user_push_to_default_branch?
- %span><
- git push -u origin --all
- git push -u origin --tags
+ %fieldset
+ %h5= _('Existing Git repository')
+ %pre.bg-light
+ :preserve
+ cd existing_repo
+ git remote rename origin old-origin
+ git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
+ - if @project.can_current_user_push_to_default_branch?
+ %span><
+ git push -u origin --all
+ git push -u origin --tags
- - if can? current_user, :remove_project, @project
- .prepend-top-20
- = link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-inverted btn-remove float-right"
+ - if can? current_user, :remove_project, @project
+ .prepend-top-20
+ = link_to _('Remove project'), [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-inverted btn-remove float-right"
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index 6c0ad34c486..d66de7ab698 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -1,6 +1,5 @@
- @no_container = true
- page_title _("Environments")
-- add_to_breadcrumbs(_("Pipelines"), project_pipelines_path(@project))
#environments-list-view{ data: { environments_data: environments_list_data,
"can-create-deployment" => can?(current_user, :create_deployment, @project).to_s,
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index b50b3ca207b..8c2fe2625c7 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -88,4 +88,4 @@
%section.issuable-discussion
= render 'projects/issues/discussion'
-= render 'shared/issuable/sidebar', issuable: @issue
+= render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @issue.assignees
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 2c6484c2c99..56b06374d6d 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -5,7 +5,7 @@
- subscribed = params[:subscribed]
- labels_or_filters = @labels.exists? || @prioritized_labels.exists? || search.present? || subscribed.present?
-- if @labels.present? && can_admin_label
+- if labels_or_filters && can_admin_label
- content_for(:header_content) do
.nav-controls
= link_to _('New label'), new_project_label_path(@project), class: "btn btn-success qa-label-create-new"
diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml
deleted file mode 100644
index a6e2565a485..00000000000
--- a/app/views/projects/merge_requests/conflicts.html.haml
+++ /dev/null
@@ -1,36 +0,0 @@
-- page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
-- content_for :page_specific_javascripts do
- = page_specific_javascript_tag('lib/ace.js')
-= render "projects/merge_requests/mr_title"
-
-.merge-request-details.issuable-details
- = render "projects/merge_requests/mr_box"
-
-= render 'shared/issuable/sidebar', issuable: @merge_request
-
-#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_project_merge_request_path(@merge_request.project, @merge_request, format: :json),
- resolve_conflicts_path: resolve_conflicts_project_merge_request_path(@merge_request.project, @merge_request) } }
- .loading{ "v-if" => "isLoading" }
- %i.fa.fa-spinner.fa-spin
-
- .nothing-here-block{ "v-if" => "hasError" }
- {{conflictsData.errorMessage}}
-
- = render partial: "projects/merge_requests/conflicts/commit_stats"
-
- .files-wrapper{ "v-if" => "!isLoading && !hasError" }
- .files
- .diff-file.file-holder.conflict{ "v-for" => "file in conflictsData.files" }
- .js-file-title.file-title
- %i.fa.fa-fw{ ":class" => "file.iconClass" }
- %strong {{file.filePath}}
- = render partial: 'projects/merge_requests/conflicts/file_actions'
- .diff-content.diff-wrap-lines
- .diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
- = render partial: "projects/merge_requests/conflicts/components/inline_conflict_lines"
- .diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
- %parallel-conflict-lines{ ":file" => "file" }
- %div{ "v-show" => "file.resolveMode === 'edit' || file.type === 'text-editor'" }
- = render partial: "projects/merge_requests/conflicts/components/diff_file_editor"
-
- = render partial: "projects/merge_requests/conflicts/submit_form"
diff --git a/app/views/projects/merge_requests/conflicts/show.html.haml b/app/views/projects/merge_requests/conflicts/show.html.haml
index a6e2565a485..09aeb81671a 100644
--- a/app/views/projects/merge_requests/conflicts/show.html.haml
+++ b/app/views/projects/merge_requests/conflicts/show.html.haml
@@ -6,7 +6,7 @@
.merge-request-details.issuable-details
= render "projects/merge_requests/mr_box"
-= render 'shared/issuable/sidebar', issuable: @merge_request
+= render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @merge_request.assignees
#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_project_merge_request_path(@merge_request.project, @merge_request, format: :json),
resolve_conflicts_path: resolve_conflicts_project_merge_request_path(@merge_request.project, @merge_request) } }
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 4ebb029e48b..d6f340d0ee2 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -5,6 +5,7 @@
- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
- 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')
.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) } }
= render "projects/merge_requests/mr_title"
@@ -67,6 +68,7 @@
noteable_data: serialize_issuable(@merge_request),
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} }
#commits.commits.tab-pane
@@ -76,13 +78,16 @@
= 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,
- project_path: project_path(@merge_request.project)} }
+ project_path: project_path(@merge_request.project),
+ changes_empty_state_illustration: image_path('illustrations/merge_request_changes_empty.svg') } }
.mr-loading-status
= spinner
-= render 'shared/issuable/sidebar', issuable: @merge_request
+= render 'shared/issuable/sidebar', issuable_sidebar: @issuable_sidebar, assignees: @merge_request.assignees
+
- if @merge_request.can_be_reverted?(current_user)
= render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title
- if @merge_request.can_be_cherry_picked?
diff --git a/app/views/projects/mirrors/_authentication_method.html.haml b/app/views/projects/mirrors/_authentication_method.html.haml
index 3effdf934fb..293a2e3ebfe 100644
--- a/app/views/projects/mirrors/_authentication_method.html.haml
+++ b/app/views/projects/mirrors/_authentication_method.html.haml
@@ -8,14 +8,14 @@
= f.label :auth_method, _('Authentication method'), class: 'label-bold'
= f.select :auth_method,
options_for_select(auth_options, mirror.auth_method),
- {}, { class: "form-control js-mirror-auth-type" }
+ {}, { class: "form-control js-mirror-auth-type qa-authentication-method" }
.form-group
.collapse.js-well-changing-auth
.changing-auth-method= icon('spinner spin lg')
.well-password-auth.collapse.js-well-password-auth
= f.label :password, _("Password"), class: "label-bold"
- = f.password_field :password, value: mirror.password, class: 'form-control', autocomplete: 'new-password'
+ = f.password_field :password, value: mirror.password, class: 'form-control qa-password', autocomplete: 'new-password'
- unless is_push
.well-ssh-auth.collapse.js-well-ssh-auth
%p.js-ssh-public-key-present{ class: ('collapse' unless ssh_public_key_present) }
diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml
index dde0fae740b..21b105e6f80 100644
--- a/app/views/projects/mirrors/_mirror_repos.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos.html.haml
@@ -1,7 +1,7 @@
- expanded = Rails.env.test?
- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
-%section.settings.project-mirror-settings.js-mirror-settings.no-animate#js-push-remote-settings{ class: ('expanded' if expanded) }
+%section.settings.project-mirror-settings.js-mirror-settings.no-animate.qa-mirroring-repositories-settings#js-push-remote-settings{ class: ('expanded' if expanded) }
.settings-header
%h4= _('Mirroring repositories')
%button.btn.js-settings-toggle
@@ -20,7 +20,7 @@
.form-group.has-feedback
= label_tag :url, _('Git repository URL'), class: 'label-light'
- = text_field_tag :url, nil, class: 'form-control js-mirror-url js-repo-url', placeholder: _('Input your repository URL'), required: true, pattern: "(#{protocols}):\/\/.+"
+ = text_field_tag :url, nil, class: 'form-control js-mirror-url js-repo-url qa-mirror-repository-url-input', placeholder: _('Input your repository URL'), required: true, pattern: "(#{protocols}):\/\/.+"
= render 'projects/mirrors/instructions'
@@ -32,7 +32,7 @@
= link_to icon('question-circle'), help_page_path('user/project/protected_branches')
.panel-footer
- = f.submit _('Mirror repository'), class: 'btn btn-success js-mirror-submit', name: :update_remote_mirror
+ = f.submit _('Mirror repository'), class: 'btn btn-success js-mirror-submit qa-mirror-repository-button', name: :update_remote_mirror
.panel.panel-default
.table-responsive
@@ -50,10 +50,10 @@
= render_if_exists 'projects/mirrors/table_pull_row'
- @project.remote_mirrors.each_with_index do |mirror, index|
- if mirror.enabled
- %tr
- %td= mirror.safe_url
+ %tr.qa-mirrored-repository-row
+ %td.qa-mirror-repository-url= mirror.safe_url
%td= _('Push')
- %td= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never')
+ %td.qa-mirror-last-update-at= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never')
%td
- if mirror.last_error.present?
.badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error')
diff --git a/app/views/projects/mirrors/_mirror_repos_form.html.haml b/app/views/projects/mirrors/_mirror_repos_form.html.haml
index a2cce83bfab..b49f1d9315e 100644
--- a/app/views/projects/mirrors/_mirror_repos_form.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos_form.html.haml
@@ -1,5 +1,5 @@
.form-group
= label_tag :mirror_direction, _('Mirror direction'), class: 'label-light'
- = select_tag :mirror_direction, options_for_select([[_('Push'), 'push']]), class: 'form-control js-mirror-direction', disabled: true
+ = select_tag :mirror_direction, options_for_select([[_('Push'), 'push']]), class: 'form-control js-mirror-direction qa-mirror-direction', disabled: true
= render partial: "projects/mirrors/mirror_repos_push", locals: { f: f }
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 2575efc0981..0f0114d513c 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -24,6 +24,38 @@
- if @pipeline.queued_duration
= "(queued for #{time_interval_in_words(@pipeline.queued_duration)})"
+ .well-segment
+ .icon-container
+ = sprite_icon('flag')
+ - if @pipeline.latest?
+ %span.js-pipeline-url-latest.badge.badge-success.has-tooltip{ title: _("Latest pipeline for this branch") }
+ latest
+ - if @pipeline.has_yaml_errors?
+ %span.js-pipeline-url-yaml.badge.badge-danger.has-tooltip{ title: @pipeline.yaml_errors }
+ yaml invalid
+ - if @pipeline.failure_reason?
+ %span.js-pipeline-url-failure.badge.badge-danger.has-tooltip{ title: @pipeline.failure_reason }
+ error
+ - if @pipeline.auto_devops_source?
+ - popover_title_text = _('This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>').html_safe
+ - popover_content_url = help_page_path('topics/autodevops/index.md')
+ - popover_content_text = _('Learn more about Auto DevOps')
+ %a.js-pipeline-url-autodevops.badge.badge-info.autodevops-badge{ href: "#", tabindex: "0", role: "button", data: { container: "body",
+ toggle: "popover",
+ placement: "top",
+ html: "true",
+ trigger: "focus",
+ title: "<div class='autodevops-title'>#{popover_title_text}</div>",
+ content: "<a class='autodevops-link' href='#{popover_content_url}' target='_blank' rel='noopener noreferrer nofollow'>#{popover_content_text}</a>",
+ } }
+ Auto DevOps
+ - if @pipeline.merge_request?
+ %span.js-pipeline-url-mergerequest.badge.badge-info.has-tooltip{ title: "This pipeline is run in a merge request context" }
+ merge request
+ - if @pipeline.stuck?
+ %span.js-pipeline-url-stuck.badge.badge-warning
+ stuck
+
.well-segment.branch-info
.icon-container.commit-icon
= custom_icon("icon_commit")
diff --git a/app/views/projects/project_members/_new_project_group.html.haml b/app/views/projects/project_members/_new_project_group.html.haml
index 74570769117..88e68f89024 100644
--- a/app/views/projects/project_members/_new_project_group.html.haml
+++ b/app/views/projects/project_members/_new_project_group.html.haml
@@ -10,7 +10,7 @@
= select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control"
= icon('chevron-down')
.form-text.text-muted.append-bottom-10
- = link_to _("Read more"), help_page_path("user/permissions"), class: "vlink"
+ = link_to _("Read more"), help_page_path("user/permissions")
about role permissions
.form-group
= label_tag :expires_at, _('Access expiration date'), class: 'label-bold'
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
index 5e21442bb60..1de7d9c6957 100644
--- a/app/views/projects/project_members/_new_project_member.html.haml
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -10,7 +10,7 @@
= select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select select-control"
= icon('chevron-down')
.form-text.text-muted.append-bottom-10
- = link_to "Read more", help_page_path("user/permissions"), class: "vlink"
+ = link_to "Read more", help_page_path("user/permissions")
about role permissions
.form-group
.clearable-input
diff --git a/app/views/projects/releases/index.html.haml b/app/views/projects/releases/index.html.haml
new file mode 100644
index 00000000000..f01d4e826b9
--- /dev/null
+++ b/app/views/projects/releases/index.html.haml
@@ -0,0 +1,5 @@
+- @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') } }
diff --git a/app/views/projects/serverless/functions/index.html.haml b/app/views/projects/serverless/functions/index.html.haml
new file mode 100644
index 00000000000..f650fa0f38f
--- /dev/null
+++ b/app/views/projects/serverless/functions/index.html.haml
@@ -0,0 +1,15 @@
+- @no_container = true
+- @content_class = "limit-container-width" unless fluid_layout
+- breadcrumb_title 'Serverless'
+- page_title 'Serverless'
+- status_path = project_serverless_functions_path(@project, format: :json)
+- clusters_path = project_clusters_path(@project)
+
+.serverless-functions-page.js-serverless-functions-page{ data: { status_path: status_path, installed: @installed, clusters_path: clusters_path, help_path: help_page_path('user/project/clusters/serverless/index') } }
+
+%div{ class: [container_class, ('limit-container-width' unless fluid_layout)] }
+ .js-serverless-functions-notice
+ .flash-container
+
+ .top-area.adjust
+ .serverless-functions-table#js-serverless-functions
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index 98e2829ba43..6966bf96724 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -43,13 +43,7 @@
%section.qa-variables-settings.settings.no-animate{ class: ('expanded' if expanded) }
.settings-header
- %h4
- = _('Variables')
- = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'variables'), target: '_blank', rel: 'noopener noreferrer'
- %button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p.append-bottom-0
- = render "ci/variables/content"
+ = render 'ci/variables/header', expanded: expanded
.settings-content
= render 'ci/variables/index', save_endpoint: project_variables_path(@project)
diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml
index c14e95a382c..cb3a035c49e 100644
--- a/app/views/projects/settings/repository/show.html.haml
+++ b/app/views/projects/settings/repository/show.html.haml
@@ -13,3 +13,4 @@
= render "projects/protected_tags/index"
= render @deploy_keys
= render "projects/deploy_tokens/index"
+= render "projects/cleanup/show"
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index f29ce4f5c06..c87a084740b 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,7 +1,6 @@
- @no_container = true
- breadcrumb_title _("Details")
- @content_class = "limit-container-width" unless fluid_layout
-- show_auto_devops_callout = show_auto_devops_callout?(@project)
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, project_path(@project, rss_url_options), title: "#{@project.name} activity")
@@ -15,20 +14,11 @@
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
= render "projects/last_push"
-= render "home_panel"
-
-- if can?(current_user, :download_code, @project)
- %nav.project-stats{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
- .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
- .fade-left= icon('angle-left')
- .fade-right= icon('angle-right')
- .nav-links.scrolling-tabs.quick-links
- = render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout)
- = render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout)
+ = render "home_panel"
+ - if can?(current_user, :download_code, @project) && @project.repository_languages.present?
= repository_languages_bar(@project.repository_languages)
-%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
- if @project.archived?
.text-warning.center.prepend-top-20
%p
@@ -41,4 +31,4 @@
= render 'shared/auto_devops_callout'
%div{ class: project_child_container_class(view_path) }
- = render view_path
+ = render view_path, is_project_overview: true
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index f495b4eaf30..da48cb207a4 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
+ %article.file-holder.snippet-file-content{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
= render 'shared/snippets/blob'
.row-content-block.top-block.content-component-block
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/tags/releases/edit.html.haml
index 52c6c7ec424..52c6c7ec424 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/tags/releases/edit.html.haml
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 601e3f25852..4e9a119ac66 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -20,7 +20,7 @@
- if can_collaborate || can_create_mr_from_fork
%li.breadcrumb-item
- %a.btn.add-to-tree{ addtotree_toggle_attributes }
+ %a.btn.add-to-tree.qa-add-to-tree{ addtotree_toggle_attributes }
= sprite_icon('plus', size: 16, css_class: 'float-left')
= sprite_icon('arrow-down', size: 16, css_class: 'float-left')
- if on_top_of_branch?
@@ -30,7 +30,7 @@
%li.dropdown-header
#{ _('This directory') }
%li
- = link_to project_new_blob_path(@project, @id) do
+ = link_to project_new_blob_path(@project, @id), class: 'qa-new-file-option' do
#{ _('New file') }
%li
= link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do
@@ -85,4 +85,8 @@
= link_to ide_edit_path(@project, @ref, @path), class: 'btn btn-default qa-web-ide-button' do
= _('Web IDE')
+ - if show_xcode_link?(@project)
+ .project-action-button.project-xcode.inline
+ = render "projects/buttons/xcode_link"
+
= render 'projects/buttons/download', project: @project, ref: @ref
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index cc38ec12fd8..4d5fd55364c 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
- .wiki
+ .wiki.md{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
= render_wiki_content(@page, legacy_render_context(params))
= render 'sidebar'
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index a8d4d4af93a..2a602095845 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -1,7 +1,7 @@
- project = find_project_for_result_blob(blob)
- return unless project
-- file_name, blob = parse_search_result(blob)
-- blob_link = project_blob_path(project, tree_join(blob.ref, file_name))
+- blob = parse_search_result(blob)
+- blob_link = project_blob_path(project, tree_join(blob.ref, blob.filename))
-= render partial: 'search/results/blob_data', locals: { blob: blob, project: project, file_name: file_name, blob_link: blob_link }
+= render partial: 'search/results/blob_data', locals: { blob: blob, project: project, file_name: blob.filename, blob_link: blob_link }
diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml
index 4346217c230..389e4cc75b9 100644
--- a/app/views/search/results/_wiki_blob.html.haml
+++ b/app/views/search/results/_wiki_blob.html.haml
@@ -1,5 +1,5 @@
- project = find_project_for_result_blob(wiki_blob)
-- file_name, wiki_blob = parse_search_result(wiki_blob)
+- wiki_blob = parse_search_result(wiki_blob)
- wiki_blob_link = project_wiki_path(project, wiki_blob.basename)
-= render partial: 'search/results/blob_data', locals: { blob: wiki_blob, project: project, file_name: file_name, blob_link: wiki_blob_link }
+= render partial: 'search/results/blob_data', locals: { blob: wiki_blob, project: project, file_name: wiki_blob.filename, blob_link: wiki_blob_link }
diff --git a/app/views/shared/_milestones_sort_dropdown.html.haml b/app/views/shared/_milestones_sort_dropdown.html.haml
index bd68a3e4c84..9a1db831ad3 100644
--- a/app/views/shared/_milestones_sort_dropdown.html.haml
+++ b/app/views/shared/_milestones_sort_dropdown.html.haml
@@ -8,15 +8,15 @@
= icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-sort
%li
- = link_to page_filter_path(sort: sort_value_due_date_soon, label: true) do
+ = link_to page_filter_path(sort: sort_value_due_date_soon) do
= sort_title_due_date_soon
- = link_to page_filter_path(sort: sort_value_due_date_later, label: true) do
+ = link_to page_filter_path(sort: sort_value_due_date_later) do
= sort_title_due_date_later
- = link_to page_filter_path(sort: sort_value_start_date_soon, label: true) do
+ = link_to page_filter_path(sort: sort_value_start_date_soon) do
= sort_title_start_date_soon
- = link_to page_filter_path(sort: sort_value_start_date_later, label: true) do
+ = link_to page_filter_path(sort: sort_value_start_date_later) do
= sort_title_start_date_later
- = link_to page_filter_path(sort: sort_value_name, label: true) do
+ = link_to page_filter_path(sort: sort_value_name) do
= sort_title_name_asc
- = link_to page_filter_path(sort: sort_value_name_desc, label: true) do
+ = link_to page_filter_path(sort: sort_value_name_desc) do
= sort_title_name_desc
diff --git a/app/views/shared/_mobile_clone_panel.html.haml b/app/views/shared/_mobile_clone_panel.html.haml
index 998985cabe1..6e2527bd1a1 100644
--- a/app/views/shared/_mobile_clone_panel.html.haml
+++ b/app/views/shared/_mobile_clone_panel.html.haml
@@ -1,13 +1,15 @@
- project = project || @project
- ssh_copy_label = _("Copy SSH clone URL")
-- http_copy_label = _("Copy HTTPS clone URL")
+- http_copy_label = _('Copy %{http_label} clone URL') % { http_label: gitlab_config.protocol.upcase }
-.btn-group.mobile-git-clone.js-mobile-git-clone
- = clipboard_button(button_text: default_clone_label, target: '#project_clone', hide_button_icon: true, class: "input-group-text clone-dropdown-btn js-clone-dropdown-label btn btn-default")
- %button.btn.btn-default.dropdown-toggle.js-dropdown-toggle{ type: "button", data: { toggle: "dropdown" } }
- = icon("caret-down", class: "dropdown-btn-icon")
+.btn-group.mobile-git-clone.js-mobile-git-clone.btn-block
+ = clipboard_button(button_text: default_clone_label, text: default_url_to_repo(project), hide_button_icon: true, class: "btn-primary flex-fill bold justify-content-center input-group-text clone-dropdown-btn js-clone-dropdown-label")
+ %button.btn.btn-primary.dropdown-toggle.js-dropdown-toggle{ type: "button", data: { toggle: "dropdown" } }
+ = sprite_icon("arrow-down", css_class: "dropdown-btn-icon icon")
%ul.dropdown-menu.dropdown-menu-selectable.dropdown-menu-right.clone-options-dropdown{ data: { dropdown: true } }
- %li
- = dropdown_item_with_description(ssh_copy_label, project.ssh_url_to_repo, href: project.ssh_url_to_repo, data: { clone_type: 'ssh' })
- %li
- = dropdown_item_with_description(http_copy_label, project.http_url_to_repo, href: project.http_url_to_repo, data: { clone_type: 'http' })
+ - if ssh_enabled?
+ %li
+ = dropdown_item_with_description(ssh_copy_label, project.ssh_url_to_repo, href: project.ssh_url_to_repo, data: { clone_type: 'ssh' }, default: true)
+ - if http_enabled?
+ %li
+ = dropdown_item_with_description(http_copy_label, project.http_url_to_repo, href: project.http_url_to_repo, data: { clone_type: 'http' })
diff --git a/app/views/shared/_remote_mirror_update_button.html.haml b/app/views/shared/_remote_mirror_update_button.html.haml
index f32cff18fa8..721a2af8069 100644
--- a/app/views/shared/_remote_mirror_update_button.html.haml
+++ b/app/views/shared/_remote_mirror_update_button.html.haml
@@ -2,5 +2,5 @@
%button.btn.disabled{ type: 'button', data: { toggle: 'tooltip', container: 'body' }, title: _('Updating') }
= icon("refresh spin")
- else
- = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn", data: { toggle: 'tooltip', container: 'body' }, title: _('Update now') do
+ = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn qa-update-now-button", data: { toggle: 'tooltip', container: 'body' }, title: _('Update now') do
= icon("refresh")
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
deleted file mode 100644
index e4463c1e0d8..00000000000
--- a/app/views/shared/_sort_dropdown.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-- sorted_by = sort_options_hash[@sort]
-- viewing_issues = controller.controller_name == 'issues' || controller.action_name == 'issues'
-
-.dropdown.inline.prepend-left-10
- %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' } }
- = sorted_by
- = icon('chevron-down')
- %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
- %li
- = sortable_item(sort_title_priority, page_filter_path(sort: sort_value_priority, label: true), sorted_by)
- = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date, label: true), sorted_by)
- = sortable_item(sort_title_recently_updated, page_filter_path(sort: sort_value_recently_updated, label: true), sorted_by)
- = sortable_item(sort_title_milestone, page_filter_path(sort: sort_value_milestone, label: true), sorted_by)
- = sortable_item(sort_title_due_date, page_filter_path(sort: sort_value_due_date, label: true), sorted_by) if viewing_issues
- = sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity, label: true), sorted_by)
- = sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority, label: true), sorted_by)
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
new file mode 100644
index 00000000000..2ca4657851c
--- /dev/null
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -0,0 +1,32 @@
+.issues-filters
+ .issues-details-filters.row-content-block.second-block
+ = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do
+ - if params[:search].present?
+ = hidden_field_tag :search, params[:search]
+ .issues-other-filters
+ .filter-item.inline
+ - if params[:author_id].present?
+ = hidden_field_tag(:author_id, params[:author_id])
+ = dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit",
+ placeholder: "Search authors", data: { any_user: "Any Author", first_user: current_user&.username, current_user: true, project_id: @project&.id, group_id: @group&.id, selected: params[:author_id], field_name: "author_id", default_label: "Author" } })
+
+ .filter-item.inline
+ - if params[:assignee_id].present?
+ = hidden_field_tag(:assignee_id, params[:assignee_id])
+ = dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
+ placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: current_user&.username, null_user: true, current_user: true, project_id: @project&.id, group_id: @group&.id, selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } })
+
+ .filter-item.inline.milestone-filter
+ = render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true, show_started: true
+
+ .filter-item.inline.labels-filter
+ = render "shared/issuable/label_dropdown", selected: selected_labels, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" }
+
+ - unless @no_filters_set
+ .float-right
+ = render 'shared/issuable/sort_dropdown'
+
+ - has_labels = @labels && @labels.any?
+ .row-content-block.second-block.filtered-labels{ class: ("hidden" unless has_labels) }
+ - if has_labels
+ = render 'shared/labels_row', labels: @labels
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 1618655182c..c6a391ae563 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -17,7 +17,7 @@
= render 'shared/issuable/form/template_selector', issuable: issuable
= render 'shared/issuable/form/title', issuable: issuable, form: form, has_wip_commits: commits && commits.detect(&:work_in_progress?)
-- if Feature.enabled?(:issue_suggestions) && Feature.enabled?(:graphql)
+- if Gitlab::Graphql.enabled?
#js-suggestions{ data: { project_path: @project.full_path } }
= render 'shared/form_elements/description', model: issuable, form: form, project: project
diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml
index 157637dbd11..71123740ee4 100644
--- a/app/views/shared/issuable/_nav.html.haml
+++ b/app/views/shared/issuable/_nav.html.haml
@@ -4,20 +4,20 @@
%ul.nav-links.issues-state-filters.mobile-separator.nav.nav-tabs
%li{ class: active_when(params[:state] == 'opened') }>
- = link_to page_filter_path(state: 'opened', label: true), id: 'state-opened', title: "Filter by #{page_context_word} that are currently opened.", data: { state: 'opened' } do
+ = link_to page_filter_path(state: 'opened'), id: 'state-opened', title: "Filter by #{page_context_word} that are currently opened.", data: { state: 'opened' } do
#{issuables_state_counter_text(type, :opened, display_count)}
- if type == :merge_requests
%li{ class: active_when(params[:state] == 'merged') }>
- = link_to page_filter_path(state: 'merged', label: true), id: 'state-merged', title: 'Filter by merge requests that are currently merged.', data: { state: 'merged' } do
+ = link_to page_filter_path(state: 'merged'), id: 'state-merged', title: 'Filter by merge requests that are currently merged.', data: { state: 'merged' } do
#{issuables_state_counter_text(type, :merged, display_count)}
%li{ class: active_when(params[:state] == 'closed') }>
- = link_to page_filter_path(state: 'closed', label: true), id: 'state-closed', title: 'Filter by merge requests that are currently closed and unmerged.', data: { state: 'closed' } do
+ = link_to page_filter_path(state: 'closed'), id: 'state-closed', title: 'Filter by merge requests that are currently closed and unmerged.', data: { state: 'closed' } do
#{issuables_state_counter_text(type, :closed, display_count)}
- else
%li{ class: active_when(params[:state] == 'closed') }>
- = link_to page_filter_path(state: 'closed', label: true), 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' } 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/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 7c5af0b9775..46634693067 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -2,7 +2,6 @@
- board = local_assigns.fetch(:board, nil)
- block_css_class = type != :boards_modal ? 'row-content-block second-block' : ''
- user_can_admin_list = board && can?(current_user, :admin_list, board.parent)
-- show_sorting_dropdown = local_assigns.fetch(:show_sorting_dropdown, true)
.issues-filters
.issues-details-filters.filtered-search-block{ class: block_css_class, "v-pre" => type == :boards_modal }
@@ -142,5 +141,5 @@
- if @project
#js-add-issues-btn.prepend-left-10{ data: { can_admin_list: can?(current_user, :admin_list, @project) } }
#js-toggle-focus-btn
- - elsif show_sorting_dropdown
- = render 'shared/sort_dropdown'
+ - elsif type != :boards_modal
+ = render 'shared/issuable/sort_dropdown'
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 5295e656ab0..0520eda37a4 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -1,32 +1,37 @@
-- todo = issuable_todo(issuable)
+-# `assignees` is being passed in for populating selected assignee values in the select box and rendering the assignee link
+ This should be removed when this sidebar is converted to Vue since assignee data is also available in the `issuable_sidebar` hash
-%aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { signed: { in: current_user.present? } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
- .issuable-sidebar{ data: { endpoint: "#{issuable_json_path(issuable)}" } }
- - can_edit_issuable = can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+- issuable_type = issuable_sidebar[:type]
+- signed_in = !!issuable_sidebar.dig(:current_user, :id)
+- can_edit_issuable = issuable_sidebar.dig(:current_user, :can_edit)
+
+%aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { signed: { in: signed_in } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
+ .issuable-sidebar
.block.issuable-sidebar-header
- - if current_user
+ - if signed_in
%span.issuable-header-text.hide-collapsed.float-left
= _('Todo')
%a.gutter-toggle.float-right.js-sidebar-toggle.has-tooltip{ role: "button", href: "#", "aria-label" => "Toggle sidebar", title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left', boundary: 'viewport' } }
= sidebar_gutter_toggle_icon
- - if current_user
- = render "shared/issuable/sidebar_todo", todo: todo, issuable: issuable
+ - if signed_in
+ = render "shared/issuable/sidebar_todo", issuable_sidebar: issuable_sidebar
- = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: { class: 'issuable-context-form inline-update js-issuable-update' } do |f|
- - if current_user
+ = form_for issuable_type, url: issuable_sidebar[:issuable_json_path], remote: true, html: { class: 'issuable-context-form inline-update js-issuable-update' } do |f|
+ - if signed_in
.block.todo.hide-expanded
- = render "shared/issuable/sidebar_todo", todo: todo, issuable: issuable, is_collapsed: true
- .block.assignee
- = render "shared/issuable/sidebar_assignees", issuable: issuable, can_edit_issuable: can_edit_issuable, signed_in: current_user.present?
+ = render "shared/issuable/sidebar_todo", issuable_sidebar: issuable_sidebar, is_collapsed: true
+ .block.assignee.qa-assignee-block
+ = render "shared/issuable/sidebar_assignees", issuable_sidebar: issuable_sidebar, assignees: assignees
- = render_if_exists 'shared/issuable/sidebar_item_epic', issuable: issuable
+ = render_if_exists 'shared/issuable/sidebar_item_epic', issuable_sidebar: issuable_sidebar
+ - milestone = issuable_sidebar[:milestone] || {}
.block.milestone
- .sidebar-collapsed-icon.has-tooltip{ title: milestone_tooltip_title(issuable.milestone), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } }
+ .sidebar-collapsed-icon.has-tooltip{ title: sidebar_milestone_tooltip_label(milestone), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } }
= icon('clock-o', 'aria-hidden': 'true')
%span.milestone-title.collapse-truncated-title
- - if issuable.milestone
- = issuable.milestone.title
+ - if milestone.present?
+ = milestone[:title]
- else
= _('None')
.title.hide-collapsed
@@ -35,49 +40,50 @@
- if can_edit_issuable
= link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right'
.value.hide-collapsed
- - if issuable.milestone
- = link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_tooltip_due_date(issuable.milestone), data: { container: "body", html: 'true', boundary: 'viewport' }
+ - if milestone.present?
+ = link_to milestone[:title], milestone[:web_url], class: "bold has-tooltip", title: sidebar_milestone_remaining_days(milestone), data: { container: "body", html: 'true', boundary: 'viewport' }
- else
%span.no-value
= _('None')
.selectbox.hide-collapsed
- = f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil
- = dropdown_tag('Milestone', options: { title: _('Assign milestone'), toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: _('Search milestones'), data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: project_milestones_path(@project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true, default_no: true, selected: (issuable.milestone.name if issuable.milestone), null_default: true, display: 'static' }})
- - if issuable.has_attribute?(:time_estimate)
- #issuable-time-tracker.block
- // Fallback while content is loading
- .title.hide-collapsed
- = _('Time tracking')
- = icon('spinner spin', 'aria-hidden': 'true')
- - if issuable.has_attribute?(:due_date)
+ = f.hidden_field 'milestone_id', value: milestone[:id], id: nil
+ = dropdown_tag('Milestone', options: { title: _('Assign milestone'), toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: _('Search milestones'), data: { show_no: true, field_name: "#{issuable_type}[milestone_id]", project_id: issuable_sidebar[:project_id], issuable_id: issuable_sidebar[:id], milestones: issuable_sidebar[:project_milestones_path], ability_name: issuable_type, issue_update: issuable_sidebar[:issuable_json_path], use_id: true, default_no: true, selected: milestone[:title], null_default: true, display: 'static' }})
+
+ #issuable-time-tracker.block
+ // Fallback while content is loading
+ .title.hide-collapsed
+ = _('Time tracking')
+ = icon('spinner spin', 'aria-hidden': 'true')
+
+ - if issuable_sidebar.has_key?(:due_date)
.block.due_date
- .sidebar-collapsed-icon.has-tooltip{ data: { placement: 'left', container: 'body', html: 'true', boundary: 'viewport' }, title: sidebar_due_date_tooltip_label(issuable) }
+ .sidebar-collapsed-icon.has-tooltip{ data: { placement: 'left', container: 'body', html: 'true', boundary: 'viewport' }, title: sidebar_due_date_tooltip_label(issuable_sidebar[:due_date]) }
= icon('calendar', 'aria-hidden': 'true')
%span.js-due-date-sidebar-value
- = issuable.due_date.try(:to_s, :medium) || 'None'
+ = issuable_sidebar[:due_date].try(:to_s, :medium) || 'None'
.title.hide-collapsed
= _('Due date')
= icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ - if can_edit_issuable
= link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right'
.value.hide-collapsed
%span.value-content
- - if issuable.due_date
- %span.bold= issuable.due_date.to_s(:medium)
+ - if issuable_sidebar[:due_date]
+ %span.bold= issuable_sidebar[:due_date].to_s(:medium)
- else
%span.no-value
= _('No due date')
- - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
- %span.no-value.js-remove-due-date-holder{ class: ("hidden" if issuable.due_date.nil?) }
+ - if can_edit_issuable
+ %span.no-value.js-remove-due-date-holder{ class: ("hidden" if issuable_sidebar[:due_date].nil?) }
\-
%a.js-remove-due-date{ href: "#", role: "button" }
= _('remove due date')
- - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
+ - if can_edit_issuable
.selectbox.hide-collapsed
- = f.hidden_field :due_date, value: issuable.due_date.try(:strftime, 'yy-mm-dd')
+ = f.hidden_field :due_date, value: issuable_sidebar[:due_date].try(:strftime, 'yy-mm-dd')
.dropdown
- %button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "#{issuable.to_ability_name}[due_date]", ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), display: 'static' } }
+ %button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "#{issuable_type}[due_date]", ability_name: issuable_type, issue_update: issuable_sidebar[:issuable_json_path], display: 'static' } }
%span.dropdown-toggle-text
= _('Due date')
= icon('chevron-down', 'aria-hidden': 'true')
@@ -86,56 +92,56 @@
= dropdown_content do
.js-due-date-calendar
- - if @labels
- - selected_labels = issuable.labels
- .block.labels
- .sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body", boundary: 'viewport' } }
- = icon('tags', 'aria-hidden': 'true')
- %span
- = selected_labels.size
- .title.hide-collapsed
- = _('Labels')
- = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- - if can_edit_issuable
- = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right'
- .value.issuable-show-labels.dont-hide.hide-collapsed.qa-labels-block{ class: ("has-labels" if selected_labels.any?) }
- - if selected_labels.any?
- - selected_labels.each do |label|
- = link_to_label(label, subject: issuable.project, type: issuable.to_ability_name)
- - else
- %span.no-value
- = _('None')
- .selectbox.hide-collapsed
+ - selected_labels = issuable_sidebar[:labels]
+ .block.labels
+ .sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(selected_labels), data: { placement: "left", container: "body", boundary: 'viewport' } }
+ = icon('tags', 'aria-hidden': 'true')
+ %span
+ = selected_labels.size
+ .title.hide-collapsed
+ = _('Labels')
+ = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
+ - if can_edit_issuable
+ = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right'
+ .value.issuable-show-labels.dont-hide.hide-collapsed.qa-labels-block{ class: ("has-labels" if selected_labels.any?) }
+ - if selected_labels.any?
- selected_labels.each do |label|
- = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil
- .dropdown
- %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (labels_filter_path_with_defaults if @project), display: 'static' } }
- %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) }
- = multi_label_name(selected_labels, "Labels")
- = icon('chevron-down', 'aria-hidden': 'true')
- .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
- = render partial: "shared/issuable/label_page_default"
- - if can? current_user, :admin_label, @project and @project
- = render partial: "shared/issuable/label_page_create"
-
- = render_if_exists 'shared/issuable/sidebar_weight', issuable: issuable
-
- - if issuable.has_attribute?(:confidential)
+ = link_to sidebar_label_filter_path(issuable_sidebar[:project_issuables_path], label[:title]) do
+ %span.badge.color-label.has-tooltip{ style: "background-color: #{label[:color]}; color: #{label[:text_color]}", title: label[:description], data: { container: "body" } }
+ = label[:title]
+ - else
+ %span.no-value
+ = _('None')
+ .selectbox.hide-collapsed
+ - selected_labels.each do |label|
+ = hidden_field_tag "#{issuable_type}[label_names][]", label[:id], id: nil
+ .dropdown
+ %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable_type}[label_names][]", ability_name: issuable_type, show_no: "true", show_any: "true", namespace_path: issuable_sidebar[:namespace_path], project_path: issuable_sidebar[:project_path], issue_update: issuable_sidebar[:issuable_json_path], labels: issuable_sidebar[:project_labels_path], display: 'static' } }
+ %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) }
+ = multi_label_name(selected_labels, "Labels")
+ = icon('chevron-down', 'aria-hidden': 'true')
+ .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
+ = render partial: "shared/issuable/label_page_default"
+ - if issuable_sidebar.dig(:current_user, :can_admin_label)
+ = render partial: "shared/issuable/label_page_create"
+
+ = render_if_exists 'shared/issuable/sidebar_weight', issuable_sidebar: issuable_sidebar
+
+ - if issuable_sidebar.has_key?(:confidential)
-# haml-lint:disable InlineJavaScript
- %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: @issue.confidential, is_editable: can_edit_issuable }.to_json.html_safe
+ %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: issuable_sidebar[:confidential], is_editable: can_edit_issuable }.to_json.html_safe
#js-confidential-entry-point
- - if issuable.has_attribute?(:discussion_locked)
- -# haml-lint:disable InlineJavaScript
- %script#js-lock-issue-data{ type: "application/json" }= { is_locked: issuable.discussion_locked?, is_editable: can_edit_issuable }.to_json.html_safe
- #js-lock-entry-point
+ -# haml-lint:disable InlineJavaScript
+ %script#js-lock-issue-data{ type: "application/json" }= { is_locked: issuable_sidebar[:discussion_locked], is_editable: can_edit_issuable }.to_json.html_safe
+ #js-lock-entry-point
.js-sidebar-participants-entry-point
- - if current_user
+ - if signed_in
.js-sidebar-subscriptions-entry-point
- - project_ref = cross_project_reference(@project, issuable)
+ - project_ref = issuable_sidebar[:reference]
.block.project-reference
.sidebar-collapsed-icon.dont-change-state
= clipboard_button(text: project_ref, title: _('Copy reference to clipboard'), placement: "left", boundary: 'viewport')
@@ -145,7 +151,8 @@
%cite{ title: project_ref }
= project_ref
= clipboard_button(text: project_ref, title: _('Copy reference to clipboard'), placement: "left", boundary: 'viewport')
- - if current_user && issuable.can_move?(current_user)
+
+ - if issuable_sidebar.dig(:current_user, :can_move)
.block.js-sidebar-move-issue-block
.sidebar-collapsed-icon{ data: { toggle: 'tooltip', placement: 'left', container: 'body', boundary: 'viewport' }, title: _('Move issue') }
= custom_icon('icon_arrow_right')
@@ -164,4 +171,4 @@
= icon('spinner spin', class: 'sidebar-move-issue-confirmation-loading-icon')
-# haml-lint:disable InlineJavaScript
- %script.js-sidebar-options{ type: "application/json" }= issuable_sidebar_options(issuable, can_edit_issuable).to_json.html_safe
+ %script.js-sidebar-options{ type: "application/json" }= issuable_sidebar_options(issuable_sidebar).to_json.html_safe
diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml
index 8a13c7a3b83..c5cce1823f0 100644
--- a/app/views/shared/issuable/_sidebar_assignees.html.haml
+++ b/app/views/shared/issuable/_sidebar_assignees.html.haml
@@ -1,12 +1,17 @@
-- if issuable.is_a?(Issue)
- #js-vue-sidebar-assignees{ data: { field: "#{issuable.to_ability_name}[assignee_ids]", signed_in: signed_in } }
+- issuable_type = issuable_sidebar[:type]
+- signed_in = !!issuable_sidebar.dig(:current_user, :id)
+- can_edit_issuable = issuable_sidebar.dig(:current_user, :can_edit)
+
+- if issuable_type == "issue"
+ #js-vue-sidebar-assignees{ data: { field: "#{issuable_type}[assignee_ids]", signed_in: signed_in } }
.title.hide-collapsed
= _('Assignee')
= icon('spinner spin')
- else
- .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body", boundary: 'viewport' }, title: sidebar_assignee_tooltip_label(issuable) }
- - if issuable.assignee
- = link_to_member(@project, issuable.assignee, size: 24)
+ - assignee = assignees.first
+ .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body", boundary: 'viewport' }, title: (issuable_sidebar.dig(:assignee, :name) || _('Assignee')) }
+ - if issuable_sidebar[:assignee]
+ = link_to_member(@project, assignee, size: 24)
- else
= icon('user', 'aria-hidden': 'true')
.title.hide-collapsed
@@ -18,13 +23,13 @@
%a.gutter-toggle.float-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => _('Toggle sidebar') }
= sidebar_gutter_toggle_icon
.value.hide-collapsed
- - if issuable.assignee
- = link_to_member(@project, issuable.assignee, size: 32, extra_class: 'bold') do
- - if !issuable.can_be_merged_by?(issuable.assignee)
+ - if issuable_sidebar[:assignee]
+ = link_to_member(@project, assignee, size: 32, extra_class: 'bold') do
+ - if issuable_sidebar[:assignee][:can_merge]
%span.float-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: _('Not allowed to merge') }
= icon('exclamation-triangle', 'aria-hidden': 'true')
%span.username
- = issuable.assignee.to_reference
+ @#{issuable_sidebar[:assignee][:username]}
- else
%span.assign-yourself.no-value
= _('No assignee')
@@ -34,19 +39,33 @@
= _('assign yourself')
.selectbox.hide-collapsed
- - issuable.assignees.each do |assignee|
- = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username }
+ - if assignees.none?
+ = hidden_field_tag "#{issuable_type}[assignee_ids][]", 0, id: nil
+ - else
+ - assignees.each do |assignee|
+ = hidden_field_tag "#{issuable_type}[assignee_ids][]", assignee.id, id: nil, data: { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username }
- - options = { toggle_class: 'js-user-search js-author-search', title: _('Assign to'), filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: _('Search users'), data: { first_user: current_user&.username, current_user: true, project_id: @project&.id, author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true, display: 'static' } }
+ - options = { toggle_class: 'js-user-search js-author-search',
+ title: _('Assign to'),
+ filter: true,
+ dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author',
+ placeholder: _('Search users'),
+ data: { first_user: issuable_sidebar.dig(:current_user, :username),
+ current_user: true,
+ project_id: issuable_sidebar[:project_id],
+ author_id: issuable_sidebar[:author_id],
+ field_name: "#{issuable_type}[assignee_ids][]",
+ issue_update: issuable_sidebar[:issuable_json_path],
+ ability_name: issuable_type,
+ null_user: true,
+ display: 'static' } }
- title = _('Select assignee')
- - if issuable.is_a?(Issue)
- - unless issuable.assignees.any?
- = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil
+ - if issuable_type == "issue"
- dropdown_options = issue_assignees_dropdown_options
- title = dropdown_options[:title]
- options[:toggle_class] += ' js-multiselect js-save-user-data'
- - data = { field_name: "#{issuable.to_ability_name}[assignee_ids][]" }
+ - data = { field_name: "#{issuable_type}[assignee_ids][]" }
- data[:multi_select] = true
- data['dropdown-title'] = title
- data['dropdown-header'] = dropdown_options[:data][:'dropdown-header']
diff --git a/app/views/shared/issuable/_sidebar_todo.html.haml b/app/views/shared/issuable/_sidebar_todo.html.haml
index 660ee6d5777..de4df016cfb 100644
--- a/app/views/shared/issuable/_sidebar_todo.html.haml
+++ b/app/views/shared/issuable/_sidebar_todo.html.haml
@@ -1,15 +1,15 @@
- is_collapsed = local_assigns.fetch(:is_collapsed, false)
-- mark_content = is_collapsed ? sprite_icon('todo-done', css_class: 'todo-undone') : _('Mark todo as done')
-- todo_content = is_collapsed ? sprite_icon('todo-add') : _('Add todo')
+- has_todo = !!issuable_sidebar.dig(:current_user, :todo, :id)
+
+- todo_button_data = issuable_todo_button_data(issuable_sidebar, is_collapsed)
+- button_title = has_todo ? todo_button_data[:mark_text] : todo_button_data[:todo_text]
+- button_icon = has_todo ? todo_button_data[:mark_icon] : todo_button_data[:todo_icon]
%button.issuable-todo-btn.js-issuable-todo{ type: 'button',
class: (is_collapsed ? 'btn-blank sidebar-collapsed-icon dont-change-state has-tooltip' : 'btn btn-default issuable-header-btn float-right'),
- title: (todo.nil? ? _('Add todo') : _('Mark todo as done')),
- 'aria-label' => (todo.nil? ? _('Add todo') : _('Mark todo as done')),
- data: issuable_todo_button_data(issuable, todo, is_collapsed) }
+ title: button_title,
+ 'aria-label' => button_title,
+ data: todo_button_data }
%span.issuable-todo-inner.js-issuable-todo-inner<
- - if todo
- = mark_content
- - else
- = todo_content
+ = is_collapsed ? button_icon : button_title
= icon('spin spinner', 'aria-hidden': 'true')
diff --git a/app/views/shared/issuable/_sort_dropdown.html.haml b/app/views/shared/issuable/_sort_dropdown.html.haml
new file mode 100644
index 00000000000..b6ea9185b10
--- /dev/null
+++ b/app/views/shared/issuable/_sort_dropdown.html.haml
@@ -0,0 +1,21 @@
+- sort_value = @sort
+- sort_title = issuable_sort_option_title(sort_value)
+- viewing_issues = controller.controller_name == 'issues' || controller.action_name == 'issues'
+
+.dropdown.inline.prepend-left-10.issue-sort-dropdown
+ .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(sort_title_priority, page_filter_path(sort: sort_value_priority), sort_title)
+ = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date), sort_title)
+ = sortable_item(sort_title_recently_updated, page_filter_path(sort: sort_value_recently_updated), sort_title)
+ = sortable_item(sort_title_milestone, page_filter_path(sort: sort_value_milestone), sort_title)
+ = sortable_item(sort_title_due_date, page_filter_path(sort: sort_value_due_date), sort_title) if viewing_issues
+ = sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity), sort_title)
+ = sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority), sort_title)
+ = render_if_exists('shared/ee/issuable/sort_dropdown', viewing_issues: viewing_issues, sort_title: sort_title)
+ = issuable_sort_direction_button(sort_value)
diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml
index ac8d58c0bfe..e370dff9526 100644
--- a/app/views/shared/issuable/form/_metadata.html.haml
+++ b/app/views/shared/issuable/form/_metadata.html.haml
@@ -19,10 +19,9 @@
.issuable-form-select-holder
= render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, show_started: false, extra_class: "qa-issuable-milestone-dropdown js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone"
.form-group.row
- - has_labels = @labels && @labels.any?
= form.label :label_ids, "Labels", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}"
= form.hidden_field :label_ids, multiple: true, value: ''
- .col-sm-10{ class: "#{"col-md-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" }
+ .col-sm-10{ class: "#{"col-md-8" if has_due_date}" }
.issuable-form-select-holder
= render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false }, dropdown_title: "Select label"
diff --git a/app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml b/app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml
index 3521f71f409..60c34094108 100644
--- a/app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml
+++ b/app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml
@@ -5,4 +5,4 @@
= dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: issuable.project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} })
- = link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignee_id == current_user.id}"
+ = link_to 'Assign to me', '#', class: "assign-to-me-link qa-assign-to-me-link #{'hide' if issuable.assignee_id == current_user.id}"
diff --git a/app/views/shared/issuable/nav_links/_all.html.haml b/app/views/shared/issuable/nav_links/_all.html.haml
index d7ad7090a45..c92a50bcb70 100644
--- a/app/views/shared/issuable/nav_links/_all.html.haml
+++ b/app/views/shared/issuable/nav_links/_all.html.haml
@@ -2,5 +2,5 @@
- counter = local_assigns.fetch(:counter)
%li{ class: active_when(params[:state] == 'all') }>
- = link_to page_filter_path(state: 'all', label: true), id: 'state-all', title: "Show all #{page_context_word}.", data: { state: 'all' } do
+ = link_to page_filter_path(state: 'all'), id: 'state-all', title: "Show all #{page_context_word}.", data: { state: 'all' } do
#{counter}
diff --git a/app/views/shared/labels/_sort_dropdown.html.haml b/app/views/shared/labels/_sort_dropdown.html.haml
index d664ef1cc2f..07e96eea062 100644
--- a/app/views/shared/labels/_sort_dropdown.html.haml
+++ b/app/views/shared/labels/_sort_dropdown.html.haml
@@ -6,4 +6,4 @@
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-sort
%li
- label_sort_options_hash.each do |value, title|
- = sortable_item(title, page_filter_path(sort: value, label: true, subscribed: params[:subscribed]), sort_title)
+ = sortable_item(title, page_filter_path(sort: value), sort_title)
diff --git a/app/views/shared/members/_access_request_links.html.haml b/app/views/shared/members/_access_request_links.html.haml
new file mode 100644
index 00000000000..f7227b9101e
--- /dev/null
+++ b/app/views/shared/members/_access_request_links.html.haml
@@ -0,0 +1,17 @@
+- model_name = source.model_name.to_s.downcase
+
+- if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id)) # rubocop: disable CodeReuse/ActiveRecord
+ - link_text = source.is_a?(Group) ? _('Leave group') : _('Leave project')
+ = link_to link_text, polymorphic_path([:leave, source, :members]),
+ method: :delete,
+ data: { confirm: leave_confirmation_message(source) },
+ class: 'access-request-link'
+- elsif requester = source.requesters.find_by(user_id: current_user.id) # rubocop: disable CodeReuse/ActiveRecord
+ = link_to _('Withdraw Access Request'), polymorphic_path([:leave, source, :members]),
+ method: :delete,
+ data: { confirm: remove_member_message(requester) },
+ class: 'access-request-link'
+- elsif source.request_access_enabled && can?(current_user, :request_access, source)
+ = link_to _('Request Access'), polymorphic_path([:request_access, source, :members]),
+ method: :post,
+ class: 'access-request-link'
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index a7fd75d85d7..6b3841ebbc4 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -75,7 +75,7 @@
= dropdown_title(_("Change permissions"))
.dropdown-content
%ul
- - member.access_level_roles.each do |role, role_id|
+ - member.valid_level_roles.each do |role, role_id|
%li
= link_to role, "javascript:void(0)",
class: ("is-active" if member.access_level == role_id),
diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml
index becd1c4884e..b24075c7849 100644
--- a/app/views/shared/milestones/_sidebar.html.haml
+++ b/app/views/shared/milestones/_sidebar.html.haml
@@ -65,7 +65,7 @@
%span.bold= milestone.due_date.to_s(:medium)
- else
%span.no-value No due date
- - remaining_days = remaining_days_in_words(milestone)
+ - remaining_days = remaining_days_in_words(milestone.due_date, milestone.start_date)
- if remaining_days.present?
= surround '(', ')' do
%span.remaining-days= remaining_days
diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml
index bc918430823..e125d7f108a 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -5,7 +5,7 @@
- note_editable = can?(current_user, :admin_note, note)
- note_counter = local_assigns.fetch(:note_counter, 0)
-%li.timeline-entry.note-wrapper.outlined{ id: dom_id(note),
+%li.timeline-entry.note-wrapper{ id: dom_id(note),
class: ["note", "note-row-#{note.id}", ('system-note' if note.system)],
data: { author_id: note.author.id,
editable: note_editable,
diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml
index f6c7ca70ebd..30860988bbb 100644
--- a/app/views/shared/notifications/_button.html.haml
+++ b/app/views/shared/notifications/_button.html.haml
@@ -1,3 +1,5 @@
+- btn_class = local_assigns.fetch(:btn_class, nil)
+
- if notification_setting
.js-notification-dropdown.notification-dropdown.project-action-button.dropdown.inline
= form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f|
@@ -6,14 +8,14 @@
.js-notification-toggle-btns
%div{ class: ("btn-group" if notification_setting.custom?) }
- if notification_setting.custom?
- %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting", "aria-label" => "Notification setting: #{notification_title(notification_setting.level)}", data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
= icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level)
%button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
= icon('caret-down')
.sr-only Toggle dropdown
- else
- %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting", "aria-label" => "Notification setting: #{notification_title(notification_setting.level)}", data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: "Notification setting", class: "#{btn_class}", "aria-label" => "Notification setting: #{notification_title(notification_setting.level)}", data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
= icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level)
= icon("caret-down")
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index 06eb3d03e31..15c29e14cc0 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -2,24 +2,29 @@
- avatar = true unless local_assigns[:avatar] == false
- use_creator_avatar = false unless local_assigns[:use_creator_avatar] == true
- stars = true unless local_assigns[:stars] == false
-- forks = false unless local_assigns[:forks] == true
+- forks = true unless local_assigns[:forks] == false
+- merge_requests = true unless local_assigns[:merge_requests] == false
+- issues = true unless local_assigns[:issues] == false
+- pipeline_status = true unless local_assigns[:pipeline_status] == false
- ci = false unless local_assigns[:ci] == true
- skip_namespace = false unless local_assigns[:skip_namespace] == true
- user = local_assigns[:user]
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true
- remote = false unless local_assigns[:remote] == true
- skip_pagination = false unless local_assigns[:skip_pagination] == true
+- compact_mode = false unless local_assigns[:compact_mode] == true
+- css_classes = "#{'compact' if compact_mode} #{'explore' if explore_projects_tab?}"
.js-projects-list-holder
- if any_projects?(projects)
- load_pipeline_status(projects)
-
- %ul.projects-list
+ %ul.projects-list{ class: css_classes }
- projects.each_with_index do |project, i|
- css_class = (i >= projects_limit) || project.pending_delete? ? 'hide' : nil
= render "shared/projects/project", project: project, skip_namespace: skip_namespace,
avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar,
- forks: forks, show_last_commit_as_description: show_last_commit_as_description, user: user
+ forks: forks, show_last_commit_as_description: show_last_commit_as_description, user: user, merge_requests: merge_requests,
+ issues: issues, pipeline_status: pipeline_status, compact_mode: compact_mode
- if @private_forks_count && @private_forks_count > 0
%li.project-row.private-forks-notice
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index aba790e1217..fea7e17be3d 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -1,62 +1,107 @@
- avatar = true unless local_assigns[:avatar] == false
- stars = true unless local_assigns[:stars] == false
-- forks = false unless local_assigns[:forks] == true
+- forks = true unless local_assigns[:forks] == false
+- merge_requests = true unless local_assigns[:merge_requests] == false
+- issues = true unless local_assigns[:issues] == false
+- pipeline_status = true unless local_assigns[:pipeline_status] == false
- skip_namespace = false unless local_assigns[:skip_namespace] == true
- access = max_project_member_access(project)
-- css_class = '' unless local_assigns[:css_class]
+- compact_mode = false unless local_assigns[:compact_mode] == true
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && can_show_last_commit_in_list?(project)
+- css_class = '' unless local_assigns[:css_class]
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
- cache_key = project_list_cache_key(project)
- updated_tooltip = time_ago_with_tooltip(project.last_activity_date)
+- css_details_class = compact_mode ? "d-flex flex-column flex-sm-row flex-md-row align-items-sm-center" : "align-items-center flex-md-fill flex-lg-column d-sm-flex d-lg-block"
+- css_controls_class = compact_mode ? "" : "align-items-md-end align-items-lg-center flex-lg-row"
-%li.project-row{ class: css_class }
+%li.project-row.d-flex{ class: css_class }
= cache(cache_key) do
- if avatar
- .avatar-container.s40
+ .avatar-container.s64.flex-grow-0.flex-shrink-0
= link_to project_path(project), class: dom_class(project) do
- if project.creator && use_creator_avatar
- = image_tag avatar_icon_for_user(project.creator, 40), class: "avatar s40", alt:''
+ = image_tag avatar_icon_for_user(project.creator, 64), class: "avatar s65", alt:''
- else
- = project_icon(project, alt: '', class: 'avatar project-avatar s40', width: 40, height: 40)
- .project-details
- %h3.prepend-top-0.append-bottom-0
- = link_to project_path(project), class: 'text-plain' do
- %span.project-full-name><
- %span.namespace-name
- - if project.namespace && !skip_namespace
- = project.namespace.human_name
- \/
- %span.project-name<
- = project.name
-
- - if access&.nonzero?
- -# haml-lint:disable UnnecessaryStringOutput
- = ' ' # prevent haml from eating the space between elements
- %span.user-access-role= Gitlab::Access.human_access(access)
-
- - if show_last_commit_as_description
- .description.prepend-top-5
- = link_to_markdown(project.commit.title, project_commit_path(project, project.commit), class: "commit-row-message")
- - elsif project.description.present?
- .description.prepend-top-5
- = markdown_field(project, :description)
-
- .controls
- .prepend-top-0
- - if project.archived
- %span.prepend-left-10.badge.badge-warning archived
- - if can?(current_user, :read_cross_project) && project.pipeline_status.has_status?
- %span.prepend-left-10
- = render_project_pipeline_status(project.pipeline_status)
- - if forks
- %span.prepend-left-10
- = sprite_icon('fork', size: 12)
- = number_with_delimiter(project.forks_count)
- - if stars
- %span.prepend-left-10
- = icon('star')
- = number_with_delimiter(project.star_count)
- %span.prepend-left-10.visibility-icon.has-tooltip{ data: { container: 'body', placement: 'left' }, title: visibility_icon_description(project) }
- = visibility_level_icon(project.visibility_level, fw: true)
- .prepend-top-0
- updated #{updated_tooltip}
+ = project_icon(project, alt: '', class: 'avatar project-avatar s64', width: 64, height: 64)
+ .project-details.flex-sm-fill{ class: css_details_class }
+ .flex-wrapper.flex-fill
+ .d-flex.align-items-center.flex-wrap
+ %h2.d-flex.prepend-top-8
+ = link_to project_path(project), class: 'text-plain' do
+ %span.project-full-name.append-right-8><
+ %span.namespace-name
+ - if project.namespace && !skip_namespace
+ = project.namespace.human_name
+ \/
+ %span.project-name<
+ = project.name
+
+ %span.metadata-info.visibility-icon.append-right-10.prepend-top-8.has-tooltip{ data: { container: 'body', placement: 'top' }, title: visibility_icon_description(project) }
+ = visibility_level_icon(project.visibility_level, fw: true)
+
+ - if explore_projects_tab? && project.repository.license
+ %span.metadata-info.d-inline-flex.align-items-center.append-right-10.prepend-top-8
+ = sprite_icon('scale', size: 14, css_class: 'append-right-4')
+ = project.repository.license.name
+
+ - if !explore_projects_tab? && access&.nonzero?
+ -# haml-lint:disable UnnecessaryStringOutput
+ = ' ' # prevent haml from eating the space between elements
+ .metadata-info.prepend-top-8
+ %span.user-access-role.d-block= Gitlab::Access.human_access(access)
+
+ - if show_last_commit_as_description
+ .description.d-none.d-sm-block.prepend-top-8.append-right-default
+ = link_to_markdown(project.commit.title, project_commit_path(project, project.commit), class: "commit-row-message")
+ - elsif project.description.present?
+ .description.d-none.d-sm-block.prepend-top-8.append-right-default
+ = markdown_field(project, :description)
+
+ .controls.d-flex.flex-row.flex-sm-column.flex-md-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0{ class: css_controls_class }
+ .icon-container.d-flex.align-items-center
+ - if project.archived
+ %span.d-flex.icon-wrapper.badge.badge-warning archived
+ - if stars
+ %span.d-flex.align-items-center.icon-wrapper.stars.has-tooltip{ data: { container: 'body', placement: 'top' }, title: _('Stars') }
+ = sprite_icon('star', size: 14, css_class: 'append-right-4')
+ = number_with_delimiter(project.star_count)
+ - if forks
+ = link_to project_forks_path(project),
+ class: "align-items-center icon-wrapper forks has-tooltip",
+ title: _('Forks'), data: { container: 'body', placement: 'top' } do
+ = sprite_icon('fork', size: 14, css_class: 'append-right-4')
+ = number_with_delimiter(project.forks_count)
+ - if show_merge_request_count?(disabled: !merge_requests, compact_mode: compact_mode)
+ = link_to project_merge_requests_path(project),
+ class: "d-none d-lg-flex align-items-center icon-wrapper merge-requests has-tooltip",
+ title: _('Merge Requests'), data: { container: 'body', placement: 'top' } do
+ = sprite_icon('git-merge', size: 14, css_class: 'append-right-4')
+ = number_with_delimiter(project.open_merge_requests_count)
+ - if show_issue_count?(disabled: !issues, compact_mode: compact_mode)
+ = link_to project_issues_path(project),
+ class: "d-none d-lg-flex align-items-center icon-wrapper issues has-tooltip",
+ title: _('Issues'), data: { container: 'body', placement: 'top' } do
+ = sprite_icon('issues', size: 14, css_class: 'append-right-4')
+ = number_with_delimiter(project.open_issues_count)
+ - if pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status?
+ %span.icon-wrapper.pipeline-status
+ = render_project_pipeline_status(project.pipeline_status, tooltip_placement: 'top')
+ .updated-note
+ %span Updated #{updated_tooltip}
+
+ .d-none.d-lg-flex.align-item-stretch
+ - unless compact_mode
+ - if current_user
+ %button.star-button.btn.btn-default.d-flex.align-items-center.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(project, :json) } }
+ - if current_user.starred?(project)
+ = sprite_icon('star', { css_class: 'icon' })
+ %span.starred= s_('ProjectOverview|Unstar')
+ - else
+ = sprite_icon('star-o', { css_class: 'icon' })
+ %span= s_('ProjectOverview|Star')
+
+ - else
+ = link_to new_user_session_path, class: 'btn btn-default has-tooltip count-badge-button d-flex align-items-center star-btn', title: s_('ProjectOverview|You must sign in to star a project') do
+ = sprite_icon('star-o', { css_class: 'icon' })
+ %span= s_('ProjectOverview|Star')
diff --git a/app/views/users/_overview.html.haml b/app/views/users/_overview.html.haml
index b5bc1180290..d22905ecc93 100644
--- a/app/views/users/_overview.html.haml
+++ b/app/views/users/_overview.html.haml
@@ -9,22 +9,24 @@
.col-md-12.col-lg-6
- if can?(current_user, :read_cross_project)
.activities-block
+ .append-right-5
+ .prepend-top-16
+ .d-flex.align-items-center.border-bottom
+ %h4.flex-grow
+ = s_('UserProfile|Activity')
+ = link_to s_('UserProfile|View all'), user_activity_path, class: "hide js-view-all"
+ .overview-content-list{ data: { href: user_path } }
+ .center.light.loading
+ = spinner nil, true
+
+ .col-md-12.col-lg-6
+ .projects-block
+ .prepend-left-5
.prepend-top-16
.d-flex.align-items-center.border-bottom
%h4.flex-grow
- = s_('UserProfile|Activity')
- = link_to s_('UserProfile|View all'), user_activity_path, class: "hide js-view-all"
- .overview-content-list{ data: { href: user_path } }
+ = s_('UserProfile|Personal projects')
+ = link_to s_('UserProfile|View all'), user_projects_path, class: "hide js-view-all"
+ .overview-content-list{ data: { href: user_projects_path } }
.center.light.loading
= spinner nil, true
-
- .col-md-12.col-lg-6
- .projects-block
- .prepend-top-16
- .d-flex.align-items-center.border-bottom
- %h4.flex-grow
- = s_('UserProfile|Personal projects')
- = link_to s_('UserProfile|View all'), user_projects_path, class: "hide js-view-all"
- .overview-content-list{ data: { href: user_projects_path } }
- .center.light.loading
- = spinner nil, true
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index d11476738e4..dd2cd36eac2 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -31,12 +31,12 @@
data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('users')
- .profile-header
+ .profile-header{ class: [('with-no-profile-tabs' if profile_tabs.empty?)] }
.avatar-holder
= link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do
= image_tag avatar_icon_for_user(@user, 90), class: "avatar s90", alt: ''
- .user-info.prepend-left-default.append-right-default
+ .user-info
.cover-title
= @user.name
@@ -81,10 +81,10 @@
= icon('briefcase')
= @user.organization
- - if @user.bio.present?
- .cover-desc
- %p.profile-user-bio
- = @user.bio
+ - if @user.bio.present?
+ .cover-desc
+ %p.profile-user-bio
+ = @user.bio
- unless profile_tabs.empty?
.scrolling-tabs-container
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index e51da79c6b5..d3cf21db335 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -10,7 +10,6 @@
- cronjob:prune_old_events
- cronjob:remove_expired_group_links
- cronjob:remove_expired_members
-- cronjob:remove_old_web_hook_logs
- cronjob:remove_unreferenced_lfs_objects
- cronjob:repository_archive_cache
- cronjob:repository_check_dispatch
@@ -28,7 +27,7 @@
- gcp_cluster:cluster_wait_for_app_installation
- gcp_cluster:wait_for_cluster_creation
- gcp_cluster:cluster_wait_for_ingress_ip_address
-- gcp_cluster:cluster_platform_configure
+- gcp_cluster:cluster_configure
- gcp_cluster:cluster_project_configure
- github_import_advance_stage
@@ -86,6 +85,11 @@
- todos_destroyer:todos_destroyer_project_private
- todos_destroyer:todos_destroyer_private_features
+- object_pool:object_pool_create
+- object_pool:object_pool_schedule_join
+- object_pool:object_pool_join
+- object_pool:object_pool_destroy
+
- default
- mailers # ActionMailer::DeliveryJob.queue_name
@@ -121,6 +125,7 @@
- propagate_service_template
- reactive_caching
- rebase
+- remote_mirror_notification
- repository_fork
- repository_import
- repository_remove_remote
@@ -133,3 +138,5 @@
- create_note_diff_file
- delete_diff_files
- detect_repository_languages
+- repository_cleanup
+- delete_stored_files
diff --git a/app/workers/cluster_platform_configure_worker.rb b/app/workers/cluster_configure_worker.rb
index aa7570caa79..63e6cc147be 100644
--- a/app/workers/cluster_platform_configure_worker.rb
+++ b/app/workers/cluster_configure_worker.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ClusterPlatformConfigureWorker
+class ClusterConfigureWorker
include ApplicationWorker
include ClusterQueue
diff --git a/app/workers/cluster_provision_worker.rb b/app/workers/cluster_provision_worker.rb
index 3d5894b73ec..926ae2b7286 100644
--- a/app/workers/cluster_provision_worker.rb
+++ b/app/workers/cluster_provision_worker.rb
@@ -10,7 +10,7 @@ class ClusterProvisionWorker
Clusters::Gcp::ProvisionService.new.execute(provider) if cluster.gcp?
end
- ClusterPlatformConfigureWorker.perform_async(cluster.id) if cluster.user?
+ ClusterConfigureWorker.perform_async(cluster.id) if cluster.user?
end
end
end
diff --git a/app/workers/concerns/object_pool_queue.rb b/app/workers/concerns/object_pool_queue.rb
new file mode 100644
index 00000000000..5b648df9c72
--- /dev/null
+++ b/app/workers/concerns/object_pool_queue.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+##
+# Concern for setting Sidekiq settings for the various ObjectPool queues
+#
+module ObjectPoolQueue
+ extend ActiveSupport::Concern
+
+ included do
+ queue_namespace :object_pool
+ end
+end
diff --git a/app/workers/delete_stored_files_worker.rb b/app/workers/delete_stored_files_worker.rb
new file mode 100644
index 00000000000..ff7931849d8
--- /dev/null
+++ b/app/workers/delete_stored_files_worker.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class DeleteStoredFilesWorker
+ include ApplicationWorker
+
+ def perform(class_name, keys)
+ klass = begin
+ class_name.constantize
+ rescue NameError
+ nil
+ end
+
+ unless klass
+ message = "Unknown class '#{class_name}'"
+ logger.error(message)
+ Gitlab::Sentry.track_exception(RuntimeError.new(message))
+ return
+ end
+
+ klass.new(logger: logger).delete_keys(keys)
+ end
+end
diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb
index 2d381c6fd6c..d3628b23189 100644
--- a/app/workers/git_garbage_collect_worker.rb
+++ b/app/workers/git_garbage_collect_worker.rb
@@ -28,6 +28,8 @@ class GitGarbageCollectWorker
# Refresh the branch cache in case garbage collection caused a ref lookup to fail
flush_ref_caches(project) if task == :gc
+ project.repository.expire_statistics_caches
+
# In case pack files are deleted, release libgit2 cache and open file
# descriptors ASAP instead of waiting for Ruby garbage collection
project.cleanup
diff --git a/app/workers/new_note_worker.rb b/app/workers/new_note_worker.rb
index 42f5b945a75..98f9f45e608 100644
--- a/app/workers/new_note_worker.rb
+++ b/app/workers/new_note_worker.rb
@@ -8,11 +8,18 @@ class NewNoteWorker
# rubocop: disable CodeReuse/ActiveRecord
def perform(note_id, _params = {})
if note = Note.find_by(id: note_id)
- NotificationService.new.new_note(note)
+ NotificationService.new.new_note(note) unless skip_notification?(note)
Notes::PostProcessService.new(note).execute
else
Rails.logger.error("NewNoteWorker: couldn't find note with ID=#{note_id}, skipping job")
end
end
+
+ private
+
+ # EE-only method
+ def skip_notification?(note)
+ false
+ end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/object_pool/create_worker.rb b/app/workers/object_pool/create_worker.rb
new file mode 100644
index 00000000000..135b99886dc
--- /dev/null
+++ b/app/workers/object_pool/create_worker.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module ObjectPool
+ class CreateWorker
+ include ApplicationWorker
+ include ObjectPoolQueue
+ include ExclusiveLeaseGuard
+
+ attr_reader :pool
+
+ def perform(pool_id)
+ @pool = PoolRepository.find_by_id(pool_id)
+ return unless pool
+
+ try_obtain_lease do
+ perform_pool_creation
+ end
+ end
+
+ private
+
+ def perform_pool_creation
+ return unless pool.failed? || pool.scheduled?
+
+ # If this is a retry and the previous execution failed, deletion will
+ # bring the pool back to a pristine state
+ pool.delete_object_pool if pool.failed?
+
+ pool.create_object_pool
+ pool.mark_ready
+ rescue => e
+ pool.mark_failed
+ raise e
+ end
+
+ def lease_key
+ "object_pool:create:#{pool.id}"
+ end
+
+ def lease_timeout
+ 1.hour
+ end
+ end
+end
diff --git a/app/workers/object_pool/destroy_worker.rb b/app/workers/object_pool/destroy_worker.rb
new file mode 100644
index 00000000000..ca00d467d9b
--- /dev/null
+++ b/app/workers/object_pool/destroy_worker.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module ObjectPool
+ class DestroyWorker
+ include ApplicationWorker
+ include ObjectPoolQueue
+
+ def perform(pool_repository_id)
+ pool = PoolRepository.find_by_id(pool_repository_id)
+ return unless pool&.obsolete?
+
+ pool.delete_object_pool
+ pool.destroy
+ end
+ end
+end
diff --git a/app/workers/object_pool/join_worker.rb b/app/workers/object_pool/join_worker.rb
new file mode 100644
index 00000000000..07676011b2a
--- /dev/null
+++ b/app/workers/object_pool/join_worker.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module ObjectPool
+ class JoinWorker
+ include ApplicationWorker
+ include ObjectPoolQueue
+
+ def perform(pool_id, project_id)
+ pool = PoolRepository.find_by_id(pool_id)
+ return unless pool&.joinable?
+
+ project = Project.find_by_id(project_id)
+ return unless project
+
+ pool.link_repository(project.repository)
+
+ Projects::HousekeepingService.new(project).execute
+ end
+ end
+end
diff --git a/app/workers/object_pool/schedule_join_worker.rb b/app/workers/object_pool/schedule_join_worker.rb
new file mode 100644
index 00000000000..647a8b72435
--- /dev/null
+++ b/app/workers/object_pool/schedule_join_worker.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module ObjectPool
+ class ScheduleJoinWorker
+ include ApplicationWorker
+ include ObjectPoolQueue
+
+ def perform(pool_id)
+ pool = PoolRepository.find_by_id(pool_id)
+ return unless pool&.joinable?
+
+ pool.member_projects.find_each do |project|
+ next if project.forked? && !project.import_finished?
+
+ ObjectPool::JoinWorker.perform_async(pool.id, project.id)
+ end
+ end
+ end
+end
diff --git a/app/workers/remote_mirror_notification_worker.rb b/app/workers/remote_mirror_notification_worker.rb
new file mode 100644
index 00000000000..70c2e857d09
--- /dev/null
+++ b/app/workers/remote_mirror_notification_worker.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class RemoteMirrorNotificationWorker
+ include ApplicationWorker
+
+ def perform(remote_mirror_id)
+ remote_mirror = RemoteMirrorFinder.new(id: remote_mirror_id).execute
+
+ # We check again if there's an error because a newer run since this job was
+ # fired could've completed successfully.
+ return unless remote_mirror && remote_mirror.last_error.present?
+
+ NotificationService.new.remote_mirror_update_failed(remote_mirror)
+ end
+end
diff --git a/app/workers/remove_old_web_hook_logs_worker.rb b/app/workers/remove_old_web_hook_logs_worker.rb
deleted file mode 100644
index 0f486f8991d..00000000000
--- a/app/workers/remove_old_web_hook_logs_worker.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-class RemoveOldWebHookLogsWorker
- include ApplicationWorker
- include CronjobQueue
-
- WEB_HOOK_LOG_LIFETIME = 2.days
-
- # rubocop: disable DestroyAll
- def perform
- WebHookLog.destroy_all(['created_at < ?', Time.now - WEB_HOOK_LOG_LIFETIME])
- end
- # rubocop: enable DestroyAll
-end
diff --git a/app/workers/repository_cleanup_worker.rb b/app/workers/repository_cleanup_worker.rb
new file mode 100644
index 00000000000..aa26c173a72
--- /dev/null
+++ b/app/workers/repository_cleanup_worker.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+class RepositoryCleanupWorker
+ include ApplicationWorker
+
+ sidekiq_options retry: 3
+
+ sidekiq_retries_exhausted do |msg, err|
+ next if err.is_a?(ActiveRecord::RecordNotFound)
+
+ args = msg['args'] + [msg['error_message']]
+
+ new.perform_failure(*args)
+ end
+
+ def perform(project_id, user_id)
+ project = Project.find(project_id)
+ user = User.find(user_id)
+
+ Projects::CleanupService.new(project, user).execute
+
+ notification_service.repository_cleanup_success(project, user)
+ end
+
+ def perform_failure(project_id, user_id, error)
+ project = Project.find(project_id)
+ user = User.find(user_id)
+
+ # Ensure the file is removed
+ project.bfg_object_map.remove!
+ notification_service.repository_cleanup_failure(project, user, error)
+ end
+
+ private
+
+ def notification_service
+ @notification_service ||= NotificationService.new
+ end
+end
diff --git a/app/workers/repository_update_remote_mirror_worker.rb b/app/workers/repository_update_remote_mirror_worker.rb
index 9d4e67deb9c..c0bae08ba85 100644
--- a/app/workers/repository_update_remote_mirror_worker.rb
+++ b/app/workers/repository_update_remote_mirror_worker.rb
@@ -5,7 +5,6 @@ class RepositoryUpdateRemoteMirrorWorker
UpdateError = Class.new(StandardError)
include ApplicationWorker
- include Gitlab::ShellAdapter
sidekiq_options retry: 3, dead: false
@@ -16,7 +15,7 @@ class RepositoryUpdateRemoteMirrorWorker
end
def perform(remote_mirror_id, scheduled_time)
- remote_mirror = RemoteMirror.find(remote_mirror_id)
+ remote_mirror = RemoteMirrorFinder.new(id: remote_mirror_id).execute
return if remote_mirror.updated_since?(scheduled_time)
raise UpdateAlreadyInProgressError if remote_mirror.update_in_progress?
diff --git a/app/workers/stuck_merge_jobs_worker.rb b/app/workers/stuck_merge_jobs_worker.rb
index 98c81956cba..f34ed6c4844 100644
--- a/app/workers/stuck_merge_jobs_worker.rb
+++ b/app/workers/stuck_merge_jobs_worker.rb
@@ -4,6 +4,10 @@ class StuckMergeJobsWorker
include ApplicationWorker
include CronjobQueue
+ def self.logger
+ Rails.logger
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def perform
stuck_merge_requests.find_in_batches(batch_size: 100) do |group|
@@ -35,7 +39,7 @@ class StuckMergeJobsWorker
# We rely on state machine callbacks to update head_pipeline_id
merge_requests_to_reopen.each(&:unlock_mr)
- Rails.logger.info("Updated state of locked merge jobs. JIDs: #{completed_jids.join(', ')}")
+ self.class.logger.info("Updated state of locked merge jobs. JIDs: #{completed_jids.join(', ')}")
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/bin/rails b/bin/rails
index d21b64b3007..07396602377 100755
--- a/bin/rails
+++ b/bin/rails
@@ -1,14 +1,4 @@
#!/usr/bin/env ruby
-
-# Remove this block when upgraded to rails 5.0.
-if %w[0 false].include?(ENV["RAILS5"])
- begin
- load File.expand_path('../spring', __FILE__)
- rescue LoadError => e
- raise unless e.message.include?('spring')
- end
-end
-
APP_PATH = File.expand_path('../config/application', __dir__)
require_relative '../config/boot'
require 'rails/commands'
diff --git a/bin/rake b/bin/rake
index 1362d51e3d6..17240489f64 100755
--- a/bin/rake
+++ b/bin/rake
@@ -1,14 +1,4 @@
#!/usr/bin/env ruby
-
-# Remove this block when upgraded to rails 5.0.
-if %w[0 false].include?(ENV["RAILS5"])
- begin
- load File.expand_path('../spring', __FILE__)
- rescue LoadError => e
- raise unless e.message.include?('spring')
- end
-end
-
require_relative '../config/boot'
require 'rake'
Rake.application.run
diff --git a/bin/rspec b/bin/rspec
index b0770e30a70..4236753c9c1 100755
--- a/bin/rspec
+++ b/bin/rspec
@@ -1,10 +1,5 @@
#!/usr/bin/env ruby
-# Remove these two lines below when upgraded to rails 5.0.
-# Allow run `rspec` command as `RAILS5=1 rspec ...` instead of `BUNDLE_GEMFILE=Gemfile.rails5 rspec ...`
-gemfile = %w[0 false].include?(ENV["RAILS5"]) ? "Gemfile.rails4" : "Gemfile"
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../#{gemfile}", __dir__)
-
begin
load File.expand_path('../spring', __FILE__)
rescue LoadError => e
diff --git a/bin/secpick b/bin/secpick
index 2b263d452c9..11acdd82226 100755
--- a/bin/secpick
+++ b/bin/secpick
@@ -1,4 +1,7 @@
#!/usr/bin/env ruby
+# frozen_string_literal: false
+
+require 'active_support/core_ext/object/to_query'
require 'optparse'
require 'open3'
require 'rainbow/refinement'
@@ -6,6 +9,7 @@ using Rainbow
BRANCH_PREFIX = 'security'.freeze
REMOTE = 'dev'.freeze
+NEW_MR_URL = 'https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/new'.freeze
options = { version: nil, branch: nil, sha: nil }
@@ -48,7 +52,24 @@ end.freeze
command = "git fetch #{REMOTE} #{stable_branch} && git checkout #{stable_branch} && git pull #{REMOTE} #{stable_branch} && git checkout -B #{branch} && git cherry-pick #{options[:sha]} && git push #{REMOTE} #{branch} && git checkout #{original_branch}"
-_stdin, stdout, stderr = Open3.popen3(command)
+stdin, stdout, stderr, wait_thr = Open3.popen3(command)
puts stdout.read&.green
puts stderr.read&.red
+
+if wait_thr.value.success?
+ params = {
+ merge_request: {
+ source_branch: branch,
+ target_branch: stable_branch,
+ title: "WIP: [#{options[:version].tr('-', '.')}] ",
+ description: '/label ~security'
+ }
+ }
+
+ puts "#{NEW_MR_URL}?#{params.to_query}".blue
+end
+
+stdin.close
+stdout.close
+stderr.close
diff --git a/bin/setup b/bin/setup
index 34bb667087a..883825bc0a2 100755
--- a/bin/setup
+++ b/bin/setup
@@ -1,18 +1,12 @@
#!/usr/bin/env ruby
-def rails5?
- !%w[0 false].include?(ENV["RAILS5"])
-end
-
require "pathname"
# path to your application root.
APP_ROOT = Pathname.new File.expand_path("../../", __FILE__)
-if rails5?
- def system!(*args)
- system(*args) || abort("\n== Command #{args} failed ==")
- end
+def system!(*args)
+ system(*args) || abort("\n== Command #{args} failed ==")
end
Dir.chdir APP_ROOT do
@@ -20,14 +14,8 @@ Dir.chdir APP_ROOT do
# Add necessary setup steps to this file:
puts "== Installing dependencies =="
-
- if rails5?
- system! "gem install bundler --conservative"
- system("bundle check") || system!("bundle install")
- else
- system "gem install bundler --conservative"
- system "bundle check || bundle install"
- end
+ system! "gem install bundler --conservative"
+ system("bundle check") || system!("bundle install")
# puts "\n== Copying sample files =="
# unless File.exist?("config/database.yml")
@@ -35,27 +23,11 @@ Dir.chdir APP_ROOT do
# end
puts "\n== Preparing database =="
-
- if rails5?
- system! "bin/rails db:setup"
- else
- system "bin/rake db:reset"
- end
+ system! "bin/rails db:setup"
puts "\n== Removing old logs and tempfiles =="
-
- if rails5?
- system! "bin/rails log:clear tmp:clear"
- else
- system "rm -f log/*"
- system "rm -rf tmp/cache"
- end
+ system! "bin/rails log:clear tmp:clear"
puts "\n== Restarting application server =="
-
- if rails5?
- system! "bin/rails restart"
- else
- system "touch tmp/restart.txt"
- end
+ system! "bin/rails restart"
end
diff --git a/changelogs/unreleased/1979-redesign-mr-widget-approvals-ce.yml b/changelogs/unreleased/1979-redesign-mr-widget-approvals-ce.yml
deleted file mode 100644
index d05b6054b22..00000000000
--- a/changelogs/unreleased/1979-redesign-mr-widget-approvals-ce.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Redesign of MR header sections (CE)
-merge_request: 23465
-author:
-type: changed
diff --git a/changelogs/unreleased/23367-clarify-docs-allow-failure.yml b/changelogs/unreleased/23367-clarify-docs-allow-failure.yml
new file mode 100644
index 00000000000..221d9e83ffb
--- /dev/null
+++ b/changelogs/unreleased/23367-clarify-docs-allow-failure.yml
@@ -0,0 +1,5 @@
+---
+title: Clarifies docs about CI `allow_failure`
+merge_request: 23367
+author: C.J. Jameson
+type: other
diff --git a/changelogs/unreleased/28682-can-merge-branch-before-build-is-started.yml b/changelogs/unreleased/28682-can-merge-branch-before-build-is-started.yml
deleted file mode 100644
index 5ffd93e098f..00000000000
--- a/changelogs/unreleased/28682-can-merge-branch-before-build-is-started.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Strictly require a pipeline to merge.
-merge_request: 22911
-author:
-type: changed
diff --git a/changelogs/unreleased/33705-merge-request-rebase-api.yml b/changelogs/unreleased/33705-merge-request-rebase-api.yml
deleted file mode 100644
index 322fe31ce87..00000000000
--- a/changelogs/unreleased/33705-merge-request-rebase-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add a rebase API endpoint for merge requests
-merge_request: 23296
-author:
-type: added
diff --git a/changelogs/unreleased/34758-deployment-cluster.yml b/changelogs/unreleased/34758-deployment-cluster.yml
deleted file mode 100644
index 06374098343..00000000000
--- a/changelogs/unreleased/34758-deployment-cluster.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use group clusters when deploying (DeploymentPlatform)
-merge_request: 22308
-author:
-type: changed
diff --git a/changelogs/unreleased/34758-group-cluster-controller.yml b/changelogs/unreleased/34758-group-cluster-controller.yml
deleted file mode 100644
index 88c4c872714..00000000000
--- a/changelogs/unreleased/34758-group-cluster-controller.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add ability to create group level clusters and install gitlab managed applications
-merge_request: 22450
-author:
-type: added
diff --git a/changelogs/unreleased/34758-list-ancestor-clusters.yml b/changelogs/unreleased/34758-list-ancestor-clusters.yml
new file mode 100644
index 00000000000..8fdba7ba90a
--- /dev/null
+++ b/changelogs/unreleased/34758-list-ancestor-clusters.yml
@@ -0,0 +1,5 @@
+---
+title: Show clusters of ancestors in cluster list page
+merge_request: 22996
+author:
+type: changed
diff --git a/changelogs/unreleased/38495-calendar-activities-in-timezone.yml b/changelogs/unreleased/38495-calendar-activities-in-timezone.yml
deleted file mode 100644
index 778d637609c..00000000000
--- a/changelogs/unreleased/38495-calendar-activities-in-timezone.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Show user contributions in correct timezone within user profile
-merge_request: 23419
-author:
-type: changed
diff --git a/changelogs/unreleased/40085-add-a-create_merge_request-quick-action.yml b/changelogs/unreleased/40085-add-a-create_merge_request-quick-action.yml
deleted file mode 100644
index e1614ac7669..00000000000
--- a/changelogs/unreleased/40085-add-a-create_merge_request-quick-action.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Creates /create_merge_request quickaction
-merge_request: 22485
-author: Jacopo Beschi @jacopo-beschi
-type: added
diff --git a/changelogs/unreleased/40260-reduce-gitaly-calls-project-pipeline-status.yml b/changelogs/unreleased/40260-reduce-gitaly-calls-project-pipeline-status.yml
deleted file mode 100644
index 8ab104e95f5..00000000000
--- a/changelogs/unreleased/40260-reduce-gitaly-calls-project-pipeline-status.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Reduce Gitaly calls in projects dashboard
-merge_request: 23307
-author:
-type: performance
diff --git a/changelogs/unreleased/40270-remove-gitlab-upgrader-completely.yml b/changelogs/unreleased/40270-remove-gitlab-upgrader-completely.yml
new file mode 100644
index 00000000000..9ea2157bfb7
--- /dev/null
+++ b/changelogs/unreleased/40270-remove-gitlab-upgrader-completely.yml
@@ -0,0 +1,5 @@
+---
+title: Removes all instances of deprecated Gitlab Upgrader calls
+merge_request: 23603
+author: '@jwolen'
+type: removed
diff --git a/changelogs/unreleased/40385-prohibit_impersonation.yml b/changelogs/unreleased/40385-prohibit_impersonation.yml
deleted file mode 100644
index dd061b17939..00000000000
--- a/changelogs/unreleased/40385-prohibit_impersonation.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add config to prohibit impersonation
-merge_request: 23338
-author:
-type: added
diff --git a/changelogs/unreleased/41766-vue-component.yml b/changelogs/unreleased/41766-vue-component.yml
new file mode 100644
index 00000000000..12343c8ce84
--- /dev/null
+++ b/changelogs/unreleased/41766-vue-component.yml
@@ -0,0 +1,5 @@
+---
+title: Creates component for release block
+merge_request: 23697
+author:
+type: added
diff --git a/changelogs/unreleased/41766-vuex-store.yml b/changelogs/unreleased/41766-vuex-store.yml
new file mode 100644
index 00000000000..f20fc736a6f
--- /dev/null
+++ b/changelogs/unreleased/41766-vuex-store.yml
@@ -0,0 +1,5 @@
+---
+title: Creates frontend app for releases
+merge_request: 23796
+author:
+type: added
diff --git a/changelogs/unreleased/41875-allow-pipelines-to-be-deleted-by-project-owners.yml b/changelogs/unreleased/41875-allow-pipelines-to-be-deleted-by-project-owners.yml
deleted file mode 100644
index 0662ff6f523..00000000000
--- a/changelogs/unreleased/41875-allow-pipelines-to-be-deleted-by-project-owners.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow deleting a Pipeline via the API.
-merge_request: 22988
-author:
-type: added
diff --git a/changelogs/unreleased/42125-extend-override-check-to-also-check-arity.yml b/changelogs/unreleased/42125-extend-override-check-to-also-check-arity.yml
new file mode 100644
index 00000000000..9892466ca50
--- /dev/null
+++ b/changelogs/unreleased/42125-extend-override-check-to-also-check-arity.yml
@@ -0,0 +1,5 @@
+---
+title: Extend override check to also check arity
+merge_request: 23498
+author: Jacopo Beschi @jacopo-beschi
+type: added
diff --git a/changelogs/unreleased/44984-use-serializer-for-issuable-sidebar.yml b/changelogs/unreleased/44984-use-serializer-for-issuable-sidebar.yml
new file mode 100644
index 00000000000..ba9edc8740d
--- /dev/null
+++ b/changelogs/unreleased/44984-use-serializer-for-issuable-sidebar.yml
@@ -0,0 +1,5 @@
+---
+title: Refactor issuable sidebar to use serializer
+merge_request: 23379
+author:
+type: other
diff --git a/changelogs/unreleased/46544-webide-ctrl-enter-commit-shortcut.yml b/changelogs/unreleased/46544-webide-ctrl-enter-commit-shortcut.yml
deleted file mode 100644
index 334c9b3ec9e..00000000000
--- a/changelogs/unreleased/46544-webide-ctrl-enter-commit-shortcut.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "WebIDE: Pressing Ctrl-Enter while typing on the commit message now performs the commit action"
-merge_request: 23049
-author: Thomas Pathier
-type: added
diff --git a/changelogs/unreleased/46950-systemcheck-ruby-version.yml b/changelogs/unreleased/46950-systemcheck-ruby-version.yml
deleted file mode 100644
index e556e14223b..00000000000
--- a/changelogs/unreleased/46950-systemcheck-ruby-version.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'SystemCheck: Use a more reliable way to detect current Ruby version'
-merge_request: 23291
-author:
-type: changed
diff --git a/changelogs/unreleased/47052-merge-button-does-not-appear-after-rebase-ing.yml b/changelogs/unreleased/47052-merge-button-does-not-appear-after-rebase-ing.yml
new file mode 100644
index 00000000000..fd1e4605f2d
--- /dev/null
+++ b/changelogs/unreleased/47052-merge-button-does-not-appear-after-rebase-ing.yml
@@ -0,0 +1,5 @@
+---
+title: Allow merge after rebase without page refresh on FF repositories
+merge_request: 23572
+author:
+type: fixed
diff --git a/changelogs/unreleased/48475-gitlab-pages-settings-regressions.yml b/changelogs/unreleased/48475-gitlab-pages-settings-regressions.yml
deleted file mode 100644
index f543730a57d..00000000000
--- a/changelogs/unreleased/48475-gitlab-pages-settings-regressions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixing regression issues on pages settings and details
-merge_request: 22821
-author:
-type: fixed
diff --git a/changelogs/unreleased/48496-merge-request-refactor-does-not-highlight-selected-line.yml b/changelogs/unreleased/48496-merge-request-refactor-does-not-highlight-selected-line.yml
deleted file mode 100644
index cfc74bef638..00000000000
--- a/changelogs/unreleased/48496-merge-request-refactor-does-not-highlight-selected-line.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: When user clicks linenumber in MR changes, highlight that line
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/49479-hide-unmerged-env-perf-stats.yml b/changelogs/unreleased/49479-hide-unmerged-env-perf-stats.yml
deleted file mode 100644
index 5118949f8a3..00000000000
--- a/changelogs/unreleased/49479-hide-unmerged-env-perf-stats.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Don't show Memory Usage for unmerged MRs
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/49565-ssh-push-mirroring.yml b/changelogs/unreleased/49565-ssh-push-mirroring.yml
deleted file mode 100644
index 2dfeffa4088..00000000000
--- a/changelogs/unreleased/49565-ssh-push-mirroring.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow SSH public-key authentication for push mirroring
-merge_request: 22982
-author:
-type: added
diff --git a/changelogs/unreleased/49726-upgrade-helm-to-2-11.yml b/changelogs/unreleased/49726-upgrade-helm-to-2-11.yml
deleted file mode 100644
index dd26af875f5..00000000000
--- a/changelogs/unreleased/49726-upgrade-helm-to-2-11.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade helm to 2.11.0 and upgrade on every install
-merge_request: 22693
-author:
-type: added
diff --git a/changelogs/unreleased/50264-add-border-around-the-repository-file-tree.yml b/changelogs/unreleased/50264-add-border-around-the-repository-file-tree.yml
deleted file mode 100644
index 6315c3e7f36..00000000000
--- a/changelogs/unreleased/50264-add-border-around-the-repository-file-tree.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve Add border around the repository file tree
-merge_request: 23018
-author:
-type: changed
diff --git a/changelogs/unreleased/50341-cleanup-useless-project-import-attributes.yml b/changelogs/unreleased/50341-cleanup-useless-project-import-attributes.yml
deleted file mode 100644
index 3893f14e15c..00000000000
--- a/changelogs/unreleased/50341-cleanup-useless-project-import-attributes.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Removes all the irrelevant code and columns that were migrated from the Project
- table over to the ProjectImportState table
-merge_request: 21497
-author:
-type: performance
diff --git a/changelogs/unreleased/50626-searching-users-by-the-admin-panel-wipes-query-when-using-sort.yml b/changelogs/unreleased/50626-searching-users-by-the-admin-panel-wipes-query-when-using-sort.yml
deleted file mode 100644
index c3251fea54d..00000000000
--- a/changelogs/unreleased/50626-searching-users-by-the-admin-panel-wipes-query-when-using-sort.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow search and sort users at same time on admin users page
-merge_request: 23439
-author:
-type: fixed
diff --git a/changelogs/unreleased/50839-webide-mr-dropdown-filter.yml b/changelogs/unreleased/50839-webide-mr-dropdown-filter.yml
deleted file mode 100644
index 1c6c8747197..00000000000
--- a/changelogs/unreleased/50839-webide-mr-dropdown-filter.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Scope default MR search in WebIDE dropdown to current project
-merge_request: 23400
-author:
-type: changed
diff --git a/changelogs/unreleased/51029-status-emoji-currently-replaces-avatar-on-mobile.yml b/changelogs/unreleased/51029-status-emoji-currently-replaces-avatar-on-mobile.yml
deleted file mode 100644
index dc11ede5c8d..00000000000
--- a/changelogs/unreleased/51029-status-emoji-currently-replaces-avatar-on-mobile.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve status emoji being replaced by avatar on mobile
-merge_request: 23408
-author:
-type: other
diff --git a/changelogs/unreleased/51061-readme-url-n-1-rpc-call-resolved.yml b/changelogs/unreleased/51061-readme-url-n-1-rpc-call-resolved.yml
deleted file mode 100644
index 86f91fcb427..00000000000
--- a/changelogs/unreleased/51061-readme-url-n-1-rpc-call-resolved.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improves performance of Project#readme_url by caching the README path
-merge_request: 23357
-author:
-type: performance
diff --git a/changelogs/unreleased/51083-fix-move-api.yml b/changelogs/unreleased/51083-fix-move-api.yml
deleted file mode 100644
index 8838f6f267e..00000000000
--- a/changelogs/unreleased/51083-fix-move-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Commits API: Preserve file content in move operations if unspecified'
-merge_request: 23387
-author:
-type: fixed
diff --git a/changelogs/unreleased/51259-ci-cd-gitlab-ui-1.yml b/changelogs/unreleased/51259-ci-cd-gitlab-ui-1.yml
deleted file mode 100644
index 1d761d6299c..00000000000
--- a/changelogs/unreleased/51259-ci-cd-gitlab-ui-1.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Uses new gitlab-ui components in Jobs and Pipelines components
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/51259-ci-cd-tooltips.yml b/changelogs/unreleased/51259-ci-cd-tooltips.yml
deleted file mode 100644
index fc0010dbeba..00000000000
--- a/changelogs/unreleased/51259-ci-cd-tooltips.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Replaces tooltip directive with the new gl-tooltip directive for consistency
- in some ci/cd code
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/51485-new-issue-labels-note.yml b/changelogs/unreleased/51485-new-issue-labels-note.yml
new file mode 100644
index 00000000000..a312d379ce2
--- /dev/null
+++ b/changelogs/unreleased/51485-new-issue-labels-note.yml
@@ -0,0 +1,5 @@
+---
+title: Create system notes on issue / MR creation when labels, milestone, or due date is set
+merge_request: 23859
+author:
+type: added
diff --git a/changelogs/unreleased/51606-expanding-a-diff-while-having-an-open-comment-form-will-always-scroll-down-to-the-comment.yml b/changelogs/unreleased/51606-expanding-a-diff-while-having-an-open-comment-form-will-always-scroll-down-to-the-comment.yml
new file mode 100644
index 00000000000..a845234b42f
--- /dev/null
+++ b/changelogs/unreleased/51606-expanding-a-diff-while-having-an-open-comment-form-will-always-scroll-down-to-the-comment.yml
@@ -0,0 +1,5 @@
+---
+title: Stop autofocusing on diff comment after initial mount
+merge_request: 23849
+author:
+type: fixed
diff --git a/changelogs/unreleased/51668-fix-line-numbers.yml b/changelogs/unreleased/51668-fix-line-numbers.yml
new file mode 100644
index 00000000000..ac6e45e3cc7
--- /dev/null
+++ b/changelogs/unreleased/51668-fix-line-numbers.yml
@@ -0,0 +1,5 @@
+---
+title: Adjust line-height of blame view line numbers
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/51792-dont-delete-failed-install-pods.yml b/changelogs/unreleased/51792-dont-delete-failed-install-pods.yml
deleted file mode 100644
index 7a900cbb86e..00000000000
--- a/changelogs/unreleased/51792-dont-delete-failed-install-pods.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Don't remove failed install pods after installing GitLab managed applications
-merge_request: 23350
-author:
-type: changed
diff --git a/changelogs/unreleased/51944-redesign-project-lists-ui.yml b/changelogs/unreleased/51944-redesign-project-lists-ui.yml
new file mode 100644
index 00000000000..56f9a86a686
--- /dev/null
+++ b/changelogs/unreleased/51944-redesign-project-lists-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Redesign project lists UI
+merge_request: 22682
+author:
+type: other
diff --git a/changelogs/unreleased/51959-branch-and-tag-name-links.yml b/changelogs/unreleased/51959-branch-and-tag-name-links.yml
deleted file mode 100644
index 64f1522c70d..00000000000
--- a/changelogs/unreleased/51959-branch-and-tag-name-links.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Chat message push notifications now include links back to GitLab branches
-merge_request: 22651
-author: Tony Castrogiovanni
-type: added
diff --git a/changelogs/unreleased/51994-disable-merging-labels-in-dropdowns.yml b/changelogs/unreleased/51994-disable-merging-labels-in-dropdowns.yml
new file mode 100644
index 00000000000..2d54cf814b7
--- /dev/null
+++ b/changelogs/unreleased/51994-disable-merging-labels-in-dropdowns.yml
@@ -0,0 +1,5 @@
+---
+title: Disable merging of labels with same names
+merge_request: 23265
+author:
+type: changed
diff --git a/changelogs/unreleased/52276-jump-to-top-in-merge-request.yml b/changelogs/unreleased/52276-jump-to-top-in-merge-request.yml
deleted file mode 100644
index 3dc95441eec..00000000000
--- a/changelogs/unreleased/52276-jump-to-top-in-merge-request.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow user to scroll to top of tab on MR page
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/52370-filter-by-none-any-for-labels-in-issues-mrs-boards.yml b/changelogs/unreleased/52370-filter-by-none-any-for-labels-in-issues-mrs-boards.yml
deleted file mode 100644
index 9e1ee3ede5e..00000000000
--- a/changelogs/unreleased/52370-filter-by-none-any-for-labels-in-issues-mrs-boards.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds Any option to label filters
-merge_request: 23111
-author: Jacopo Beschi @jacopo-beschi
-type: added
diff --git a/changelogs/unreleased/52371-filter-by-none-any-for-labels-in-issues-mrs-api.yml b/changelogs/unreleased/52371-filter-by-none-any-for-labels-in-issues-mrs-api.yml
deleted file mode 100644
index bb196af3e90..00000000000
--- a/changelogs/unreleased/52371-filter-by-none-any-for-labels-in-issues-mrs-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Filter by None/Any for labels in issues/mrs API
-merge_request: 22622
-author: Jacopo Beschi @jacopo-beschi
-type: added
diff --git a/changelogs/unreleased/52371-removes-patially-matching-no-label-and-makes-it-case-insensitive.yml b/changelogs/unreleased/52371-removes-patially-matching-no-label-and-makes-it-case-insensitive.yml
deleted file mode 100644
index c1fc21c641a..00000000000
--- a/changelogs/unreleased/52371-removes-patially-matching-no-label-and-makes-it-case-insensitive.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: removes partially matching of No Label filter and makes it case-insensitive
-merge_request: 22622
-author: Jacopo Beschi @jacopo-beschi
-type: changed
diff --git a/changelogs/unreleased/52385-search-bar-for-dashboard-list.yml b/changelogs/unreleased/52385-search-bar-for-dashboard-list.yml
deleted file mode 100644
index a437ae560cb..00000000000
--- a/changelogs/unreleased/52385-search-bar-for-dashboard-list.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use search bar for filtering in dashboard issues / MRs
-merge_request: 22641
-author: Heinrich Lee Yu
-type: changed
diff --git a/changelogs/unreleased/52453-show-subgroups-in-group-create-issue.yml b/changelogs/unreleased/52453-show-subgroups-in-group-create-issue.yml
deleted file mode 100644
index d5877e96d07..00000000000
--- a/changelogs/unreleased/52453-show-subgroups-in-group-create-issue.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix project selector consistency in groups issues / MRs / boards pages
-merge_request: 22612
-author: Heinrich Lee Yu
-type: fixed
diff --git a/changelogs/unreleased/52620-fix-loader-animation-alignment.yml b/changelogs/unreleased/52620-fix-loader-animation-alignment.yml
new file mode 100644
index 00000000000..5cfb7fc019f
--- /dev/null
+++ b/changelogs/unreleased/52620-fix-loader-animation-alignment.yml
@@ -0,0 +1,5 @@
+---
+title: Aligns build loader animation with the job log
+merge_request: 23959
+author:
+type: fixed
diff --git a/changelogs/unreleased/52712-further-ui-improvements-to-profile-overview-tab.yml b/changelogs/unreleased/52712-further-ui-improvements-to-profile-overview-tab.yml
deleted file mode 100644
index 65aa9323d2e..00000000000
--- a/changelogs/unreleased/52712-further-ui-improvements-to-profile-overview-tab.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: UI improvements to user's profile
-merge_request: 22977
-author:
-type: other
diff --git a/changelogs/unreleased/52828-inconsistency-in-fonts-used-for-branch-name-and-create-from-fields-when-creating-new-branch-from-ui.yml b/changelogs/unreleased/52828-inconsistency-in-fonts-used-for-branch-name-and-create-from-fields-when-creating-new-branch-from-ui.yml
deleted file mode 100644
index 8132dde8636..00000000000
--- a/changelogs/unreleased/52828-inconsistency-in-fonts-used-for-branch-name-and-create-from-fields-when-creating-new-branch-from-ui.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make new branch form fields' fonts consistent
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/52940-fix-internal-email-pattern-not-respected.yml b/changelogs/unreleased/52940-fix-internal-email-pattern-not-respected.yml
deleted file mode 100644
index 98e15a5cc0a..00000000000
--- a/changelogs/unreleased/52940-fix-internal-email-pattern-not-respected.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix a bug where internal email pattern wasn't respected
-merge_request: 22516
-author:
-type: fixed
diff --git a/changelogs/unreleased/53020-user-specific-profile-page-settings-fields-don-t-have-help-text-placeholders.yml b/changelogs/unreleased/53020-user-specific-profile-page-settings-fields-don-t-have-help-text-placeholders.yml
new file mode 100644
index 00000000000..99da02dd31a
--- /dev/null
+++ b/changelogs/unreleased/53020-user-specific-profile-page-settings-fields-don-t-have-help-text-placeholders.yml
@@ -0,0 +1,5 @@
+---
+title: Adds explanatory text to input fields on user profile settings page
+merge_request: 23673
+author:
+type: other
diff --git a/changelogs/unreleased/53289-update-haml_lint-to-0-28-0.yml b/changelogs/unreleased/53289-update-haml_lint-to-0-28-0.yml
deleted file mode 100644
index 9a16666c416..00000000000
--- a/changelogs/unreleased/53289-update-haml_lint-to-0-28-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update haml_lint to 0.28.0
-merge_request: 22660
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/53290-incorrect-project-list-order-select-default-label.yml b/changelogs/unreleased/53290-incorrect-project-list-order-select-default-label.yml
deleted file mode 100644
index d076352a27b..00000000000
--- a/changelogs/unreleased/53290-incorrect-project-list-order-select-default-label.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix default sorting for subgroups and projects list
-merge_request: 23058
-author: Jacopo Beschi @jacopo-beschi
-type: fixed
diff --git a/changelogs/unreleased/53291-update-ffaker-to-2-10-0.yml b/changelogs/unreleased/53291-update-ffaker-to-2-10-0.yml
deleted file mode 100644
index a1b95df5e32..00000000000
--- a/changelogs/unreleased/53291-update-ffaker-to-2-10-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update ffaker to 2.10.0
-merge_request: 22661
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/53326-improve-issues-empty-state.yml b/changelogs/unreleased/53326-improve-issues-empty-state.yml
deleted file mode 100644
index 7632db808b5..00000000000
--- a/changelogs/unreleased/53326-improve-issues-empty-state.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Show different empty state for filtered issues and MRs
-merge_request: 22775
-author: Heinrich Lee Yu
-type: changed
diff --git a/changelogs/unreleased/53400-unstar-icon-button-is-misaligned.yml b/changelogs/unreleased/53400-unstar-icon-button-is-misaligned.yml
deleted file mode 100644
index b393795f491..00000000000
--- a/changelogs/unreleased/53400-unstar-icon-button-is-misaligned.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Fix: Unstar icon button is misaligned'
-merge_request: 23444
-author:
-type: fixed
diff --git a/changelogs/unreleased/53493-list-id-email-header.yml b/changelogs/unreleased/53493-list-id-email-header.yml
new file mode 100644
index 00000000000..09a0639f6f5
--- /dev/null
+++ b/changelogs/unreleased/53493-list-id-email-header.yml
@@ -0,0 +1,5 @@
+---
+title: Add project identifier as List-Id email Header to ease filtering
+merge_request: 22817
+author: Olivier Crête
+type: added
diff --git a/changelogs/unreleased/53578-fe-deployment-status.yml b/changelogs/unreleased/53578-fe-deployment-status.yml
deleted file mode 100644
index b88bd70ee2e..00000000000
--- a/changelogs/unreleased/53578-fe-deployment-status.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds states to the deployment widget
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/53626-update-config-map-on-install-retry.yml b/changelogs/unreleased/53626-update-config-map-on-install-retry.yml
deleted file mode 100644
index 38e79c06c89..00000000000
--- a/changelogs/unreleased/53626-update-config-map-on-install-retry.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update config map for gitlab managed application if already present on install
-merge_request: 22969
-author:
-type: other
diff --git a/changelogs/unreleased/53640-follow-up-from-resolve-redesign-activity-feed.yml b/changelogs/unreleased/53640-follow-up-from-resolve-redesign-activity-feed.yml
deleted file mode 100644
index 66301329c52..00000000000
--- a/changelogs/unreleased/53640-follow-up-from-resolve-redesign-activity-feed.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-title: Adds new icon size to Vue icon component
-merge_request: 22899
-author:
-type: other
diff --git a/changelogs/unreleased/53659-use-padded-key-for-gcm-ciphers.yml b/changelogs/unreleased/53659-use-padded-key-for-gcm-ciphers.yml
deleted file mode 100644
index fe9ac7b3dc7..00000000000
--- a/changelogs/unreleased/53659-use-padded-key-for-gcm-ciphers.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix web hook functionality when the database encryption key is too short
-merge_request: 23573
-author:
-type: fixed
diff --git a/changelogs/unreleased/53700-hashed-storagemigration.yml b/changelogs/unreleased/53700-hashed-storagemigration.yml
deleted file mode 100644
index 899012ffd22..00000000000
--- a/changelogs/unreleased/53700-hashed-storagemigration.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Hashed Storage: allow migration to be retried in partially migrated projects'
-merge_request: 23087
-author:
-type: fixed
diff --git a/changelogs/unreleased/53728-warn-in-web-editor-when-user-navigates-away.yml b/changelogs/unreleased/53728-warn-in-web-editor-when-user-navigates-away.yml
deleted file mode 100644
index 8377fdc6133..00000000000
--- a/changelogs/unreleased/53728-warn-in-web-editor-when-user-navigates-away.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent user from navigating away from file edit without commit
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/53816-empty-label-menu-if-not-logged-in.yml b/changelogs/unreleased/53816-empty-label-menu-if-not-logged-in.yml
deleted file mode 100644
index a9ca56303eb..00000000000
--- a/changelogs/unreleased/53816-empty-label-menu-if-not-logged-in.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Removes promote to group label for anonymous user
-merge_request: 23042
-author: Jacopo Beschi @jacopo-beschi
-type: fixed
diff --git a/changelogs/unreleased/53874-navbar-lowres.yml b/changelogs/unreleased/53874-navbar-lowres.yml
deleted file mode 100644
index 3b31b8f93fe..00000000000
--- a/changelogs/unreleased/53874-navbar-lowres.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Fix overlapping navbar separator and overflowing navbar dropdown on small displays"
-merge_request: 23126
-author: Thomas Pathier
-type: fix
diff --git a/changelogs/unreleased/53907-improve-milestone-links.yml b/changelogs/unreleased/53907-improve-milestone-links.yml
new file mode 100644
index 00000000000..8e867e783cc
--- /dev/null
+++ b/changelogs/unreleased/53907-improve-milestone-links.yml
@@ -0,0 +1,5 @@
+---
+title: Add % prefix to milestone reference links
+merge_request: 23928
+author:
+type: changed
diff --git a/changelogs/unreleased/53933-include-dates-in-milestone-change-email.yml b/changelogs/unreleased/53933-include-dates-in-milestone-change-email.yml
new file mode 100644
index 00000000000..5c40a1e900c
--- /dev/null
+++ b/changelogs/unreleased/53933-include-dates-in-milestone-change-email.yml
@@ -0,0 +1,5 @@
+---
+title: Add date range in milestone change email notifications
+merge_request: 23762
+author:
+type: changed
diff --git a/changelogs/unreleased/53954-resolved-non-diff-discussions-on-merge-requests-no-longer-show-who-resolved-them-and-when-at-a-glance.yml b/changelogs/unreleased/53954-resolved-non-diff-discussions-on-merge-requests-no-longer-show-who-resolved-them-and-when-at-a-glance.yml
new file mode 100644
index 00000000000..0632c1992c7
--- /dev/null
+++ b/changelogs/unreleased/53954-resolved-non-diff-discussions-on-merge-requests-no-longer-show-who-resolved-them-and-when-at-a-glance.yml
@@ -0,0 +1,5 @@
+---
+title: Show message on non-diff discussions
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/53988-remove-notes-index-on-updated-at.yml b/changelogs/unreleased/53988-remove-notes-index-on-updated-at.yml
deleted file mode 100644
index f0bbf69736d..00000000000
--- a/changelogs/unreleased/53988-remove-notes-index-on-updated-at.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove index for notes on updated_at
-merge_request: 23356
-author:
-type: performance
diff --git a/changelogs/unreleased/53992-add-events-index-on-project-id-and-created-at.yml b/changelogs/unreleased/53992-add-events-index-on-project-id-and-created-at.yml
deleted file mode 100644
index a2a3fa00f01..00000000000
--- a/changelogs/unreleased/53992-add-events-index-on-project-id-and-created-at.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add index for events on project_id and created_at
-merge_request: 23354
-author:
-type: performance
diff --git a/changelogs/unreleased/54004-update-asana-to-0-8-1.yml b/changelogs/unreleased/54004-update-asana-to-0-8-1.yml
deleted file mode 100644
index a47b4f3c4d9..00000000000
--- a/changelogs/unreleased/54004-update-asana-to-0-8-1.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update asana to 0.8.1
-merge_request: 23039
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/54010-update-asciidoctor-to-1-5-8.yml b/changelogs/unreleased/54010-update-asciidoctor-to-1-5-8.yml
deleted file mode 100644
index f0b0aa0ee1c..00000000000
--- a/changelogs/unreleased/54010-update-asciidoctor-to-1-5-8.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update asciidoctor to 1.5.8
-merge_request: 23047
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/54015-Markdown-Editor-improve-Cursor-placement.yml b/changelogs/unreleased/54015-Markdown-Editor-improve-Cursor-placement.yml
deleted file mode 100644
index 28e3fae01a9..00000000000
--- a/changelogs/unreleased/54015-Markdown-Editor-improve-Cursor-placement.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Refine cursor positioning in Markdown Editor for wrap tags
-merge_request: 23085
-author: Johann Hubert Sonntagbauer
-type: changed
diff --git a/changelogs/unreleased/54021-empty-button.yml b/changelogs/unreleased/54021-empty-button.yml
deleted file mode 100644
index 3b03665cf95..00000000000
--- a/changelogs/unreleased/54021-empty-button.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent empty button being rendered in empty state
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/54032-reply-shortcut-only-discussion-text.yml b/changelogs/unreleased/54032-reply-shortcut-only-discussion-text.yml
deleted file mode 100644
index 5c1f6e74b39..00000000000
--- a/changelogs/unreleased/54032-reply-shortcut-only-discussion-text.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make reply shortcut only quote selected discussion text
-merge_request: 23096
-author: Thomas Pathier
-type: fix
diff --git a/changelogs/unreleased/54048-Line-numbers-are-misaligned-in-file-blame-view.yml b/changelogs/unreleased/54048-Line-numbers-are-misaligned-in-file-blame-view.yml
deleted file mode 100644
index 8ceac4ec869..00000000000
--- a/changelogs/unreleased/54048-Line-numbers-are-misaligned-in-file-blame-view.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix line height of numbers in file blame view
-merge_request: 23090
-author: Johann Hubert Sonntagbauer
-type: fixed
diff --git a/changelogs/unreleased/54093-the-default_value_for-gem-doesn-t-handle-actioncontroller-parameters-correctly.yml b/changelogs/unreleased/54093-the-default_value_for-gem-doesn-t-handle-actioncontroller-parameters-correctly.yml
deleted file mode 100644
index 3d6fd2d065a..00000000000
--- a/changelogs/unreleased/54093-the-default_value_for-gem-doesn-t-handle-actioncontroller-parameters-correctly.yml
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: Fixes an issue where default values from models would override values set in
- the interface (e.g. users would be set to external even though their emails matches
- the internal email address pattern)
-merge_request: 23114
-author:
-type: fixed
diff --git a/changelogs/unreleased/54146-fix-calendar-query.yml b/changelogs/unreleased/54146-fix-calendar-query.yml
new file mode 100644
index 00000000000..dcac343108a
--- /dev/null
+++ b/changelogs/unreleased/54146-fix-calendar-query.yml
@@ -0,0 +1,5 @@
+---
+title: Fix project calendar feed when sorted by priority
+merge_request: 23870
+author:
+type: fixed
diff --git a/changelogs/unreleased/54201-update-rack-to-2-0-6.yml b/changelogs/unreleased/54201-update-rack-to-2-0-6.yml
deleted file mode 100644
index 020b2bc0957..00000000000
--- a/changelogs/unreleased/54201-update-rack-to-2-0-6.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update rack to 2.0.6 (for QA environments)
-merge_request: 23171
-author: Takuya Noguchi
-type: security
diff --git a/changelogs/unreleased/54218-fix-mergeUrlParams.yml b/changelogs/unreleased/54218-fix-mergeUrlParams.yml
deleted file mode 100644
index dae06b66e8e..00000000000
--- a/changelogs/unreleased/54218-fix-mergeUrlParams.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Fix mergeUrlParams with fragment URL"
-merge_request: 54218
-author: Thomas Holder
-type: fixed
diff --git a/changelogs/unreleased/54391-tag.yml b/changelogs/unreleased/54391-tag.yml
deleted file mode 100644
index be571c6b0c3..00000000000
--- a/changelogs/unreleased/54391-tag.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Correctly styles tags in sidebar for job page
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/54407-fix-limited-intersection-observers.yml b/changelogs/unreleased/54407-fix-limited-intersection-observers.yml
deleted file mode 100644
index 2c2bedb170b..00000000000
--- a/changelogs/unreleased/54407-fix-limited-intersection-observers.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix Image Lazy Loader for some older browsers
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/54571-runner-tags.yml b/changelogs/unreleased/54571-runner-tags.yml
deleted file mode 100644
index 1bb19d22e9c..00000000000
--- a/changelogs/unreleased/54571-runner-tags.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds margins between tags when a job is stuck
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/54648-fix-order-by-dropdown-tablet-screens.yml b/changelogs/unreleased/54648-fix-order-by-dropdown-tablet-screens.yml
deleted file mode 100644
index 671d1590991..00000000000
--- a/changelogs/unreleased/54648-fix-order-by-dropdown-tablet-screens.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix Order By dropdown menu styling in tablet and mobile screens
-merge_request: 23446
-author:
-type: fixed
diff --git a/changelogs/unreleased/54736-sign-in-bottom-margin.yml b/changelogs/unreleased/54736-sign-in-bottom-margin.yml
new file mode 100644
index 00000000000..32b5b44fe35
--- /dev/null
+++ b/changelogs/unreleased/54736-sign-in-bottom-margin.yml
@@ -0,0 +1,5 @@
+---
+title: Fix login box bottom margins on signin page
+merge_request: 23739
+author: '@gear54'
+type: fixed
diff --git a/changelogs/unreleased/54786-mr-empty-file-display.yml b/changelogs/unreleased/54786-mr-empty-file-display.yml
new file mode 100644
index 00000000000..5adf5744755
--- /dev/null
+++ b/changelogs/unreleased/54786-mr-empty-file-display.yml
@@ -0,0 +1,5 @@
+---
+title: Display empty files properly on MR diffs
+merge_request: 23671
+author: Sean Nichols
+type: fixed
diff --git a/changelogs/unreleased/54814-sidebar-styling-updates.yml b/changelogs/unreleased/54814-sidebar-styling-updates.yml
new file mode 100644
index 00000000000..98e3836ee14
--- /dev/null
+++ b/changelogs/unreleased/54814-sidebar-styling-updates.yml
@@ -0,0 +1,5 @@
+---
+title: Fix label and header styles in the job details sidebar.
+merge_request: 23816
+author: Nathan Friend
+type: changed
diff --git a/changelogs/unreleased/54826-use-read_repository-scope-on-read-only-files-endpoints.yml b/changelogs/unreleased/54826-use-read_repository-scope-on-read-only-files-endpoints.yml
deleted file mode 100644
index ef8e93fca43..00000000000
--- a/changelogs/unreleased/54826-use-read_repository-scope-on-read-only-files-endpoints.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use read_repository scope on read-only files API
-merge_request: 23534
-author:
-type: fixed
diff --git a/changelogs/unreleased/54953-error-500-viewing-merge-request-due-to-nil-commit_email_hostname.yml b/changelogs/unreleased/54953-error-500-viewing-merge-request-due-to-nil-commit_email_hostname.yml
new file mode 100644
index 00000000000..8fd127acf2b
--- /dev/null
+++ b/changelogs/unreleased/54953-error-500-viewing-merge-request-due-to-nil-commit_email_hostname.yml
@@ -0,0 +1,5 @@
+---
+title: Return an ApplicationSetting in CurrentSettings
+merge_request: 23766
+author:
+type: fixed
diff --git a/changelogs/unreleased/54953-fix-commit_email_hostname-accessor-in-fake_application_settings.yml b/changelogs/unreleased/54953-fix-commit_email_hostname-accessor-in-fake_application_settings.yml
new file mode 100644
index 00000000000..623b3a7319c
--- /dev/null
+++ b/changelogs/unreleased/54953-fix-commit_email_hostname-accessor-in-fake_application_settings.yml
@@ -0,0 +1,5 @@
+---
+title: Fix a 500 error that could occur until all migrations are done
+merge_request: 23939
+author:
+type: fixed
diff --git a/changelogs/unreleased/55103-hide-group-cluster-features.yml b/changelogs/unreleased/55103-hide-group-cluster-features.yml
new file mode 100644
index 00000000000..fbe780d6f01
--- /dev/null
+++ b/changelogs/unreleased/55103-hide-group-cluster-features.yml
@@ -0,0 +1,5 @@
+---
+title: Hide cluster features that don't work yet with Group Clusters
+merge_request: 23935
+author:
+type: fixed
diff --git a/changelogs/unreleased/55191-update-workhorse.yml b/changelogs/unreleased/55191-update-workhorse.yml
new file mode 100644
index 00000000000..d16518e673a
--- /dev/null
+++ b/changelogs/unreleased/55191-update-workhorse.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Workhorse to v8.0.0
+merge_request: 23740
+author:
+type: other
diff --git a/changelogs/unreleased/55293-split-bio-into-individual-line-in-extended-user-tooltips.yml b/changelogs/unreleased/55293-split-bio-into-individual-line-in-extended-user-tooltips.yml
new file mode 100644
index 00000000000..c6ff52b0fa1
--- /dev/null
+++ b/changelogs/unreleased/55293-split-bio-into-individual-line-in-extended-user-tooltips.yml
@@ -0,0 +1,5 @@
+---
+title: Split bio into individual line in extended user tooltips
+merge_request: 23940
+author:
+type: other
diff --git a/changelogs/unreleased/55344-only-prompt-user-once-when-navigating-away-from-file-editor.yml b/changelogs/unreleased/55344-only-prompt-user-once-when-navigating-away-from-file-editor.yml
new file mode 100644
index 00000000000..9c4d73c5323
--- /dev/null
+++ b/changelogs/unreleased/55344-only-prompt-user-once-when-navigating-away-from-file-editor.yml
@@ -0,0 +1,5 @@
+---
+title: Only prompt user once when navigating away from file editor
+merge_request: 23820
+author: Sam Bigelow
+type: fixed
diff --git a/changelogs/unreleased/55484-fix-edit-button.yml b/changelogs/unreleased/55484-fix-edit-button.yml
new file mode 100644
index 00000000000..c8998cba248
--- /dev/null
+++ b/changelogs/unreleased/55484-fix-edit-button.yml
@@ -0,0 +1,4 @@
+title: Fix edit button disappearing in issue title
+merge_request: 23948
+author: Ruben Moya
+type: fixed
diff --git a/changelogs/unreleased/_acet-fix-flash-styling.yml b/changelogs/unreleased/_acet-fix-flash-styling.yml
deleted file mode 100644
index 57354c04899..00000000000
--- a/changelogs/unreleased/_acet-fix-flash-styling.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix flash notice styling for fluid layout
-merge_request: 23382
-author:
-type: fixed
diff --git a/changelogs/unreleased/ab-approximate-counts.yml b/changelogs/unreleased/ab-approximate-counts.yml
deleted file mode 100644
index 8a67239d031..00000000000
--- a/changelogs/unreleased/ab-approximate-counts.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Approximate counting strategy with TABLESAMPLE.
-merge_request: 22650
-author:
-type: performance
diff --git a/changelogs/unreleased/ac-releases-name-sha-author.yml b/changelogs/unreleased/ac-releases-name-sha-author.yml
new file mode 100644
index 00000000000..e84b82847eb
--- /dev/null
+++ b/changelogs/unreleased/ac-releases-name-sha-author.yml
@@ -0,0 +1,5 @@
+---
+title: Add name, author_id, and sha to releases table
+merge_request: 23763
+author:
+type: added
diff --git a/changelogs/unreleased/add-new-nginx-metrics.yml b/changelogs/unreleased/add-new-nginx-metrics.yml
new file mode 100644
index 00000000000..57221056d6e
--- /dev/null
+++ b/changelogs/unreleased/add-new-nginx-metrics.yml
@@ -0,0 +1,5 @@
+---
+title: Add NGINX 0.16.0 and above metrics
+merge_request: 22133
+author:
+type: added
diff --git a/changelogs/unreleased/added-glob-for-ci-changes-detection.yml b/changelogs/unreleased/added-glob-for-ci-changes-detection.yml
deleted file mode 100644
index 887c6ef0346..00000000000
--- a/changelogs/unreleased/added-glob-for-ci-changes-detection.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Added glob for CI changes detection
-merge_request: 23128
-author: Kirill Zaitsev
-type: added
diff --git a/changelogs/unreleased/an-gitaly-version-0-133-0.yml b/changelogs/unreleased/an-gitaly-version-0-133-0.yml
deleted file mode 100644
index 4f3943ceacb..00000000000
--- a/changelogs/unreleased/an-gitaly-version-0-133-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Updated Gitaly to v0.133.0
-merge_request: 23148
-author:
-type: other
diff --git a/changelogs/unreleased/ashmckenzie-hmac-token-decode-and-tests.yml b/changelogs/unreleased/ashmckenzie-hmac-token-decode-and-tests.yml
deleted file mode 100644
index d15c5654d99..00000000000
--- a/changelogs/unreleased/ashmckenzie-hmac-token-decode-and-tests.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Relocate JSONWebToken::HMACToken from EE
-merge_request: 22906
-author:
-type: changed
diff --git a/changelogs/unreleased/auto_devops_kubernetes_active.yml b/changelogs/unreleased/auto_devops_kubernetes_active.yml
deleted file mode 100644
index 310d37128c9..00000000000
--- a/changelogs/unreleased/auto_devops_kubernetes_active.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Switch kubernetes:active with checking in Auto-DevOps.gitlab-ci.yml
-merge_request: 22929
-author:
-type: fixed
diff --git a/changelogs/unreleased/blackst0ne-add-discord-service.yml b/changelogs/unreleased/blackst0ne-add-discord-service.yml
deleted file mode 100644
index 85dedf6d81f..00000000000
--- a/changelogs/unreleased/blackst0ne-add-discord-service.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Discord integration
-merge_request: 22684
-author: "@blackst0ne"
-type: added
diff --git a/changelogs/unreleased/blackst0ne-convert-specs-rails5-style.yml b/changelogs/unreleased/blackst0ne-convert-specs-rails5-style.yml
new file mode 100644
index 00000000000..c29cfec075c
--- /dev/null
+++ b/changelogs/unreleased/blackst0ne-convert-specs-rails5-style.yml
@@ -0,0 +1,5 @@
+---
+title: "[Rails5.1] Update functional specs to use new keyword format"
+merge_request: 23095
+author: "@blackst0ne"
+type: other
diff --git a/changelogs/unreleased/blackst0ne-improve-encoding-helper-spec.yml b/changelogs/unreleased/blackst0ne-improve-encoding-helper-spec.yml
new file mode 100644
index 00000000000..09480499b87
--- /dev/null
+++ b/changelogs/unreleased/blackst0ne-improve-encoding-helper-spec.yml
@@ -0,0 +1,5 @@
+---
+title: Update specs to exclude possible false positive pass
+merge_request: 23893
+author: "@blackst0ne"
+type: other
diff --git a/changelogs/unreleased/bump_gpgme_gem.yml b/changelogs/unreleased/bump_gpgme_gem.yml
deleted file mode 100644
index 4c0067cb824..00000000000
--- a/changelogs/unreleased/bump_gpgme_gem.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bump gpgme gem version from 2.0.13 to 2.0.18
-merge_request:
-author: asaparov
-type: other
diff --git a/changelogs/unreleased/bvl-hide-confidential-events-take2.yml b/changelogs/unreleased/bvl-hide-confidential-events-take2.yml
new file mode 100644
index 00000000000..a5abd496a9d
--- /dev/null
+++ b/changelogs/unreleased/bvl-hide-confidential-events-take2.yml
@@ -0,0 +1,5 @@
+---
+title: Hide confidential events in the API
+merge_request: 23746
+author:
+type: other
diff --git a/changelogs/unreleased/bvl-use-shell-writeref.yml b/changelogs/unreleased/bvl-use-shell-writeref.yml
deleted file mode 100644
index 682d428e8c5..00000000000
--- a/changelogs/unreleased/bvl-use-shell-writeref.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Avoid creating invalid refs using rugged, shelling out for writing refs
-merge_request: 23286
-author:
-type: fixed
diff --git a/changelogs/unreleased/ce-52811-fix_namespaces_api_routing.yml b/changelogs/unreleased/ce-52811-fix_namespaces_api_routing.yml
deleted file mode 100644
index b5fd99c304f..00000000000
--- a/changelogs/unreleased/ce-52811-fix_namespaces_api_routing.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix API::Namespaces routing to accept namepaces with dots
-merge_request: 22912
-author:
-type: fixed
diff --git a/changelogs/unreleased/ce-54109-fix_user_by_any_email.yml b/changelogs/unreleased/ce-54109-fix_user_by_any_email.yml
deleted file mode 100644
index eb5d2e3244c..00000000000
--- a/changelogs/unreleased/ce-54109-fix_user_by_any_email.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Respect confirmed flag on secondary emails
-merge_request: 23181
-author:
-type: fixed
diff --git a/changelogs/unreleased/certmanager-temp.yml b/changelogs/unreleased/certmanager-temp.yml
deleted file mode 100644
index 3f908d01c9f..00000000000
--- a/changelogs/unreleased/certmanager-temp.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "#40635: Adds support for cert-manager"
-merge_request: 23036
-author: Amit Rathi
-type: added
diff --git a/changelogs/unreleased/check-if-fetched-data-does-is-complete.yml b/changelogs/unreleased/check-if-fetched-data-does-is-complete.yml
deleted file mode 100644
index 31c131045b9..00000000000
--- a/changelogs/unreleased/check-if-fetched-data-does-is-complete.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Validate chunk size when persist
-merge_request: 23341
-author:
-type: fixed
diff --git a/changelogs/unreleased/define-default-value-for-only-except-keys.yml b/changelogs/unreleased/define-default-value-for-only-except-keys.yml
deleted file mode 100644
index 3e5ecdcf51e..00000000000
--- a/changelogs/unreleased/define-default-value-for-only-except-keys.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Define the default value for only/except policies
-merge_request: 23531
-author:
-type: changed
diff --git a/changelogs/unreleased/depracated-migration-inheritance.yml b/changelogs/unreleased/depracated-migration-inheritance.yml
new file mode 100644
index 00000000000..1ea9b2df59c
--- /dev/null
+++ b/changelogs/unreleased/depracated-migration-inheritance.yml
@@ -0,0 +1,5 @@
+---
+title: ActiveRecord::Migration -> ActiveRecord::Migration[5.0]
+merge_request: 23910
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/deprecated-actiondispatch-paramsparser.yml b/changelogs/unreleased/deprecated-actiondispatch-paramsparser.yml
new file mode 100644
index 00000000000..9cfb00a9544
--- /dev/null
+++ b/changelogs/unreleased/deprecated-actiondispatch-paramsparser.yml
@@ -0,0 +1,5 @@
+---
+title: Remove deprecated ActionDispatch::ParamsParser
+merge_request: 23848
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/deprecated-alias-method-chain.yml b/changelogs/unreleased/deprecated-alias-method-chain.yml
new file mode 100644
index 00000000000..76dd016e4cc
--- /dev/null
+++ b/changelogs/unreleased/deprecated-alias-method-chain.yml
@@ -0,0 +1,6 @@
+---
+title: 'Fix deprecation: alias_method_chain is deprecated. Please, use Module#prepend
+ instead'
+merge_request: 23887
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/deprecated-comparing-actioncontroller-params-hash.yml b/changelogs/unreleased/deprecated-comparing-actioncontroller-params-hash.yml
new file mode 100644
index 00000000000..a7b9d054a4c
--- /dev/null
+++ b/changelogs/unreleased/deprecated-comparing-actioncontroller-params-hash.yml
@@ -0,0 +1,6 @@
+---
+title: 'Fix deprecation: Comparing equality between ActionController::Parameters and
+ a Hash is deprecated'
+merge_request: 23855
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/deprecated-delete-all-params.yml b/changelogs/unreleased/deprecated-delete-all-params.yml
new file mode 100644
index 00000000000..e23fe92a738
--- /dev/null
+++ b/changelogs/unreleased/deprecated-delete-all-params.yml
@@ -0,0 +1,5 @@
+---
+title: 'Fix deprecation: Passing conditions to delete_all is deprecated'
+merge_request: 23817
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/deprecated-directly-inheriting-migration.yml b/changelogs/unreleased/deprecated-directly-inheriting-migration.yml
new file mode 100644
index 00000000000..2793cc0d44f
--- /dev/null
+++ b/changelogs/unreleased/deprecated-directly-inheriting-migration.yml
@@ -0,0 +1,5 @@
+---
+title: 'Fix deprecation: Directly inheriting from ActiveRecord::Migration is deprecated.'
+merge_request: 23884
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/deprecated-passing-activerecord-objects.yml b/changelogs/unreleased/deprecated-passing-activerecord-objects.yml
new file mode 100644
index 00000000000..e58647186b8
--- /dev/null
+++ b/changelogs/unreleased/deprecated-passing-activerecord-objects.yml
@@ -0,0 +1,5 @@
+---
+title: 'Fix deprecation: Passing ActiveRecord::Base objects to sanitize_sql_hash_for_assignment'
+merge_request: 23818
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/deprecated-positional-seperator-parameter.yml b/changelogs/unreleased/deprecated-positional-seperator-parameter.yml
new file mode 100644
index 00000000000..0d952e0d5eb
--- /dev/null
+++ b/changelogs/unreleased/deprecated-positional-seperator-parameter.yml
@@ -0,0 +1,5 @@
+---
+title: Passing the separator argument as a positional parameter is deprecated
+merge_request: 23334
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/diff-empty-state-fixes.yml b/changelogs/unreleased/diff-empty-state-fixes.yml
new file mode 100644
index 00000000000..0d347dd17e4
--- /dev/null
+++ b/changelogs/unreleased/diff-empty-state-fixes.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed merge request diffs empty states
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/diff-fix-expanding.yml b/changelogs/unreleased/diff-fix-expanding.yml
deleted file mode 100644
index 8ba7f87addc..00000000000
--- a/changelogs/unreleased/diff-fix-expanding.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed multiple diff line discussions not expanding
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/discussion-perf-improvement.yml b/changelogs/unreleased/discussion-perf-improvement.yml
deleted file mode 100644
index defff8a55f5..00000000000
--- a/changelogs/unreleased/discussion-perf-improvement.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve initial discussion rendering performance
-merge_request: 22607
-author:
-type: changed
diff --git a/changelogs/unreleased/dm-batch-loader-key.yml b/changelogs/unreleased/dm-batch-loader-key.yml
deleted file mode 100644
index 047fdbc4b3f..00000000000
--- a/changelogs/unreleased/dm-batch-loader-key.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Batch load only data from same repository when lazy object is accessed
-merge_request: 23309
-author:
-type: performance
diff --git a/changelogs/unreleased/dm-note-email-image-diff-discussion.yml b/changelogs/unreleased/dm-note-email-image-diff-discussion.yml
new file mode 100644
index 00000000000..6532052e132
--- /dev/null
+++ b/changelogs/unreleased/dm-note-email-image-diff-discussion.yml
@@ -0,0 +1,5 @@
+---
+title: Fix notification email for image diff notes
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/docs-minor-aws-fixes.yml b/changelogs/unreleased/docs-minor-aws-fixes.yml
deleted file mode 100644
index 64fa6b12afe..00000000000
--- a/changelogs/unreleased/docs-minor-aws-fixes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes to AWS documentation spelling and grammar
-merge_request: 23198
-author: Brendan O'Leary
-type: other
diff --git a/changelogs/unreleased/document-raw-snippet-api.yml b/changelogs/unreleased/document-raw-snippet-api.yml
deleted file mode 100644
index 3b8818cea5c..00000000000
--- a/changelogs/unreleased/document-raw-snippet-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix lack of documentation on how to fetch a snippet's content using API
-merge_request: 23448
-author: Colin Leroy
-type: other
diff --git a/changelogs/unreleased/drop-default-value-status-deployments.yml b/changelogs/unreleased/drop-default-value-status-deployments.yml
deleted file mode 100644
index fdb826a0507..00000000000
--- a/changelogs/unreleased/drop-default-value-status-deployments.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Drop default value on status column in deployments table
-merge_request: 22971
-author:
-type: other
diff --git a/changelogs/unreleased/drop-gcp-cluster-table.yml b/changelogs/unreleased/drop-gcp-cluster-table.yml
deleted file mode 100644
index 15964ec2eaf..00000000000
--- a/changelogs/unreleased/drop-gcp-cluster-table.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Drop gcp_clusters table
-merge_request: 22713
-author:
-type: other
diff --git a/changelogs/unreleased/feature-option-to-make-variables-protected.yml b/changelogs/unreleased/feature-option-to-make-variables-protected.yml
new file mode 100644
index 00000000000..c99c0481c35
--- /dev/null
+++ b/changelogs/unreleased/feature-option-to-make-variables-protected.yml
@@ -0,0 +1,5 @@
+---
+title: Add option to make ci variables protected by default
+merge_request: 22744
+author: Alexis Reigel
+type: added
diff --git a/changelogs/unreleased/fix-55448.yml b/changelogs/unreleased/fix-55448.yml
new file mode 100644
index 00000000000..e0bdbb6eda4
--- /dev/null
+++ b/changelogs/unreleased/fix-55448.yml
@@ -0,0 +1,5 @@
+---
+title: Remove deprecated xhr from specs
+merge_request: 23949
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/fix-calendar-events-fetching-error.yml b/changelogs/unreleased/fix-calendar-events-fetching-error.yml
new file mode 100644
index 00000000000..ad4a40cd9a0
--- /dev/null
+++ b/changelogs/unreleased/fix-calendar-events-fetching-error.yml
@@ -0,0 +1,5 @@
+---
+title: Fix calendar events fetching error on private profile page
+merge_request: 23718
+author: Harry Kiselev
+type: other
diff --git a/changelogs/unreleased/fix-gb-encrypt-runners-tokens.yml b/changelogs/unreleased/fix-gb-encrypt-runners-tokens.yml
deleted file mode 100644
index 4ce4f96c1dd..00000000000
--- a/changelogs/unreleased/fix-gb-encrypt-runners-tokens.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Encrypt runners tokens
-merge_request: 23412
-author:
-type: security
diff --git a/changelogs/unreleased/fix-gb-improve-timeout-inputs-help-sections.yml b/changelogs/unreleased/fix-gb-improve-timeout-inputs-help-sections.yml
deleted file mode 100644
index 52b431edf2c..00000000000
--- a/changelogs/unreleased/fix-gb-improve-timeout-inputs-help-sections.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve help and validation sections of maximum build timeout inputs
-merge_request: 23586
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-mr-widget-unrelated-deployment-status.yml b/changelogs/unreleased/fix-mr-widget-unrelated-deployment-status.yml
deleted file mode 100644
index ab926fbd43b..00000000000
--- a/changelogs/unreleased/fix-mr-widget-unrelated-deployment-status.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix unrelated deployment status in MR widget
-merge_request: 23175
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-multiple-comments-shade-overlap.yml b/changelogs/unreleased/fix-multiple-comments-shade-overlap.yml
deleted file mode 100644
index 20005ba355e..00000000000
--- a/changelogs/unreleased/fix-multiple-comments-shade-overlap.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix multiple commits shade overlapping vertical discussion line
-merge_request: 23515
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-n-plus-1-queries-projects.yml b/changelogs/unreleased/fix-n-plus-1-queries-projects.yml
new file mode 100644
index 00000000000..cb625784267
--- /dev/null
+++ b/changelogs/unreleased/fix-n-plus-1-queries-projects.yml
@@ -0,0 +1,6 @@
+---
+title: Fix some N+1 queries related to Admin Dashboard, User Dashboards and Activity
+ Stream
+merge_request: 23034
+author:
+type: performance
diff --git a/changelogs/unreleased/fj-47494-upgrade-git-to-2-18-0.yml b/changelogs/unreleased/fj-47494-upgrade-git-to-2-18-0.yml
deleted file mode 100644
index 0f01552ff7e..00000000000
--- a/changelogs/unreleased/fj-47494-upgrade-git-to-2-18-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade minimum required Git version to 2.18.0
-merge_request: 22803
-author:
-type: other
diff --git a/changelogs/unreleased/fj-force-content-disposition.yml b/changelogs/unreleased/fj-force-content-disposition.yml
deleted file mode 100644
index d84555a489f..00000000000
--- a/changelogs/unreleased/fj-force-content-disposition.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Force content disposition attachment to several endpoints
-merge_request: 23223
-author:
-type: other
diff --git a/changelogs/unreleased/force-reload-arguments-1.yml b/changelogs/unreleased/force-reload-arguments-2.yml
index 29f34b8bdbe..23ab9433b3d 100644
--- a/changelogs/unreleased/force-reload-arguments-1.yml
+++ b/changelogs/unreleased/force-reload-arguments-2.yml
@@ -1,5 +1,5 @@
---
title: Passing an argument to force an association to reload is now deprecated
-merge_request: 23334
+merge_request: 23894
author: Jasper Maes
type: other
diff --git a/changelogs/unreleased/frozen-string-lib-gitlab-even-even-even-more.yml b/changelogs/unreleased/frozen-string-lib-gitlab-even-even-even-more.yml
deleted file mode 100644
index e718d716647..00000000000
--- a/changelogs/unreleased/frozen-string-lib-gitlab-even-even-even-more.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable even more frozen string for lib/gitlab
-merge_request:
-author: gfyoung
-type: performance
diff --git a/changelogs/unreleased/frozen-string-lib-gitlab-even-even-more.yml b/changelogs/unreleased/frozen-string-lib-gitlab-even-even-more.yml
deleted file mode 100644
index e718d716647..00000000000
--- a/changelogs/unreleased/frozen-string-lib-gitlab-even-even-more.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable even more frozen string for lib/gitlab
-merge_request:
-author: gfyoung
-type: performance
diff --git a/changelogs/unreleased/frozen-string-lib-gitlab-even-more.yml b/changelogs/unreleased/frozen-string-lib-gitlab-even-more.yml
deleted file mode 100644
index cfbc4ced635..00000000000
--- a/changelogs/unreleased/frozen-string-lib-gitlab-even-more.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable even more frozen string in lib/gitlab/**/*.rb
-merge_request:
-author: gfyoung
-type: performance
diff --git a/changelogs/unreleased/frozen-string-lib-gitlab-more.yml b/changelogs/unreleased/frozen-string-lib-gitlab-more.yml
deleted file mode 100644
index cfbc4ced635..00000000000
--- a/changelogs/unreleased/frozen-string-lib-gitlab-more.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable even more frozen string in lib/gitlab/**/*.rb
-merge_request:
-author: gfyoung
-type: performance
diff --git a/changelogs/unreleased/frozen-string-lib-rubocop.yml b/changelogs/unreleased/frozen-string-lib-rubocop.yml
deleted file mode 100644
index 9fe342e251b..00000000000
--- a/changelogs/unreleased/frozen-string-lib-rubocop.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable Rubocop on lib/gitlab
-merge_request:
-author: gfyoung
-type: other
diff --git a/changelogs/unreleased/gt-align-issue-status-and-confidential-icon.yml b/changelogs/unreleased/gt-align-issue-status-and-confidential-icon.yml
deleted file mode 100644
index 481ce656dc7..00000000000
--- a/changelogs/unreleased/gt-align-issue-status-and-confidential-icon.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Align issue status label and confidential icon.
-merge_request: 23046
-author: George Tsiolis
-type: fixed
diff --git a/changelogs/unreleased/gt-change-breadcrumb-title-for-contribution-charts.yml b/changelogs/unreleased/gt-change-breadcrumb-title-for-contribution-charts.yml
deleted file mode 100644
index 233cc43117d..00000000000
--- a/changelogs/unreleased/gt-change-breadcrumb-title-for-contribution-charts.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Change breadcrumb title for contribution charts
-merge_request: 23071
-author: George Tsiolis
-type: changed
diff --git a/changelogs/unreleased/gt-change-container-width-for-project-import.yml b/changelogs/unreleased/gt-change-container-width-for-project-import.yml
deleted file mode 100644
index ec2beb15912..00000000000
--- a/changelogs/unreleased/gt-change-container-width-for-project-import.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Change container width for project import
-merge_request: 23318
-author: George Tsiolis
-type: fixed
diff --git a/changelogs/unreleased/gt-externalize-app-views-invites.yml b/changelogs/unreleased/gt-externalize-app-views-invites.yml
deleted file mode 100644
index b5a22177f9b..00000000000
--- a/changelogs/unreleased/gt-externalize-app-views-invites.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Externalize strings from `/app/views/invites`
-merge_request: 23205
-author: Tao Wang
-type: other
diff --git a/changelogs/unreleased/gt-externalize-app-views-project-runners.yml b/changelogs/unreleased/gt-externalize-app-views-project-runners.yml
deleted file mode 100644
index d7d591e2175..00000000000
--- a/changelogs/unreleased/gt-externalize-app-views-project-runners.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Externalize strings from `/app/views/project/runners`
-merge_request: 23208
-author: Tao Wang
-type: other
diff --git a/changelogs/unreleased/gt-externalize-app-views-snippets.yml b/changelogs/unreleased/gt-externalize-app-views-snippets.yml
deleted file mode 100644
index 633aa9f2534..00000000000
--- a/changelogs/unreleased/gt-externalize-app-views-snippets.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Externalize strings from `/app/views/snippets`
-merge_request: 23351
-author: Tao Wang
-type: other
diff --git a/changelogs/unreleased/gt-fix-typo-in-notebook-props.yml b/changelogs/unreleased/gt-fix-typo-in-notebook-props.yml
deleted file mode 100644
index 60603905a2d..00000000000
--- a/changelogs/unreleased/gt-fix-typo-in-notebook-props.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix typo in notebook props
-merge_request: 23103
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/gt-fix-typos-in-lib.yml b/changelogs/unreleased/gt-fix-typos-in-lib.yml
deleted file mode 100644
index 32ccd03b063..00000000000
--- a/changelogs/unreleased/gt-fix-typos-in-lib.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix typos in lib
-merge_request: 23106
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/gt-remove-instances-of-extend-monospace.yml b/changelogs/unreleased/gt-remove-instances-of-extend-monospace.yml
deleted file mode 100644
index dc41de61046..00000000000
--- a/changelogs/unreleased/gt-remove-instances-of-extend-monospace.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove monospace extend
-merge_request: 23089
-author: George Tsiolis
-type: performance
diff --git a/changelogs/unreleased/gt-remove-unnecessary-line-before-reply-holder.yml b/changelogs/unreleased/gt-remove-unnecessary-line-before-reply-holder.yml
new file mode 100644
index 00000000000..142a9c1f2cc
--- /dev/null
+++ b/changelogs/unreleased/gt-remove-unnecessary-line-before-reply-holder.yml
@@ -0,0 +1,5 @@
+---
+title: Remove unnecessary line before reply holder
+merge_request: 23092
+author: George Tsiolis
+type: changed
diff --git a/changelogs/unreleased/gt-remove-unused-project-method.yml b/changelogs/unreleased/gt-remove-unused-project-method.yml
deleted file mode 100644
index 2d60c2fe423..00000000000
--- a/changelogs/unreleased/gt-remove-unused-project-method.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove unused project method
-merge_request: 54103
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/gt-rename-diffs-store-variable.yml b/changelogs/unreleased/gt-rename-diffs-store-variable.yml
deleted file mode 100644
index 0aed49f3d60..00000000000
--- a/changelogs/unreleased/gt-rename-diffs-store-variable.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Rename diffs store variable
-merge_request: 23123
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/gt-reorder-group-sidebar-menu-items.yml b/changelogs/unreleased/gt-reorder-group-sidebar-menu-items.yml
new file mode 100644
index 00000000000..b1ecf2bb1ed
--- /dev/null
+++ b/changelogs/unreleased/gt-reorder-group-sidebar-menu-items.yml
@@ -0,0 +1,5 @@
+---
+title: Reorder sidebar menu item for group clusters
+merge_request: 24001
+author: George Tsiolis
+type: changed
diff --git a/changelogs/unreleased/gt-update-env-metrics-empty-state.yml b/changelogs/unreleased/gt-update-env-metrics-empty-state.yml
deleted file mode 100644
index a05dc07e65c..00000000000
--- a/changelogs/unreleased/gt-update-env-metrics-empty-state.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update environments metrics empty state
-merge_request: 23074
-author: George Tsiolis
-type: changed
diff --git a/changelogs/unreleased/gt-update-environment-breadcrumb.yml b/changelogs/unreleased/gt-update-environment-breadcrumb.yml
new file mode 100644
index 00000000000..53b9673a96c
--- /dev/null
+++ b/changelogs/unreleased/gt-update-environment-breadcrumb.yml
@@ -0,0 +1,5 @@
+---
+title: Update environments breadcrumb
+merge_request: 23751
+author: George Tsiolis
+type: changed
diff --git a/changelogs/unreleased/gt-update-navigation-theme-colors.yml b/changelogs/unreleased/gt-update-navigation-theme-colors.yml
new file mode 100644
index 00000000000..749587a6343
--- /dev/null
+++ b/changelogs/unreleased/gt-update-navigation-theme-colors.yml
@@ -0,0 +1,5 @@
+---
+title: Update header navigation theme colors
+merge_request: 23734
+author: George Tsiolis
+type: fixed
diff --git a/changelogs/unreleased/gt-use-gl-tooltip-directive.yml b/changelogs/unreleased/gt-use-gl-tooltip-directive.yml
deleted file mode 100644
index 91fdb73e3c6..00000000000
--- a/changelogs/unreleased/gt-use-gl-tooltip-directive.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Replace tooltip directive with gl-tooltip diretive in badges, cycle analytics, and diffs
-merge_request: 22770
-author: George Tsiolis
-type: performance
diff --git a/changelogs/unreleased/ide-open-all-mr-files.yml b/changelogs/unreleased/ide-open-all-mr-files.yml
deleted file mode 100644
index 6a5ea8908fc..00000000000
--- a/changelogs/unreleased/ide-open-all-mr-files.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Open first 10 merge request files in IDE
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/ignore-failed-pipeline-creation-on-pipeline-schedule.yml b/changelogs/unreleased/ignore-failed-pipeline-creation-on-pipeline-schedule.yml
deleted file mode 100644
index 90f47aa12db..00000000000
--- a/changelogs/unreleased/ignore-failed-pipeline-creation-on-pipeline-schedule.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove auto deactivation when failed to create a pipeline via pipeline schedules
-merge_request: 22243
-author:
-type: changed
diff --git a/changelogs/unreleased/improve_auto_devops_migration_debug.yml b/changelogs/unreleased/improve_auto_devops_migration_debug.yml
deleted file mode 100644
index 96a78808361..00000000000
--- a/changelogs/unreleased/improve_auto_devops_migration_debug.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: 'Auto DevOps: Add echo for each branch of the deploy() function where we run
- helm upgrade'
-merge_request: 23499
-author:
-type: changed
diff --git a/changelogs/unreleased/include-new-link-in-breadcrumb.yml b/changelogs/unreleased/include-new-link-in-breadcrumb.yml
deleted file mode 100644
index 68c808d66d7..00000000000
--- a/changelogs/unreleased/include-new-link-in-breadcrumb.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Include new link in breadcrumb for issues, merge requests, milestones, and labels
-merge_request: 18515
-author: George Tsiolis
-type: changed
diff --git a/changelogs/unreleased/jivl-add-empty-state-graphs-null-values.yml b/changelogs/unreleased/jivl-add-empty-state-graphs-null-values.yml
deleted file mode 100644
index d21254b16d0..00000000000
--- a/changelogs/unreleased/jivl-add-empty-state-graphs-null-values.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add empty state for graphs with no values
-merge_request: 22630
-author:
-type: fixed
diff --git a/changelogs/unreleased/jlenny-CI_COMMIT_SHORT_SHA.yml b/changelogs/unreleased/jlenny-CI_COMMIT_SHORT_SHA.yml
new file mode 100644
index 00000000000..abece81a20d
--- /dev/null
+++ b/changelogs/unreleased/jlenny-CI_COMMIT_SHORT_SHA.yml
@@ -0,0 +1,5 @@
+---
+title: Add new pipeline variable CI_COMMIT_SHORT_SHA
+merge_request: 23822
+author:
+type: added
diff --git a/changelogs/unreleased/jupyter-tls.yml b/changelogs/unreleased/jupyter-tls.yml
deleted file mode 100644
index 4111edd34ff..00000000000
--- a/changelogs/unreleased/jupyter-tls.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "#52753: HTTPS for JupyterHub installation"
-merge_request: 23479
-author: Amit Rathi
-type: added
diff --git a/changelogs/unreleased/kcj-add-philosophy.yml b/changelogs/unreleased/kcj-add-philosophy.yml
deleted file mode 100644
index d164ce165ea..00000000000
--- a/changelogs/unreleased/kcj-add-philosophy.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds a PHILOSOPHY.md which references GitLab Product Handbook
-merge_request: 23200
-author:
-type: other
diff --git a/changelogs/unreleased/kubernetes-http-response-code.yml b/changelogs/unreleased/kubernetes-http-response-code.yml
deleted file mode 100644
index 551fe2edc3c..00000000000
--- a/changelogs/unreleased/kubernetes-http-response-code.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Show HTTP response code for Kubernetes errors
-merge_request: 22964
-author:
-type: other
diff --git a/changelogs/unreleased/legacy_fallback_for_project_clusters_only.yml b/changelogs/unreleased/legacy_fallback_for_project_clusters_only.yml
deleted file mode 100644
index c8e959176d0..00000000000
--- a/changelogs/unreleased/legacy_fallback_for_project_clusters_only.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fallback to admin KUBE_TOKEN for project clusters only
-merge_request: 23527
-author:
-type: other
diff --git a/changelogs/unreleased/lock-trace-writes.yml b/changelogs/unreleased/lock-trace-writes.yml
deleted file mode 100644
index 9c5239081b9..00000000000
--- a/changelogs/unreleased/lock-trace-writes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Lock writes to trace stream
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-deadlock-chunked-io.yml b/changelogs/unreleased/markdown-toolbar-btn-fix.yml
index def7a59e86e..eefb4d19f86 100644
--- a/changelogs/unreleased/fix-deadlock-chunked-io.yml
+++ b/changelogs/unreleased/markdown-toolbar-btn-fix.yml
@@ -1,5 +1,5 @@
---
-title: Fix deadlock on ChunkedIO
+title: Fixed markdown toolbar buttons
merge_request:
author:
type: fixed
diff --git a/changelogs/unreleased/mk-avoid-read-only-error.yml b/changelogs/unreleased/mk-avoid-read-only-error.yml
new file mode 100644
index 00000000000..8641f5db9f0
--- /dev/null
+++ b/changelogs/unreleased/mk-avoid-read-only-error.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent admins from attempting hashed storage migration on read only DB
+merge_request: 23597
+author:
+type: fixed
diff --git a/changelogs/unreleased/mr-file-tree-commit.yml b/changelogs/unreleased/mr-file-tree-commit.yml
deleted file mode 100644
index e0d47e6e61f..00000000000
--- a/changelogs/unreleased/mr-file-tree-commit.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Show tree collapse button for merge request commit diffs
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/mr-origin-23218.yml b/changelogs/unreleased/mr-origin-23218.yml
deleted file mode 100644
index 49867f04343..00000000000
--- a/changelogs/unreleased/mr-origin-23218.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix typo for scheduled pipeline
-merge_request: 23218
-author: Davy Defaud
-type: other
diff --git a/changelogs/unreleased/mr-pipelines-2.yml b/changelogs/unreleased/mr-pipelines-2.yml
deleted file mode 100644
index 683c626c3ce..00000000000
--- a/changelogs/unreleased/mr-pipelines-2.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Merge request pipelines
-merge_request: 23217
-author:
-type: added
diff --git a/changelogs/unreleased/mr-sticky-headers.yml b/changelogs/unreleased/mr-sticky-headers.yml
deleted file mode 100644
index c20829bc2d7..00000000000
--- a/changelogs/unreleased/mr-sticky-headers.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make diff file headers sticky
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/mr-tree-filter-path-name.yml b/changelogs/unreleased/mr-tree-filter-path-name.yml
deleted file mode 100644
index 152f8a67337..00000000000
--- a/changelogs/unreleased/mr-tree-filter-path-name.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Changed merge request filtering to be by path instead of name
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/multiple-diff-line-discussions-fix.yml b/changelogs/unreleased/multiple-diff-line-discussions-fix.yml
deleted file mode 100644
index 870a8ab3815..00000000000
--- a/changelogs/unreleased/multiple-diff-line-discussions-fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed duplicate discussions getting added to diff lines
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/non-webkit-scrollbar-fixing.yml b/changelogs/unreleased/non-webkit-scrollbar-fixing.yml
deleted file mode 100644
index 526a9f25486..00000000000
--- a/changelogs/unreleased/non-webkit-scrollbar-fixing.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix horizontal scrollbar overlapping on horizontal scrolling-tabs
-merge_request: 23167
-author: Harry Kiselev
-type: other
diff --git a/changelogs/unreleased/none-syntax-highlighting.yml b/changelogs/unreleased/none-syntax-highlighting.yml
new file mode 100644
index 00000000000..b373aac7c02
--- /dev/null
+++ b/changelogs/unreleased/none-syntax-highlighting.yml
@@ -0,0 +1,5 @@
+---
+title: Add no-color theme for syntax highlighting.
+merge_request: !20170
+author: khm
+type: added
diff --git a/changelogs/unreleased/optimise-job-request.yml b/changelogs/unreleased/optimise-job-request.yml
deleted file mode 100644
index e1265841b48..00000000000
--- a/changelogs/unreleased/optimise-job-request.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use cached size when passing artifacts to Runner
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/order-of-notification-settings.yml b/changelogs/unreleased/order-of-notification-settings.yml
deleted file mode 100644
index 0f0243bcb40..00000000000
--- a/changelogs/unreleased/order-of-notification-settings.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: reorder notification settings by noisy-ness
-merge_request:
-author: C.J. Jameson
-type: changed
diff --git a/changelogs/unreleased/osw-cache-discussions-diff-highlighting.yml b/changelogs/unreleased/osw-cache-discussions-diff-highlighting.yml
new file mode 100644
index 00000000000..7abc7d85794
--- /dev/null
+++ b/changelogs/unreleased/osw-cache-discussions-diff-highlighting.yml
@@ -0,0 +1,6 @@
+---
+title: Improve the loading time on merge request's discussion page by caching diff
+ highlight
+merge_request: 23857
+author:
+type: performance
diff --git a/changelogs/unreleased/osw-fallback-on-blank-refs.yml b/changelogs/unreleased/osw-fallback-on-blank-refs.yml
deleted file mode 100644
index 039179f5829..00000000000
--- a/changelogs/unreleased/osw-fallback-on-blank-refs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Avoid Gitaly RPC errors when fetching diff stats
-merge_request: 22995
-author:
-type: fixed
diff --git a/changelogs/unreleased/osw-fix-grouping-by-file-path.yml b/changelogs/unreleased/osw-fix-grouping-by-file-path.yml
deleted file mode 100644
index dff3116e7c6..00000000000
--- a/changelogs/unreleased/osw-fix-grouping-by-file-path.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Avoid 500's when serializing legacy diff notes
-merge_request: 23544
-author:
-type: fixed
diff --git a/changelogs/unreleased/project_identicon_fix.yml b/changelogs/unreleased/project_identicon_fix.yml
deleted file mode 100644
index de4876fc4a5..00000000000
--- a/changelogs/unreleased/project_identicon_fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix project identicon aligning Harry Kiselev
-merge_request: 23166
-author: Harry Kiselev
-type: other
diff --git a/changelogs/unreleased/rails5-active-record-class-value.yml b/changelogs/unreleased/rails5-active-record-class-value.yml
deleted file mode 100644
index 9f9fdf10cd1..00000000000
--- a/changelogs/unreleased/rails5-active-record-class-value.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Rails5: Passing a class as a value in an Active Record query is deprecated'
-merge_request: 23164
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/rails5-deprecation-render-nothing.yml b/changelogs/unreleased/rails5-deprecation-render-nothing.yml
deleted file mode 100644
index 32e2d5800c7..00000000000
--- a/changelogs/unreleased/rails5-deprecation-render-nothing.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: render :nothing option is deprecated, Use head method to respond with empty
- response body.
-merge_request: 23311
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/rails5-env-deprecated.yml b/changelogs/unreleased/rails5-env-deprecated.yml
deleted file mode 100644
index 2f8573e2ff6..00000000000
--- a/changelogs/unreleased/rails5-env-deprecated.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Rails5: env is deprecated and will be removed from Rails 5.1'
-merge_request: 22626
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/remote-mirror-update-failed-notification.yml b/changelogs/unreleased/remote-mirror-update-failed-notification.yml
new file mode 100644
index 00000000000..50ec8624ae5
--- /dev/null
+++ b/changelogs/unreleased/remote-mirror-update-failed-notification.yml
@@ -0,0 +1,5 @@
+---
+title: Send a notification email to project maintainers when a mirror update fails
+merge_request: 23595
+author:
+type: added
diff --git a/changelogs/unreleased/remove-deployment-status-hack-from-backend.yml b/changelogs/unreleased/remove-deployment-status-hack-from-backend.yml
deleted file mode 100644
index 2348bfab7d9..00000000000
--- a/changelogs/unreleased/remove-deployment-status-hack-from-backend.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Return real deployment status to frontend
-merge_request: 23270
-author:
-type: fixed
diff --git a/changelogs/unreleased/remove-duplicate-primary-button-in-dashboard-snippets.yml b/changelogs/unreleased/remove-duplicate-primary-button-in-dashboard-snippets.yml
deleted file mode 100644
index 3a8b3a0df5d..00000000000
--- a/changelogs/unreleased/remove-duplicate-primary-button-in-dashboard-snippets.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove duplicate primary button in dashboard snippets on small viewports
-merge_request: 22902
-author: George Tsiolis
-type: fixed
diff --git a/changelogs/unreleased/remove-rails4-specific-code.yml b/changelogs/unreleased/remove-rails4-specific-code.yml
new file mode 100644
index 00000000000..c6c4c0a5d5b
--- /dev/null
+++ b/changelogs/unreleased/remove-rails4-specific-code.yml
@@ -0,0 +1,5 @@
+---
+title: Remove rails4 specific code
+merge_request: 23847
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/remove-rails4-support.yml b/changelogs/unreleased/remove-rails4-support.yml
new file mode 100644
index 00000000000..a05c913a70c
--- /dev/null
+++ b/changelogs/unreleased/remove-rails4-support.yml
@@ -0,0 +1,5 @@
+---
+title: Remove rails 4 support in CI, Gemfiles, bin/ and config/
+merge_request: 23717
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/render-text-deprecated.yml b/changelogs/unreleased/render-text-deprecated.yml
deleted file mode 100644
index 7dbbd13bcef..00000000000
--- a/changelogs/unreleased/render-text-deprecated.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: 'Fix deprecation: render :text is deprecated because it does not actually render
- a text/plain response'
-merge_request: 23425
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/retryable_create_or_update_kubernetes_namespace.yml b/changelogs/unreleased/retryable_create_or_update_kubernetes_namespace.yml
deleted file mode 100644
index 607f2709f90..00000000000
--- a/changelogs/unreleased/retryable_create_or_update_kubernetes_namespace.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Updates service to update Kubernetes project namespaces and restricted service
- account if present
-merge_request: 23525
-author:
-type: changed
diff --git a/changelogs/unreleased/rs-cherry-pick-api.yml b/changelogs/unreleased/rs-cherry-pick-api.yml
deleted file mode 100644
index ce844dfc939..00000000000
--- a/changelogs/unreleased/rs-cherry-pick-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve possible cherry pick API race condition
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/security-182-update-workhorse.yml b/changelogs/unreleased/security-182-update-workhorse.yml
deleted file mode 100644
index 76850901b68..00000000000
--- a/changelogs/unreleased/security-182-update-workhorse.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Redact sensitive information on gitlab-workhorse log
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-2717-xss-username-autocomplete.yml b/changelogs/unreleased/security-2717-xss-username-autocomplete.yml
deleted file mode 100644
index d9b1015eeb4..00000000000
--- a/changelogs/unreleased/security-2717-xss-username-autocomplete.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Escape user fullname while rendering autocomplete template to prevent XSS
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-2736-prometheus-ssrf.yml b/changelogs/unreleased/security-2736-prometheus-ssrf.yml
deleted file mode 100644
index 9d0dda8a75f..00000000000
--- a/changelogs/unreleased/security-2736-prometheus-ssrf.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Do not follow redirects in Prometheus service when making http requests to the configured api url
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-bvl-exposure-in-commits-list.yml b/changelogs/unreleased/security-bvl-exposure-in-commits-list.yml
deleted file mode 100644
index 0361fb0c041..00000000000
--- a/changelogs/unreleased/security-bvl-exposure-in-commits-list.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Don't expose confidential information in commit message list
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-email-change-notification.yml b/changelogs/unreleased/security-email-change-notification.yml
deleted file mode 100644
index 45075ff20bb..00000000000
--- a/changelogs/unreleased/security-email-change-notification.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Provide email notification when a user changes their email address
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-fix-pat-web-access.yml b/changelogs/unreleased/security-fix-pat-web-access.yml
deleted file mode 100644
index 62ffb908fe5..00000000000
--- a/changelogs/unreleased/security-fix-pat-web-access.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Restrict Personal Access Tokens to API scope on web requests
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-fix-uri-xss-applications.yml b/changelogs/unreleased/security-fix-uri-xss-applications.yml
deleted file mode 100644
index 0eaa1b1c4a3..00000000000
--- a/changelogs/unreleased/security-fix-uri-xss-applications.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve reflected XSS in Ouath authorize window
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-fix-webhook-ssrf-ipv6.yml b/changelogs/unreleased/security-fix-webhook-ssrf-ipv6.yml
deleted file mode 100644
index 32c85a2a7da..00000000000
--- a/changelogs/unreleased/security-fix-webhook-ssrf-ipv6.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix SSRF in project integrations
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-fj-crlf-injection.yml b/changelogs/unreleased/security-fj-crlf-injection.yml
deleted file mode 100644
index 861167b8a6e..00000000000
--- a/changelogs/unreleased/security-fj-crlf-injection.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix CRLF vulnerability in Project hooks
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-guest-comments.yml b/changelogs/unreleased/security-guest-comments.yml
deleted file mode 100644
index 2c99512433b..00000000000
--- a/changelogs/unreleased/security-guest-comments.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed ability to comment on locked/confidential issues.
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-guest-comments_2.yml b/changelogs/unreleased/security-guest-comments_2.yml
deleted file mode 100644
index be6f2d6a490..00000000000
--- a/changelogs/unreleased/security-guest-comments_2.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed ability of guest users to edit/delete comments on locked or confidential issues.
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-issue_51301.yml b/changelogs/unreleased/security-issue_51301.yml
deleted file mode 100644
index cf8ebb54b1c..00000000000
--- a/changelogs/unreleased/security-issue_51301.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix milestone promotion authorization check
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-mermaid-xss.yml b/changelogs/unreleased/security-mermaid-xss.yml
deleted file mode 100644
index bcf93ef37ff..00000000000
--- a/changelogs/unreleased/security-mermaid-xss.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Configure mermaid to not render HTML content in diagrams
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-pages-toctou-race.yml b/changelogs/unreleased/security-pages-toctou-race.yml
deleted file mode 100644
index 1c055f6087f..00000000000
--- a/changelogs/unreleased/security-pages-toctou-race.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Fix a possible symlink time of check to time of use race condition in GitLab
- Pages
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-private-group.yml b/changelogs/unreleased/security-private-group.yml
deleted file mode 100644
index dbb7794dfed..00000000000
--- a/changelogs/unreleased/security-private-group.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Removed ability to see private group names when the group id is entered in
- the url.
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-stored-xss-for-environments.yml b/changelogs/unreleased/security-stored-xss-for-environments.yml
deleted file mode 100644
index 5d78ca00942..00000000000
--- a/changelogs/unreleased/security-stored-xss-for-environments.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix stored XSS for Environments
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-xss-in-markdown-following-unrecognized-html-element.yml b/changelogs/unreleased/security-xss-in-markdown-following-unrecognized-html-element.yml
deleted file mode 100644
index 3bd8123a346..00000000000
--- a/changelogs/unreleased/security-xss-in-markdown-following-unrecognized-html-element.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix possible XSS attack in Markdown urls with spaces
-merge_request: 2599
-author:
-type: security
diff --git a/changelogs/unreleased/set-kubeconfig-nil-when-token-nil.yml b/changelogs/unreleased/set-kubeconfig-nil-when-token-nil.yml
deleted file mode 100644
index 6eac2a0146c..00000000000
--- a/changelogs/unreleased/set-kubeconfig-nil-when-token-nil.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make KUBECONFIG nil if KUBE_TOKEN is nil
-merge_request: 23414
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-53180-append-path.yml b/changelogs/unreleased/sh-53180-append-path.yml
deleted file mode 100644
index 64fae5522d8..00000000000
--- a/changelogs/unreleased/sh-53180-append-path.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make sure there's only one slash as path separator
-merge_request: 22954
-author:
-type: other
diff --git a/changelogs/unreleased/sh-bump-gems-security.yml b/changelogs/unreleased/sh-bump-gems-security.yml
deleted file mode 100644
index 06489f6f979..00000000000
--- a/changelogs/unreleased/sh-bump-gems-security.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bump nokogiri, loofah, and rack gems for security updates
-merge_request: 23204
-author:
-type: security
diff --git a/changelogs/unreleased/sh-bump-ruby-2-5-3.yml b/changelogs/unreleased/sh-bump-ruby-2-5-3.yml
deleted file mode 100644
index 13cadc73e9c..00000000000
--- a/changelogs/unreleased/sh-bump-ruby-2-5-3.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade to Ruby 2.5.3
-merge_request: 2806
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-cache-avatar-paths.yml b/changelogs/unreleased/sh-cache-avatar-paths.yml
new file mode 100644
index 00000000000..b59a4db413d
--- /dev/null
+++ b/changelogs/unreleased/sh-cache-avatar-paths.yml
@@ -0,0 +1,5 @@
+---
+title: Cache avatar URLs and paths within a request
+merge_request: 23950
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-carrierwave-patch-google-acl.yml b/changelogs/unreleased/sh-carrierwave-patch-google-acl.yml
new file mode 100644
index 00000000000..206253a100c
--- /dev/null
+++ b/changelogs/unreleased/sh-carrierwave-patch-google-acl.yml
@@ -0,0 +1,5 @@
+---
+title: Fix object storage not working properly with Google S3 compatibility
+merge_request: 23858
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-disable-autocomplete-mirror-settings.yml b/changelogs/unreleased/sh-disable-autocomplete-mirror-settings.yml
deleted file mode 100644
index e42906e88f2..00000000000
--- a/changelogs/unreleased/sh-disable-autocomplete-mirror-settings.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Disable password autocomplete in mirror form fill
-merge_request: 23402
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-clone-geo-dropdown.yml b/changelogs/unreleased/sh-fix-clone-geo-dropdown.yml
new file mode 100644
index 00000000000..1c0cbdc3a2c
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-clone-geo-dropdown.yml
@@ -0,0 +1,5 @@
+---
+title: Fix clone dropdown parent inheritance issues in HAML
+merge_request: 24029
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-github-import-without-oauth2-config.yml b/changelogs/unreleased/sh-fix-github-import-without-oauth2-config.yml
new file mode 100644
index 00000000000..ad548a6ff35
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-github-import-without-oauth2-config.yml
@@ -0,0 +1,5 @@
+---
+title: Allow GitHub imports via token even if OAuth2 provider not configured
+merge_request: 23703
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-http-clone-panel.yml b/changelogs/unreleased/sh-fix-http-clone-panel.yml
new file mode 100644
index 00000000000..ab220bd5076
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-http-clone-panel.yml
@@ -0,0 +1,5 @@
+---
+title: Fix missing Git clone button when protocol restriction setting enabled
+merge_request: 24015
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-issue-38317.yml b/changelogs/unreleased/sh-fix-issue-38317.yml
deleted file mode 100644
index 13fcb5b8f96..00000000000
--- a/changelogs/unreleased/sh-fix-issue-38317.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove needless auto-capitalization on Wiki page titles
-merge_request: 23288
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-issue-51220.yml b/changelogs/unreleased/sh-fix-issue-51220.yml
deleted file mode 100644
index 048f58611cb..00000000000
--- a/changelogs/unreleased/sh-fix-issue-51220.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Handle force_remove_source_branch when creating merge request
-merge_request: 23281
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-issue-53783-ce.yml b/changelogs/unreleased/sh-fix-issue-53783-ce.yml
deleted file mode 100644
index 10be1d81768..00000000000
--- a/changelogs/unreleased/sh-fix-issue-53783-ce.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix enabling project deploy key for admins
-merge_request: 23043
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-mirrors-protected-branches.yml b/changelogs/unreleased/sh-fix-mirrors-protected-branches.yml
deleted file mode 100644
index 627de25650d..00000000000
--- a/changelogs/unreleased/sh-fix-mirrors-protected-branches.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix "protected branches only" checkbox not set properly at init
-merge_request: 23409
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-handle-string-null-bytes.yml b/changelogs/unreleased/sh-handle-string-null-bytes.yml
deleted file mode 100644
index edc045274e3..00000000000
--- a/changelogs/unreleased/sh-handle-string-null-bytes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Gracefully handle references with null bytes
-merge_request: 23365
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-remove-local-sidekiq-admin-check.yml b/changelogs/unreleased/sh-remove-local-sidekiq-admin-check.yml
deleted file mode 100644
index 3ec15908fc7..00000000000
--- a/changelogs/unreleased/sh-remove-local-sidekiq-admin-check.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove display of local Sidekiq process in /admin/sidekiq
-merge_request: 23118
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-use-nakayoshi-fork.yml b/changelogs/unreleased/sh-use-nakayoshi-fork.yml
deleted file mode 100644
index 5977d9b0974..00000000000
--- a/changelogs/unreleased/sh-use-nakayoshi-fork.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve memory performance by reducing dirty pages after fork()
-merge_request: 23169
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-use-nokogiri-xml-backend.yml b/changelogs/unreleased/sh-use-nokogiri-xml-backend.yml
deleted file mode 100644
index 6a82e32c416..00000000000
--- a/changelogs/unreleased/sh-use-nokogiri-xml-backend.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use Nokogiri as the ActiveSupport XML backend
-merge_request: 23136
-author:
-type: performance
diff --git a/changelogs/unreleased/spec-positional-arguments.yml b/changelogs/unreleased/spec-positional-arguments.yml
new file mode 100644
index 00000000000..9dc114e5595
--- /dev/null
+++ b/changelogs/unreleased/spec-positional-arguments.yml
@@ -0,0 +1,5 @@
+---
+title: 'Fix deprecation: Using positional arguments in integration tests'
+merge_request: 24009
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/specs-positional-arguments.yml b/changelogs/unreleased/specs-positional-arguments.yml
new file mode 100644
index 00000000000..38b831bd72c
--- /dev/null
+++ b/changelogs/unreleased/specs-positional-arguments.yml
@@ -0,0 +1,5 @@
+---
+title: convert specs in javascripts/ and support/ to new syntax
+merge_request: 23947
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/speed-up-relative-positioning.yml b/changelogs/unreleased/speed-up-relative-positioning.yml
deleted file mode 100644
index 3bd865fb5de..00000000000
--- a/changelogs/unreleased/speed-up-relative-positioning.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Speed up issue board lists in groups with many projects
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/support-gitaly-tls.yml b/changelogs/unreleased/support-gitaly-tls.yml
new file mode 100644
index 00000000000..2a15500d6da
--- /dev/null
+++ b/changelogs/unreleased/support-gitaly-tls.yml
@@ -0,0 +1,5 @@
+---
+title: Support tls communication in gitaly
+merge_request: 22602
+author:
+type: added
diff --git a/changelogs/unreleased/switch-rails.yml b/changelogs/unreleased/switch-rails.yml
deleted file mode 100644
index 4edf709dbd4..00000000000
--- a/changelogs/unreleased/switch-rails.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Switch to Rails 5
-merge_request: 21492
-author:
-type: other
diff --git a/changelogs/unreleased/tc-backfill-full-path-config.yml b/changelogs/unreleased/tc-backfill-full-path-config.yml
deleted file mode 100644
index 4f06284d0e3..00000000000
--- a/changelogs/unreleased/tc-backfill-full-path-config.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Migration to write fullpath in all repository configs
-merge_request: 22322
-author:
-type: other
diff --git a/changelogs/unreleased/tc-repo-full-path-in-db.yml b/changelogs/unreleased/tc-repo-full-path-in-db.yml
deleted file mode 100644
index ead8feabeb9..00000000000
--- a/changelogs/unreleased/tc-repo-full-path-in-db.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add model and relation to store repo full path in database
-merge_request: 23143
-author:
-type: added
diff --git a/changelogs/unreleased/triggermesh-knative-version.yml b/changelogs/unreleased/triggermesh-knative-version.yml
new file mode 100644
index 00000000000..27f400962da
--- /dev/null
+++ b/changelogs/unreleased/triggermesh-knative-version.yml
@@ -0,0 +1,5 @@
+---
+title: Knative version bump 0.1.3 -> 0.2.2
+merge_request:
+author: Chris Baumbauer
+type: changed
diff --git a/changelogs/unreleased/triggermesh-phase2-external-ip.yml b/changelogs/unreleased/triggermesh-phase2-external-ip.yml
deleted file mode 100644
index 582c8f6df2e..00000000000
--- a/changelogs/unreleased/triggermesh-phase2-external-ip.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add an external IP address to the knative cluster application page
-merge_request:
-author: Chris Baumbauer
-type: fixed
diff --git a/changelogs/unreleased/triggermesh-phase2-knative-description.yml b/changelogs/unreleased/triggermesh-phase2-knative-description.yml
deleted file mode 100644
index c6cee1984d5..00000000000
--- a/changelogs/unreleased/triggermesh-phase2-knative-description.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Modify the wording for the knative cluster application to match upstream
-merge_request: 23289
-author: Chris Baumbauer
-type: fixed
diff --git a/changelogs/unreleased/triggermesh-phase2-serverless.yml b/changelogs/unreleased/triggermesh-phase2-serverless.yml
deleted file mode 100644
index bee2b5e1e2c..00000000000
--- a/changelogs/unreleased/triggermesh-phase2-serverless.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add knative client to kubeclient library
-merge_request: 22968
-author: cab105
-type: added
diff --git a/changelogs/unreleased/unicorn-monkey-patch.yml b/changelogs/unreleased/unicorn-monkey-patch.yml
deleted file mode 100644
index 6b0e00ca291..00000000000
--- a/changelogs/unreleased/unicorn-monkey-patch.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add monkey patch to unicorn to fix eof? problem
-merge_request: 23385
-author:
-type: fixed
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-version.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-version.yml
deleted file mode 100644
index 9051e4f79c8..00000000000
--- a/changelogs/unreleased/update-gitlab-runner-helm-chart-version.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update used version of Runner Helm Chart to 0.1.38
-merge_request: 23304
-author:
-type: other
diff --git a/changelogs/unreleased/upgrade_kubeclient_400.yml b/changelogs/unreleased/upgrade_kubeclient_400.yml
deleted file mode 100644
index edb38710e6a..00000000000
--- a/changelogs/unreleased/upgrade_kubeclient_400.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade kubeclient to 4.0.0
-merge_request: 23261
-author: Praveen Arimbrathodiyil @pravi
-type: other
diff --git a/changelogs/unreleased/validate-foreign-keys-being-indexed.yml b/changelogs/unreleased/validate-foreign-keys-being-indexed.yml
deleted file mode 100644
index 6608a93c08f..00000000000
--- a/changelogs/unreleased/validate-foreign-keys-being-indexed.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Validate foreign keys being created and indexed for column with _id
-merge_request: 22808
-author:
-type: performance
diff --git a/changelogs/unreleased/winh-collapse-discussions.yml b/changelogs/unreleased/winh-collapse-discussions.yml
deleted file mode 100644
index 19d04506318..00000000000
--- a/changelogs/unreleased/winh-collapse-discussions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix collapsing discussion replies
-merge_request: 23462
-author:
-type: fixed
diff --git a/changelogs/unreleased/winh-divider-margin.yml b/changelogs/unreleased/winh-divider-margin.yml
deleted file mode 100644
index db84090c15c..00000000000
--- a/changelogs/unreleased/winh-divider-margin.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adjust divider margin to comply with design specs
-merge_request: 23548
-author:
-type: changed
diff --git a/changelogs/unreleased/winh-dropdown-title-padding.yml b/changelogs/unreleased/winh-dropdown-title-padding.yml
new file mode 100644
index 00000000000..9d65175b536
--- /dev/null
+++ b/changelogs/unreleased/winh-dropdown-title-padding.yml
@@ -0,0 +1,5 @@
+---
+title: Adjust padding of .dropdown-title to comply with design specs
+merge_request: 23546
+author:
+type: changed
diff --git a/changelogs/unreleased/winh-merge-request-commit-context.yml b/changelogs/unreleased/winh-merge-request-commit-context.yml
new file mode 100644
index 00000000000..9e12a926af4
--- /dev/null
+++ b/changelogs/unreleased/winh-merge-request-commit-context.yml
@@ -0,0 +1,5 @@
+---
+title: Display commit ID for discussions made on merge request commits
+merge_request: 23837
+author:
+type: fixed
diff --git a/changelogs/unreleased/winh-merge-request-commit-discussion.yml b/changelogs/unreleased/winh-merge-request-commit-discussion.yml
deleted file mode 100644
index b0c6264369b..00000000000
--- a/changelogs/unreleased/winh-merge-request-commit-discussion.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display commit ID for commit diff discussion on merge request
-merge_request: 23370
-author:
-type: fixed
diff --git a/changelogs/unreleased/winh-princess-mononospace.yml b/changelogs/unreleased/winh-princess-mononospace.yml
new file mode 100644
index 00000000000..e2d33de375e
--- /dev/null
+++ b/changelogs/unreleased/winh-princess-mononospace.yml
@@ -0,0 +1,5 @@
+---
+title: Make commit IDs in merge request discussion header monospace
+merge_request: 23562
+author:
+type: changed
diff --git a/changelogs/unreleased/winh-upgrade-gitlab-ui.yml b/changelogs/unreleased/winh-upgrade-gitlab-ui.yml
new file mode 100644
index 00000000000..b312a329f5d
--- /dev/null
+++ b/changelogs/unreleased/winh-upgrade-gitlab-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade @gitlab/ui to 1.16.2
+merge_request: 23946
+author:
+type: other
diff --git a/changelogs/unreleased/workhorse-7-3-0.yml b/changelogs/unreleased/workhorse-7-3-0.yml
deleted file mode 100644
index 6708b8a3cbb..00000000000
--- a/changelogs/unreleased/workhorse-7-3-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade GitLab Workhorse to v7.3.0
-merge_request: 23489
-author:
-type: other
diff --git a/changelogs/unreleased/zj-backup-restore-object-pools.yml b/changelogs/unreleased/zj-backup-restore-object-pools.yml
new file mode 100644
index 00000000000..26e1d49aa04
--- /dev/null
+++ b/changelogs/unreleased/zj-backup-restore-object-pools.yml
@@ -0,0 +1,5 @@
+---
+title: Restore Object Pools when restoring an object pool
+merge_request: 23682
+author:
+type: added
diff --git a/changelogs/unreleased/zj-improve-gitaly-pb.yml b/changelogs/unreleased/zj-improve-gitaly-pb.yml
deleted file mode 100644
index 506a0303d8a..00000000000
--- a/changelogs/unreleased/zj-improve-gitaly-pb.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Show what RPC is called in the performance bar
-merge_request: 23140
-author:
-type: other
diff --git a/changelogs/unreleased/zj-remove-broken-storage.yml b/changelogs/unreleased/zj-remove-broken-storage.yml
deleted file mode 100644
index 9df87b40e09..00000000000
--- a/changelogs/unreleased/zj-remove-broken-storage.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove obsolete gitlab_shell rake tasks
-merge_request: 22417
-author:
-type: removed
diff --git a/config/application.rb b/config/application.rb
index 63a5b483fc2..349c7258852 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -5,12 +5,6 @@ require 'rails/all'
Bundler.require(:default, Rails.env)
module Gitlab
- # This method is used for smooth upgrading from the current Rails 4.x to Rails 5.0.
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/14286
- def self.rails5?
- !%w[0 false].include?(ENV["RAILS5"])
- end
-
class Application < Rails::Application
require_dependency Rails.root.join('lib/gitlab/redis/wrapper')
require_dependency Rails.root.join('lib/gitlab/redis/cache')
@@ -26,9 +20,6 @@ module Gitlab
# setting disabled
require_dependency Rails.root.join('lib/mysql_zero_date')
- # This can be removed when we drop support for rails 4
- require_dependency Rails.root.join('lib/rails4_migration_version')
-
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
@@ -86,7 +77,7 @@ module Gitlab
# namespaces/users.
# https://github.com/rails/rails/blob/5-0-stable/actioncable/lib/action_cable.rb#L38
# Please change this value when configuring ActionCable for real usage.
- config.action_cable.mount_path = "/-/cable" if rails5?
+ config.action_cable.mount_path = "/-/cable"
# Configure sensitive parameters which will be filtered from the log file.
#
@@ -154,6 +145,7 @@ module Gitlab
config.assets.precompile << "locale/**/app.js"
config.assets.precompile << "emoji_sprites.css"
config.assets.precompile << "errors.css"
+ config.assets.precompile << "csslab.css"
# Import gitlab-svgs directly from vendored directory
config.assets.paths << "#{config.root}/node_modules/@gitlab/svgs/dist"
@@ -212,8 +204,6 @@ module Gitlab
config.cache_store = :redis_store, caching_config_hash
- config.active_record.raise_in_transactional_callbacks = true unless rails5?
-
config.active_job.queue_adapter = :sidekiq
# This is needed for gitlab-shell
diff --git a/config/boot.rb b/config/boot.rb
index 725473ac7f6..2811f0e6188 100644
--- a/config/boot.rb
+++ b/config/boot.rb
@@ -1,11 +1,4 @@
-def rails5?
- !%w[0 false].include?(ENV["RAILS5"])
-end
-
-require 'rubygems' unless rails5?
-
-gemfile = rails5? ? "Gemfile" : "Gemfile.rails4"
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../#{gemfile}", __dir__)
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
# Set up gems listed in the Gemfile.
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
diff --git a/config/dependency_decisions.yml b/config/dependency_decisions.yml
index 84d47bd52ad..af76bace577 100644
--- a/config/dependency_decisions.yml
+++ b/config/dependency_decisions.yml
@@ -470,8 +470,8 @@
- - :license
- pikaday
- MIT
- - :who:
- :why:
+ - :who: Filipa Lacerda
+ :why: MIT License
:versions: []
:when: 2017-10-17 17:46:12.367554000 Z
- - :license
@@ -592,3 +592,10 @@
in compiled/distributed product so attribution not needed.
:versions: []
:when: 2018-10-02 19:23:54.840151000 Z
+- - :license
+ - echarts
+ - Apache 2.0
+ - :who: Adriel Santiago
+ :why: https://github.com/apache/incubator-echarts/blob/master/LICENSE
+ :versions: []
+ :when: 2018-12-07 20:46:12.421256000 Z
diff --git a/config/environment.rb b/config/environment.rb
index 3a52656a2c1..7e55c7803d3 100644
--- a/config/environment.rb
+++ b/config/environment.rb
@@ -1,11 +1,6 @@
# Load the rails application
-# Remove this condition when upgraded to rails 5.0.
-if %w[0 false].include?(ENV["RAILS5"])
- require File.expand_path('application', __dir__)
-else
- require_relative 'application'
-end
+require_relative 'application'
# Initialize the rails application
Rails.application.initialize!
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 49a4e873093..09bcf49a9a5 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -9,11 +9,7 @@ Rails.application.configure do
config.action_controller.perform_caching = true
# Disable Rails's static asset server (Apache or nginx will already do this)
- if Gitlab.rails5?
- config.public_file_server.enabled = false
- else
- config.serve_static_files = false
- end
+ config.public_file_server.enabled = false
# Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier
diff --git a/config/environments/test.rb b/config/environments/test.rb
index 072f93150a3..3461099253a 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -19,13 +19,8 @@ Rails.application.configure do
# Configure static asset server for tests with Cache-Control for performance
config.assets.compile = false if ENV['CI']
- if Gitlab.rails5?
- config.public_file_server.enabled = true
- config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' }
- else
- config.serve_static_files = true
- config.static_cache_control = "public, max-age=3600"
- end
+ config.public_file_server.enabled = true
+ config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' }
# Show full error reports and disable caching
config.consider_all_requests_local = true
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 58b7c248aaf..7fe85f0e0d7 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -548,15 +548,15 @@ production: &base
# app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET' }
# - { name: 'jwt',
- # app_secret: 'YOUR_APP_SECRET',
# args: {
- # algorithm: 'HS256',
- # uid_claim: 'email',
- # required_claims: ["name", "email"],
- # info_map: { name: "name", email: "email" },
- # auth_url: 'https://example.com/',
- # valid_within: null,
- # }
+ # secret: 'YOUR_APP_SECRET',
+ # algorithm: 'HS256', # Supported algorithms: 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512'
+ # uid_claim: 'email',
+ # required_claims: ['name', 'email'],
+ # info_map: { name: 'name', email: 'email' },
+ # auth_url: 'https://example.com/',
+ # valid_within: 3600 # 1 hour
+ # }
# }
# - { name: 'saml',
# label: 'Our SAML Provider',
@@ -612,7 +612,7 @@ production: &base
storages: # You must have at least a `default` storage path.
default:
path: /home/git/repositories/
- gitaly_address: unix:/home/git/gitlab/tmp/sockets/private/gitaly.socket # TCP connections are supported too (e.g. tcp://host:port)
+ gitaly_address: unix:/home/git/gitlab/tmp/sockets/private/gitaly.socket # TCP connections are supported too (e.g. tcp://host:port). TLS connections are also supported using the system certificate pool (eg: tls://host:port).
# gitaly_token: 'special token' # Optional: override global gitaly.token for this storage.
## Backup settings
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 82e3b490378..db35fa96ea2 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -302,10 +302,6 @@ Settings.cron_jobs['gitlab_usage_ping_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['gitlab_usage_ping_worker']['cron'] ||= Settings.__send__(:cron_for_usage_ping)
Settings.cron_jobs['gitlab_usage_ping_worker']['job_class'] = 'GitlabUsagePingWorker'
-Settings.cron_jobs['remove_old_web_hook_logs_worker'] ||= Settingslogic.new({})
-Settings.cron_jobs['remove_old_web_hook_logs_worker']['cron'] ||= '40 0 * * *'
-Settings.cron_jobs['remove_old_web_hook_logs_worker']['job_class'] = 'RemoveOldWebHookLogsWorker'
-
Settings.cron_jobs['stuck_merge_jobs_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['stuck_merge_jobs_worker']['cron'] ||= '0 */2 * * *'
Settings.cron_jobs['stuck_merge_jobs_worker']['job_class'] = 'StuckMergeJobsWorker'
diff --git a/config/initializers/devise.rb b/config/initializers/8_devise.rb
index 67eabb0b4fc..67eabb0b4fc 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/8_devise.rb
diff --git a/config/initializers/active_record_array_type_casting.rb b/config/initializers/active_record_array_type_casting.rb
deleted file mode 100644
index a149e048ee2..00000000000
--- a/config/initializers/active_record_array_type_casting.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# Remove this initializer when upgraded to Rails 5.0
-unless Gitlab.rails5?
- module ActiveRecord
- class PredicateBuilder
- class ArrayHandler
- module TypeCasting
- def call(attribute, value)
- # This is necessary because by default ActiveRecord does not respect
- # custom type definitions (like our `ShaAttribute`) when providing an
- # array in `where`, like in `where(commit_sha: [sha1, sha2, sha3])`.
- model = attribute.relation&.engine
- type = model.user_provided_columns[attribute.name] if model
- value = value.map { |value| type.type_cast_for_database(value) } if type
-
- super(attribute, value)
- end
- end
-
- prepend TypeCasting
- end
- end
- end
-end
diff --git a/config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb b/config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb
index ef4abb77bd7..3e765469995 100644
--- a/config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb
+++ b/config/initializers/active_record_avoid_type_casting_in_uniqueness_validator.rb
@@ -20,75 +20,73 @@
#
# This bug was fixed in Rails 5.1 by https://github.com/rails/rails/pull/24745/commits/aa062318c451512035c10898a1af95943b1a3803
-if Gitlab.rails5?
- if Rails.version.start_with?("5.1")
- raise "Remove this monkey patch: #{__FILE__}"
- end
+if Rails.version.start_with?("5.1")
+ raise "Remove this monkey patch: #{__FILE__}"
+end
- # Copy-paste from https://github.com/kamipo/rails/blob/aa062318c451512035c10898a1af95943b1a3803/activerecord/lib/active_record/validations/uniqueness.rb
- # including local fixes to make Rubocop happy again.
- module ActiveRecord
- module Validations
- class UniquenessValidator < ActiveModel::EachValidator # :nodoc:
- def validate_each(record, attribute, value)
- finder_class = find_finder_class_for(record)
- table = finder_class.arel_table
- value = map_enum_attribute(finder_class, attribute, value)
+# Copy-paste from https://github.com/kamipo/rails/blob/aa062318c451512035c10898a1af95943b1a3803/activerecord/lib/active_record/validations/uniqueness.rb
+# including local fixes to make Rubocop happy again.
+module ActiveRecord
+ module Validations
+ class UniquenessValidator < ActiveModel::EachValidator # :nodoc:
+ def validate_each(record, attribute, value)
+ finder_class = find_finder_class_for(record)
+ table = finder_class.arel_table
+ value = map_enum_attribute(finder_class, attribute, value)
- relation = build_relation(finder_class, table, attribute, value)
+ relation = build_relation(finder_class, table, attribute, value)
- if record.persisted?
- if finder_class.primary_key
- relation = relation.where.not(finder_class.primary_key => record.id_was || record.id)
- else
- raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
- end
+ if record.persisted?
+ if finder_class.primary_key
+ relation = relation.where.not(finder_class.primary_key => record.id_was || record.id)
+ else
+ raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
end
+ end
- relation = scope_relation(record, table, relation)
- relation = relation.merge(options[:conditions]) if options[:conditions]
+ relation = scope_relation(record, table, relation)
+ relation = relation.merge(options[:conditions]) if options[:conditions]
- if relation.exists?
- error_options = options.except(:case_sensitive, :scope, :conditions)
- error_options[:value] = value
+ if relation.exists?
+ error_options = options.except(:case_sensitive, :scope, :conditions)
+ error_options[:value] = value
- record.errors.add(attribute, :taken, error_options)
- end
- rescue RangeError
+ record.errors.add(attribute, :taken, error_options)
end
+ rescue RangeError
+ end
- protected
-
- def build_relation(klass, table, attribute, value) #:nodoc:
- if reflection = klass._reflect_on_association(attribute)
- attribute = reflection.foreign_key
- value = value.attributes[reflection.klass.primary_key] unless value.nil?
- end
+ protected
- # the attribute may be an aliased attribute
- if klass.attribute_alias?(attribute)
- attribute = klass.attribute_alias(attribute)
- end
+ def build_relation(klass, table, attribute, value) #:nodoc:
+ if reflection = klass._reflect_on_association(attribute)
+ attribute = reflection.foreign_key
+ value = value.attributes[reflection.klass.primary_key] unless value.nil?
+ end
- attribute_name = attribute.to_s
+ # the attribute may be an aliased attribute
+ if klass.attribute_alias?(attribute)
+ attribute = klass.attribute_alias(attribute)
+ end
- column = klass.columns_hash[attribute_name]
- cast_type = klass.type_for_attribute(attribute_name)
+ attribute_name = attribute.to_s
- comparison =
- if !options[:case_sensitive] && !value.nil?
- # will use SQL LOWER function before comparison, unless it detects a case insensitive collation
- klass.connection.case_insensitive_comparison(table, attribute, column, value)
- else
- klass.connection.case_sensitive_comparison(table, attribute, column, value)
- end
+ column = klass.columns_hash[attribute_name]
+ cast_type = klass.type_for_attribute(attribute_name)
- if value.nil?
- klass.unscoped.where(comparison)
+ comparison =
+ if !options[:case_sensitive] && !value.nil?
+ # will use SQL LOWER function before comparison, unless it detects a case insensitive collation
+ klass.connection.case_insensitive_comparison(table, attribute, column, value)
else
- bind = Relation::QueryAttribute.new(attribute_name, value, cast_type)
- klass.unscoped.where(comparison, bind)
+ klass.connection.case_sensitive_comparison(table, attribute, column, value)
end
+
+ if value.nil?
+ klass.unscoped.where(comparison)
+ else
+ bind = Relation::QueryAttribute.new(attribute_name, value, cast_type)
+ klass.unscoped.where(comparison, bind)
end
end
end
diff --git a/config/initializers/active_record_data_types.rb b/config/initializers/active_record_data_types.rb
index 717e30b5b7e..e95157bfde5 100644
--- a/config/initializers/active_record_data_types.rb
+++ b/config/initializers/active_record_data_types.rb
@@ -65,7 +65,7 @@ elsif Gitlab::Database.mysql?
prepend RegisterDateTimeWithTimeZone
# Add the class `DateTimeWithTimeZone` so we can map `timestamp` to it.
- class MysqlDateTimeWithTimeZone < (Gitlab.rails5? ? ActiveRecord::Type::DateTime : MysqlDateTime)
+ class MysqlDateTimeWithTimeZone < ActiveRecord::Type::DateTime
def type
:datetime_with_timezone
end
diff --git a/config/initializers/active_record_locking.rb b/config/initializers/active_record_locking.rb
index 21ff323927b..bfe41e6029a 100644
--- a/config/initializers/active_record_locking.rb
+++ b/config/initializers/active_record_locking.rb
@@ -64,21 +64,13 @@ module ActiveRecord
# This is patched because we want `lock_version` default to `NULL`
# rather than `0`
- if Gitlab.rails5?
- class LockingType
- def deserialize(value)
- super
- end
-
- def serialize(value)
- super
- end
+ class LockingType
+ def deserialize(value)
+ super
end
- else
- class LockingType < SimpleDelegator
- def type_cast_from_database(value)
- super
- end
+
+ def serialize(value)
+ super
end
end
end
diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb
deleted file mode 100644
index a65f8aecf9e..00000000000
--- a/config/initializers/application_controller_renderer.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# Remove this `if` condition when upgraded to rails 5.0.
-# The body must be kept.
-if Gitlab.rails5?
- # Be sure to restart your server when you modify this file.
-
- # ActiveSupport::Reloader.to_prepare do
- # ApplicationController.renderer.defaults.merge!(
- # http_host: 'example.org',
- # https: false
- # )
- # end
-end
diff --git a/config/initializers/ar5_batching.rb b/config/initializers/ar5_batching.rb
deleted file mode 100644
index 874455ce5af..00000000000
--- a/config/initializers/ar5_batching.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-# Remove this file when upgraded to rails 5.0.
-unless Gitlab.rails5?
- module ActiveRecord
- module Batches
- # Differences from upstream: enumerator support was removed, and custom
- # order/limit clauses are ignored without a warning.
- def in_batches(of: 1000, start: nil, finish: nil, load: false)
- raise "Must provide a block" unless block_given?
-
- relation = self.reorder(batch_order).limit(of)
- relation = relation.where(arel_table[primary_key].gteq(start)) if start
- relation = relation.where(arel_table[primary_key].lteq(finish)) if finish
- batch_relation = relation
-
- loop do
- if load
- records = batch_relation.records
- ids = records.map(&:id)
- yielded_relation = self.where(primary_key => ids)
- yielded_relation.load_records(records)
- else
- ids = batch_relation.pluck(primary_key)
- yielded_relation = self.where(primary_key => ids)
- end
-
- break if ids.empty?
-
- primary_key_offset = ids.last
- raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
-
- yield yielded_relation
-
- break if ids.length < of
-
- batch_relation = relation.where(arel_table[primary_key].gt(primary_key_offset))
- end
- end
- end
- end
-end
diff --git a/config/initializers/ar5_pg_10_support.rb b/config/initializers/ar5_pg_10_support.rb
deleted file mode 100644
index 40548290ce8..00000000000
--- a/config/initializers/ar5_pg_10_support.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-# Remove this file when upgraded to rails 5.0.
-if !Gitlab.rails5? && Gitlab::Database.postgresql?
- require 'active_record/connection_adapters/postgresql_adapter'
- require 'active_record/connection_adapters/postgresql/schema_statements'
-
- #
- # Monkey-patch the refused Rails 4.2 patch at https://github.com/rails/rails/pull/31330
- #
- # Updates sequence logic to support PostgreSQL 10.
- #
- # rubocop:disable all
- module ActiveRecord
- module ConnectionAdapters
-
- # We need #postgresql_version to be public as in ActiveRecord 5 for seed_fu
- # to work. In ActiveRecord 4, it is protected.
- # https://github.com/mbleigh/seed-fu/issues/123
- class PostgreSQLAdapter
- public :postgresql_version
- end
-
- module PostgreSQL
- module SchemaStatements
- # Resets the sequence of a table's primary key to the maximum value.
- def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
- unless pk and sequence
- default_pk, default_sequence = pk_and_sequence_for(table)
-
- pk ||= default_pk
- sequence ||= default_sequence
- end
-
- if @logger && pk && !sequence
- @logger.warn "#{table} has primary key #{pk} with no default sequence"
- end
-
- if pk && sequence
- quoted_sequence = quote_table_name(sequence)
- max_pk = select_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}")
- if max_pk.nil?
- if postgresql_version >= 100000
- minvalue = select_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass")
- else
- minvalue = select_value("SELECT min_value FROM #{quoted_sequence}")
- end
- end
-
- select_value <<-end_sql, 'SCHEMA'
- SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})
- end_sql
- end
- end
- end
- end
- end
- end
- # rubocop:enable all
-end
diff --git a/config/initializers/carrierwave_patch.rb b/config/initializers/carrierwave_patch.rb
deleted file mode 100644
index 35ffff03abe..00000000000
--- a/config/initializers/carrierwave_patch.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# This monkey patches CarrierWave 1.2.3 to make Google Cloud Storage work with
-# extra query parameters:
-# https://github.com/carrierwaveuploader/carrierwave/pull/2332/files
-module CarrierWave
- module Storage
- class Fog < Abstract
- class File
- def authenticated_url(options = {})
- if %w(AWS Google Rackspace OpenStack).include?(@uploader.fog_credentials[:provider])
- # avoid a get by using local references
- local_directory = connection.directories.new(key: @uploader.fog_directory)
- local_file = local_directory.files.new(key: path)
- expire_at = ::Fog::Time.now + @uploader.fog_authenticated_url_expiration
- case @uploader.fog_credentials[:provider]
- when 'AWS', 'Google'
- local_file.url(expire_at, options)
- when 'Rackspace'
- connection.get_object_https_url(@uploader.fog_directory, path, expire_at, options)
- when 'OpenStack'
- connection.get_object_https_url(@uploader.fog_directory, path, expire_at)
- else
- local_file.url(expire_at)
- end
- end
- end
- end
- end
- end
-end
diff --git a/config/initializers/correlation_id.rb b/config/initializers/correlation_id.rb
new file mode 100644
index 00000000000..2a7c138dc40
--- /dev/null
+++ b/config/initializers/correlation_id.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+Rails.application.config.middleware.use(Gitlab::Middleware::CorrelationId)
diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb
index 840404e0ec0..c897bc30e76 100644
--- a/config/initializers/lograge.rb
+++ b/config/initializers/lograge.rb
@@ -29,6 +29,7 @@ unless Sidekiq.server?
gitaly_calls = Gitlab::GitalyClient.get_request_count
payload[:gitaly_calls] = gitaly_calls if gitaly_calls > 0
payload[:response] = event.payload[:response] if event.payload[:response]
+ payload[Gitlab::CorrelationId::LOG_KEY] = Gitlab::CorrelationId.current_id
payload
end
diff --git a/config/initializers/mysql_set_length_for_binary_indexes.rb b/config/initializers/mysql_set_length_for_binary_indexes.rb
index 0445d8fcae2..552f3a20a95 100644
--- a/config/initializers/mysql_set_length_for_binary_indexes.rb
+++ b/config/initializers/mysql_set_length_for_binary_indexes.rb
@@ -2,28 +2,6 @@
# MySQL adapter apply a length of 20. Otherwise MySQL can't create an index on
# binary columns.
-# This module can be removed once a Rails 5 schema is used.
-# It can't be wrapped in a check that checks Gitlab.rails5? because
-# the old Rails 4 schema layout is still used
-module MysqlSetLengthForBinaryIndex
- def add_index(table_name, column_names, options = {})
- options[:length] ||= {}
- Array(column_names).each do |column_name|
- column = ActiveRecord::Base.connection.columns(table_name).find { |c| c.name == column_name }
-
- if column&.type == :binary
- options[:length][column_name] = 20
- end
- end
-
- super(table_name, column_names, options)
- end
-end
-
-if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
- ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:prepend, MysqlSetLengthForBinaryIndex)
-end
-
module MysqlSetLengthForBinaryIndexAndIgnorePostgresOptionsForSchema
# This method is used in Rails 5 schema loading as t.index
def index(column_names, options = {})
@@ -34,15 +12,6 @@ module MysqlSetLengthForBinaryIndexAndIgnorePostgresOptionsForSchema
return
end
- # when running rails 4 with rails 5 schema, rails 4 doesn't support multiple
- # indexes on the same set of columns. Mysql doesn't support partial indexes, so if
- # an index already exists and we add another index, skip it if it's partial:
- # see https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21492#note_102821326
- if !Gitlab.rails5? && indexes[column_names] && options[:where]
- warn "WARNING: index on columns #{column_names} already exists and partial index is not supported, skipping."
- return
- end
-
options[:length] ||= {}
Array(column_names).each do |column_name|
column = columns.find { |c| c.name == column_name }
@@ -56,14 +25,6 @@ module MysqlSetLengthForBinaryIndexAndIgnorePostgresOptionsForSchema
end
end
-def mysql_adapter?
- defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) && ActiveRecord::Base.connection.is_a?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
-end
-
-if Gitlab.rails5?
- if defined?(ActiveRecord::ConnectionAdapters::MySQL::TableDefinition)
- ActiveRecord::ConnectionAdapters::MySQL::TableDefinition.send(:prepend, MysqlSetLengthForBinaryIndexAndIgnorePostgresOptionsForSchema)
- end
-elsif mysql_adapter? && defined?(ActiveRecord::ConnectionAdapters::TableDefinition)
- ActiveRecord::ConnectionAdapters::TableDefinition.send(:prepend, MysqlSetLengthForBinaryIndexAndIgnorePostgresOptionsForSchema)
+if defined?(ActiveRecord::ConnectionAdapters::MySQL::TableDefinition)
+ ActiveRecord::ConnectionAdapters::MySQL::TableDefinition.send(:prepend, MysqlSetLengthForBinaryIndexAndIgnorePostgresOptionsForSchema)
end
diff --git a/config/initializers/new_framework_defaults.rb b/config/initializers/new_framework_defaults.rb
index 2d130bc0bf8..5adb9f7a4b4 100644
--- a/config/initializers/new_framework_defaults.rb
+++ b/config/initializers/new_framework_defaults.rb
@@ -1,29 +1,27 @@
# Remove this `if` condition when upgraded to rails 5.0.
# The body must be kept.
-if Gitlab.rails5?
- # Be sure to restart your server when you modify this file.
- #
- # This file contains migration options to ease your Rails 5.0 upgrade.
- #
- # Once upgraded flip defaults one by one to migrate to the new default.
- #
- # Read the Guide for Upgrading Ruby on Rails for more info on each option.
+# Be sure to restart your server when you modify this file.
+#
+# This file contains migration options to ease your Rails 5.0 upgrade.
+#
+# Once upgraded flip defaults one by one to migrate to the new default.
+#
+# Read the Guide for Upgrading Ruby on Rails for more info on each option.
- Rails.application.config.action_controller.raise_on_unfiltered_parameters = true
+Rails.application.config.action_controller.raise_on_unfiltered_parameters = true
- # Enable per-form CSRF tokens. Previous versions had false.
- Rails.application.config.action_controller.per_form_csrf_tokens = false
+# Enable per-form CSRF tokens. Previous versions had false.
+Rails.application.config.action_controller.per_form_csrf_tokens = false
- # Enable origin-checking CSRF mitigation. Previous versions had false.
- Rails.application.config.action_controller.forgery_protection_origin_check = false
+# Enable origin-checking CSRF mitigation. Previous versions had false.
+Rails.application.config.action_controller.forgery_protection_origin_check = false
- # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`.
- # Previous versions had false.
- ActiveSupport.to_time_preserves_timezone = false
+# Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`.
+# Previous versions had false.
+ActiveSupport.to_time_preserves_timezone = false
- # Require `belongs_to` associations by default. Previous versions had false.
- Rails.application.config.active_record.belongs_to_required_by_default = false
+# Require `belongs_to` associations by default. Previous versions had false.
+Rails.application.config.active_record.belongs_to_required_by_default = false
- # Do not halt callback chains when a callback returns false. Previous versions had true.
- ActiveSupport.halt_callback_chains_on_return_false = true
-end
+# Do not halt callback chains when a callback returns false. Previous versions had true.
+ActiveSupport.halt_callback_chains_on_return_false = true
diff --git a/config/initializers/postgresql_opclasses_support.rb b/config/initializers/postgresql_opclasses_support.rb
index 07b06629dea..70b530415f5 100644
--- a/config/initializers/postgresql_opclasses_support.rb
+++ b/config/initializers/postgresql_opclasses_support.rb
@@ -41,10 +41,7 @@ module ActiveRecord
# Abstract representation of an index definition on a table. Instances of
# this type are typically created and returned by methods in database
# adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
- attrs = [:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :opclasses]
-
- # In Rails 5 the second last attribute is newly `:comment`
- attrs.insert(-2, :comment) if Gitlab.rails5?
+ attrs = [:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment, :opclasses]
class IndexDefinition < Struct.new(*attrs) #:nodoc:
end
@@ -81,7 +78,7 @@ module ActiveRecord
if index_name.length > max_index_length
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
end
- if table_exists?(table_name) && index_name_exists?(table_name, index_name, false)
+ if data_source_exists?(table_name) && index_name_exists?(table_name, index_name, false)
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
@@ -112,15 +109,8 @@ module ActiveRecord
result.map do |row|
index_name = row[0]
- unique = if Gitlab.rails5?
- row[1]
- else
- row[1] == 't'
- end
- indkey = row[2].split(" ")
- if Gitlab.rails5?
- indkey = indkey.map(&:to_i)
- end
+ unique = row[1]
+ indkey = row[2].split(" ").map(&:to_i)
inddef = row[3]
oid = row[4]
@@ -144,8 +134,7 @@ module ActiveRecord
[column, opclass] if opclass
end.compact]
- index_attrs = [table_name, index_name, unique, column_names, [], orders, where, nil, using, opclasses]
- index_attrs.insert(-2, nil) if Gitlab.rails5? # include index comment for Rails 5
+ index_attrs = [table_name, index_name, unique, column_names, [], orders, where, nil, using, nil, opclasses]
IndexDefinition.new(*index_attrs)
end
@@ -205,7 +194,7 @@ module ActiveRecord
index_parts << "using: #{index.using.inspect}" if index.using
index_parts << "type: #{index.type.inspect}" if index.type
index_parts << "opclasses: #{index.opclasses.inspect}" if index.opclasses.present?
- index_parts << "comment: #{index.comment.inspect}" if Gitlab.rails5? && index.comment
+ index_parts << "comment: #{index.comment.inspect}" if index.comment
index_parts
end
diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb
index 17d09293205..2a6c5148f71 100644
--- a/config/initializers/sentry.rb
+++ b/config/initializers/sentry.rb
@@ -24,4 +24,4 @@ def configure_sentry
end
end
-configure_sentry if Rails.env.production?
+configure_sentry if Rails.env.production? || Rails.env.development?
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index 4210be2c701..be4183f39be 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -21,6 +21,7 @@ Sidekiq.configure_server do |config|
chain.add Gitlab::SidekiqMiddleware::Shutdown
chain.add Gitlab::SidekiqMiddleware::RequestStoreMiddleware unless ENV['SIDEKIQ_REQUEST_STORE'] == '0'
chain.add Gitlab::SidekiqMiddleware::BatchLoader
+ chain.add Gitlab::SidekiqMiddleware::CorrelationLogger
chain.add Gitlab::SidekiqStatus::ServerMiddleware
end
@@ -31,6 +32,7 @@ Sidekiq.configure_server do |config|
config.client_middleware do |chain|
chain.add Gitlab::SidekiqStatus::ClientMiddleware
+ chain.add Gitlab::SidekiqMiddleware::CorrelationInjector
end
config.on :startup do
@@ -39,8 +41,10 @@ Sidekiq.configure_server do |config|
ActiveRecord::Base.clear_all_connections!
end
- if Feature.enabled?(:gitlab_sidekiq_reliable_fetcher)
- Sidekiq::ReliableFetcher.setup_reliable_fetch!(config)
+ if Feature::FlipperFeature.table_exists? && Feature.enabled?(:gitlab_sidekiq_reliable_fetcher)
+ # By default we're going to use Semi Reliable Fetch
+ config.options[:semi_reliable_fetch] = Feature.enabled?(:gitlab_sidekiq_enable_semi_reliable_fetcher, default_enabled: true)
+ Sidekiq::ReliableFetch.setup_reliable_fetch!(config)
end
# Sidekiq-cron: load recurring jobs from gitlab.yml
@@ -75,6 +79,7 @@ Sidekiq.configure_client do |config|
config.redis = queues_config_hash
config.client_middleware do |chain|
+ chain.add Gitlab::SidekiqMiddleware::CorrelationInjector
chain.add Gitlab::SidekiqStatus::ClientMiddleware
end
end
diff --git a/config/initializers/static_files.rb b/config/initializers/static_files.rb
index a0b8b68f3ef..e02f0868e9f 100644
--- a/config/initializers/static_files.rb
+++ b/config/initializers/static_files.rb
@@ -1,26 +1,17 @@
app = Rails.application
-if (Gitlab.rails5? && app.config.public_file_server.enabled) || app.config.serve_static_files
+if app.config.public_file_server.enabled
# The `ActionDispatch::Static` middleware intercepts requests for static files
# by checking if they exist in the `/public` directory.
# We're replacing it with our `Gitlab::Middleware::Static` that does the same,
# except ignoring `/uploads`, letting those go through to the GitLab Rails app.
- if Gitlab.rails5?
- app.config.middleware.swap(
- ActionDispatch::Static,
- Gitlab::Middleware::Static,
- app.paths["public"].first,
- headers: app.config.public_file_server.headers
- )
- else
- app.config.middleware.swap(
- ActionDispatch::Static,
- Gitlab::Middleware::Static,
- app.paths["public"].first,
- app.config.static_cache_control
- )
- end
+ app.config.middleware.swap(
+ ActionDispatch::Static,
+ Gitlab::Middleware::Static,
+ app.paths["public"].first,
+ headers: app.config.public_file_server.headers
+ )
# If webpack-dev-server is configured, proxy webpack's public directory
# instead of looking for static assets
diff --git a/config/initializers/trusted_proxies.rb b/config/initializers/trusted_proxies.rb
index ca2eed664ed..7af465d8443 100644
--- a/config/initializers/trusted_proxies.rb
+++ b/config/initializers/trusted_proxies.rb
@@ -26,12 +26,10 @@ Rails.application.config.action_dispatch.trusted_proxies = (
# A monkey patch to make trusted proxies work with Rails 5.0.
# Inspired by https://github.com/rails/rails/issues/5223#issuecomment-263778719
# Remove this monkey patch when upstream is fixed.
-if Gitlab.rails5?
- module TrustedProxyMonkeyPatch
- def ip
- @ip ||= (get_header("action_dispatch.remote_ip") || super).to_s
- end
+module TrustedProxyMonkeyPatch
+ def ip
+ @ip ||= (get_header("action_dispatch.remote_ip") || super).to_s
end
-
- ActionDispatch::Request.send(:include, TrustedProxyMonkeyPatch)
end
+
+ActionDispatch::Request.send(:include, TrustedProxyMonkeyPatch)
diff --git a/config/initializers/8_metrics.rb b/config/initializers/zz_metrics.rb
index 468f80939d7..462e8c811a6 100644
--- a/config/initializers/8_metrics.rb
+++ b/config/initializers/zz_metrics.rb
@@ -1,3 +1,6 @@
+# This file was prefixed with zz_ because we want to load it the last!
+# See: https://gitlab.com/gitlab-org/gitlab-ce/issues/55611
+
# Autoload all classes that we want to instrument, and instrument the methods we
# need. This takes the Gitlab::Metrics::Instrumentation module as an argument so
# that we can stub it for testing, as it is only called when metrics are
diff --git a/config/prometheus/common_metrics.yml b/config/prometheus/common_metrics.yml
index 52023a2e3cb..9bdaf1575e9 100644
--- a/config/prometheus/common_metrics.yml
+++ b/config/prometheus/common_metrics.yml
@@ -1,4 +1,5 @@
-- group: Response metrics (NGINX Ingress)
+ # NGINX Ingress metrics for pre-0.16.0 versions
+- group: Response metrics (NGINX Ingress VTS)
priority: 10
metrics:
- title: "Throughput"
@@ -40,6 +41,51 @@
query_range: 'sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) / sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) * 100'
label: 5xx Errors
unit: "%"
+# NGINX Ingress metrics for post-0.16.0 versions
+- group: Response metrics (NGINX Ingress)
+ priority: 10
+ metrics:
+ - title: "Throughput"
+ y_label: "Requests / Sec"
+ required_metrics:
+ - nginx_ingress_controller_requests
+ weight: 1
+ queries:
+ - id: response_metrics_nginx_ingress_16_throughput_status_code
+ query_range: 'sum(label_replace(rate(nginx_ingress_controller_requests{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m]), "status_code", "${1}xx", "status", "(.)..")) by (status_code)'
+ unit: req / sec
+ label: Status Code
+ series:
+ - label: status_code
+ when:
+ - value: 2xx
+ color: green
+ - value: 3xx
+ color: blue
+ - value: 4xx
+ color: orange
+ - value: 5xx
+ color: red
+ - title: "Latency"
+ y_label: "Latency (ms)"
+ required_metrics:
+ - nginx_ingress_controller_ingress_upstream_latency_seconds_sum
+ weight: 1
+ queries:
+ - id: response_metrics_nginx_ingress_16_latency_pod_average
+ query_range: 'sum(rate(nginx_ingress_controller_ingress_upstream_latency_seconds_sum{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) / sum(rate(nginx_ingress_controller_ingress_upstream_latency_seconds_count{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) * 1000'
+ label: Pod average
+ unit: ms
+ - title: "HTTP Error Rate"
+ y_label: "HTTP Errors"
+ required_metrics:
+ - nginx_ingress_controller_requests
+ weight: 1
+ queries:
+ - id: response_metrics_nginx_ingress_16_http_error_rate
+ query_range: 'sum(rate(nginx_ingress_controller_requests{status=~"5.*",namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) / sum(rate(nginx_ingress_controller_requests{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) * 100'
+ label: 5xx Errors
+ unit: "%"
- group: Response metrics (HA Proxy)
priority: 10
metrics:
diff --git a/config/routes/api.rb b/config/routes/api.rb
index b1aebf4d606..3719b7d3a1e 100644
--- a/config/routes/api.rb
+++ b/config/routes/api.rb
@@ -1,4 +1,4 @@
-constraints(::Constraints::FeatureConstrainer.new(:graphql)) do
+constraints(::Constraints::FeatureConstrainer.new(:graphql, default_enabled: true)) do
post '/api/graphql', to: 'graphql#execute'
mount GraphiQL::Rails::Engine, at: '/-/graphql-explorer', graphql_path: '/api/graphql'
end
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 3f1ad90dfca..03c95b61e51 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -95,6 +95,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
+ resources :releases, only: [:index]
resources :forks, only: [:index, :new, :create]
resource :import, only: [:new, :create, :show]
@@ -245,6 +246,10 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
+ namespace :serverless do
+ resources :functions, only: [:index]
+ end
+
scope '-' do
get 'archive/*id', constraints: { format: Gitlab::PathRegex.archive_formats_regex, id: /.+?/ }, to: 'repositories#archive', as: 'archive'
@@ -432,6 +437,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resource :integrations, only: [:show]
resource :repository, only: [:show], controller: :repository do
post :create_deploy_token, path: 'deploy_token/create'
+ post :cleanup
end
end
diff --git a/config/routes/repository.rb b/config/routes/repository.rb
index d439cb9acbd..96975759709 100644
--- a/config/routes/repository.rb
+++ b/config/routes/repository.rb
@@ -55,7 +55,7 @@ scope format: false do
resources :branches, only: [:index, :new, :create, :destroy]
delete :merged_branches, controller: 'branches', action: :destroy_all_merged
resources :tags, only: [:index, :show, :new, :create, :destroy] do
- resource :release, only: [:edit, :update]
+ resource :release, controller: 'tags/releases', only: [:edit, :update]
end
resources :protected_branches, only: [:index, :show, :create, :update, :destroy]
diff --git a/config/routes/uploads.rb b/config/routes/uploads.rb
index 6becadd57ae..b594f55f8a0 100644
--- a/config/routes/uploads.rb
+++ b/config/routes/uploads.rb
@@ -17,7 +17,8 @@ scope path: :uploads do
# Appearance
get "-/system/:model/:mounted_as/:id/:filename",
to: "uploads#show",
- constraints: { model: /appearance/, mounted_as: /logo|header_logo|favicon/, filename: /.+/ }
+ constraints: { model: /appearance/, mounted_as: /logo|header_logo|favicon/, filename: /.+/ },
+ as: 'appearance_upload'
# Project markdown uploads
get ":namespace_id/:project_id/:secret/:filename",
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 53e1c8778b6..3ee32678f34 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -81,3 +81,7 @@
- [delete_diff_files, 1]
- [detect_repository_languages, 1]
- [auto_devops, 2]
+ - [object_pool, 1]
+ - [repository_cleanup, 1]
+ - [delete_stored_files, 1]
+ - [remote_mirror_notification, 2]
diff --git a/danger/commit_messages/Dangerfile b/danger/commit_messages/Dangerfile
index c5ebb9b457e..abb36098629 100644
--- a/danger/commit_messages/Dangerfile
+++ b/danger/commit_messages/Dangerfile
@@ -149,7 +149,7 @@ def lint_commits(commits)
if !details && too_many_changed_lines?(commit)
fail_commit(
commit,
- 'Commits that change 30 or more lines in more than three files ' \
+ 'Commits that change 30 or more lines across at least three files ' \
'must describe these changes in the commit body'
)
diff --git a/danger/documentation/Dangerfile b/danger/documentation/Dangerfile
index 87c61d6e90d..52af837c261 100644
--- a/danger/documentation/Dangerfile
+++ b/danger/documentation/Dangerfile
@@ -24,23 +24,24 @@ The following files require a review from the Documentation team:
* #{docs_paths_to_review.map { |path| "`#{path}`" }.join("\n* ")}
-When your content is ready for review, mention a technical writer in a separate
-comment and explain what needs to be reviewed.
-
-You are welcome to mention them sooner if you have questions about writing or updating
-the documentation. GitLabbers are also welcome to use the [#docs](https://gitlab.slack.com/archives/C16HYA2P5) channel on Slack.
-
-Who to ping [based on DevOps stages](https://about.gitlab.com/handbook/product/categories/#devops-stages):
+When your content is ready for review, assign the MR to a technical writer
+according to the [DevOps stages](https://about.gitlab.com/handbook/product/categories/#devops-stages)
+in the table below. If necessary, mention them in a comment explaining what needs
+to be reviewed.
| Tech writer | Stage(s) |
| ------------ | ------------------------------------------------------------ |
-| `@marcia` | ~Create ~Release |
-| `@axil` | ~Distribution ~Gitaly ~Gitter ~Monitoring ~Packaging ~Secure |
+| `@marcia` | ~Create ~Release + ~"development guidelines" |
+| `@axil` | ~Distribution ~Gitaly ~Gitter ~Monitoring ~Package ~Secure |
| `@eread` | ~Manage ~Configure ~Geo ~Verify |
| `@mikelewis` | ~Plan |
+You are welcome to mention them sooner if you have questions about writing or
+updating the documentation. GitLabbers are also welcome to use the
+[#docs](https://gitlab.slack.com/archives/C16HYA2P5) channel on Slack.
+
If you are not sure which category the change falls within, or the change is not
-part of one of these categories, you can mention one of the usernames above.
+part of one of these categories, mention one of the usernames above.
MARKDOWN
unless gitlab.mr_labels.include?('Documentation')
diff --git a/danger/duplicate_yarn_dependencies/Dangerfile b/danger/duplicate_yarn_dependencies/Dangerfile
new file mode 100644
index 00000000000..25f81ec86a4
--- /dev/null
+++ b/danger/duplicate_yarn_dependencies/Dangerfile
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+return unless helper.all_changed_files.include? 'yarn.lock'
+
+duplicate = `node_modules/.bin/yarn-deduplicate --list --strategy fewer yarn.lock`
+ .split(/$/)
+ .map(&:strip)
+ .reject(&:empty?)
+
+return if duplicate.empty?
+
+warn 'This merge request has introduced duplicated yarn dependencies.'
+
+markdown(<<~MARKDOWN)
+ ## Duplicate yarn dependencies
+
+ The following dependencies should be de-duplicated:
+
+ * #{duplicate.map { |path| "`#{path}`" }.join("\n* ")}
+
+ Please run the following command and commit the changes to `yarn.lock`:
+
+ ```
+ node_modules/.bin/yarn-deduplicate --strategy fewer yarn.lock \\
+ && yarn install
+ ```
+MARKDOWN
diff --git a/danger/gemfile/Dangerfile b/danger/gemfile/Dangerfile
index 8ef4a464fe4..4e91abc371a 100644
--- a/danger/gemfile/Dangerfile
+++ b/danger/gemfile/Dangerfile
@@ -4,7 +4,7 @@ GEMFILE_LOCK_NOT_UPDATED_MESSAGE = <<~MSG.freeze
Usually, when %<gemfile>s is updated, you should run
```
bundle install && \
- BUNDLE_GEMFILE=Gemfile.rails5 bundle install
+ bundle install
```
or
diff --git a/db/fixtures/development/03_settings.rb b/db/fixtures/development/03_settings.rb
new file mode 100644
index 00000000000..3a4a5d436bf
--- /dev/null
+++ b/db/fixtures/development/03_settings.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+# Enable hashed storage, in development mode, for all projects by default.
+Gitlab::Seeder.quiet do
+ ApplicationSetting.create_from_defaults unless ApplicationSetting.current_without_cache
+ ApplicationSetting.current_without_cache.update!(hashed_storage_enabled: true)
+ print '.'
+end
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index 089de211380..aa8686ac7d8 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -71,13 +71,17 @@ Sidekiq::Testing.inline! do
params[:storage_version] = Project::LATEST_STORAGE_VERSION
end
- project = Projects::CreateService.new(User.first, params).execute
- # Seed-Fu runs this entire fixture in a transaction, so the `after_commit`
- # hook won't run until after the fixture is loaded. That is too late
- # since the Sidekiq::Testing block has already exited. Force clearing
- # the `after_commit` queue to ensure the job is run now.
+ project = nil
+
Sidekiq::Worker.skipping_transaction_check do
+ project = Projects::CreateService.new(User.first, params).execute
+
+ # Seed-Fu runs this entire fixture in a transaction, so the `after_commit`
+ # hook won't run until after the fixture is loaded. That is too late
+ # since the Sidekiq::Testing block has already exited. Force clearing
+ # the `after_commit` queue to ensure the job is run now.
project.send(:_run_after_commit_queue)
+ project.import_state.send(:_run_after_commit_queue)
end
if project.valid? && project.valid_repo?
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index bcfdd058a1c..8bdc7c6556c 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -25,7 +25,9 @@ Gitlab::Seeder.quiet do
developer = project.team.developers.sample
break unless developer
- MergeRequests::CreateService.new(project, developer, params).execute
+ Sidekiq::Worker.skipping_transaction_check do
+ MergeRequests::CreateService.new(project, developer, params).execute
+ end
print '.'
end
end
@@ -39,7 +41,9 @@ Gitlab::Seeder.quiet do
target_branch: 'master',
title: 'Can be automatically merged'
}
- MergeRequests::CreateService.new(project, User.admins.first, params).execute
+ Sidekiq::Worker.skipping_transaction_check do
+ MergeRequests::CreateService.new(project, User.admins.first, params).execute
+ end
print '.'
params = {
@@ -47,6 +51,8 @@ Gitlab::Seeder.quiet do
target_branch: 'feature',
title: 'Cannot be automatically merged'
}
- MergeRequests::CreateService.new(project, User.admins.first, params).execute
+ Sidekiq::Worker.skipping_transaction_check do
+ MergeRequests::CreateService.new(project, User.admins.first, params).execute
+ end
print '.'
end
diff --git a/db/fixtures/development/24_forks.rb b/db/fixtures/development/24_forks.rb
new file mode 100644
index 00000000000..5eb5956ec74
--- /dev/null
+++ b/db/fixtures/development/24_forks.rb
@@ -0,0 +1,22 @@
+require './spec/support/sidekiq'
+
+Sidekiq::Testing.inline! do
+ Gitlab::Seeder.quiet do
+ User.all.sample(10).each do |user|
+ source_project = Project.public_only.sample
+
+ ##
+ # 04_project.rb might not have created a public project because
+ # we use randomized approach (e.g. `Array#sample`).
+ return unless source_project
+
+ fork_project = Projects::ForkService.new(source_project, user, namespace: user.namespace).execute
+
+ if fork_project.valid?
+ puts '.'
+ else
+ puts 'F'
+ end
+ end
+ end
+end
diff --git a/db/fixtures/production/001_application_settings.rb b/db/fixtures/production/001_application_settings.rb
new file mode 100644
index 00000000000..ab15717e9a9
--- /dev/null
+++ b/db/fixtures/production/001_application_settings.rb
@@ -0,0 +1,2 @@
+puts "Creating the default ApplicationSetting record.".color(:green)
+Gitlab::CurrentSettings.current_application_settings
diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/002_admin.rb
index 1c7c89f7bbd..1c7c89f7bbd 100644
--- a/db/fixtures/production/001_admin.rb
+++ b/db/fixtures/production/002_admin.rb
diff --git a/db/importers/common_metrics_importer.rb b/db/importers/common_metrics_importer.rb
index 6302394d7a6..deadd653ae9 100644
--- a/db/importers/common_metrics_importer.rb
+++ b/db/importers/common_metrics_importer.rb
@@ -4,11 +4,12 @@ module Importers
class PrometheusMetric < ActiveRecord::Base
enum group: {
# built-in groups
- nginx_ingress: -1,
+ nginx_ingress_vts: -1,
ha_proxy: -2,
aws_elb: -3,
nginx: -4,
kubernetes: -5,
+ nginx_ingress: -6,
# custom groups
business: 0,
@@ -22,6 +23,7 @@ module Importers
business: _('Business metrics (Custom)'),
response: _('Response metrics (Custom)'),
system: _('System metrics (Custom)'),
+ nginx_ingress_vts: _('Response metrics (NGINX Ingress VTS)'),
nginx_ingress: _('Response metrics (NGINX Ingress)'),
ha_proxy: _('Response metrics (HA Proxy)'),
aws_elb: _('Response metrics (AWS ELB)'),
diff --git a/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb
index 82b54c552e0..af8b08c095a 100644
--- a/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb
+++ b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb
@@ -37,12 +37,7 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration[4.2]
res = execute("SELECT true AS enabled FROM pg_available_extensions WHERE name = 'pg_trgm' AND installed_version IS NOT NULL;")
row = res.first
- check = if Gitlab.rails5?
- true
- else
- 't'
- end
- row && row['enabled'] == check ? true : false
+ row && row['enabled'] == true
end
def create_trigrams_extension
diff --git a/db/migrate/20161207231620_fixup_environment_name_uniqueness.rb b/db/migrate/20161207231620_fixup_environment_name_uniqueness.rb
index 7cae09021cd..420f0ccb45c 100644
--- a/db/migrate/20161207231620_fixup_environment_name_uniqueness.rb
+++ b/db/migrate/20161207231620_fixup_environment_name_uniqueness.rb
@@ -1,5 +1,4 @@
class FixupEnvironmentNameUniqueness < ActiveRecord::Migration[4.2]
- include Gitlab::Database::ArelMethods
include Gitlab::Database::MigrationHelpers
DOWNTIME = true
@@ -42,7 +41,7 @@ class FixupEnvironmentNameUniqueness < ActiveRecord::Migration[4.2]
conflicts.each do |id, name|
update_sql =
- arel_update_manager
+ Arel::UpdateManager.new
.table(environments)
.set(environments[:name] => name + "-" + id.to_s)
.where(environments[:id].eq(id))
diff --git a/db/migrate/20161207231626_add_environment_slug.rb b/db/migrate/20161207231626_add_environment_slug.rb
index 4657b023dfa..993b9bd3330 100644
--- a/db/migrate/20161207231626_add_environment_slug.rb
+++ b/db/migrate/20161207231626_add_environment_slug.rb
@@ -2,7 +2,6 @@
# for more information on how to write migrations for GitLab.
class AddEnvironmentSlug < ActiveRecord::Migration[4.2]
- include Gitlab::Database::ArelMethods
include Gitlab::Database::MigrationHelpers
DOWNTIME = true
@@ -20,7 +19,7 @@ class AddEnvironmentSlug < ActiveRecord::Migration[4.2]
finder = environments.project(:id, :name)
connection.exec_query(finder.to_sql).rows.each do |id, name|
- updater = arel_update_manager
+ updater = Arel::UpdateManager.new
.table(environments)
.set(environments[:slug] => generate_slug(name))
.where(environments[:id].eq(id))
diff --git a/db/migrate/20181006004100_import_common_metrics_nginx_vts.rb b/db/migrate/20181006004100_import_common_metrics_nginx_vts.rb
new file mode 100644
index 00000000000..5cd312837df
--- /dev/null
+++ b/db/migrate/20181006004100_import_common_metrics_nginx_vts.rb
@@ -0,0 +1,15 @@
+class ImportCommonMetricsNginxVts < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ require Rails.root.join('db/importers/common_metrics_importer.rb')
+
+ DOWNTIME = false
+
+ def up
+ Importers::CommonMetricsImporter.new.execute
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20181031145139_add_protected_ci_variables_to_application_settings.rb b/db/migrate/20181031145139_add_protected_ci_variables_to_application_settings.rb
new file mode 100644
index 00000000000..85ee34afe1e
--- /dev/null
+++ b/db/migrate/20181031145139_add_protected_ci_variables_to_application_settings.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddProtectedCiVariablesToApplicationSettings < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(:application_settings, :protected_ci_variables, :boolean, default: false, allow_null: false)
+ end
+
+ def down
+ remove_column(:application_settings, :protected_ci_variables)
+ 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 4966b89964a..0b6155356d9 100644
--- a/db/migrate/20181101191341_create_clusters_applications_cert_manager.rb
+++ b/db/migrate/20181101191341_create_clusters_applications_cert_manager.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class CreateClustersApplicationsCertManager < ActiveRecord::Migration
+class CreateClustersApplicationsCertManager < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
diff --git a/db/migrate/20181108091549_cleanup_environments_external_url.rb b/db/migrate/20181108091549_cleanup_environments_external_url.rb
index 8d6c20a4b15..8439f6e55e6 100644
--- a/db/migrate/20181108091549_cleanup_environments_external_url.rb
+++ b/db/migrate/20181108091549_cleanup_environments_external_url.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class CleanupEnvironmentsExternalUrl < ActiveRecord::Migration
+class CleanupEnvironmentsExternalUrl < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
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 36d9ad45b19..5b2bb4f6b08 100644
--- a/db/migrate/20181115140140_add_encrypted_runners_token_to_settings.rb
+++ b/db/migrate/20181115140140_add_encrypted_runners_token_to_settings.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddEncryptedRunnersTokenToSettings < ActiveRecord::Migration
+class AddEncryptedRunnersTokenToSettings < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
diff --git a/db/migrate/20181116050532_knative_external_ip.rb b/db/migrate/20181116050532_knative_external_ip.rb
index f1f903fb692..5645b040a23 100644
--- a/db/migrate/20181116050532_knative_external_ip.rb
+++ b/db/migrate/20181116050532_knative_external_ip.rb
@@ -3,7 +3,7 @@
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
-class KnativeExternalIp < ActiveRecord::Migration
+class KnativeExternalIp < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
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 b92b1b50218..dcf565cd6c0 100644
--- a/db/migrate/20181116141415_add_encrypted_runners_token_to_namespaces.rb
+++ b/db/migrate/20181116141415_add_encrypted_runners_token_to_namespaces.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddEncryptedRunnersTokenToNamespaces < ActiveRecord::Migration
+class AddEncryptedRunnersTokenToNamespaces < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
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 53e475bd180..13cd80e5c8b 100644
--- a/db/migrate/20181116141504_add_encrypted_runners_token_to_projects.rb
+++ b/db/migrate/20181116141504_add_encrypted_runners_token_to_projects.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddEncryptedRunnersTokenToProjects < ActiveRecord::Migration
+class AddEncryptedRunnersTokenToProjects < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
diff --git a/db/migrate/20181119081539_add_merge_request_id_to_ci_pipelines.rb b/db/migrate/20181119081539_add_merge_request_id_to_ci_pipelines.rb
index 65476109c61..f96d80787f9 100644
--- a/db/migrate/20181119081539_add_merge_request_id_to_ci_pipelines.rb
+++ b/db/migrate/20181119081539_add_merge_request_id_to_ci_pipelines.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddMergeRequestIdToCiPipelines < ActiveRecord::Migration
+class AddMergeRequestIdToCiPipelines < ActiveRecord::Migration[4.2]
DOWNTIME = false
def up
diff --git a/db/migrate/20181120091639_add_foreign_key_to_ci_pipelines_merge_requests.rb b/db/migrate/20181120091639_add_foreign_key_to_ci_pipelines_merge_requests.rb
index c2b5b239279..f8b46395941 100644
--- a/db/migrate/20181120091639_add_foreign_key_to_ci_pipelines_merge_requests.rb
+++ b/db/migrate/20181120091639_add_foreign_key_to_ci_pipelines_merge_requests.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddForeignKeyToCiPipelinesMergeRequests < ActiveRecord::Migration
+class AddForeignKeyToCiPipelinesMergeRequests < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
@@ -8,7 +8,7 @@ class AddForeignKeyToCiPipelinesMergeRequests < ActiveRecord::Migration
disable_ddl_transaction!
def up
- add_concurrent_index :ci_pipelines, :merge_request_id
+ add_concurrent_index :ci_pipelines, :merge_request_id, where: 'merge_request_id IS NOT NULL'
add_concurrent_foreign_key :ci_pipelines, :merge_requests, column: :merge_request_id, on_delete: :cascade
end
@@ -17,6 +17,6 @@ class AddForeignKeyToCiPipelinesMergeRequests < ActiveRecord::Migration
remove_foreign_key :ci_pipelines, :merge_requests
end
- remove_concurrent_index :ci_pipelines, :merge_request_id
+ remove_concurrent_index :ci_pipelines, :merge_request_id, where: 'merge_request_id IS NOT NULL'
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 40db6b399ab..8b990451adc 100644
--- a/db/migrate/20181120151656_add_token_encrypted_to_ci_runners.rb
+++ b/db/migrate/20181120151656_add_token_encrypted_to_ci_runners.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AddTokenEncryptedToCiRunners < ActiveRecord::Migration
+class AddTokenEncryptedToCiRunners < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
diff --git a/db/migrate/20181121101842_add_ci_builds_partial_index_on_project_id_and_status.rb b/db/migrate/20181121101842_add_ci_builds_partial_index_on_project_id_and_status.rb
new file mode 100644
index 00000000000..a524709faf8
--- /dev/null
+++ b/db/migrate/20181121101842_add_ci_builds_partial_index_on_project_id_and_status.rb
@@ -0,0 +1,33 @@
+# 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 AddCiBuildsPartialIndexOnProjectIdAndStatus < ActiveRecord::Migration[4.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index(*index_arguments)
+ end
+
+ def down
+ remove_concurrent_index(*index_arguments)
+ end
+
+ private
+
+ def index_arguments
+ [
+ :ci_builds,
+ [:project_id, :status],
+ {
+ name: 'index_ci_builds_project_id_and_status_for_live_jobs_partial2',
+ where: "(((type)::text = 'Ci::Build'::text) AND ((status)::text = ANY (ARRAY[('running'::character varying)::text, ('pending'::character varying)::text, ('created'::character varying)::text])))"
+ }
+ ]
+ end
+end
diff --git a/db/migrate/20181121101843_remove_redundant_ci_builds_partial_index.rb b/db/migrate/20181121101843_remove_redundant_ci_builds_partial_index.rb
new file mode 100644
index 00000000000..e4fb703e887
--- /dev/null
+++ b/db/migrate/20181121101843_remove_redundant_ci_builds_partial_index.rb
@@ -0,0 +1,33 @@
+# 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 RemoveRedundantCiBuildsPartialIndex < ActiveRecord::Migration[4.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index(*index_arguments)
+ end
+
+ def down
+ add_concurrent_index(*index_arguments)
+ end
+
+ private
+
+ def index_arguments
+ [
+ :ci_builds,
+ [:project_id, :status],
+ {
+ name: 'index_ci_builds_project_id_and_status_for_live_jobs_partial',
+ where: "((status)::text = ANY (ARRAY[('running'::character varying)::text, ('pending'::character varying)::text, ('created'::character varying)::text]))"
+ }
+ ]
+ end
+end
diff --git a/db/migrate/20181123144235_create_suggestions.rb b/db/migrate/20181123144235_create_suggestions.rb
new file mode 100644
index 00000000000..1723f6de7eb
--- /dev/null
+++ b/db/migrate/20181123144235_create_suggestions.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class CreateSuggestions < ActiveRecord::Migration[5.0]
+ DOWNTIME = false
+
+ def change
+ create_table :suggestions, id: :bigserial do |t|
+ 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.text :from_content, null: false
+ t.text :to_content, null: false
+
+ t.index [:note_id, :relative_order],
+ name: 'index_suggestions_on_note_id_and_relative_order',
+ unique: true
+ end
+ end
+end
diff --git a/db/migrate/20181128123704_add_state_to_pool_repository.rb b/db/migrate/20181128123704_add_state_to_pool_repository.rb
new file mode 100644
index 00000000000..714232ede56
--- /dev/null
+++ b/db/migrate/20181128123704_add_state_to_pool_repository.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddStateToPoolRepository < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ # Given the table is empty, and the non concurrent methods are chosen so
+ # 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, :source_project_id, :integer
+ add_index :pool_repositories, :source_project_id, unique: true
+ add_foreign_key :pool_repositories, :projects, column: :source_project_id, on_delete: :nullify
+ end
+ # rubocop: enable Migration/AddConcurrentForeignKey, Migration/AddIndex
+end
diff --git a/db/migrate/20181129104854_add_token_encrypted_to_ci_builds.rb b/db/migrate/20181129104854_add_token_encrypted_to_ci_builds.rb
new file mode 100644
index 00000000000..11b98203793
--- /dev/null
+++ b/db/migrate/20181129104854_add_token_encrypted_to_ci_builds.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddTokenEncryptedToCiBuilds < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :ci_builds, :token_encrypted, :string
+ end
+end
diff --git a/db/migrate/20181129104944_add_index_to_ci_builds_token_encrypted.rb b/db/migrate/20181129104944_add_index_to_ci_builds_token_encrypted.rb
new file mode 100644
index 00000000000..f90aca008e5
--- /dev/null
+++ b/db/migrate/20181129104944_add_index_to_ci_builds_token_encrypted.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexToCiBuildsTokenEncrypted < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :ci_builds, :token_encrypted, unique: true, where: 'token_encrypted IS NOT NULL'
+ end
+
+ def down
+ remove_concurrent_index :ci_builds, :token_encrypted
+ 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
new file mode 100644
index 00000000000..8b42cd6f941
--- /dev/null
+++ b/db/migrate/20181203002526_add_project_bfg_object_map_column.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddProjectBfgObjectMapColumn < ActiveRecord::Migration[5.0]
+ DOWNTIME = false
+
+ def change
+ add_column :projects, :bfg_object_map, :string
+ 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
new file mode 100644
index 00000000000..60815e0c31a
--- /dev/null
+++ b/db/migrate/20181211092510_add_name_author_id_and_sha_to_releases.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddNameAuthorIdAndShaToReleases < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :releases, :author_id, :integer
+ add_column :releases, :name, :string
+ add_column :releases, :sha, :string
+ end
+end
diff --git a/db/migrate/20181211092514_add_author_id_index_and_fk_to_releases.rb b/db/migrate/20181211092514_add_author_id_index_and_fk_to_releases.rb
new file mode 100644
index 00000000000..f6350a49944
--- /dev/null
+++ b/db/migrate/20181211092514_add_author_id_index_and_fk_to_releases.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class AddAuthorIdIndexAndFkToReleases < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :releases, :author_id
+
+ add_concurrent_foreign_key :releases, :users, column: :author_id, on_delete: :nullify
+ end
+
+ def down
+ remove_foreign_key :releases, column: :author_id
+
+ remove_concurrent_index :releases, column: :author_id
+ end
+end
diff --git a/db/migrate/20181212104941_backfill_releases_name_with_tag_name.rb b/db/migrate/20181212104941_backfill_releases_name_with_tag_name.rb
new file mode 100644
index 00000000000..e152dc87bc1
--- /dev/null
+++ b/db/migrate/20181212104941_backfill_releases_name_with_tag_name.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class BackfillReleasesNameWithTagName < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ update_column_in_batches(:releases, :name, Release.arel_table[:tag])
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb b/db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb
index d77a22bfb69..6d9c7e4ed72 100644
--- a/db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb
+++ b/db/post_migrate/20161109150329_fix_project_records_with_invalid_visibility.rb
@@ -1,5 +1,4 @@
class FixProjectRecordsWithInvalidVisibility < ActiveRecord::Migration[4.2]
- include Gitlab::Database::ArelMethods
include Gitlab::Database::MigrationHelpers
BATCH_SIZE = 500
@@ -34,7 +33,7 @@ class FixProjectRecordsWithInvalidVisibility < ActiveRecord::Migration[4.2]
end
updates.each do |visibility_level, project_ids|
- updater = arel_update_manager
+ updater = Arel::UpdateManager.new
.table(projects)
.set(projects[:visibility_level] => visibility_level)
.where(projects[:id].in(project_ids))
diff --git a/db/post_migrate/20161221153951_rename_reserved_project_names.rb b/db/post_migrate/20161221153951_rename_reserved_project_names.rb
index b7665e98490..50e1c8449ba 100644
--- a/db/post_migrate/20161221153951_rename_reserved_project_names.rb
+++ b/db/post_migrate/20161221153951_rename_reserved_project_names.rb
@@ -1,6 +1,5 @@
class RenameReservedProjectNames < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
- include Gitlab::ShellAdapter
DOWNTIME = false
diff --git a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
index cac3fd713eb..bef669b459d 100644
--- a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
+++ b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
@@ -1,6 +1,5 @@
class RenameMoreReservedProjectNames < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
- include Gitlab::ShellAdapter
DOWNTIME = false
diff --git a/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb b/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb
index 73989339ad9..61a5c364b2f 100644
--- a/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb
+++ b/db/post_migrate/20170324160416_migrate_user_activities_to_users_last_activity_on.rb
@@ -1,6 +1,5 @@
# rubocop:disable Migration/UpdateLargeTable
class MigrateUserActivitiesToUsersLastActivityOn < ActiveRecord::Migration[4.2]
- include Gitlab::Database::ArelMethods
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
@@ -40,7 +39,7 @@ class MigrateUserActivitiesToUsersLastActivityOn < ActiveRecord::Migration[4.2]
activities = activities(day.at_beginning_of_day, day.at_end_of_day, page: page)
update_sql =
- arel_update_manager
+ Arel::UpdateManager.new
.table(users_table)
.set(users_table[:last_activity_on] => day.to_date)
.where(users_table[:username].in(activities.map(&:first)))
diff --git a/db/post_migrate/20181010133639_backfill_store_project_full_path_in_repo.rb b/db/post_migrate/20181010133639_backfill_store_project_full_path_in_repo.rb
index e9ab45ae9a1..247f5980f7e 100644
--- a/db/post_migrate/20181010133639_backfill_store_project_full_path_in_repo.rb
+++ b/db/post_migrate/20181010133639_backfill_store_project_full_path_in_repo.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class BackfillStoreProjectFullPathInRepo < ActiveRecord::Migration
+class BackfillStoreProjectFullPathInRepo < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
diff --git a/db/post_migrate/20181026091631_migrate_forbidden_redirect_uris.rb b/db/post_migrate/20181026091631_migrate_forbidden_redirect_uris.rb
index ff5510e8eb7..7c2df832882 100644
--- a/db/post_migrate/20181026091631_migrate_forbidden_redirect_uris.rb
+++ b/db/post_migrate/20181026091631_migrate_forbidden_redirect_uris.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class MigrateForbiddenRedirectUris < ActiveRecord::Migration
+class MigrateForbiddenRedirectUris < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
diff --git a/db/post_migrate/20181121111200_schedule_runners_token_encryption.rb b/db/post_migrate/20181121111200_schedule_runners_token_encryption.rb
index 753e052f7a7..ba82072fc98 100644
--- a/db/post_migrate/20181121111200_schedule_runners_token_encryption.rb
+++ b/db/post_migrate/20181121111200_schedule_runners_token_encryption.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ScheduleRunnersTokenEncryption < ActiveRecord::Migration
+class ScheduleRunnersTokenEncryption < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
diff --git a/db/post_migrate/20181130102132_backfill_hashed_project_repositories.rb b/db/post_migrate/20181130102132_backfill_hashed_project_repositories.rb
new file mode 100644
index 00000000000..7814cdba58a
--- /dev/null
+++ b/db/post_migrate/20181130102132_backfill_hashed_project_repositories.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class BackfillHashedProjectRepositories < ActiveRecord::Migration[4.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 1_000
+ DELAY_INTERVAL = 5.minutes
+ MIGRATION = 'BackfillHashedProjectRepositories'
+
+ disable_ddl_transaction!
+
+ class Project < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'projects'
+ end
+
+ def up
+ queue_background_migration_jobs_by_range_at_intervals(Project, MIGRATION, DELAY_INTERVAL)
+ end
+
+ def down
+ # no-op: since there could have been existing rows before the migration do not remove anything
+ end
+end
diff --git a/db/post_migrate/20181204154019_populate_mr_metrics_with_events_data.rb b/db/post_migrate/20181204154019_populate_mr_metrics_with_events_data.rb
new file mode 100644
index 00000000000..1e43e3dd790
--- /dev/null
+++ b/db/post_migrate/20181204154019_populate_mr_metrics_with_events_data.rb
@@ -0,0 +1,38 @@
+# 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 PopulateMrMetricsWithEventsData < ActiveRecord::Migration[4.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 10_000
+ MIGRATION = 'PopulateMergeRequestMetricsWithEventsDataImproved'
+ PREVIOUS_MIGRATION = 'PopulateMergeRequestMetricsWithEventsData'
+
+ disable_ddl_transaction!
+
+ def up
+ # Perform any ongoing background migration that might still be running from
+ # previous try (see https://gitlab.com/gitlab-org/gitlab-ce/issues/47676).
+ Gitlab::BackgroundMigration.steal(PREVIOUS_MIGRATION)
+
+ say 'Scheduling `PopulateMergeRequestMetricsWithEventsData` jobs'
+ # It will update around 4_000_000 records in batches of 10_000 merge
+ # requests (running between 5 minutes) and should take around 53 hours to complete.
+ # Apparently, production PostgreSQL is able to vacuum 10k-20k dead_tuples
+ # per minute. So this should give us enough space.
+ #
+ # More information about the updates in `PopulateMergeRequestMetricsWithEventsDataImproved` class.
+ #
+ MergeRequest.all.each_batch(of: BATCH_SIZE) do |relation, index|
+ range = relation.pluck('MIN(id)', 'MAX(id)').first
+
+ BackgroundMigrationWorker.perform_in(index * 8.minutes, MIGRATION, range)
+ end
+ end
+
+ def down
+ end
+end
diff --git a/db/post_migrate/20181218192239_backfill_project_repositories_for_legacy_storage_projects.rb b/db/post_migrate/20181218192239_backfill_project_repositories_for_legacy_storage_projects.rb
new file mode 100644
index 00000000000..42f96750789
--- /dev/null
+++ b/db/post_migrate/20181218192239_backfill_project_repositories_for_legacy_storage_projects.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class BackfillProjectRepositoriesForLegacyStorageProjects < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 1_000
+ DELAY_INTERVAL = 5.minutes
+ MIGRATION = 'BackfillLegacyProjectRepositories'
+
+ disable_ddl_transaction!
+
+ class Project < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'projects'
+ end
+
+ def up
+ queue_background_migration_jobs_by_range_at_intervals(Project, MIGRATION, DELAY_INTERVAL)
+ end
+
+ def down
+ # no-op: since there could have been existing rows before the migration do not remove anything
+ end
+end
diff --git a/db/post_migrate/20181219145520_migrate_cluster_configure_worker_sidekiq_queue.rb b/db/post_migrate/20181219145520_migrate_cluster_configure_worker_sidekiq_queue.rb
new file mode 100644
index 00000000000..c37f8c039c0
--- /dev/null
+++ b/db/post_migrate/20181219145520_migrate_cluster_configure_worker_sidekiq_queue.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class MigrateClusterConfigureWorkerSidekiqQueue < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ sidekiq_queue_migrate 'gcp_cluster:cluster_platform_configure', to: 'gcp_cluster:cluster_configure'
+ end
+
+ def down
+ sidekiq_queue_migrate 'gcp_cluster:cluster_configure', to: 'gcp_cluster:cluster_platform_configure'
+ end
+end
diff --git a/doc/administration/auth/README.md b/doc/administration/auth/README.md
index 373d4239f71..54be7b616cc 100644
--- a/doc/administration/auth/README.md
+++ b/doc/administration/auth/README.md
@@ -10,7 +10,7 @@ providers.
- [LDAP](ldap.md) Includes Active Directory, Apple Open Directory, Open LDAP,
and 389 Server
- [OmniAuth](../../integration/omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google,
- Bitbucket, Facebook, Shibboleth, Crowd, Azure and Authentiq ID
+ Bitbucket, Facebook, Shibboleth, Crowd, Azure, Authentiq ID, and JWT
- [CAS](../../integration/cas.md) Configure GitLab to sign in using CAS
- [SAML](../../integration/saml.md) Configure GitLab as a SAML 2.0 Service Provider
- [Okta](okta.md) Configure GitLab to sign in using Okta
diff --git a/doc/administration/auth/jwt.md b/doc/administration/auth/jwt.md
index 8b00f52ffc1..497298503ad 100644
--- a/doc/administration/auth/jwt.md
+++ b/doc/administration/auth/jwt.md
@@ -26,15 +26,15 @@ JWT will provide you with a secret key for you to use.
```ruby
gitlab_rails['omniauth_providers'] = [
{ name: 'jwt',
- app_secret: 'YOUR_APP_SECRET',
args: {
- algorithm: 'HS256',
- uid_claim: 'email',
- required_claims: ["name", "email"],
- info_maps: { name: "name", email: "email" },
- auth_url: 'https://example.com/',
- valid_within: nil,
- }
+ secret: 'YOUR_APP_SECRET',
+ algorithm: 'HS256', # Supported algorithms: 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512'
+ uid_claim: 'email',
+ required_claims: ['name', 'email'],
+ info_maps: { name: 'name', email: 'email' },
+ auth_url: 'https://example.com/',
+ valid_within: 3600 # 1 hour
+ }
}
]
```
@@ -43,15 +43,15 @@ JWT will provide you with a secret key for you to use.
```
- { name: 'jwt',
- app_secret: 'YOUR_APP_SECRET',
args: {
- algorithm: 'HS256',
- uid_claim: 'email',
- required_claims: ["name", "email"],
- info_map: { name: "name", email: "email" },
- auth_url: 'https://example.com/',
- valid_within: null,
- }
+ secret: 'YOUR_APP_SECRET',
+ algorithm: 'HS256', # Supported algorithms: 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512'
+ uid_claim: 'email',
+ required_claims: ['name', 'email'],
+ info_map: { name: 'name', email: 'email' },
+ auth_url: 'https://example.com/',
+ valid_within: 3600 # 1 hour
+ }
}
```
@@ -60,7 +60,7 @@ JWT will provide you with a secret key for you to use.
1. Change `YOUR_APP_SECRET` to the client secret and set `auth_url` to your redirect URL.
1. Save the configuration file.
-1. [Reconfigure GitLab][] or [restart GitLab][] for the changes to take effect if you
+1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
installed GitLab via Omnibus or from source respectively.
On the sign in page there should now be a JWT icon below the regular sign in form.
@@ -68,5 +68,5 @@ Click the icon to begin the authentication process. JWT will ask the user to
sign in and authorize the GitLab application. If everything goes well, the user
will be redirected to GitLab and will be signed in.
-[reconfigure GitLab]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
+[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
[restart GitLab]: ../restart_gitlab.md#installations-from-source
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index cfe7b0e05e3..5b7a61ef8ff 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -604,6 +604,52 @@ Registry out of the box, it is possible to make it work if you follow
[Docker's documentation][docker-insecure-self-signed]. You may find some additional
information in [issue 18239][ce-18239].
+## Troubleshooting
+
+When using AWS S3 with the GitLab registry, an error may occur when pushing
+large images. Look in the Registry log for the following error:
+
+```
+level=error msg="response completed with error" err.code=unknown err.detail="unexpected EOF" err.message="unknown error"
+```
+
+To resolve the error specify a `chunksize` value in the Registry configuration.
+Start with a value between `25000000` (25MB) and `50000000` (50MB).
+
+**For Omnibus installations**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ registry['storage'] = {
+ 's3' => {
+ 'accesskey' => 'AKIAKIAKI',
+ 'secretkey' => 'secret123',
+ 'bucket' => 'gitlab-registry-bucket-AKIAKIAKI',
+ 'chunksize' => 25000000
+ }
+ }
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+---
+
+**For installations from source**
+
+1. Edit `config/gitlab.yml`:
+
+ ```yaml
+ storage:
+ s3:
+ accesskey: 'AKIAKIAKI'
+ secretkey: 'secret123'
+ bucket: 'gitlab-registry-bucket-AKIAKIAKI'
+ chunksize: 25000000
+ ```
+
+1. Save the file and [restart GitLab][] for the changes to take effect.
+
[ce-18239]: https://gitlab.com/gitlab-org/gitlab-ce/issues/18239
[docker-insecure-self-signed]: https://docs.docker.com/registry/insecure/#use-self-signed-certificates
[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index dc6a71e2ebd..cf37eaa0b61 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -1,6 +1,6 @@
# Gitaly
-[Gitaly](https://gitlab.com/gitlab-org/gitaly) is the service that
+[Gitaly](https://gitlab.com/gitlab-org/gitaly) is the service that
provides high-level RPC access to Git repositories. Without it, no other
components can read or write Git data.
@@ -23,7 +23,7 @@ gitaly['prometheus_listen_addr'] = 'localhost:9236'
```
To change a Gitaly setting in installations from source you can edit
-`/home/git/gitaly/config.toml`. Changes will be applied when you run
+`/home/git/gitaly/config.toml`. Changes will be applied when you run
`service gitlab restart`.
```toml
@@ -91,13 +91,13 @@ documentation on configuring Gitaly
authentication](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/configuration/README.md#authentication)
.
-Gitaly must trigger some callbacks to GitLab via GitLab Shell. As a result,
+Gitaly must trigger some callbacks to GitLab via GitLab Shell. As a result,
the GitLab Shell secret must be the same between the other GitLab servers and
the Gitaly server. The easiest way to accomplish this is to copy `/etc/gitlab/gitlab-secrets.json`
from an existing GitLab server to the Gitaly server. Without this shared secret,
-Git operations in GitLab will result in an API error.
+Git operations in GitLab will result in an API error.
-> **NOTE:** In most or all cases the storage paths below end in `/repositories` which is
+> **NOTE:** In most or all cases the storage paths below end in `/repositories` which is
different than `path` in `git_data_dirs` of Omnibus installations. Check the
directory layout on your Gitaly server to be sure.
@@ -133,6 +133,11 @@ gitaly['storage'] = [
{ 'name' => 'default', 'path' => '/mnt/gitlab/default/repositories' },
{ 'name' => 'storage1', 'path' => '/mnt/gitlab/storage1/repositories' },
]
+
+# To use tls for gitaly you need to add
+gitaly['tls_listen_addr'] = "0.0.0.0:9999"
+gitaly['certificate_path'] = "path/to/cert.pem"
+gitaly['key_path'] = "path/to/key.pem"
```
Source installations:
@@ -140,6 +145,11 @@ Source installations:
```toml
# /home/git/gitaly/config.toml
listen_addr = '0.0.0.0:8075'
+tls_listen_addr = '0.0.0.0:9999'
+
+[tls]
+certificate_path = /path/to/cert.pem
+key_path = /path/to/key.pem
[auth]
token = 'abc123secret'
@@ -205,6 +215,70 @@ Gitaly logs on your Gitaly server (`sudo gitlab-ctl tail gitaly` or
coming in. One sure way to trigger a Gitaly request is to clone a
repository from your GitLab server over HTTP.
+## TLS support
+
+Gitaly supports TLS credentials for GRPC authentication. To be able to communicate
+with a gitaly instance that listens for secure connections you will need to use `tls://` url
+scheme in the `gitaly_address` of the corresponding storage entry in the gitlab configuration.
+
+The admin needs to bring their own certificate as we do not provide that automatically.
+The certificate to be used needs to be installed on all gitaly nodes and on all client nodes that communicate with it following procedures described in [GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates)
+
+### Example TLS configuration
+
+### Omnibus installations:
+
+#### On client nodes:
+
+```ruby
+# /etc/gitlab/gitlab.rb
+git_data_dirs({
+ 'default' => { 'path' => '/mnt/gitlab/default', 'gitaly_address' => 'tls://gitaly.internal:9999' },
+ 'storage1' => { 'path' => '/mnt/gitlab/storage1', 'gitaly_address' => 'tls://gitaly.internal:9999' },
+})
+
+gitlab_rails['gitaly_token'] = 'abc123secret'
+```
+
+#### On gitaly server nodes:
+
+```ruby
+gitaly['tls_listen_addr'] = "0.0.0.0:9999"
+gitaly['certificate_path'] = "path/to/cert.pem"
+gitaly['key_path'] = "path/to/key.pem"
+```
+
+### Source installations:
+
+#### On client nodes:
+
+```yaml
+# /home/git/gitlab/config/gitlab.yml
+gitlab:
+ repositories:
+ storages:
+ default:
+ path: /mnt/gitlab/default/repositories
+ gitaly_address: tls://gitaly.internal:9999
+ storage1:
+ path: /mnt/gitlab/storage1/repositories
+ gitaly_address: tls://gitaly.internal:9999
+
+ gitaly:
+ token: 'abc123secret'
+```
+
+#### On gitaly server nodes:
+
+```toml
+# /home/git/gitaly/config.toml
+tls_listen_addr = '0.0.0.0:9999'
+
+[tls]
+certificate_path = '/path/to/cert.pem'
+key_path = '/path/to/key.pem'
+```
+
## Disabling or enabling the Gitaly service in a cluster environment
If you are running Gitaly [as a remote
diff --git a/doc/administration/index.md b/doc/administration/index.md
index 6083806af6c..89132cd95f0 100644
--- a/doc/administration/index.md
+++ b/doc/administration/index.md
@@ -95,6 +95,7 @@ created in snippets, wikis, and repos.
- [Postfix for incoming email](reply_by_email_postfix_setup.md): Set up a
basic Postfix mail server with IMAP authentication on Ubuntu for incoming
emails.
+- [Abuse reports](../user/admin_area/abuse_reports.md): View and resolve abuse reports from your users.
[reply by email]: reply_by_email.md
[issues by email]: ../user/project/issues/create_new_issue.md#new-issue-via-email
diff --git a/doc/administration/monitoring/performance/img/request_profiling_token.png b/doc/administration/monitoring/performance/img/request_profiling_token.png
index a9160b62acb..9f3dd7f08ca 100644
--- a/doc/administration/monitoring/performance/img/request_profiling_token.png
+++ b/doc/administration/monitoring/performance/img/request_profiling_token.png
Binary files differ
diff --git a/doc/administration/operations/filesystem_benchmarking.md b/doc/administration/operations/filesystem_benchmarking.md
index 44018e966e0..0397452e650 100644
--- a/doc/administration/operations/filesystem_benchmarking.md
+++ b/doc/administration/operations/filesystem_benchmarking.md
@@ -44,12 +44,10 @@ user 0m0.025s
sys 0m0.091s
```
-From experience with multiple customers, the following are ranges that indicate
-whether your filesystem performance is satisfactory or less than ideal:
-
-| Rating | Benchmark result |
-|:----------|:------------------------|
-| Best | Less than 10 seconds |
-| OK | 10-18 seconds |
-| Poor | 18-25 seconds |
-| Very poor | Greater than 25 seconds |
+From experience with multiple customers, this task should take under 10
+seconds to indicate good filesystem performance.
+
+NOTE: **Note:**
+This test is naive and only evaluates write performance. It's possible to
+receive good results on this test but still have poor performance due to read
+speed and various other factors. \ No newline at end of file
diff --git a/doc/administration/repository_storage_paths.md b/doc/administration/repository_storage_paths.md
index c03f23a8931..7f25423171f 100644
--- a/doc/administration/repository_storage_paths.md
+++ b/doc/administration/repository_storage_paths.md
@@ -11,6 +11,25 @@ storage load between several mount points.
> - The paths are defined in key-value pairs. The key is an arbitrary name you
> can pick to name the file path.
> - The target directories and any of its subpaths must not be a symlink.
+> - No target directory may be a sub-directory of another; no nesting.
+
+Example: this is OK:
+
+```
+default:
+ path: /mnt/git-storage-1
+storage2:
+ path: /mnt/git-storage-2
+```
+
+This is not OK because it nests storage paths:
+
+```
+default:
+ path: /mnt/git-storage-1
+storage2:
+ path: /mnt/git-storage-1/git-storage-2 # <- NOT OK because of nesting
+```
## Configure GitLab
diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md
index 9379944b250..12238ba7b32 100644
--- a/doc/administration/repository_storage_types.md
+++ b/doc/administration/repository_storage_types.md
@@ -94,6 +94,23 @@ need to be performed on these nodes as well. Database changes will propagate wit
You must make sure the migration event was already processed or otherwise it may migrate
the files back to Hashed state again.
+#### Hashed object pools
+
+For deduplication of public forks and their parent repository, objects are pooled
+in an object pool. These object pools are a third repository where shared objects
+are stored.
+
+```ruby
+# object pool paths
+"@pools/#{hash[0..1]}/#{hash[2..3]}/#{hash}.git"
+```
+
+The object pool feature is behind the `object_pools` feature flag, and can be
+enabled for individual projects by executing
+`Feature.enable(:object_pools, Project.find(<id>))`. Note that the project has to
+be on hashed storage, should not be a fork itself, and hashed storage should be
+enabled for all new projects.
+
##### Attachments
To rollback single Attachment migration, rename `aa/bb/abcdef1234567890...` folder back to `namespace/project`.
diff --git a/doc/administration/uploads.md b/doc/administration/uploads.md
index f85a1f791f9..476ae8e8a76 100644
--- a/doc/administration/uploads.md
+++ b/doc/administration/uploads.md
@@ -120,30 +120,7 @@ _The uploads are stored by default in
```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
-1. Migrate any existing local uploads to the object storage:
-
- > **Notes:**
- > These task complies with the `BATCH` environment variable to process uploads in batch (200 by default). All of the processing will be done in a background worker and requires **no downtime**.
-
- ```bash
- # gitlab-rake gitlab:uploads:migrate[uploader_class, model_class, mount_point]
-
- # Avatars
- gitlab-rake "gitlab:uploads:migrate[AvatarUploader, Project, :avatar]"
- gitlab-rake "gitlab:uploads:migrate[AvatarUploader, Group, :avatar]"
- gitlab-rake "gitlab:uploads:migrate[AvatarUploader, User, :avatar]"
-
- # Attachments
- gitlab-rake "gitlab:uploads:migrate[AttachmentUploader, Note, :attachment]"
- gitlab-rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :logo]"
- gitlab-rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :header_logo]"
-
- # Markdown
- gitlab-rake "gitlab:uploads:migrate[FileUploader, Project]"
- gitlab-rake "gitlab:uploads:migrate[PersonalFileUploader, Snippet]"
- gitlab-rake "gitlab:uploads:migrate[NamespaceFileUploader, Snippet]"
- gitlab-rake "gitlab:uploads:migrate[FileUploader, MergeRequest]"
- ```
+1. Migrate any existing local uploads to the object storage using [`gitlab:uploads:migrate` rake task](raketasks/uploads/migrate.md).
---
@@ -168,32 +145,7 @@ _The uploads are stored by default in
```
1. Save the file and [restart GitLab][] for the changes to take effect.
-1. Migrate any existing local uploads to the object storage:
-
- > **Notes:**
- > - These task comply with the `BATCH` environment variable to process uploads in batch (200 by default). All of the processing will be done in a background worker and requires **no downtime**.
- > - To migrate in production use `RAILS_ENV=production` environment variable.
-
- ```bash
- # sudo -u git -H bundle exec rake gitlab:uploads:migrate
-
- # Avatars
- sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AvatarUploader, Project, :avatar]"
- sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AvatarUploader, Group, :avatar]"
- sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AvatarUploader, User, :avatar]"
-
- # Attachments
- sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AttachmentUploader, Note, :attachment]"
- sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :logo]"
- sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :header_logo]"
-
- # Markdown
- sudo -u git -H bundle exec rake "gitlab:uploads:migrate[FileUploader, Project]"
- sudo -u git -H bundle exec rake "gitlab:uploads:migrate[PersonalFileUploader, Snippet]"
- sudo -u git -H bundle exec rake "gitlab:uploads:migrate[NamespaceFileUploader, Snippet]"
- sudo -u git -H bundle exec rake "gitlab:uploads:migrate[FileUploader, MergeRequest]"
-
- ```
+1. Migrate any existing local uploads to the object storage using [`gitlab:uploads:migrate` rake task](raketasks/uploads/migrate.md).
[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab"
[restart gitlab]: restart_gitlab.md#installations-from-source "How to restart GitLab"
diff --git a/doc/api/README.md b/doc/api/README.md
index b49c3a198f1..fd5e88cb9d5 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -5,78 +5,86 @@ under [`/lib/api`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/api).
The main GitLab API is a [REST](https://en.wikipedia.org/wiki/Representational_state_transfer) API. Therefore, documentation in this section assumes knowledge of REST concepts.
-## Resources
+## API Resources
-Documentation for various API resources can be found separately in the
-following locations:
+The following API resources are available:
-- [Award Emoji](award_emoji.md)
+- [Applications](applications.md)
+- [Avatar](avatar.md)
+- [Award emoji](award_emoji.md)
- [Branches](branches.md)
-- [Broadcast Messages](broadcast_messages.md)
-- [Project-level Variables](project_level_variables.md)
-- [Group-level Variables](group_level_variables.md)
-- [Code Snippets](snippets.md)
+- [Broadcast messages](broadcast_messages.md)
+- [Code snippets](snippets.md)
- [Commits](commits.md)
-- [Custom Attributes](custom_attributes.md)
+- [Custom attributes](custom_attributes.md)
+- [Deploy keys](deploy_keys.md), and [deploy keys for multiple projects](deploy_key_multiple_projects.md)
- [Deployments](deployments.md)
-- [Deploy Keys](deploy_keys.md)
-- [Dockerfile templates](templates/dockerfiles.md)
+- [Discussions](discussions.md) (threaded comments)
- [Environments](environments.md)
- [Events](events.md)
- [Feature flags](features.md)
-- [Gitignore templates](templates/gitignores.md)
-- [GitLab CI Config templates](templates/gitlab_ci_ymls.md)
-- [Groups](groups.md)
-- [Group Access Requests](access_requests.md)
-- [Group Badges](group_badges.md)
-- [Group Members](members.md)
+- Group-related resources, including:
+ - [Groups](groups.md)
+ - [Group access requests](access_requests.md)
+ - [Group badges](group_badges.md)
+ - [Group issue boards](group_boards.md)
+ - [Group-level variables](group_level_variables.md)
+ - [Group members](members.md)
+ - [Group milestones](group_milestones.md)
- [Issues](issues.md)
-- [Issue Boards](boards.md)
-- [Group Issue Boards](group_boards.md)
+- [Issue boards](boards.md)
- [Jobs](jobs.md)
- [Keys](keys.md)
- [Labels](labels.md)
- [Markdown](markdown.md)
-- [Merge Requests](merge_requests.md)
-- [Project milestones](milestones.md)
-- [Group milestones](group_milestones.md)
+- [Merge requests](merge_requests.md)
- [Namespaces](namespaces.md)
- [Notes](notes.md) (comments)
-- [Discussions](discussions.md) (threaded comments)
-- [Resource Label Events](resource_label_events.md)
- [Notification settings](notification_settings.md)
-- [Open source license templates](templates/licenses.md)
-- [Pages Domains](pages_domains.md)
+- [Pages domains](pages_domains.md)
- [Pipelines](pipelines.md)
-- [Pipeline Triggers](pipeline_triggers.md)
-- [Pipeline Schedules](pipeline_schedules.md)
-- [Projects](projects.md) including setting Webhooks
-- [Project Access Requests](access_requests.md)
-- [Project Badges](project_badges.md)
-- [Project import/export](project_import_export.md)
-- [Project Members](members.md)
-- [Project Snippets](project_snippets.md)
-- [Project Templates](project_templates.md)
-- [Protected Branches](protected_branches.md)
-- [Protected Tags](protected_tags.md)
+- [Pipeline schedules](pipeline_schedules.md)
+- [Pipeline triggers](pipeline_triggers.md) and [triggering pipelines](../ci/triggers/README.md)
+- Project-related resources, including:
+ - [Projects](projects.md) including setting Webhooks
+ - [Project access requests](access_requests.md)
+ - [Project badges](project_badges.md)
+ - [Project-level variables](project_level_variables.md)
+ - [Project import/export](project_import_export.md)
+ - [Project members](members.md)
+ - [Project milestones](milestones.md)
+ - [Project snippets](project_snippets.md)
+ - [Project templates](project_templates.md) (see also [Templates API Resources](#templates-api-resources))
+- [Protected branches](protected_branches.md)
+- [Protected tags](protected_tags.md)
- [Repositories](repositories.md)
-- [Repository Files](repository_files.md)
-- [Repository Submodules](repository_submodules.md)
+- [Repository files](repository_files.md)
+- [Repository submodules](repository_submodules.md)
+- [Resource label events](resource_label_events.md)
- [Runners](runners.md)
- [Search](search.md)
- [Services](services.md)
- [Settings](settings.md)
- [Sidekiq metrics](sidekiq_metrics.md)
-- [System Hooks](system_hooks.md)
+- [System hooks](system_hooks.md)
- [Tags](tags.md)
- [Todos](todos.md)
-- [Triggering Pipelines](../ci/triggers/README.md)
- [Users](users.md)
-- [Validate CI configuration](lint.md)
-- [V3 to V4](v3_to_v4.md)
+- [Validate CI configuration](lint.md) (linting)
- [Version](version.md)
- [Wikis](wikis.md)
+See also [V3 to V4](v3_to_v4.md).
+
+### Templates API Resources
+
+Endpoints are available for:
+
+- [Dockerfile templates](templates/dockerfiles.md).
+- [gitignore templates](templates/gitignores.md).
+- [GitLab CI YAML templates](templates/gitlab_ci_ymls.md).
+- [Open source license templates](templates/licenses.md).
+
## Road to GraphQL
Going forward, we will start on moving to
@@ -98,7 +106,7 @@ specification.
## Compatibility Guidelines
The HTTP API is versioned using a single number, the current one being 4. This
-number symbolises the same as the major version number as described by
+number symbolizes the same as the major version number as described by
[SemVer](https://semver.org/). This mean that backward incompatible changes
will require this version number to change. However, the minor version is
not explicit. This allows for a stable API endpoint, but also means new
diff --git a/doc/api/jobs.md b/doc/api/jobs.md
index aa290ff4cf8..d2dd9c676e3 100644
--- a/doc/api/jobs.md
+++ b/doc/api/jobs.md
@@ -8,12 +8,12 @@ Get a list of jobs in a project.
GET /projects/:id/jobs
```
-| 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 |
-| `scope` | string **or** array of strings | no | The scope of jobs to show, one or array of: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, `manual`; showing all jobs if none provided |
+| 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. |
+| `scope` | string **or** array of strings | no | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. |
-```
+```sh
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/jobs?scope[]=pending&scope[]=running'
```
@@ -140,13 +140,13 @@ Get a list of jobs for a pipeline.
GET /projects/:id/pipelines/:pipeline_id/jobs
```
-| 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 |
-| `pipeline_id` | integer | yes | The ID of a pipeline |
-| `scope` | string **or** array of strings | no | The scope of jobs to show, one or array of: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, `manual`; showing all jobs if none provided |
+| 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. |
+| `pipeline_id` | integer | yes | The ID of a pipeline. |
+| `scope` | string **or** array of strings | no | Scope of jobs to show. Either one of or an array of the following: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, or `manual`. All jobs are returned if `scope` is not provided. |
-```
+```sh
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/pipelines/6/jobs?scope[]=pending&scope[]=running'
```
@@ -273,12 +273,12 @@ Get a single job of a project
GET /projects/:id/jobs/:job_id
```
-| 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 |
-| `job_id` | integer | yes | The ID of a job |
+| 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. |
+| `job_id` | integer | yes | The ID of a job. |
-```
+```sh
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/8"
```
@@ -348,18 +348,18 @@ Get job artifacts of a project.
GET /projects/:id/jobs/:job_id/artifacts
```
-| 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 |
-| `job_id` | integer | yes | The ID of a job |
+| 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. |
+| `job_id` | integer | yes | The ID of a job. |
Example requests:
-```
+```sh
curl --location --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/8/artifacts"
```
-Response:
+Possible response status codes:
| Status | Description |
|-----------|---------------------------------|
@@ -383,19 +383,19 @@ GET /projects/:id/jobs/artifacts/:ref_name/download?job=name
Parameters
-| 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 |
-| `ref_name` | string | yes | The ref from a repository (can only be branch or tag name, not HEAD or SHA) |
-| `job` | string | yes | The name of the job |
+| 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. |
+| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. |
+| `job` | string | yes | The name of the job. |
Example requests:
-```
+```sh
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/download?job=test"
```
-Example response:
+Possible response status codes:
| Status | Description |
|-----------|---------------------------------|
@@ -404,13 +404,13 @@ Example response:
[ce-5347]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5347
-## Download a single artifact file
+## Download a single artifact file by job ID
> Introduced in GitLab 10.0
-Download a single artifact file from within the job's artifacts archive.
-
-Only a single file is going to be extracted from the archive and streamed to a client.
+Download a single artifact file from a job with a specified ID from within
+the job's artifacts archive. The file is extracted from the archive and
+streamed to the client.
```
GET /projects/:id/jobs/:job_id/artifacts/*artifact_path
@@ -418,19 +418,54 @@ GET /projects/:id/jobs/:job_id/artifacts/*artifact_path
Parameters
-| 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 |
-| `job_id ` | integer | yes | The unique job identifier |
-| `artifact_path` | string | yes | Path to a file inside the artifacts archive |
+| 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. |
+| `job_id ` | integer | yes | The unique job identifier. |
+| `artifact_path` | string | yes | Path to a file inside the artifacts archive. |
Example request:
-```
+```sh
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/5/artifacts/some/release/file.pdf"
```
-Example response:
+Possible response status codes:
+
+| Status | Description |
+|-----------|--------------------------------------|
+| 200 | Sends a single artifact file |
+| 400 | Invalid path provided |
+| 404 | Build not found or no file/artifacts |
+
+## Download a single artifact file from specific tag or branch
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23538) in GitLab 11.5.
+
+Download a single artifact file from a specific tag or branch from within the
+job's artifacts archive. The file is extracted from the archive and streamed to
+the client.
+
+```
+GET /projects/:id/jobs/artifacts/:ref_name/raw/*artifact_path?job=name
+```
+
+Parameters:
+
+| 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. |
+| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. |
+| `artifact_path` | string | yes | Path to a file inside the artifacts archive. |
+| `job` | string | yes | The name of the job. |
+
+Example request:
+
+```sh
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/raw/some/release/file.pdf?job=pdf"
+```
+
+Possible response status codes:
| Status | Description |
|-----------|--------------------------------------|
@@ -446,16 +481,16 @@ Get a trace of a specific job of a project
GET /projects/:id/jobs/:job_id/trace
```
-| 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 |
-| job_id | integer | yes | The ID of a job |
+| 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. |
+| job_id | integer | yes | The ID of a job. |
-```
+```sh
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/8/trace"
```
-Response:
+Possible response status codes:
| Status | Description |
|-----------|-----------------------------------|
@@ -470,12 +505,12 @@ Cancel a single job of a project
POST /projects/:id/jobs/:job_id/cancel
```
-| 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 |
-| `job_id` | integer | yes | The ID of a job |
+| 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. |
+| `job_id` | integer | yes | The ID of a job. |
-```
+```sh
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/cancel"
```
@@ -518,12 +553,12 @@ Retry a single job of a project
POST /projects/:id/jobs/:job_id/retry
```
-| 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 |
-| `job_id` | integer | yes | The ID of a job |
+| 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. |
+| `job_id` | integer | yes | The ID of a job. |
-```
+```sh
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/retry"
```
@@ -568,14 +603,14 @@ POST /projects/:id/jobs/:job_id/erase
Parameters
-| 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 |
-| `job_id` | integer | yes | The ID of a job |
+| 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. |
+| `job_id` | integer | yes | The ID of a job. |
Example of request
-```
+```sh
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/erase"
```
@@ -621,14 +656,14 @@ POST /projects/:id/jobs/:job_id/artifacts/keep
Parameters
-| 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 |
-| `job_id` | integer | yes | The ID of a job |
+| 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. |
+| `job_id` | integer | yes | The ID of a job. |
Example request:
-```
+```sh
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/artifacts/keep"
```
@@ -672,12 +707,12 @@ Triggers a manual action to start a job.
POST /projects/:id/jobs/:job_id/play
```
-| 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 |
-| `job_id` | integer | yes | The ID of a job |
+| 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. |
+| `job_id` | integer | yes | The ID of a job. |
-```
+```sh
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/play"
```
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index fc03cf6cc39..9ff6c73b1b6 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -974,10 +974,9 @@ curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://git
Merge changes submitted with MR using this API.
+If merge request is unable to be accepted (ie: Work in Progress, Closed, Pipeline Pending Completion, or Failed while requiring Success) - you'll get a `405` and the error message 'Method Not Allowed'
-If it has some conflicts and can not be merged - you'll get a `405` and the error message 'Branch cannot be merged'
-
-If merge request is already merged or closed - you'll get a `406` and the error message 'Method Not Allowed'
+If it has some conflicts and can not be merged - you'll get a `406` and the error message 'Branch cannot be merged'
If the `sha` parameter is passed and does not match the HEAD of the source - you'll get a `409` and the error message 'SHA does not match HEAD of source branch'
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
index 8f1a5c8e19b..7ac97edc7ae 100644
--- a/doc/api/milestones.md
+++ b/doc/api/milestones.md
@@ -1,4 +1,4 @@
-# Milestones API
+# Project milestones API
## List project milestones
@@ -45,7 +45,6 @@ Example Response:
]
```
-
## Get single milestone
Gets a single project milestone.
diff --git a/doc/api/search.md b/doc/api/search.md
index 9716f682ace..7e3ae7404a3 100644
--- a/doc/api/search.md
+++ b/doc/api/search.md
@@ -722,6 +722,23 @@ Example response:
### Scope: wiki_blobs
+Filters are available for this scope:
+
+- filename
+- path
+- extension
+
+To use a filter simply include it in your query like: `a query filename:some_name*`.
+You may use wildcards (`*`) to use glob matching.
+
+Wiki blobs searches are performed on both filenames and contents. Search
+results:
+
+- Found in filenames are displayed before results found in contents.
+- May contain multiple matches for the same blob because the search string
+ might be found in both the filename and content, or might appear multiple
+ times in the content.
+
```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=wiki_blobs&search=bye
```
@@ -777,14 +794,21 @@ Example response:
### Scope: blobs
Filters are available for this scope:
+
- filename
- path
- extension
-to use a filter simply include it in your query like so: `a query filename:some_name*`.
-
+To use a filter simply include it in your query like: `a query filename:some_name*`.
You may use wildcards (`*`) to use glob matching.
+Blobs searches are performed on both filenames and contents. Search results:
+
+- Found in filenames are displayed before results found in contents.
+- May contain multiple matches for the same blob because the search string
+ might be found in both the filename and content, or might appear multiple
+ times in the content.
+
```bash
curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/6/search?scope=blobs&search=installation
```
diff --git a/doc/api/suggestions.md b/doc/api/suggestions.md
new file mode 100644
index 00000000000..9d76ef0c4bf
--- /dev/null
+++ b/doc/api/suggestions.md
@@ -0,0 +1,36 @@
+# Suggest Changes API
+
+Every API call to suggestions must be authenticated.
+
+## Applying suggestions
+
+Applies a suggested patch in a merge request. Users must be
+at least [Developer](../user/permissions.md) to perform such action.
+
+```
+PUT /suggestions/:id/apply
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID of a suggestion |
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/suggestions/5/apply
+```
+
+Example response:
+
+```json
+ {
+ "id": 36,
+ "from_original_line": 10,
+ "to_original_line": 10,
+ "from_line": 10,
+ "to_line": 10,
+ "appliable": false,
+ "applied": true,
+ "from_content": " \"--talk-name=org.freedesktop.\",\n",
+ "to_content": " \"--talk-name=org.free.\",\n \"--talk-name=org.desktop.\",\n"
+ }
+```
diff --git a/doc/ci/README.md b/doc/ci/README.md
index dba1f38abe2..4e066a0df97 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -71,6 +71,7 @@ learn how to leverage its potential even more.
- [Caching dependencies](caching/index.md)
- [Git submodules](git_submodules.md) - How to run your CI jobs when Git
submodules are involved
+- [Pipelines for merge requests](merge_request_pipelines/index.md)
- [Use SSH keys in your build environment](ssh_keys/README.md)
- [Trigger pipelines through the GitLab API](triggers/README.md)
- [Trigger pipelines on a schedule](../user/project/pipelines/schedules.md)
diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md
index 758ab37861b..f93ccc4e3c1 100644
--- a/doc/ci/caching/index.md
+++ b/doc/ci/caching/index.md
@@ -29,8 +29,8 @@ needed to compile the project:
Cache was designed to be used to speed up invocations of subsequent runs of a
given job, by keeping things like dependencies (e.g., npm packages, Go vendor
packages, etc.) so they don't have to be re-fetched from the public internet.
- While the cache can be abused to pass intermediate build results between stages,
- there may be cases where artifacts are a better fit.
+ While the cache can be abused to pass intermediate build results between
+ stages, there may be cases where artifacts are a better fit.
- `artifacts`: **Use for stage results that will be passed between stages.**
Artifacts were designed to upload some compiled/generated bits of the build,
and they can be fetched by any number of concurrent Runners. They are
@@ -39,11 +39,13 @@ needed to compile the project:
directories relative to the build directory** and specifying paths which don't
comply to this rule trigger an unintuitive and illogical error message (an
enhancement is discussed at
- https://gitlab.com/gitlab-org/gitlab-ce/issues/15530). Artifacts need to be
- uploaded to the GitLab instance (not only the GitLab runner) before the next
- stage job(s) can start, so you need to evaluate carefully whether your
- bandwidth allows you to profit from parallelization with stages and shared
- artifacts before investing time in changes to the setup.
+ [https://gitlab.com/gitlab-org/gitlab-ce/issues/15530](https://gitlab.com/gitlab-org/gitlab-ce/issues/15530)
+ ). Artifacts need to be uploaded to the GitLab instance (not only the GitLab
+ runner) before the next stage job(s) can start, so you need to evaluate
+ carefully whether your bandwidth allows you to profit from parallelization
+ with stages and shared artifacts before investing time in changes to the
+ setup.
+
It's sometimes confusing because the name artifact sounds like something that
is only useful outside of the job, like for downloading a final image. But
diff --git a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/cloud_foundry_variables.png b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/cloud_foundry_variables.png
index 28323e2d8de..e76767741ce 100644
--- a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/cloud_foundry_variables.png
+++ b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/cloud_foundry_variables.png
Binary files differ
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
index cae051daa56..1e2be2e8475 100644
--- a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
+++ b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
@@ -254,7 +254,7 @@ To ensure our changes don't break the build and all tests still pass, we utilize
Continuous Integration (CI) to run these checks automatically for every push.
Read through this article to understand [Continuous Integration, Continuous Delivery, and Continuous Deployment](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/),
and how these methods are leveraged by GitLab.
-From the [last tutorial](https://ryanhallcs.wordpress.com/2017/03/15/devops-and-game-dev/) we already have a `gitlab-ci.yml` file set up for building our app from
+From the [last tutorial](https://ryanhallcs.wordpress.com/2017/03/15/devops-and-game-dev/) we already have a `.gitlab-ci.yml` file set up for building our app from
every push. We need to set up a new CI job for testing, which GitLab CI/CD will run after the build job using our generated artifacts from gulp.
Please read through the [documentation on CI/CD configuration](../../../ci/yaml/README.md) file to explore its contents and adjust it to your needs.
diff --git a/doc/ci/examples/laravel_with_gitlab_and_envoy/img/variables_page.png b/doc/ci/examples/laravel_with_gitlab_and_envoy/img/variables_page.png
index 80d8eb0f4fc..4675e20ef79 100644
--- a/doc/ci/examples/laravel_with_gitlab_and_envoy/img/variables_page.png
+++ b/doc/ci/examples/laravel_with_gitlab_and_envoy/img/variables_page.png
Binary files differ
diff --git a/doc/ci/img/pipelines-goal.png b/doc/ci/img/pipelines-goal.png
index a96368e562b..f15716d0b8f 100644
--- a/doc/ci/img/pipelines-goal.png
+++ b/doc/ci/img/pipelines-goal.png
Binary files differ
diff --git a/doc/ci/img/types-of-pipelines.png b/doc/ci/img/types-of-pipelines.png
index bd809de5e68..829a53d5d52 100644
--- a/doc/ci/img/types-of-pipelines.png
+++ b/doc/ci/img/types-of-pipelines.png
Binary files differ
diff --git a/doc/ci/img/view_on_mr_widget.png b/doc/ci/img/view_on_mr_widget.png
index 04f4b58df62..efe023b07b5 100644
--- a/doc/ci/img/view_on_mr_widget.png
+++ b/doc/ci/img/view_on_mr_widget.png
Binary files differ
diff --git a/doc/ci/interactive_web_terminal/index.md b/doc/ci/interactive_web_terminal/index.md
index 2c799e83a5f..d299e28d2e6 100644
--- a/doc/ci/interactive_web_terminal/index.md
+++ b/doc/ci/interactive_web_terminal/index.md
@@ -1,4 +1,4 @@
-# Getting started with interactive web terminals **[CORE ONLY]**
+# Interactive Web Terminals **[CORE ONLY]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/50144) in GitLab 11.3.
@@ -6,8 +6,9 @@ Interactive web terminals give the user access to a terminal in GitLab for
running one-off commands for their CI pipeline.
NOTE: **Note:**
-GitLab.com does not support interactive web terminal at the moment. Please
-follow [this issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/52611) for
+GitLab.com does not support interactive web terminal at the moment – neither
+using shared GitLab.com runners nor your own runners. Please follow
+[this issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/52611) for
progress.
## Configuration
@@ -15,7 +16,7 @@ progress.
Two things need to be configured for the interactive web terminal to work:
- The Runner needs to have [`[session_server]` configured
- properly][session-server]
+ properly](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section)
- If you are using a reverse proxy with your GitLab instance, web terminals need to be
[enabled](../../administration/integration/terminal.md#enabling-and-disabling-terminal-support)
@@ -45,9 +46,7 @@ the terminal and type commands like a normal shell.
If you have the terminal open and the job has finished with its tasks, the
terminal will block the job from finishing for the duration configured in
-[`[session_server].terminal_max_retention_time`][session-server] until you
+[`[session_server].terminal_max_retention_time`](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section) until you
close the terminal window.
![finished job with terminal open](img/finished_job_with_terminal_open.png)
-
-[session-server]: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section
diff --git a/doc/ci/merge_request_pipelines/img/merge_request.png b/doc/ci/merge_request_pipelines/img/merge_request.png
new file mode 100644
index 00000000000..cf9c628e9a0
--- /dev/null
+++ b/doc/ci/merge_request_pipelines/img/merge_request.png
Binary files differ
diff --git a/doc/ci/merge_request_pipelines/img/pipeline_detail.png b/doc/ci/merge_request_pipelines/img/pipeline_detail.png
new file mode 100644
index 00000000000..6094a0975fb
--- /dev/null
+++ b/doc/ci/merge_request_pipelines/img/pipeline_detail.png
Binary files differ
diff --git a/doc/ci/merge_request_pipelines/index.md b/doc/ci/merge_request_pipelines/index.md
new file mode 100644
index 00000000000..bf1e61442d4
--- /dev/null
+++ b/doc/ci/merge_request_pipelines/index.md
@@ -0,0 +1,83 @@
+# Pipelines for merge requests
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/15310) in GitLab 11.6.
+
+Usually, when you create a new merge request, a pipeline runs on the
+new change and checks if it's qualified to be merged into a target branch. This
+pipeline should contain only necessary jobs for checking the new changes.
+For example, unit tests, lint checks, and [Review Apps](../review_apps/index.md)
+are often used in this cycle.
+
+With pipelines for merge requests, you can design a specific pipeline structure
+for merge requests. All you need to do is just adding `only: [merge_requests]` to
+the jobs that you want it to run for only merge requests.
+Every time, when developers create or update merge requests, a pipeline runs on
+their new commits at every push to GitLab.
+
+NOTE: **Note**:
+If you use both this feature and [Merge When Pipeline Succeeds](../../user/project/merge_requests/merge_when_pipeline_succeeds.md),
+pipelines for merge requests take precedence over the other regular pipelines.
+
+For example, consider the following [`.gitlab-ci.yml`](../yaml/README.md):
+
+```yaml
+build:
+ stage: build
+ script: ./build
+ only:
+ - branches
+ - tags
+ - merge_requests
+
+test:
+ stage: test
+ script: ./test
+ only:
+ - merge_requests
+
+deploy:
+ stage: deploy
+ script: ./deploy
+```
+
+After the merge request is updated with new commits, GitLab detects that changes
+have occurred and creates a new pipeline for the merge request.
+The pipeline fetches the latest code from the source branch and run tests against it.
+In the above example, the pipeline contains only `build` and `test` jobs.
+Since the `deploy` job doesn't have the `only: [merge_requests]` rule,
+deployment jobs will not happen in the merge request.
+
+Pipelines tagged as **merge request** indicate that they were triggered
+when a merge request was created or updated.
+
+![Merge request page](img/merge_request.png)
+
+The same tag is shown on the pipeline's details:
+
+![Pipeline's details](img/pipeline_detail.png)
+
+## Important notes about merge requests from forked projects
+
+Note that the current behavior is subject to change. In the usual contribution
+flow, external contributors follow the following steps:
+
+1. Fork a parent project.
+1. Create a merge request from the forked project that targets the `master` branch
+in the parent project.
+1. A pipeline runs on the merge request.
+1. A maintainer from the parent project checks the pipeline result, and merge
+into a target branch if the latest pipeline has passed.
+
+Currently, those pipelines are created in a **forked** project, not in the
+parent project. This means you cannot completely trust the pipeline result,
+because, technically, external contributors can disguise their pipeline results
+by tweaking their GitLab Runner in the forked project.
+
+There are multiple reasons about why GitLab doesn't allow those pipelines to be
+created in the parent project, but one of the biggest reasons is security concern.
+External users could steal secret variables from the parent project by modifying
+`.gitlab-ci.yml`, which could be some sort of credentials. This should not happen.
+
+We're discussing a secure solution of running pipelines for merge requests
+that submitted from forked projects,
+see [the issue about the permission extension](https://gitlab.com/gitlab-org/gitlab-ce/issues/23902).
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index bffb0121603..c9a60feb73f 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -148,7 +148,7 @@ file. The parameter is of the form:
variables[key]=value
```
-This information is also exposed in the UI.
+This information is also exposed in the UI. Please note that _values_ are only viewable by Owners and Maintainers.
![Job variables in UI](img/trigger_variables.png)
@@ -172,6 +172,7 @@ stages:
- package
run_tests:
+ stage: test
script:
- make test
diff --git a/doc/ci/triggers/img/trigger_variables.png b/doc/ci/triggers/img/trigger_variables.png
index 0c2a761cfa9..d273b1fe3a2 100644
--- a/doc/ci/triggers/img/trigger_variables.png
+++ b/doc/ci/triggers/img/trigger_variables.png
Binary files differ
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index bdbcf8c9435..209a2c15d90 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -40,75 +40,87 @@ Starting with GitLab 9.0, we have deprecated some variables. Read the
strongly advised to use the new variables as we will remove the old ones in
future GitLab releases.**
-| Variable | GitLab | Runner | Description |
-|-------------------------------- |--------|--------|-------------|
-| **ARTIFACT_DOWNLOAD_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to download artifacts running a job |
-| **CI** | all | 0.4 | Mark that job is executed in CI environment |
-| **CI_COMMIT_REF_NAME** | 9.0 | all | The branch or tag name for which project is built |
-| **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. |
-| **CI_COMMIT_SHA** | 9.0 | all | The commit revision for which project is built |
-| **CI_COMMIT_BEFORE_SHA** | 11.2 | all | The previous latest commit present on a branch before a push request. |
-| **CI_COMMIT_TAG** | 9.0 | 0.5 | The commit tag name. Present only when building tags. |
-| **CI_COMMIT_MESSAGE** | 10.8 | all | The full commit message. |
-| **CI_COMMIT_TITLE** | 10.8 | all | The title of the commit - the full first line of the message |
-| **CI_COMMIT_DESCRIPTION** | 10.8 | all | The description of the commit: the message without first line, if the title is shorter than 100 characters; full message in other case. |
-| **CI_CONFIG_PATH** | 9.4 | 0.5 | The path to CI config file. Defaults to `.gitlab-ci.yml` |
-| **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled |
-| **CI_DEPLOY_USER** | 10.8 | all | Authentication username of the [GitLab Deploy Token][gitlab-deploy-token], only present if the Project has one related.|
-| **CI_DEPLOY_PASSWORD** | 10.8 | all | Authentication password of the [GitLab Deploy Token][gitlab-deploy-token], only present if the Project has one related.|
-| **CI_DISPOSABLE_ENVIRONMENT** | all | 10.1 | Marks that the job is executed in a disposable environment (something that is created only for this job and disposed of/destroyed after the execution - all executors except `shell` and `ssh`). If the environment is disposable, it is set to true, otherwise it is not defined at all. |
-| **CI_ENVIRONMENT_NAME** | 8.15 | all | The name of the environment for this job |
-| **CI_ENVIRONMENT_SLUG** | 8.15 | all | A simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, etc. |
-| **CI_ENVIRONMENT_URL** | 9.3 | all | The URL of the environment for this job |
-| **CI_JOB_ID** | 9.0 | all | The unique id of the current job that GitLab CI uses internally |
-| **CI_JOB_MANUAL** | 8.12 | all | The flag to indicate that job was manually started |
-| **CI_JOB_NAME** | 9.0 | 0.5 | The name of the job as defined in `.gitlab-ci.yml` |
-| **CI_JOB_STAGE** | 9.0 | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
-| **CI_JOB_TOKEN** | 9.0 | 1.2 | Token used for authenticating with the [GitLab Container Registry][registry] and downloading [dependent repositories][dependent-repositories] |
-| **CI_NODE_INDEX** | 11.5 | all | Index of the job in the job set. If the job is not parallelized, this variable is not set. |
-| **CI_NODE_TOTAL** | 11.5 | all | Total number of instances of this job running in parallel. If the job is not parallelized, this variable is set to `1`. |
-| **CI_JOB_URL** | 11.1 | 0.5 | Job details URL |
-| **CI_REPOSITORY_URL** | 9.0 | all | The URL to clone the Git repository |
-| **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab |
-| **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used |
-| **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags |
-| **CI_RUNNER_VERSION** | all | 10.6 | GitLab Runner version that is executing the current job |
-| **CI_RUNNER_REVISION** | all | 10.6 | GitLab Runner revision that is executing the current job |
-| **CI_RUNNER_EXECUTABLE_ARCH** | all | 10.6 | The OS/architecture of the GitLab Runner executable (note that this is not necessarily the same as the environment of the executor) |
-| **CI_PIPELINE_ID** | 8.10 | 0.5 | The unique id of the current pipeline that GitLab CI uses internally |
-| **CI_PIPELINE_IID** | 11.0 | all | The unique id of the current pipeline scoped to project |
-| **CI_PIPELINE_TRIGGERED** | all | all | The flag to indicate that job was [triggered] |
-| **CI_PIPELINE_SOURCE** | 10.0 | all | Indicates how the pipeline was triggered. Possible options are: `push`, `web`, `trigger`, `schedule`, `api`, and `pipeline`. For pipelines created before GitLab 9.5, this will show as `unknown` |
-| **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the job is run |
-| **CI_PROJECT_ID** | all | all | The unique id of the current project that GitLab CI uses internally |
-| **CI_PROJECT_NAME** | 8.10 | 0.5 | The project name that is currently being built (actually it is project folder name) |
-| **CI_PROJECT_NAMESPACE** | 8.10 | 0.5 | The project namespace (username or groupname) that is currently being built |
-| **CI_PROJECT_PATH** | 8.10 | 0.5 | The namespace with project name |
-| **CI_PROJECT_PATH_SLUG** | 9.3 | all | `$CI_PROJECT_PATH` lowercased and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
-| **CI_PIPELINE_URL** | 11.1 | 0.5 | Pipeline details URL |
-| **CI_PROJECT_URL** | 8.10 | 0.5 | The HTTP address to access project |
-| **CI_PROJECT_VISIBILITY** | 10.3 | all | The project visibility (internal, private, public) |
-| **CI_REGISTRY** | 8.10 | 0.5 | If the Container Registry is enabled it returns the address of GitLab's Container Registry |
-| **CI_REGISTRY_IMAGE** | 8.10 | 0.5 | If the Container Registry is enabled for the project it returns the address of the registry tied to the specific project |
-| **CI_REGISTRY_PASSWORD** | 9.0 | all | The password to use to push containers to the GitLab Container Registry |
-| **CI_REGISTRY_USER** | 9.0 | all | The username to use to push containers to the GitLab Container Registry |
-| **CI_SERVER** | all | all | Mark that job is executed in CI environment |
-| **CI_SERVER_NAME** | all | all | The name of CI server that is used to coordinate jobs |
-| **CI_SERVER_REVISION** | all | all | GitLab revision that is used to schedule jobs |
-| **CI_SERVER_VERSION** | all | all | GitLab version that is used to schedule jobs |
-| **CI_SERVER_VERSION_MAJOR** | 11.4 | all | GitLab version major component |
-| **CI_SERVER_VERSION_MINOR** | 11.4 | all | GitLab version minor component |
-| **CI_SERVER_VERSION_PATCH** | 11.4 | all | GitLab version patch component |
-| **CI_SHARED_ENVIRONMENT** | all | 10.1 | Marks that the job is executed in a shared environment (something that is persisted across CI invocations like `shell` or `ssh` executor). If the environment is shared, it is set to true, otherwise it is not defined at all. |
-| **GET_SOURCES_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to fetch sources running a job |
-| **GITLAB_CI** | all | all | Mark that job is executed in GitLab CI environment |
-| **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the job |
-| **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the job |
-| **GITLAB_USER_LOGIN** | 10.0 | all | The login username of the user who started the job |
-| **GITLAB_USER_NAME** | 10.0 | all | The real name of the user who started the job |
-| **RESTORE_CACHE_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to restore the cache running a job |
-
-## 9.0 Renaming
+| Variable | GitLab | Runner | Description |
+|-------------------------------------------|--------|--------|-------------|
+| **ARTIFACT_DOWNLOAD_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to download artifacts running a job |
+| **CI** | all | 0.4 | Mark that job is executed in CI environment |
+| **CI_COMMIT_BEFORE_SHA** | 11.2 | all | The previous latest commit present on a branch before a push request. |
+| **CI_COMMIT_DESCRIPTION** | 10.8 | all | The description of the commit: the message without first line, if the title is shorter than 100 characters; full message in other case. |
+| **CI_COMMIT_MESSAGE** | 10.8 | all | The full commit message. |
+| **CI_COMMIT_REF_NAME** | 9.0 | all | The branch or tag name for which project is built |
+| **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. |
+| **CI_COMMIT_SHA** | 9.0 | all | The commit revision for which project is built |
+| **CI_COMMIT_SHORT_SHA** | 11.7 | all | The first eight characters of `CI_COMMIT_SHA` |
+| **CI_COMMIT_TAG** | 9.0 | 0.5 | The commit tag name. Present only when building tags. |
+| **CI_COMMIT_TITLE** | 10.8 | all | The title of the commit - the full first line of the message |
+| **CI_CONFIG_PATH** | 9.4 | 0.5 | The path to CI config file. Defaults to `.gitlab-ci.yml` |
+| **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled |
+| **CI_DEPLOY_PASSWORD** | 10.8 | all | Authentication password of the [GitLab Deploy Token][gitlab-deploy-token], only present if the Project has one related.|
+| **CI_DEPLOY_USER** | 10.8 | all | Authentication username of the [GitLab Deploy Token][gitlab-deploy-token], only present if the Project has one related.|
+| **CI_DISPOSABLE_ENVIRONMENT** | all | 10.1 | Marks that the job is executed in a disposable environment (something that is created only for this job and disposed of/destroyed after the execution - all executors except `shell` and `ssh`). If the environment is disposable, it is set to true, otherwise it is not defined at all. |
+| **CI_ENVIRONMENT_NAME** | 8.15 | all | The name of the environment for this job |
+| **CI_ENVIRONMENT_SLUG** | 8.15 | all | A simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, etc. |
+| **CI_ENVIRONMENT_URL** | 9.3 | all | The URL of the environment for this job |
+| **CI_JOB_ID** | 9.0 | all | The unique id of the current job that GitLab CI uses internally |
+| **CI_JOB_MANUAL** | 8.12 | all | The flag to indicate that job was manually started |
+| **CI_JOB_NAME** | 9.0 | 0.5 | The name of the job as defined in `.gitlab-ci.yml` |
+| **CI_JOB_STAGE** | 9.0 | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
+| **CI_JOB_TOKEN** | 9.0 | 1.2 | Token used for authenticating with the [GitLab Container Registry][registry] and downloading [dependent repositories][dependent-repositories] |
+| **CI_JOB_URL** | 11.1 | 0.5 | Job details URL |
+| **CI_MERGE_REQUEST_ID** | 11.6 | all | The ID of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
+| **CI_MERGE_REQUEST_IID** | 11.6 | all | The IID of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
+| **CI_MERGE_REQUEST_PROJECT_ID** | 11.6 | all | The ID of the project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
+| **CI_MERGE_REQUEST_PROJECT_PATH** | 11.6 | all | The path of the project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) (e.g. `namespace/awesome-project`) |
+| **CI_MERGE_REQUEST_PROJECT_URL** | 11.6 | all | The URL of the project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) (e.g. `http://192.168.10.15:3000/namespace/awesome-project`) |
+| **CI_MERGE_REQUEST_REF_PATH** | 11.6 | all | The ref path of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md). (e.g. `refs/merge-requests/1/head`) |
+| **CI_MERGE_REQUEST_SOURCE_BRANCH_NAME** | 11.6 | all | The source branch name of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
+| **CI_MERGE_REQUEST_SOURCE_PROJECT_ID** | 11.6 | all | The ID of the source project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
+| **CI_MERGE_REQUEST_SOURCE_PROJECT_PATH** | 11.6 | all | The path of the source project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
+| **CI_MERGE_REQUEST_SOURCE_PROJECT_URL** | 11.6 | all | The URL of the source project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
+| **CI_MERGE_REQUEST_TARGET_BRANCH_NAME** | 11.6 | all | The target branch name of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) |
+| **CI_NODE_INDEX** | 11.5 | all | Index of the job in the job set. If the job is not parallelized, this variable is not set. |
+| **CI_NODE_TOTAL** | 11.5 | all | Total number of instances of this job running in parallel. If the job is not parallelized, this variable is set to `1`. |
+| **CI_PIPELINE_ID** | 8.10 | 0.5 | The unique id of the current pipeline that GitLab CI uses internally |
+| **CI_PIPELINE_IID** | 11.0 | all | The unique id of the current pipeline scoped to project |
+| **CI_PIPELINE_SOURCE** | 10.0 | all | Indicates how the pipeline was triggered. Possible options are: `push`, `web`, `trigger`, `schedule`, `api`, and `pipeline`. For pipelines created before GitLab 9.5, this will show as `unknown` |
+| **CI_PIPELINE_TRIGGERED** | all | all | The flag to indicate that job was [triggered] |
+| **CI_PIPELINE_URL** | 11.1 | 0.5 | Pipeline details URL |
+| **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the job is run |
+| **CI_PROJECT_ID** | all | all | The unique id of the current project that GitLab CI uses internally |
+| **CI_PROJECT_NAME** | 8.10 | 0.5 | The project name that is currently being built (actually it is project folder name) |
+| **CI_PROJECT_NAMESPACE** | 8.10 | 0.5 | The project namespace (username or groupname) that is currently being built |
+| **CI_PROJECT_PATH** | 8.10 | 0.5 | The namespace with project name |
+| **CI_PROJECT_PATH_SLUG** | 9.3 | all | `$CI_PROJECT_PATH` lowercased and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
+| **CI_PROJECT_URL** | 8.10 | 0.5 | The HTTP address to access project |
+| **CI_PROJECT_VISIBILITY** | 10.3 | all | The project visibility (internal, private, public) |
+| **CI_REGISTRY** | 8.10 | 0.5 | If the Container Registry is enabled it returns the address of GitLab's Container Registry |
+| **CI_REGISTRY_IMAGE** | 8.10 | 0.5 | If the Container Registry is enabled for the project it returns the address of the registry tied to the specific project |
+| **CI_REGISTRY_PASSWORD** | 9.0 | all | The password to use to push containers to the GitLab Container Registry |
+| **CI_REGISTRY_USER** | 9.0 | all | The username to use to push containers to the GitLab Container Registry |
+| **CI_REPOSITORY_URL** | 9.0 | all | The URL to clone the Git repository |
+| **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab |
+| **CI_RUNNER_EXECUTABLE_ARCH** | all | 10.6 | The OS/architecture of the GitLab Runner executable (note that this is not necessarily the same as the environment of the executor) |
+| **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used |
+| **CI_RUNNER_REVISION** | all | 10.6 | GitLab Runner revision that is executing the current job |
+| **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags |
+| **CI_RUNNER_VERSION** | all | 10.6 | GitLab Runner version that is executing the current job |
+| **CI_SERVER** | all | all | Mark that job is executed in CI environment |
+| **CI_SERVER_NAME** | all | all | The name of CI server that is used to coordinate jobs |
+| **CI_SERVER_REVISION** | all | all | GitLab revision that is used to schedule jobs |
+| **CI_SERVER_VERSION** | all | all | GitLab version that is used to schedule jobs |
+| **CI_SERVER_VERSION_MAJOR** | 11.4 | all | GitLab version major component |
+| **CI_SERVER_VERSION_MINOR** | 11.4 | all | GitLab version minor component |
+| **CI_SERVER_VERSION_PATCH** | 11.4 | all | GitLab version patch component |
+| **CI_SHARED_ENVIRONMENT** | all | 10.1 | Marks that the job is executed in a shared environment (something that is persisted across CI invocations like `shell` or `ssh` executor). If the environment is shared, it is set to true, otherwise it is not defined at all. |
+| **GET_SOURCES_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to fetch sources running a job |
+| **GITLAB_CI** | all | all | Mark that job is executed in GitLab CI environment |
+| **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the job |
+| **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the job |
+| **GITLAB_USER_LOGIN** | 10.0 | all | The login username of the user who started the job |
+| **GITLAB_USER_NAME** | 10.0 | all | The real name of the user who started the job |
+| **RESTORE_CACHE_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to restore the cache running a job |
+
+## GitLab 9.0 renaming
To follow conventions of naming across GitLab, and to further move away from the
`build` term and toward `job` CI variables have been renamed for the 9.0
@@ -137,7 +149,7 @@ future GitLab releases.**
## `.gitlab-ci.yml` defined variables
NOTE **Note:**
-This feature requires GitLab Runner 0.5.0 or higher and GitLab CI 7.14 or higher.
+This feature requires GitLab Runner 0.5.0 or higher and GitLab 7.14 or higher.
GitLab CI allows you to add to `.gitlab-ci.yml` variables that are set in the
build environment. The variables are hence saved in the repository, and they
@@ -176,8 +188,7 @@ script:
## Variables
-NOTE: **Note:**
-Group-level variables were added in GitLab 9.4.
+> Group-level variables were introduced in GitLab 9.4.
CAUTION: **Important:**
Be aware that variables are not masked, and their values can be shown
@@ -206,8 +217,7 @@ Once you set them, they will be available for all subsequent pipelines. You can
### Protected variables
->**Notes:**
-This feature requires GitLab 9.3 or higher.
+> Introduced in GitLab 9.3.
Variables could be protected. Whenever a variable is
protected, it would only be securely passed to pipelines running on the
@@ -228,8 +238,7 @@ Variables can be specified for a single pipeline run when a [manual pipeline](..
## Deployment variables
-NOTE: **Note:**
-This feature requires GitLab CI 8.15 or higher.
+> Introduced in GitLab 8.15.
[Project services](../../user/project/integrations/project_services.md) that are
responsible for deployment configuration may define their own variables that
@@ -308,6 +317,8 @@ Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-mach
++ CI_DEBUG_TRACE=false
++ export CI_COMMIT_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045
++ CI_COMMIT_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045
+++ export CI_COMMIT_SHORT_SHA=dd648b2e
+++ CI_COMMIT_SHORT_SHA=dd648b2e
++ export CI_COMMIT_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045
++ CI_COMMIT_BEFORE_SHA=dd648b2e48ce6518303b0bb580b2ee32fadaf045
++ export CI_COMMIT_REF_NAME=master
@@ -454,6 +465,7 @@ Example values:
```bash
export CI_JOB_ID="50"
export CI_COMMIT_SHA="1ecfd275763eff1d6b4844ea3168962458c9f27a"
+export CI_COMMIT_SHORT_SHA="1ecfd275"
export CI_COMMIT_REF_NAME="master"
export CI_REPOSITORY_URL="https://gitlab-ci-token:abcde-1234ABCD5678ef@example.com/gitlab-org/gitlab-ce.git"
export CI_COMMIT_TAG="1.0.0"
@@ -490,7 +502,7 @@ export CI_REGISTRY_PASSWORD="longalfanumstring"
## Variables expressions
-> Variables expressions were added in GitLab 10.7.
+> Introduced in GitLab 10.7.
It is possible to use variables expressions with only / except policies in
`.gitlab-ci.yml`. By using this approach you can limit what jobs are going to
diff --git a/doc/ci/variables/img/variables.png b/doc/ci/variables/img/variables.png
index d2dc99bbac0..0795f7c888f 100644
--- a/doc/ci/variables/img/variables.png
+++ b/doc/ci/variables/img/variables.png
Binary files differ
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index af7e41db443..440254e58bd 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -55,27 +55,27 @@ A job is defined by a list of parameters that define the job behavior.
| Keyword | Required | Description |
|---------------|----------|-------------|
-| script | yes | Defines a shell script which is executed by Runner |
-| extends | no | Defines a configuration entry that this job is going to inherit from |
-| image | no | Use docker image, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
-| services | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
-| stage | no | Defines a job stage (default: `test`) |
-| type | no | Alias for `stage` |
-| variables | no | Define job variables on a job level |
-| only | no | Defines a list of git refs for which job is created |
-| except | no | Defines a list of git refs for which job is not created |
-| tags | no | Defines a list of tags which are used to select Runner |
-| allow_failure | no | Allow job to fail. Failed job doesn't contribute to commit status |
-| when | no | Define when to run job. Can be `on_success`, `on_failure`, `always` or `manual` |
-| dependencies | no | Define other jobs that a job depends on so that you can pass artifacts between them|
-| artifacts | no | Define list of [job artifacts](#artifacts) |
-| cache | no | Define list of files that should be cached between subsequent runs |
-| before_script | no | Override a set of commands that are executed before job |
-| after_script | no | Override a set of commands that are executed after job |
-| environment | no | Defines a name of environment to which deployment is done by this job |
-| coverage | no | Define code coverage settings for a given job |
-| retry | no | Define when and how many times a job can be auto-retried in case of a failure |
-| parallel | no | Defines how many instances of a job should be run in parallel |
+| [script](#script) | yes | Defines a shell script which is executed by Runner |
+| [extends](#extends) | no | Defines a configuration entry that this job is going to inherit from |
+| [image](#image-and-services) | no | Use docker image, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
+| [services](#image-and-services) | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
+| [stage](#stage) | no | Defines a job stage (default: `test`) |
+| type | no | Alias for `stage` |
+| [variables](#variables) | no | Define job variables on a job level |
+| [only](#only-and-except-simplified) | no | Defines a list of git refs for which job is created |
+| [except](#only-and-except-simplified) | no | Defines a list of git refs for which job is not created |
+| [tags](#tags) | no | Defines a list of tags which are used to select Runner |
+| [allow_failure](#allow_failure) | no | Allow job to fail. Failed job doesn't contribute to commit status |
+| [when](#when) | no | Define when to run job. Can be `on_success`, `on_failure`, `always` or `manual` |
+| [dependencies](#dependencies) | no | Define other jobs that a job depends on so that you can pass artifacts between them|
+| [artifacts](#artifacts) | no | Define list of [job artifacts](#artifacts) |
+| [cache](#cache) | no | Define list of files that should be cached between subsequent runs |
+| [before_script](#before_script-and-after_script) | no | Override a set of commands that are executed before job |
+| [after_script](#before_script-and-after_script) | no | Override a set of commands that are executed after job |
+| [environment](#environment) | no | Defines a name of environment to which deployment is done by this job |
+| [coverage](#coverage) | no | Define code coverage settings for a given job |
+| [retry](#retry) | no | Define when and how many times a job can be auto-retried in case of a failure |
+| [parallel](#parallel) | no | Defines how many instances of a job should be run in parallel |
### `extends`
@@ -203,7 +203,7 @@ used for time of the job. The configuration of this feature is covered in
jobs, including deploy jobs, but after the restoration of [artifacts](#artifacts).
This can be an array or a multi-line string.
-`after_script` is used to define the command that will be run after for all
+`after_script` is used to define the command that will be run after all
jobs, including failed ones. This has to be an array or a multi-line string.
The `before_script` and the main `script` are concatenated and run in a single context/container.
@@ -342,15 +342,16 @@ In addition, `only` and `except` allow the use of special keywords:
| **Value** | **Description** |
| --------- | ---------------- |
-| `branches` | When a branch is pushed. |
-| `tags` | When a tag is pushed. |
-| `api` | When pipeline has been triggered by a second pipelines API (not triggers API). |
-| `external` | When using CI services other than GitLab. |
-| `pipelines` | For multi-project triggers, created using the API with `CI_JOB_TOKEN`. |
-| `pushes` | Pipeline is triggered by a `git push` by the user. |
-| `schedules` | For [scheduled pipelines][schedules]. |
-| `triggers` | For pipelines created using a trigger token. |
-| `web` | For pipelines created using **Run pipeline** button in GitLab UI (under your project's **Pipelines**). |
+| `branches` | When a git reference of a pipeline is a branch. |
+| `tags` | When a git reference of a pipeline is a tag. |
+| `api` | When pipeline has been triggered by a second pipelines API (not triggers API). |
+| `external` | When using CI services other than GitLab. |
+| `pipelines` | For multi-project triggers, created using the API with `CI_JOB_TOKEN`. |
+| `pushes` | Pipeline is triggered by a `git push` by the user. |
+| `schedules` | For [scheduled pipelines][schedules]. |
+| `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)). |
In the example below, `job` will run only for refs that start with `issue-`,
whereas all branches will be skipped:
@@ -391,51 +392,84 @@ job:
The above example will run `job` for all branches on `gitlab-org/gitlab-ce`,
except master.
+If a job does not have neither `only` nor `except` rule,
+`only: ['branches', 'tags']` is set by default.
+
+For example,
+
+```yaml
+job:
+ script: echo 'test'
+```
+
+is translated to:
+
+```yaml
+job:
+ script: echo 'test'
+ only: ['branches', 'tags']
+```
+
## `only` and `except` (complex)
-> `refs` and `kubernetes` policies introduced in GitLab 10.0
->
-> `variables` policy introduced in 10.7
->
-> `changes` policy [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/19232) in 11.4
+> - `refs` and `kubernetes` policies introduced in GitLab 10.0.
+> - `variables` policy introduced in GitLab 10.7.
+> - `changes` policy [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/19232) in GitLab 11.4.
CAUTION: **Warning:**
This an _alpha_ feature, and it is subject to change at any time without
prior notice!
-Since GitLab 10.0 it is possible to define a more elaborate only/except job
-policy configuration.
+GitLab supports both simple and complex strategies, so it's possible to use an
+array and a hash configuration scheme.
-GitLab now supports both, simple and complex strategies, so it is possible to
-use an array and a hash configuration scheme.
+Four keys are available:
-Four keys are now available: `refs`, `kubernetes` and `variables` and `changes`.
+- `refs`
+- `variables`
+- `changes`
+- `kubernetes`
-### `refs` and `kubernetes`
+If you use multiple keys under `only` or `except`, they act as an AND. The logic is:
-Refs strategy equals to simplified only/except configuration, whereas
-kubernetes strategy accepts only `active` keyword.
+> (any of refs) AND (any of variables) AND (any of changes) AND (if kubernetes is active)
-### `only:variables`
+### `only:refs` and `except:refs`
-`variables` keyword is used to define variables expressions. In other words
-you can use predefined variables / project / group or
-environment-scoped variables to define an expression GitLab is going to
-evaluate in order to decide whether a job should be created or not.
+The `refs` strategy can take the same values as the
+[simplified only/except configuration](#only-and-except-simplified).
-See the example below. Job is going to be created only when pipeline has been
-scheduled or runs for a `master` branch, and only if kubernetes service is
-active in the project.
+In the example below, the `deploy` job is going to be created only when the
+pipeline has been [scheduled][schedules] or runs for the `master` branch:
```yaml
-job:
+deploy:
only:
refs:
- master
- schedules
+```
+
+### `only:kubernetes` and `except:kubernetes`
+
+The `kubernetes` strategy accepts only the `active` keyword.
+
+In the example below, the `deploy` job is going to be created only when the
+Kubernetes service is active in the project:
+
+```yaml
+deploy:
+ only:
kubernetes: active
```
+### `only:variables` and `except:variables`
+
+The `variables` keyword is used to define variables expressions. In other words,
+you can use predefined variables / project / group or
+environment-scoped variables to define an expression GitLab is going to
+evaluate in order to decide whether a job should be created or not.
+
Examples of using variables expressions:
```yaml
@@ -449,7 +483,7 @@ deploy:
- $STAGING
```
-Another use case is exluding jobs depending on a commit message _(added in 11.0)_:
+Another use case is excluding jobs depending on a commit message:
```yaml
end-to-end:
@@ -459,11 +493,11 @@ end-to-end:
- $CI_COMMIT_MESSAGE =~ /skip-end-to-end-tests/
```
-Learn more about variables expressions on [a separate page][variables-expressions].
+Learn more about [variables expressions](../variables/README.md#variables-expressions).
-### `only:changes`
+### `only:changes` and `except:changes`
-Using `changes` keyword with `only` or `except` makes it possible to define if
+Using the `changes` keyword with `only` or `except`, makes it possible to define if
a job should be created based on files modified by a git push event.
For example:
@@ -480,13 +514,13 @@ docker build:
```
In the scenario above, if you are pushing multiple commits to GitLab to an
-existing branch, GitLab creates and triggers `docker build` job, provided that
+existing branch, GitLab creates and triggers the `docker build` job, provided that
one of the commits contains changes to either:
- The `Dockerfile` file.
- Any of the files inside `docker/scripts/` directory.
-- Any of the files and subfolders inside `dockerfiles` directory.
-- Any of the files with `rb`, `py`, `sh` extensions inside `more_scripts` directory.
+- Any of the files and subdirectories inside the `dockerfiles` directory.
+- Any of the files with `rb`, `py`, `sh` extensions inside the `more_scripts` directory.
CAUTION: **Warning:**
There are some caveats when using this feature with new branches and tags. See
@@ -550,15 +584,17 @@ osx job:
## `allow_failure`
-`allow_failure` is used when you want to allow a job to fail without impacting
-the rest of the CI suite. Failed jobs don't contribute to the commit status.
-The default value is `false`.
+`allow_failure` allows a job to fail without impacting the rest of the CI
+suite.
+The default value is `false`, except for [manual](#whenmanual) jobs.
+
+When enabled and the job fails, the job will show an orange warning in the UI.
+However, the logical flow of the pipeline will consider the job a
+success/passed, and is not blocked.
-When enabled and the job fails, the pipeline will be successful/green for all
-intents and purposes, but a "CI build passed with warnings" message will be
-displayed on the merge request or commit or job page. This is to be used by
-jobs that are allowed to fail, but where failure indicates some other (manual)
-steps should be taken elsewhere.
+Assuming all other jobs are successful, the job's stage and its pipeline will
+show the same orange warning. However, the associated commit will be marked
+"passed", without warnings.
In the example below, `job1` and `job2` will run in parallel, but if `job1`
fails, it will not stop the next stage from running, since it's marked with
@@ -590,12 +626,13 @@ failure.
`when` can be set to one of the following values:
1. `on_success` - execute job only when all jobs from prior stages
- succeed. This is the default.
+ succeed (or are considered succeeding because they are marked
+ `allow_failure`). This is the default.
1. `on_failure` - execute job only when at least one job from prior stages
fails.
1. `always` - execute job regardless of the status of jobs from prior stages.
1. `manual` - execute job manually (added in GitLab 8.10). Read about
- [manual actions](#when-manual) below.
+ [manual actions](#whenmanual) below.
For example:
@@ -655,7 +692,7 @@ Manual actions are a special type of job that are not executed automatically,
they need to be explicitly started by a user. An example usage of manual actions
would be a deployment to a production environment. Manual actions can be started
from the pipeline, job, environment, and deployment views. Read more at the
-[environments documentation][env-manual].
+[environments documentation](../environments.md#manually-deploying-to-environments).
Manual actions can be either optional or blocking. Blocking manual actions will
block the execution of the pipeline at the stage this action is defined in. It's
@@ -1304,7 +1341,7 @@ The test reports are collected regardless of the job results (success or failure
You can use [`artifacts:expire_in`](#artifacts-expire_in) to set up an expiration
date for their artifacts.
-NOTE: **Note:**
+NOTE: **Note:**
If you also want the ability to browse the report output files, include the
[`artifacts:paths`](#artifactspaths) keyword.
@@ -1638,7 +1675,7 @@ rspec:
```
NOTE: **Note:**
-`include` requires the external YAML files to have the extensions `.yml` or `.yaml`.
+`include` requires the external YAML files to have the extensions `.yml` or `.yaml`.
The external file will not be included if the extension is missing.
You can define it either as a single string, or, in case you want to include
@@ -1690,20 +1727,24 @@ include:
The remote file must be publicly accessible through a simple GET request, as we don't support authentication schemas in the remote URL.
NOTE: **Note:**
- In order to include files from another repository inside your local network,
+ In order to include files from another repository inside your local network,
you may need to enable the **Allow requests to the local network from hooks and services** checkbox
located in the **Settings > Network > Outbound requests** section within the **Admin area**.
---
-Since GitLab 10.8 we are now recursively merging the files defined in `include`
+Since GitLab 10.8 we are now deep merging the files defined in `include`
with those in `.gitlab-ci.yml`. Files defined by `include` are always
-evaluated first and recursively merged with the content of `.gitlab-ci.yml`, no
+evaluated first and merged with the content of `.gitlab-ci.yml`, no
matter the position of the `include` keyword. You can take advantage of
-recursive merging to customize and override details in included CI
+merging to customize and override details in included CI
configurations with local definitions.
+NOTE: **Note:**
+The recursive includes are not supported, meaning your external files
+should not use the `include` keyword, as it will be ignored.
+
The following example shows specific YAML-defined variables and details of the
`production` job from an include file being customized in `.gitlab-ci.yml`.
@@ -1753,11 +1794,7 @@ with the environment url of the `production` job defined in
`autodevops-template.yml` have been overridden by new values defined in
`.gitlab-ci.yml`.
-NOTE: **Note:**
-Recursive includes are not supported meaning your external files
-should not use the `include` keyword, as it will be ignored.
-
-Recursive merging lets you extend and override dictionary mappings, but
+The merging lets you extend and override dictionary mappings, but
you cannot add or modify items to an included array. For example, to add
an additional item to the production job script, you must repeat the
existing script items.
@@ -2176,19 +2213,14 @@ try to quote them, or change them to a different form (e.g., `/bin/true`).
## Examples
-Visit the [examples README][examples] to see a list of examples using GitLab
-CI with various languages.
+See a [list of examples](../examples/README.md "CI/CD examples") for using
+GitLab CI/CD with various languages.
-[env-manual]: ../environments.md#manually-deploying-to-environments
-[examples]: ../examples/README.md
[ce-6323]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6323
-[environment]: ../environments.md
[ce-6669]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6669
-[variables]: ../variables/README.md
[ce-7983]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7983
[ce-7447]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7447
[ce-12909]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12909
-[schedules]: ../../user/project/pipelines/schedules.md
-[variables-expressions]: ../variables/README.md#variables-expressions
-[ee]: https://about.gitlab.com/gitlab-ee/
-[gitlab-versions]: https://about.gitlab.com/products/
+[environment]: ../environments.md "CI/CD environments"
+[schedules]: ../../user/project/pipelines/schedules.md "Pipelines schedules"
+[variables]: ../variables/README.md "CI/CD variables"
diff --git a/doc/development/README.md b/doc/development/README.md
index bcf57a223f5..f22dde32de9 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -52,7 +52,6 @@ description: 'Learn how to contribute to GitLab.'
- [Prometheus metrics](prometheus_metrics.md)
- [Guidelines for reusing abstractions](reusing_abstractions.md)
- [DeclarativePolicy framework](policies.md)
-- [Switching to Rails 5](switching_to_rails5.md)
## Performance guides
diff --git a/doc/development/automatic_ce_ee_merge.md b/doc/development/automatic_ce_ee_merge.md
index e4eb26b3aca..0cc083cefc0 100644
--- a/doc/development/automatic_ce_ee_merge.md
+++ b/doc/development/automatic_ce_ee_merge.md
@@ -1,33 +1,28 @@
# Automatic CE->EE merge
-Whenever a commit is pushed to the CE `master` branch, it is automatically
-merged into the EE `master` branch. If the commit produces any conflicts, it is
-instead reverted from CE `master`. When this happens, a merge request will be
-set up automatically that can be used to reinstate the changes. This merge
-request will be assigned to the author of the conflicting commit, or the merge
-request author if the commit author could not be associated with a GitLab user.
-If no author could be found, the merge request is assigned to a random member of
-the Delivery team. It is then up to this team member to figure out who to assign
-the merge request to.
-
-Because some commits can not be reverted if new commits depend on them, we also
-run a job periodically that processes a range of commits and tries to merge or
-revert them. This should ensure that all commits are either merged into EE
-`master`, or reverted, instead of just being left behind in CE.
+Commits pushed to CE `master` are automatically merged into EE `master` roughly
+every 5 minutes. Changes are merged using the `recursive=ours` merge strategy in
+the context of EE. This means that any merge conflicts are resolved by taking
+the EE changes and discarding the CE changes. This removes the need for
+resolving conflicts or reverting changes, at the cost of **absolutely
+requiring** EE merge requests to be created whenever a CE merge request causes
+merge conflicts. Failing to do so can result in changes not making their way
+into EE.
+
+## Always create an EE merge request if there are conflicts
+
+In CI there is a job called `ee_compat_check`, which checks if a CE MR causes
+merge conflicts with EE. If this job reports conflicts, you **must** create an
+EE merge request. If you are an external contributor you can ask the reviewer to
+do this for you.
## Always merge EE merge requests before their CE counterparts
**In order to avoid conflicts in the CE->EE merge, you should always merge the
EE version of your CE merge request first, if present.**
-The rationale for this is that as CE->EE merges are done automatically, it can
-happen that:
-
-1. A CE merge request that needs EE-specific changes is merged.
-1. The automatic CE->EE merge happens.
-1. Conflicts due to the CE merge request occur since its EE merge request isn't
- merged yet.
-1. The CE changes are reverted.
+Failing to do so will lead to CE changes being discarded when merging into EE,
+if they cause merge conflicts.
## Avoiding CE->EE merge conflicts beforehand
@@ -45,76 +40,181 @@ detect if the current branch's changes will conflict during the CE->EE merge.
The job reports what files are conflicting and how to set up a merge request
against EE.
-## How to reinstate changes
-
-When a commit is reverted, the corresponding merge request to reinstate the
-changes will include all the details necessary to ensure the changes make it
-back into CE and EE. However, you still need to manually set up an EE merge
-request that resolves the conflicts.
-
-Each merge request used to reinstate changes will have the "reverted" label
-applied. Please do not remove this label, as it will be used to determine how
-many times commits are reverted and how long it takes to reinstate the changes.
+#### How the job works
+
+1. Generates the diff between your branch and current CE `master`
+1. Tries to apply it to current EE `master`
+1. If it applies cleanly, the job succeeds, otherwise...
+1. Detects a branch with the `ee-` prefix or `-ee` suffix in EE
+1. If it exists, generate the diff between this branch and current EE `master`
+1. Tries to apply it to current EE `master`
+1. If it applies cleanly, the job succeeds
+
+In the case where the job fails, it means you should create an `ee-<ce_branch>`
+or `<ce_branch>-ee` branch, push it to EE and open a merge request against EE
+`master`.
+At this point if you retry the failing job in your CE merge request, it should
+now pass.
+
+Notes:
+
+- This task is not a silver-bullet, its current goal is to bring awareness to
+ developers that their work needs to be ported to EE.
+- Community contributors shouldn't be required to submit merge requests against
+ EE, but reviewers should take actions by either creating such EE merge request
+ or asking a GitLab developer to do it **before the merge request is merged**.
+- If you branch is too far behind `master`, the job will fail. In that case you
+ should rebase your branch upon latest `master`.
+- Code reviews for merge requests often consist of multiple iterations of
+ feedback and fixes. There is no need to update your EE MR after each
+ iteration. Instead, create an EE MR as soon as you see the
+ `ee_compat_check` job failing. After you receive the final approval
+ from a Maintainer (but **before the CE MR is merged**) update the EE MR.
+ This helps to identify significant conflicts sooner, but also reduces the
+ number of times you have to resolve conflicts.
+- Please remember to
+ [always have your EE merge request merged before the CE version](#always-merge-ee-merge-requests-before-their-ce-counterparts).
+- You can use [`git rerere`](https://git-scm.com/docs/git-rerere)
+ to avoid resolving the same conflicts multiple times.
+
+### Cherry-picking from CE to EE
+
+For avoiding merge conflicts, we use a method of creating equivalent branches
+for CE and EE. If the `ee-compat-check` job fails, this process is required.
+
+This method only requires that you have cloned both CE and EE into your computer.
+If you don't have them yet, please go ahead and clone them:
+
+- Clone CE repo: `git clone git@gitlab.com:gitlab-org/gitlab-ce.git`
+- Clone EE repo: `git clone git@gitlab.com:gitlab-org/gitlab-ee.git`
+
+And the only additional setup we need is to add CE as remote of EE and vice-versa:
+
+- Open two terminal windows, one in CE, and another one in EE:
+ - In EE: `git remote add ce git@gitlab.com:gitlab-org/gitlab-ce.git`
+ - In CE: `git remote add ee git@gitlab.com:gitlab-org/gitlab-ee.git`
+
+That's all setup we need, so that we can cherry-pick a commit from CE to EE, and
+from EE to CE.
+
+Now, every time you create an MR for CE and EE:
+
+1. Open two terminal windows, one in CE, and another one in EE
+1. In the CE terminal:
+ 1. Create the CE branch, e.g., `branch-example`
+ 1. Make your changes and push a commit (commit A)
+ 1. Create the CE merge request in GitLab
+1. In the EE terminal:
+ 1. Create the EE-equivalent branch ending with `-ee`, e.g.,
+ `git checkout -b branch-example-ee`
+ 1. Fetch the CE branch: `git fetch ce branch-example`
+ 1. Cherry-pick the commit A: `git cherry-pick commit-A-SHA`
+ 1. If Git prompts you to fix the conflicts, do a `git status`
+ to check which files contain conflicts, fix them, save the files
+ 1. Add the changes with `git add .` but **DO NOT commit** them
+ 1. Continue cherry-picking: `git cherry-pick --continue`
+ 1. Push to EE: `git push origin branch-example-ee`
+1. Create the EE-equivalent MR and link to the CE MR from the
+description "Ports [CE-MR-LINK] to EE"
+1. Once all the jobs are passing in both CE and EE, you've addressed the
+feedback from your own team, and got them approved, the merge requests can be merged.
+1. When both MRs are ready, the EE merge request will be merged first, and the
+CE-equivalent will be merged next.
+
+**Important notes:**
+
+- The commit SHA can be easily found from the GitLab UI. From a merge request,
+open the tab **Commits** and click the copy icon to copy the commit SHA.
+- To cherry-pick a **commit range**, such as [A > B > C > D] use:
+
+ ```shell
+ git cherry-pick "oldest-commit-SHA^..newest-commit-SHA"
+ ```
+
+ For example, suppose the commit A is the oldest, and its SHA is `4f5e4018c09ed797fdf446b3752f82e46f5af502`,
+ and the commit D is the newest, and its SHA is `80e1c9e56783bd57bd7129828ec20b252ebc0538`.
+ The cherry-pick command will be:
+
+ ```shell
+ git cherry-pick "4f5e4018c09ed797fdf446b3752f82e46f5af502^..80e1c9e56783bd57bd7129828ec20b252ebc0538"
+ ```
+
+- To cherry-pick a **merge commit**, use the flag `-m 1`. For example, suppose that the
+merge commit SHA is `138f5e2f20289bb376caffa0303adb0cac859ce1`:
+
+ ```shell
+ git cherry-pick -m 1 138f5e2f20289bb376caffa0303adb0cac859ce1
+ ```
+- To cherry-pick multiple commits, such as B and D in a range [A > B > C > D], use:
+
+ ```shell
+ git cherry-pick commmit-B-SHA commit-D-SHA
+ ```
+
+ For example, suppose commit B SHA = `4f5e4018c09ed797fdf446b3752f82e46f5af502`,
+ and the commit D SHA = `80e1c9e56783bd57bd7129828ec20b252ebc0538`.
+ The cherry-pick command will be:
+
+ ```shell
+ git cherry-pick 4f5e4018c09ed797fdf446b3752f82e46f5af502 80e1c9e56783bd57bd7129828ec20b252ebc0538
+ ```
+
+ This case is particularly useful when you have a merge commit in a sequence of
+ commits and you want to cherry-pick all but the merge commit.
+
+- If you push more commits to the CE branch, you can safely repeat the procedure
+to cherry-pick them to the EE-equivalent branch. You can do that as many times as
+necessary, using the same CE and EE branches.
+- If you submitted the merge request to the CE repo and the `ee-compat-check` job passed,
+you are not required to submit the EE-equivalent MR, but it's still recommended. If the
+job failed, you are required to submit the EE MR so that you can fix the conflicts in EE
+before merging your changes into CE.
-An example merge request can be found in [CE merge request
-23280](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23280).
+## FAQ
-## How it works
+### How does automatic merging work?
The automatic merging is performed using a project called [Merge
-Train](https://gitlab.com/gitlab-org/merge-train/). For every commit to merge or
-revert, we generate patches using `git format-patch` which we then try to apply
-using `git am --3way`. If this succeeds we push the changes to EE, if this fails
-we decide what to do based on the failure reason:
-
-1. If the patch could not be applied because it was already applied, we just
- skip it.
-1. If the patch caused conflicts, we revert the source commits.
-
-Commits are reverted in reverse order, ensuring that if commit B depends on A,
-and both conflict, we first revert B followed by reverting A.
+Train](https://gitlab.com/gitlab-org/merge-train/). This project will clone CE
+and EE master, and merge CE master into EE master using `git merge
+--strategy=recursive --strategy-option=ours`. This process runs multiple times
+per hour.
-## FAQ
-
-### Why?
+For more information on the exact implementation you can refer to the source
+code.
-We want to work towards being able to deploy continuously, but this requires
-that `master` is always stable and has all the changes we need. If CE `master`
-can not be merged into EE `master` due to merge conflicts, this prevents _any_
-change from CE making its way into EE. Since GitLab.com runs on EE, this
-effectively prevents us from deploying changes.
+### Why merge automatically?
-Past experiences and data have shown that periodic CE to EE merge requests do
-not scale, and often take a very long time to complete. For example, [in this
+As we work towards continuous deployments and a single repository for both CE
+and EE, we need to first make sure that all CE changes make their way into CE as
+fast as possible. Past experiences and data have shown that periodic CE to EE
+merge requests do not scale, and often take a very long time to complete. For
+example, [in this
comment](https://gitlab.com/gitlab-org/release/framework/issues/49#note_114614619)
we determined that the average time to close an upstream merge request is around
5 hours, with peaks up to several days. Periodic merge requests are also
frustrating to work with, because they often include many changes unrelated to
your own changes.
-Automatically merging or reverting commits allows us to keep merging changes
-from CE into EE, as we never have to wait hours for somebody to resolve a set of
-merge conflicts.
-
-### Does the CE to EE merge take into account merge commits?
-
-No. When merging CE changes into EE, merge commits are ignored.
-
-### My changes are reverted, but I set up an EE MR to resolve conflicts
-
-Most likely the automatic merge job ran before the EE merge request was merged.
-If this keeps happening, consider reporting a bug in the [Merge Train issue
-tracker](https://gitlab.com/gitlab-org/merge-train/issues).
+To resolve these problems, we now merge changes using the `ours` strategy to
+automatically resolve merge conflicts. This removes the need for resolving
+conflicts in a periodic merge request, and allows us to merge changes from CE
+into EE much faster.
-### My changes keep getting reverted, and this is really annoying!
+### My CE merge request caused conflicts after it was merged. What do I do?
-This is understandable, but the solution to this is fairly straightforward:
-simply set up an EE merge request for every CE merge request, and resolve your
-conflicts before the changes are reverted.
+If you notice this, you should set up an EE merge request that resolves these
+conflicts as **soon as possible**. Failing to do so can lead to your changes not
+being available in EE, which may break tests. This in turn would prevent us from
+being able to deploy.
-### Will we allow certain people to still merge changes, even if they conflict?
+### Won't this setup be risky?
-No.
+No, not if there is an EE merge request for every CE merge request that causes
+conflicts _and_ that EE merge request is merged first. In the past we may have
+been a bit more relaxed when it comes to enforcing EE merge requests, but to
+enable automatic merging have to start requiring such merge requests even for
+the smallest conflicts.
### Some files I work with often conflict, how can I best deal with this?
@@ -124,10 +224,6 @@ you can do this by moving the EE code to a separate module, which can then be
injected into the appropriate classes or modules. See [Guidelines for
implementing Enterprise Edition features](ee_features.md) for more information.
-### Will changelog entries be reverted automatically?
+---
-Only if the changelog was added in the commit that was reverted. If a changelog
-entry was added in a separate commit, it is possible for it to be left behind.
-Since changelog entries are related to the changes in question, there is no real
-reason to commit the changelog separately, and as such this should not be a big
-problem.
+[Return to Development documentation](README.md)
diff --git a/doc/development/background_migrations.md b/doc/development/background_migrations.md
index bb9a296ef12..dd4a9e058d7 100644
--- a/doc/development/background_migrations.md
+++ b/doc/development/background_migrations.md
@@ -211,7 +211,7 @@ existing data. Since we're dealing with a lot of rows we'll schedule jobs in
batches instead of doing this one by one:
```ruby
-class ScheduleExtractServicesUrl < ActiveRecord::Migration
+class ScheduleExtractServicesUrl < ActiveRecord::Migration[4.2]
disable_ddl_transaction!
class Service < ActiveRecord::Base
@@ -242,7 +242,7 @@ jobs and manually run on any un-migrated rows. Such a migration would look like
this:
```ruby
-class ConsumeRemainingExtractServicesUrlJobs < ActiveRecord::Migration
+class ConsumeRemainingExtractServicesUrlJobs < ActiveRecord::Migration[4.2]
disable_ddl_transaction!
class Service < ActiveRecord::Base
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 7788d155154..25ea2211b64 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -10,7 +10,7 @@ code is effective, understandable, maintainable, and secure.
## Getting your merge request reviewed, approved, and merged
You are strongly encouraged to get your code **reviewed** by a
-[reviewer](https://about.gitlab.com/handbook/engineering/#reviewer) as soon as
+[reviewer](https://about.gitlab.com/handbook/engineering/workflow/code-review/#reviewer) as soon as
there is any code to review, to get a second opinion on the chosen solution and
implementation, and an extra pair of eyes looking for bugs, logic problems, or
uncovered edge cases. The reviewer can be from a different team, but it is
@@ -24,7 +24,7 @@ If you need assistance with security scans or comments, feel free to include the
Security Team (`@gitlab-com/gl-security`) in the review.
Depending on the areas your merge request touches, it must be **approved** by one
-or more [maintainers](https://about.gitlab.com/handbook/engineering/#maintainer):
+or more [maintainers](https://about.gitlab.com/handbook/engineering/workflow/code-review/#maintainer):
For approvals, we use the approval functionality found in the merge request
widget. Reviewers can add their approval by [approving additionally](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html#adding-or-removing-an-approval).
diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md
index 9da4c66933c..9dffca5b99c 100644
--- a/doc/development/contributing/index.md
+++ b/doc/development/contributing/index.md
@@ -3,7 +3,8 @@
Thank you for your interest in contributing to GitLab. This guide details how
to contribute to GitLab in a way that is easy for everyone.
-We want to create a welcoming environment for everyone who is interested in contributing. Please visit our [Code of Conduct page](https://about.gitlab.com/contributing/code-of-conduct) to learn more about our committment to an open and welcoming environment.
+We want to create a welcoming environment for everyone who is interested in contributing.
+Please visit our [Code of Conduct page](https://about.gitlab.com/contributing/code-of-conduct) to learn more about our committment to an open and welcoming environment.
For a first-time step-by-step guide to the contribution process, please see
["Contributing to GitLab"](https://about.gitlab.com/contributing/).
@@ -92,9 +93,9 @@ 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.
-## Workflow labels
+## Issues
-This [documentation](issue_workflow.md) outlines the current workflow labels.
+This [documentation](issue_workflow.md) outlines the current issue process.
* [Type labels](issue_workflow.md#type-labels)
* [Subject labels](issue_workflow.md#subject-labels)
@@ -128,6 +129,6 @@ This [documentation](style_guides.md) outlines the current style guidelines.
[Return to Development documentation](../README.md)
[core team]: https://about.gitlab.com/core-team/
-[team]: https://about.gitlab.com/team/
+[team]: https://about.gitlab.com/company/team/
[getting-help]: https://about.gitlab.com/getting-help/
[codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md
index 5b32b5cd46f..6bcee74a3dd 100644
--- a/doc/development/contributing/merge_request_workflow.md
+++ b/doc/development/contributing/merge_request_workflow.md
@@ -179,7 +179,6 @@ merge request:
1. Note the addition in the release blog post (create one if it doesn't exist yet) https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/
1. Upgrade guide, for example https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/7.5-to-7.6.md
-1. Upgrader https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/upgrader.md#2-run-gitlab-upgrade-tool
1. Installation guide https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies
1. GitLab Development Kit https://gitlab.com/gitlab-org/gitlab-development-kit
1. Test suite https://gitlab.com/gitlab-org/gitlab-ce/blob/master/scripts/prepare_build.sh
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index b7990e1b558..4e5b4a85a97 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -368,6 +368,26 @@ You can combine one or more of the following:
= link_to 'Help page', help_page_path('user/permissions')
```
+### GitLab `/help` tests
+
+Several [rspec tests](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/features/help_pages_spec.rb)
+are run to ensure GitLab documentation renders and works correctly. In particular, that [main docs landing page](../../README.md) will work correctly from `/help`.
+For example, [GitLab.com's `/help`](https://gitlab.com/help).
+
+CAUTION: **Caution:**
+Because the rspec tests only run in a full pipeline, and not a special [docs-only pipeline](#branch-naming), it is possible
+to merge changes that will break `master` from a merge request with a successful docs-only pipeline run.
+
+## Docs site architecture
+
+Read through [docs architecture](site_architecture/index.md) to learn
+how we architecture, build, and deploy the docs site, <https://docs.gitlab.com>, and
+to check all the assets and libraries available.
+
+### Global navigation
+
+Read through the [global navigation](site_architecture/global_nav.md) doc.
+
## General Documentation vs Technical Articles
### General documentation
@@ -552,6 +572,7 @@ Currently, the following tests are in place:
As CE is merged into EE once a day, it's important to avoid merge conflicts.
Submitting an EE-equivalent merge request cherry-picking all commits from CE to EE is
essential to avoid them.
+1. In a full pipeline, tests for [`/help`](#gitlab-help-tests).
### Linting
@@ -679,6 +700,3 @@ GitLab uses [danger bot](https://github.com/danger/danger) for some elements in
code review. For docs changes in merge requests, whenever a change under `/doc`
is made, the bot leaves a comment for the author to mention `@gl-docsteam`, so
that the docs can be properly reviewed.
-
-[gitlab-map]: https://gitlab.com/gitlab-org/gitlab-design/raw/master/production/resources/gitlab-map.png
-[graffle]: https://gitlab.com/gitlab-org/gitlab-design/blob/d8d39f4a87b90fb9ae89ca12dc565347b4900d5e/production/resources/gitlab-map.graffle
diff --git a/doc/development/documentation/site_architecture/global_nav.md b/doc/development/documentation/site_architecture/global_nav.md
new file mode 100644
index 00000000000..62ca7d6c805
--- /dev/null
+++ b/doc/development/documentation/site_architecture/global_nav.md
@@ -0,0 +1,342 @@
+---
+description: "Learn how GitLab docs' global navigation works and how to add new items."
+---
+
+# Global navigation
+
+> [Introduced](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/362)
+in November 2018 for GitLab 11.6.
+
+The global nav adds to the left sidebar the ability to
+navigate and explore the contents of GitLab's documentation.
+
+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.
+
+## Adding new items to the global nav
+
+To add a new doc to the nav, first and foremost, check with the technical writing team:
+
+- If it's applicable
+- What's the exact position the doc will be added to the nav
+
+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-com/gitlab-docs/blob/master/content/_data/global-nav.yaml).
+
+Don't forget to ask a technical writer to review your changes before merging.
+
+## How it works
+
+The global nav has 3 components:
+
+- **Section**
+ - Category
+ - Doc
+
+The available sections are described on the table below:
+
+| Section | Description |
+| ------------- | ------------------------------------------ |
+| User | Documentation for the GitLab's user UI. |
+| Administrator | Documentation for the GitLab's admin area. |
+| Contributor | Documentation for developing GitLab. |
+
+The majority of the links available on the nav were added according to the UI.
+The match is not perfect, as for some UI nav items the documentation doesn't
+apply, and there are also other links to help the new users to discover the
+documentation. The docs under **Administration** are ordered alphabetically
+for clarity.
+
+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
+the consent of one of the technical writers.
+
+## Composition
+
+The global nav is built from two files:
+
+- [Data](#data-file)
+- [Layout](#layout-file)
+
+The data file feeds the layout with the links to the docs. The layout organizes
+the data among the nav in containers properly [styled](#css-classes).
+
+### Data file
+
+The [data file](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/content/_data/global-nav.yaml)
+is structured in three components: sections, categories, and docs.
+
+#### Sections
+
+Each section represents the higher-level nav item. It's composed by
+title and URL:
+
+```yaml
+sections:
+ - section_title: Text
+ section_url: 'link'
+```
+
+The section can stand alone or contain categories within.
+
+#### Categories
+
+Each category within a section composes the second level of the nav.
+It includes the category title and link. It can stand alone in the nav or contain
+a third level of sub-items.
+
+Example of section with one stand-alone category:
+
+```yaml
+- section_title: Section title
+ section_url: 'section-link'
+ section_categories:
+ - category_title: Category title
+ category_url: 'category-link'
+```
+
+Example of section with two stand-alone categories:
+
+```yaml
+- section_title: Section title
+ section_url: 'section-link'
+ section_categories:
+ - category_title: Category 1 title
+ category_url: 'category-1-link'
+
+ - category_title: Category 2 title
+ category_url: 'category-2-link'
+```
+
+For clarity, **always** add a blank line between categories.
+
+If a category URL is not present in CE (it's an EE-only document), add the
+attribute `ee_only: true` below the category link. Example:
+
+```yaml
+- category_title: Category title
+ category_url: 'category-link'
+ ee_only: true
+```
+
+If the category links to an external URL, e.g., [GitLab Design System](https://design.gitlab.com),
+add the attribute `external_url: true` below the category title. Example:
+
+```yaml
+- category_title: GitLab Design System
+ category_url: 'https://design.gitlab.com'
+ external_url: true
+```
+
+#### Docs
+
+Each doc represents the third level of nav links. They must be always
+added within a category.
+
+Example with one doc link:
+
+```yaml
+- category_title: Category title
+ category_url: 'category-link'
+ docs:
+ - doc_title: Document title
+ doc_url: 'doc-link'
+```
+
+A category supports as many docs as necessary, but, for clarity, try to not
+overpopulate a category.
+
+Example with multiple docs:
+
+```yaml
+- category_title: Category title
+ category_url: 'category-link'
+ docs:
+ - doc_title: Document 1 title
+ doc_url: 'doc-1-link'
+ - doc_title: Document 2 title
+ doc_url: 'doc-2-link'
+```
+
+Whenever a document is only present in EE, add the attribute `ee-only: true`
+below the doc link. Example:
+
+```yaml
+- doc_title: Document 2 title
+ doc_url: 'doc-2-link'
+ ee_only: true
+```
+
+If you need to add a document in an external URL, add the attribute `external_url`
+below the doc link:
+
+```yaml
+- doc_title: Document 2 title
+ doc_url: 'doc-2-link'
+ external_url: true
+```
+
+All nav links are clickable. If the higher-level link does not have a link
+of its own, it must link to its first sub-item link, mimicking GitLab's navigation.
+This must be avoided so that we don't have duplicated links nor two `.active` links
+at the same time.
+
+Example:
+
+```yaml
+- category_title: Operations
+ category_url: 'user/project/integrations/prometheus_library/'
+ # until we have a link to operations, the first doc link is
+ # repeated in the category link
+ docs:
+ - doc_title: Metrics
+ doc_url: 'user/project/integrations/prometheus_library/'
+```
+
+#### Syntax
+
+For all components (sections, categories, and docs), **respect the indentation**
+and the following syntax rules.
+
+##### Titles
+
+- Use sentence case, capitalizing feature names.
+- There's no need to wrap the titles, unless there's a special char in it. E.g.,
+ in `GitLab CI/CD`, there's a `/` present, therefore, it must be wrapped in quotes.
+ As convention, wrap the titles in double quotes: `category_title: "GitLab CI/CD"`.
+
+##### URLs
+
+- As convention, always wrap URLs in single quotes `'url'`.
+- Always use relative paths against the home of CE and EE. Examples:
+ - For `https://docs.gitlab.com/ee/README.html`, the relative URL is `README.html`.
+ - For `https://docs.gitlab.com/ee/user/project/cycle_analytics.html`, the relative
+ URL is `user/project/cycle_analytics.html`
+- For `README.html` files, add the complete path `path/to/README.html`.
+- For `index.html` files, use the clean (canonical) URL: `path/to/`.
+- For EE-only docs, use the same relative path, but add the attribute `ee_only: true` below
+ the `doc_url` or `category_url`, as explained above. This will guarantee that when
+ the user is looking at the CE docs, it will link to the EE docs. It also displays
+ an "info" icon on the CE nav to make the user aware that it's a different link.
+
+DANGER: **Important!**
+All links present on the data file must end in `.html`, not `.md`. Do not
+start any relative link with a forward slash `/`.
+
+Examples:
+
+```yaml
+- category_title: Issues
+ category_url: 'user/project/issues/'
+ # note that the above URL does not start with a slash and
+ # does not include index.html at the end
+
+ docs:
+ - doc_title: Service Desk
+ doc_url: 'user/project/service_desk.html'
+ ee_only: true
+ # note that the URL above ends in html and, as the
+ # document is EE-only, the attribute ee_only is set to true.
+```
+
+### Layout file (logic)
+
+The [layout](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/layouts/global_nav.html)
+is fed by the [data file](#data-file), builds the global nav, and is rendered by the
+[default](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/layouts/default.html) layout.
+
+There are three main considerations on the logic built for the nav:
+
+- [Path](#path): first-level directories underneath `docs.gitlab.com/`:
+ - `https://docs.gitlab.com/ce/`
+ - `https://docs.gitlab.com/ee/`
+ - `https://docs.gitlab.com/omnibus/`
+ - `https://docs.gitlab.com/runner/`
+ - `https://docs.gitlab.com/debug/`
+ - `https://docs.gitlab.com/*`
+- [EE-only](#ee-only-docs): documentation only available in `/ee/`, not on `/ce/`, e.g.:
+ - `https://docs.gitlab.com/ee/user/group/epics/`
+ - `https://docs.gitlab.com/ee/user/project/security_dashboard.html`
+- [Default URL](#default-url): between CE and EE docs, the default is `ee`, therefore, all docs
+ should link to `/ee/` unless if on `/ce/` linking internally to `ce`.
+
+#### Path
+
+To use relative paths in the data file, we defined the variable `dir`
+from the root's first-child directory, which defines the path to build
+all the nav links to other pages:
+
+```html
+<% dir = @item.identifier.to_s[%r{(?<=/)[^/]+}] %>
+```
+
+For instance, for `https://docs.gitlab.com/ce/user/index.html`,
+`dir` == `ce`, and for `https://docs.gitlab.com/omnibus/README.html`,
+`dir` == `omnibus`.
+
+#### Default URL
+
+The default and canonical URL for GitLab documentation is
+`http://docs.gitlab.com/ee/`, thus, all links
+in the docs site should link to `/ee/` except when linking
+among `/ce/` docs themselves.
+
+Therefore, if the user is looking at `/ee/`, `/omnibus/`,
+`/runner/`, or any other highest-level dir, the nav should
+point to `/ee/` docs.
+
+On the other hand, if the user is looking at `/ce/` docs,
+all the links in the CE nav should link internally to `/ce/`
+files, except for [`ee-only` docs](#ee-only-docs).
+
+
+```html
+<% if dir != 'ce' %>
+ <a href="/ee/<%= sec[:section_url] %>">...</a>
+ <% else %>
+ <a href="/<%= dir %>/<%= sec[:section_url] %>">...</a>
+ <% end %>
+ ...
+<% end %>
+```
+
+This also allows the nav to be displayed on other
+highest-level dirs (`/omnibus/`, `/runner/`, etc),
+linking them back to `/ee/`.
+
+The same logic is applied to all sections (`sec[:section_url]`),
+categories (`cat[:category_url]`), and docs (`doc[:doc_url]`) URLs.
+
+#### `ee-only` docs
+
+If the user is looking at the CE nav, a given doc is present only
+in `/ee/`, it's tagged in the data file by `ee-only`, linking it
+directly to `/ee/`.
+
+```html
+<% if dir == 'ce' && cat[:ee_only] %>
+ <a href="/ee/<%= cat[:category_url] %>">...</a>
+<% end %>
+```
+
+To make it clear that it it's a different link, an icon is displayed
+on the nav link indicating that the `ee-only` doc is not available in CE.
+
+The `ee-only` attribute is available for `categories` (`<% if dir == 'ce' && cat[:ee_only] %>`)
+and `docs` (`<% if dir == 'ce' && doc[:ee_only] %>`), but not for `sections`.
+
+### CSS classes
+
+The nav is styled in the general `stylesheet.scss`. To change
+its styles, keep them grouped for better development among the team.
+
+The URL components have their unique styles set by the CSS classes `.level-0`,
+`.level-1`, and `.level-2`. To adjust the link's font size, padding, color, etc,
+use these classes. This way we guarantee that the rules for each link do not conflict
+ with other rules in the stylesheets.
diff --git a/doc/development/documentation/site_architecture/index.md b/doc/development/documentation/site_architecture/index.md
new file mode 100644
index 00000000000..956bf90a5d9
--- /dev/null
+++ b/doc/development/documentation/site_architecture/index.md
@@ -0,0 +1,59 @@
+---
+description: "Learn how GitLab's documentation website is architectured."
+---
+
+# Docs site architecture
+
+Learn how we build and architecture [`gitlab-docs`](https://gitlab.com/gitlab-com/gitlab-docs)
+and deploy it to <https://docs.gitlab.com>.
+
+## Assets
+
+To provide an optimized site structure, design, and a search-engine friendly
+website, along with a discoverable documentation, we use a few assets for
+the GitLab Documentation website.
+
+### Libraries
+
+- [Bootstrap 3.3 components](https://getbootstrap.com/docs/3.3/components/)
+- [Bootstrap 3.3 JS](https://getbootstrap.com/docs/3.3/javascript/)
+- [jQuery](https://jquery.com/) 3.2.1
+- [Clipboard JS](https://clipboardjs.com/)
+- [Font Awesome 4.7.0](https://fontawesome.com/v4.7.0/icons/)
+
+### SEO
+
+- [Schema.org](https://schema.org/)
+- [Google Analytics](https://marketingplatform.google.com/about/analytics/)
+- [Google Tag Manager](https://developers.google.com/tag-manager/)
+
+## Global nav
+
+To understand how the global nav (left sidebar) is built, please
+read through the [global navigation](global_nav.md) doc.
+
+## Deployment
+
+The docs site is deployed to production with GitLab Pages, and previewed in
+merge requests with Review Apps.
+
+The deployment aspects will be soon transfered from the [original document](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md)
+to this page.
+
+<!--
+## Repositories
+
+TBA
+
+## Search engine
+
+TBA
+
+## Versions
+
+TBA
+
+## Helpers
+
+TBA
+-->
diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md
index 0f57835fb87..65963b959f7 100644
--- a/doc/development/fe_guide/vuex.md
+++ b/doc/development/fe_guide/vuex.md
@@ -137,6 +137,7 @@ By following this pattern we guarantee:
#### Dispatching actions
To dispatch an action from a component, use the `mapActions` helper:
+
```javascript
import { mapActions } from 'vuex';
@@ -204,6 +205,7 @@ export const getUsersWithPets = (state, getters) => {
```
To access a getter from a component, use the `mapGetters` helper:
+
```javascript
import { mapGetters } from 'vuex';
@@ -226,6 +228,7 @@ export const ADD_USER = 'ADD_USER';
### How to include the store in your application
The store should be included in the main component of your application:
+
```javascript
// app.vue
import store from 'store'; // it will include the index.js file
@@ -364,7 +367,8 @@ Because we're currently using [`babel-plugin-rewire`](https://github.com/speedsk
`[vuex] actions should be function or object with "handler" function`
To prevent this error from happening, you need to export an empty function as `default`:
-```
+
+```javascript
// getters.js or actions.js
// prevent babel-plugin-rewire from generating an invalid default during karma tests
diff --git a/doc/development/feature_flags.md b/doc/development/feature_flags.md
index 1019a1fd0e2..b6161cd6163 100644
--- a/doc/development/feature_flags.md
+++ b/doc/development/feature_flags.md
@@ -113,7 +113,15 @@ feature flag. You can stub a feature flag as follows:
stub_feature_flags(my_feature_flag: false)
```
-## Enabling a feature flag
+## Enabling a feature flag (in development)
+
+In the rails console (`rails c`), enter the following command to enable your feature flag
+
+```ruby
+Feature.enable(:feature_flag_name)
+```
+
+## Enabling a feature flag (in production)
Check how to [roll out changes using feature flags](rolling_out_changes_using_feature_flags.md).
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index 8f1317e235d..ac910e80a89 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -12,7 +12,7 @@ are very appreciative of the work done by translators and proofreaders!
- Bulgarian
- Lyubomir Vasilev - [Crowdin](https://crowdin.com/profile/lyubomirv)
- Catalan
- - Proofreaders needed.
+ - David Planella - [GitLab](https://gitlab.com/dplanella), [Crowdin](https://crowdin.com/profile/dplanella)
- Chinese Simplified
- Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve)
- Chinese Traditional
diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md
index bef166f2aec..a36dc6424a7 100644
--- a/doc/development/instrumentation.md
+++ b/doc/development/instrumentation.md
@@ -35,7 +35,7 @@ Using this method is in general preferred over directly calling the various
instrumentation methods.
Method instrumentation should be added in the initializer
-`config/initializers/8_metrics.rb`.
+`config/initializers/zz_metrics.rb`.
### Examples
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index a99267bfbba..d0a054c3290 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -67,7 +67,7 @@ body:
For example:
```ruby
-class MyMigration < ActiveRecord::Migration
+class MyMigration < ActiveRecord::Migration[4.2]
DOWNTIME = true
DOWNTIME_REASON = 'This migration requires downtime because ...'
@@ -95,7 +95,7 @@ migration. For this to work your migration needs to include the module
`Gitlab::Database::MultiThreadedMigration`:
```ruby
-class MyMigration < ActiveRecord::Migration
+class MyMigration < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
include Gitlab::Database::MultiThreadedMigration
end
@@ -105,7 +105,7 @@ You can then use the method `with_multiple_threads` to perform work in separate
threads. For example:
```ruby
-class MyMigration < ActiveRecord::Migration
+class MyMigration < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
include Gitlab::Database::MultiThreadedMigration
@@ -139,7 +139,7 @@ by calling the method `disable_ddl_transaction!` in the body of your migration
class like so:
```ruby
-class MyMigration < ActiveRecord::Migration
+class MyMigration < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
@@ -167,7 +167,7 @@ the method `disable_ddl_transaction!` in the body of your migration class like
so:
```ruby
-class MyMigration < ActiveRecord::Migration
+class MyMigration < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
@@ -193,7 +193,7 @@ 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.
```ruby
-class Migration < ActiveRecord::Migration
+class Migration < ActiveRecord::Migration[4.2]
def change
add_reference :model, :other_model, index: true, foreign_key: { on_delete: :cascade }
@@ -216,7 +216,7 @@ For example, to add the column `foo` to the `projects` table with a default
value of `10` you'd write the following:
```ruby
-class MyMigration < ActiveRecord::Migration
+class MyMigration < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
@@ -365,7 +365,7 @@ If you need more complex logic you can define and use models local to a
migration. For example:
```ruby
-class MyMigration < ActiveRecord::Migration
+class MyMigration < ActiveRecord::Migration[4.2]
class Project < ActiveRecord::Base
self.table_name = 'projects'
end
diff --git a/doc/development/new_fe_guide/style/prettier.md b/doc/development/new_fe_guide/style/prettier.md
index baaea67d38b..4495f38f262 100644
--- a/doc/development/new_fe_guide/style/prettier.md
+++ b/doc/development/new_fe_guide/style/prettier.md
@@ -57,3 +57,38 @@ node ./scripts/frontend/prettier.js save-all ./vendor/
```
This will go over all files in a specific folder and save it.
+
+## VSCode Settings
+
+### Format on Save
+
+To automatically format your files with Prettier, add the following properties to your User or Workspace Settings:
+
+```javascript
+{
+ "[javascript]": {
+ "editor.formatOnSave": true
+ },
+ "[vue]": {
+ "editor.formatOnSave": true
+ },
+}
+```
+
+### Conflicts with Vetur Extension
+
+There are some [runtime issues](https://github.com/vuejs/vetur/issues/950) with [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) and [the Vetur extension](https://marketplace.visualstudio.com/items?itemName=octref.vetur) for VSCode. To fix this, try adding the following properties to your User or Workspace Settings:
+
+```javascript
+{
+ "prettier.disableLanguages": [],
+ "vetur.format.defaultFormatter.html": "none",
+ "vetur.format.defaultFormatter.js": "none",
+ "vetur.format.defaultFormatter.css": "none",
+ "vetur.format.defaultFormatter.less": "none",
+ "vetur.format.defaultFormatter.postcss": "none",
+ "vetur.format.defaultFormatter.scss": "none",
+ "vetur.format.defaultFormatter.stylus": "none",
+ "vetur.format.defaultFormatter.ts": "none",
+}
+```
diff --git a/doc/development/profiling.md b/doc/development/profiling.md
index 0ca8bb67a77..0b0c6dfc8cf 100644
--- a/doc/development/profiling.md
+++ b/doc/development/profiling.md
@@ -77,8 +77,11 @@ that builds on this to add some additional niceties, such as allowing
configuration with a single Yaml file for multiple URLs, and uploading of the
profile and log output to S3.
-For GitLab.com, you can find the latest results here:
-<http://redash.gitlab.com/dashboard/gitlab-profiler-statistics>
+For GitLab.com, currently the latest profiling data has been [moved from
+Redash to Looker](https://gitlab.com/gitlab-com/Product/issues/5#note_121194467).
+We are [currently investigating how to make this data
+public](https://gitlab.com/meltano/looker/issues/294).
+
## Sherlock
diff --git a/doc/development/prometheus_metrics.md b/doc/development/prometheus_metrics.md
index b6b6d9665ea..0511e735843 100644
--- a/doc/development/prometheus_metrics.md
+++ b/doc/development/prometheus_metrics.md
@@ -30,7 +30,7 @@ You might want to add additional database migration that makes a decision what t
For example: you might be interested in migrating all dependent data to a different metric.
```ruby
-class ImportCommonMetrics < ActiveRecord::Migration
+class ImportCommonMetrics < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
require Rails.root.join('db/importers/common_metrics_importer.rb')
diff --git a/doc/development/sql.md b/doc/development/sql.md
index e1e1d31a85f..06005a0a6f8 100644
--- a/doc/development/sql.md
+++ b/doc/development/sql.md
@@ -106,7 +106,7 @@ transaction. Transactions for migrations can be disabled using the following
pattern:
```ruby
-class MigrationName < ActiveRecord::Migration
+class MigrationName < ActiveRecord::Migration[4.2]
disable_ddl_transaction!
end
```
@@ -114,7 +114,7 @@ end
For example:
```ruby
-class AddUsersLowerUsernameEmailIndexes < ActiveRecord::Migration
+class AddUsersLowerUsernameEmailIndexes < ActiveRecord::Migration[4.2]
disable_ddl_transaction!
def up
diff --git a/doc/development/switching_to_rails5.md b/doc/development/switching_to_rails5.md
deleted file mode 100644
index c9a4ce1a1d1..00000000000
--- a/doc/development/switching_to_rails5.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# Switching to Rails 5
-
-GitLab switched recently to Rails 5. This is a big change (especially for backend development) and it introduces couple of temporary inconveniences.
-
-## After the switch, I found a broken feature. What do I do?
-
-Many fixes and tweaks were done to make our codebase compatible with Rails 5, but it's possible that not all issues were found. If you find an bug, please create an issue and assign it the ~rails5 label.
-
-## It takes much longer to run CI pipelines that build GitLab. Why?
-
-We are temporarily running CI pipelines with Rails 4 and 5 so that we ensure we remain compatible with Rails 4 in case we must revert back to Rails 4 from Rails 5 (this can double the duration of CI pipelines).
-
-We might revert back to Rails 4 if we found a major issue we were unable to quickly fix.
-
-Once we are sure we can stay with Rails 5, we will stop running CI pipelines with Rails 4.
-
-## Can I skip running Rails 4 tests?
-
-If you are sure that your merge request doesn't introduce any incompatibility, you can just include `norails4` anywhere in your branch name and Rails 4 tests will be skipped.
-
-## CI is failing on my test with Rails 4. How can I debug it?
-
-You can run specs locally with Rails 4 using the following command:
-
-```sh
-BUNDLE_GEMFILE=Gemfile.rails4 RAILS5=0 bundle exec rspec ...
-```
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index 72abda26e3d..24f4d457d45 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -19,6 +19,16 @@ Here are some things to keep in mind regarding test performance:
## RSpec
+To run rspec tests:
+
+```sh
+# run all tests
+bundle exec rspec
+
+# run test for path
+bundle exec rspec spec/[path]/[to]/[spec].rb
+```
+
### General guidelines
- Use a single, top-level `describe ClassName` block.
diff --git a/doc/development/testing_guide/ci.md b/doc/development/testing_guide/ci.md
index 8d9706a9501..d685cacf9ea 100644
--- a/doc/development/testing_guide/ci.md
+++ b/doc/development/testing_guide/ci.md
@@ -31,11 +31,7 @@ After that, the next pipeline will use the up-to-date
The GitLab test suite is [monitored] for the `master` branch, and any branch
that includes `rspec-profile` in their name.
-A [public dashboard] is available for everyone to see. Feel free to look at the
-slowest test files and try to improve them.
-
[monitored]: ../performance.md#rspec-profiling
-[public dashboard]: https://redash.gitlab.com/public/dashboards/l1WhHXaxrCWM5Ai9D7YDqHKehq6OU3bx5gssaiWe?org_slug=default
## CI setup
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index a6ed9e85a41..309babb5f94 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -62,6 +62,41 @@ You can also manually start the `review-qa-all`: it runs the full QA suite.
Note that both jobs first wait for the `review-deploy` job to be finished.
+## How to?
+
+### Find my Review App slug?
+
+1. Open the `review-deploy` job.
+1. Look for `Checking for previous deployment of review-*`.
+1. For instance for `Checking for previous deployment of review-qa-raise-e-12chm0`,
+ your Review App slug would be `review-qa-raise-e-12chm0` in this case.
+
+### Run a Rails console?
+
+1. [Filter Workloads by your Review App slug](https://console.cloud.google.com/kubernetes/workload?project=gitlab-review-apps)
+ , e.g. `review-29951-issu-id2qax`.
+1. Find and open the `task-runner` Deployment, e.g. `review-29951-issu-id2qax-task-runner`.
+1. Click on the Pod in the "Managed pods" section, e.g. `review-29951-issu-id2qax-task-runner-d5455cc8-2lsvz`.
+1. Click on the `KUBECTL` dropdown, then `Exec` -> `task-runner`.
+1. Replace `-c task-runner -- ls` with `-- /srv/gitlab/bin/rails c` from the
+ default command or
+ - Run `kubectl exec --namespace review-apps-ce -it review-29951-issu-id2qax-task-runner-d5455cc8-2lsvz -- /srv/gitlab/bin/rails c`
+ and
+ - Replace `review-apps-ce` with `review-apps-ee` if the Review App
+ is running EE, and
+ - Replace `review-29951-issu-id2qax-task-runner-d5455cc8-2lsvz`
+ with your Pod's name.
+
+### Dig into a Pod's logs?
+
+1. [Filter Workloads by your Review App slug](https://console.cloud.google.com/kubernetes/workload?project=gitlab-review-apps)
+ , e.g. `review-1979-1-mul-dnvlhv`.
+1. Find and open the `migrations` Deployment, e.g.
+ `review-1979-1-mul-dnvlhv-migrations.1`.
+1. Click on the Pod in the "Managed pods" section, e.g.
+ `review-1979-1-mul-dnvlhv-migrations.1-nqwtx`.
+1. Click on the `Container logs` link.
+
## Frequently Asked Questions
**Isn't it too much to trigger CNG image builds on every test run? This creates
diff --git a/doc/development/what_requires_downtime.md b/doc/development/what_requires_downtime.md
index 3630a28fae9..24edd05da2f 100644
--- a/doc/development/what_requires_downtime.md
+++ b/doc/development/what_requires_downtime.md
@@ -88,7 +88,7 @@ renaming. For example
```ruby
# A regular migration in db/migrate
-class RenameUsersUpdatedAtToUpdatedAtTimestamp < ActiveRecord::Migration
+class RenameUsersUpdatedAtToUpdatedAtTimestamp < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
@@ -118,7 +118,7 @@ We can perform this cleanup using
```ruby
# A post-deployment migration in db/post_migrate
-class CleanupUsersUpdatedAtRename < ActiveRecord::Migration
+class CleanupUsersUpdatedAtRename < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
@@ -157,7 +157,7 @@ as follows:
```ruby
# A regular migration in db/migrate
-class ChangeUsersUsernameStringToText < ActiveRecord::Migration
+class ChangeUsersUsernameStringToText < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
@@ -178,7 +178,7 @@ Next we need to clean up our changes using a post-deployment migration:
```ruby
# A post-deployment migration in db/post_migrate
-class ChangeUsersUsernameStringToTextCleanup < ActiveRecord::Migration
+class ChangeUsersUsernameStringToTextCleanup < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
@@ -213,7 +213,7 @@ the work / load over a longer time period, without slowing down deployments.
For example, to change the column type using a background migration:
```ruby
-class ExampleMigration < ActiveRecord::Migration
+class ExampleMigration < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
@@ -257,7 +257,7 @@ release) by a cleanup migration, which should steal from the queue and handle
any remaining rows. For example:
```ruby
-class MigrateRemainingIssuesClosedAt < ActiveRecord::Migration
+class MigrateRemainingIssuesClosedAt < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
@@ -322,7 +322,7 @@ Migrations can take advantage of this by using the method
`add_concurrent_index`. For example:
```ruby
-class MyMigration < ActiveRecord::Migration
+class MyMigration < ActiveRecord::Migration[4.2]
def up
add_concurrent_index :projects, :column_name
end
diff --git a/doc/img/devops-stages.png b/doc/img/devops-stages.png
index a971e81a419..424bce95607 100644
--- a/doc/img/devops-stages.png
+++ b/doc/img/devops-stages.png
Binary files differ
diff --git a/doc/install/README.md b/doc/install/README.md
index 92116305775..ae48306e65e 100644
--- a/doc/install/README.md
+++ b/doc/install/README.md
@@ -5,8 +5,25 @@ description: Read through the GitLab installation methods.
# Installation
-GitLab can be installed via various ways. Check the [installation methods][methods]
-for an overview.
+GitLab can be installed in most GNU/Linux distributions and in a number
+of cloud providers. To get the best experience from GitLab you need to balance
+performance, reliability, ease of administration (backups, upgrades and troubleshooting),
+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
+ 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.
+1. **Docker**: The Omnibus GitLab packages dockerized.
+1. **Source**: Install GitLab and all its components from scratch.
+
+TIP: **If in doubt, choose Omnibus:**
+The Omnibus GitLab packages are mature, scalable, support
+[high availability](../administration/high_availability/README.md) and are used
+today on GitLab.com. The Helm charts are recommended for those who are familiar
+with Kubernetes.
## Requirements
@@ -14,36 +31,58 @@ Before installing GitLab, make sure to check the [requirements documentation](re
which includes useful information on the supported Operating Systems as well as
the hardware requirements.
-## Installation methods
-
-- [Installation using the Omnibus packages](https://about.gitlab.com/downloads/) -
- Install GitLab using our official deb/rpm repositories. This is the
- recommended way.
-- [Installation from source](installation.md) - Install GitLab from source.
- Useful for unsupported systems like *BSD. For an overview of the directory
- structure, read the [structure documentation](structure.md).
-- [Docker](docker.md) - Install GitLab using Docker.
-
-## Install GitLab on cloud providers
-
-- [Installing in Kubernetes](kubernetes/index.md): Install GitLab into a Kubernetes
- Cluster using our official Helm Chart Repository.
-- [Install GitLab on OpenShift](openshift_and_gitlab/index.md)
-- [Install GitLab on DC/OS](https://mesosphere.com/blog/gitlab-dcos/) via [GitLab-Mesosphere integration](https://about.gitlab.com/2016/09/16/announcing-gitlab-and-mesosphere/)
-- [Install GitLab on Azure](azure/index.md)
-- [Install GitLab on Google Cloud Platform](google_cloud_platform/index.md)
-- [Install GitLab on Google Kubernetes Engine (GKE)](https://about.gitlab.com/2017/01/23/video-tutorial-idea-to-production-on-google-container-engine-gke/): video tutorial on
-the full process of installing GitLab on Google Kubernetes Engine (GKE), pushing an application to GitLab, building the app with GitLab CI/CD, and deploying to production.
-- [Install on AWS](aws/index.md): Install GitLab on AWS using the community AMIs that GitLab provides.
-- [Getting started with GitLab and DigitalOcean](https://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/): requirements, installation process, updates.
-- [Demo: Cloud Native Development with GitLab](https://about.gitlab.com/2017/04/18/cloud-native-demo/): video demonstration on how to install GitLab on Kubernetes, build a project, create Review Apps, store Docker images in Container Registry, deploy to production on Kubernetes, and monitor with Prometheus.
-- _Testing only!_ [DigitalOcean and Docker Machine](digitaloceandocker.md) -
- Quickly test any version of GitLab on DigitalOcean using Docker Machine.
+## Installing GitLab using the Omnibus GitLab package (recommended)
+
+The Omnibus GitLab package uses our official deb/rpm repositories. This is
+recommended for most users.
+
+If you need additional flexibility and resilience, we recommend deploying
+GitLab as described in our [High Availability documentation](../administration/high_availability/README.md).
+
+[**> Install GitLab using the Omnibus GitLab package.**](https://about.gitlab.com/install/)
+
+## Installing GitLab on Kubernetes via the GitLab Helm charts
+
+NOTE: **Kubernetes experience required:**
+We recommend being familiar with Kubernetes before using it to deploy GitLab in
+production. The methods for management, observability, and some concepts are
+different than traditional deployments.
+
+When installing GitLab on Kubernetes, there are some trade-offs that you
+need to be aware of:
-## Database
+- Administration and troubleshooting requires Kubernetes knowledge.
+- It can be more expensive for smaller installations. The default installation
+ requires more resources than a single node Omnibus deployment, as most services
+ are deployed in a redundant fashion.
+- There are some feature [limitations to be aware of](kubernetes/gitlab_chart.md#limitations).
-While the recommended database is PostgreSQL, we provide information to install
-GitLab using MySQL. Check the [MySQL documentation](database_mysql.md) for more
-information.
+[**> Install GitLab on Kubernetes using the GitLab Helm charts.**](kubernetes/index.md)
-[methods]: https://about.gitlab.com/installation/
+## Installing GitLab with Docker
+
+GitLab maintains a set of official Docker images based on the Omnibus GitLab package.
+
+[**> Install GitLab using the official GitLab Docker images.**](docker.md)
+
+## Installing GitLab from source
+
+If the GitLab Omnibus package is not available in your distribution, you can
+install GitLab from source: Useful for unsupported systems like *BSD. For an
+overview of the directory structure, read the [structure documentation](structure.md).
+
+[**> Install GitLab from source.**](installation.md)
+
+## Installing GitLab on cloud providers
+
+GitLab can be installed on a variety of cloud providers by using any of
+the above methods, provided the cloud provider supports it.
+
+- [Install on AWS](aws/index.md): Install Omnibus GitLab on AWS using the community AMIs that GitLab provides.
+- [Install GitLab on Google Cloud Platform](google_cloud_platform/index.md): Install Omnibus GitLab on a VM in GCP.
+- [Install GitLab on Azure](azure/index.md): Install Omnibus GitLab from Azure Marketplace.
+- [Install GitLab on OpenShift](openshift_and_gitlab/index.md): Install GitLab using the Docker image on OpenShift.
+- [Install GitLab on DC/OS](https://mesosphere.com/blog/gitlab-dcos/): Install GitLab on Mesosphere DC/OS via the [GitLab-Mesosphere integration](https://about.gitlab.com/2016/09/16/announcing-gitlab-and-mesosphere/).
+- [Install GitLab on DigitalOcean](https://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/): Install Omnibus GitLab on DigitalOcean.
+- _Testing only!_ [DigitalOcean and Docker Machine](digitaloceandocker.md):
+ Quickly test any version of GitLab on DigitalOcean using Docker Machine.
diff --git a/doc/install/aws/img/associate_subnet_gateway_2.png b/doc/install/aws/img/associate_subnet_gateway_2.png
index 76e101d32a3..6e10d9647b1 100644
--- a/doc/install/aws/img/associate_subnet_gateway_2.png
+++ b/doc/install/aws/img/associate_subnet_gateway_2.png
Binary files differ
diff --git a/doc/install/aws/img/choose_ami.png b/doc/install/aws/img/choose_ami.png
index 034ac92691d..a07d42dd6fb 100644
--- a/doc/install/aws/img/choose_ami.png
+++ b/doc/install/aws/img/choose_ami.png
Binary files differ
diff --git a/doc/install/aws/img/ec_az.png b/doc/install/aws/img/ec_az.png
index 22a8291c593..431dbb0251b 100644
--- a/doc/install/aws/img/ec_az.png
+++ b/doc/install/aws/img/ec_az.png
Binary files differ
diff --git a/doc/install/aws/img/ec_subnet.png b/doc/install/aws/img/ec_subnet.png
index c44fb4485e3..08a9b169267 100644
--- a/doc/install/aws/img/ec_subnet.png
+++ b/doc/install/aws/img/ec_subnet.png
Binary files differ
diff --git a/doc/install/docker.md b/doc/install/docker.md
index e90f6645b0c..d0129f0f5c4 100644
--- a/doc/install/docker.md
+++ b/doc/install/docker.md
@@ -8,9 +8,9 @@ GitLab provides official Docker images to allowing you to easily take advantage
GitLab maintains a set of [official Docker images](https://hub.docker.com/r/gitlab) based on our [Omnibus GitLab package](https://docs.gitlab.com/omnibus/README.html). These images include:
-- [GitLab Community Edition](https://hub.docker.com/r/gitlab/gitlab-ce/).
-- [GitLab Enterprise Edition](https://hub.docker.com/r/gitlab/gitlab-ee/).
-- [GitLab Runner](https://hub.docker.com/r/gitlab/gitlab-runner/).
+- [GitLab Community Edition](https://hub.docker.com/r/gitlab/gitlab-ce/)
+- [GitLab Enterprise Edition](https://hub.docker.com/r/gitlab/gitlab-ee/)
+- [GitLab Runner](https://hub.docker.com/r/gitlab/gitlab-runner/)
A [complete usage guide](https://docs.gitlab.com/omnibus/docker/) to these images is available, as well as the [Dockerfile used for building the images](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master/docker).
diff --git a/doc/install/kubernetes/gitlab_chart.md b/doc/install/kubernetes/gitlab_chart.md
index 5749eb0a9ec..26ced45de7b 100644
--- a/doc/install/kubernetes/gitlab_chart.md
+++ b/doc/install/kubernetes/gitlab_chart.md
@@ -1,7 +1,14 @@
# GitLab Helm Chart
-This is the official and recommended way to install GitLab on a cloud native environment.
-For more information on other available GitLab Helm Charts, see the [charts overview](index.md#chart-overview).
+This is the official way to install GitLab on a cloud native environment.
+
+NOTE: **Kubernetes experience required:**
+Our Helm charts are recommended for those who are familiar with Kubernetes.
+If you're not sure if Kubernetes is for you, our
+[Omnibus GitLab packages](../README.md#install-gitlab-using-the-omnibus-gitlab-package-recommended)
+are mature, scalable, support [high availability](../../administration/high_availability/README.md)
+and are used today on GitLab.com.
+It is not necessary to have GitLab installed on Kubernetes in order to use [GitLab Kubernetes integration](https://docs.gitlab.com/ee/user/project/clusters/index.html).
## Introduction
@@ -40,8 +47,9 @@ In order to deploy GitLab on Kubernetes, the following are required:
1. `helm` and `kubectl` [installed on your computer](preparation/tools_installation.md).
1. A Kubernetes cluster, version 1.8 or higher. 6vCPU and 16GB of RAM is recommended.
- - [Google GKE](https://cloud.google.com/kubernetes-engine/docs/how-to/creating-a-container-cluster)
- [Amazon EKS](https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html)
+ - [Google GKE](https://cloud.google.com/kubernetes-engine/docs/how-to/creating-a-container-cluster)
+ - [IBM IKS](https://console.bluemix.net/docs/tutorials/scalable-webapp-kubernetes.html#create_kube_cluster)
- [Microsoft AKS](https://docs.microsoft.com/en-us/azure/aks/kubernetes-walkthrough-portal)
1. A [wildcard DNS entry and external IP address](preparation/networking.md)
1. [Authenticate and connect](preparation/connect.md) to the cluster
diff --git a/doc/install/kubernetes/index.md b/doc/install/kubernetes/index.md
index 69171fbb341..281630174e7 100644
--- a/doc/install/kubernetes/index.md
+++ b/doc/install/kubernetes/index.md
@@ -4,11 +4,19 @@ description: 'Read through the different methods to deploy GitLab on Kubernetes.
# Installing GitLab on Kubernetes
+NOTE: **Kubernetes experience required:**
+Our Helm charts are recommended for those who are familiar with Kubernetes.
+If you're not sure if Kubernetes is for you, our
+[Omnibus GitLab packages](../README.md#install-gitlab-using-the-omnibus-gitlab-package-recommended)
+are mature, scalable, support [high availability](../../administration/high_availability/README.md)
+and are used today on GitLab.com.
+It is not necessary to have GitLab installed on Kubernetes in order to use [GitLab Kubernetes integration](https://docs.gitlab.com/ee/user/project/clusters/index.html).
+
The easiest method to deploy GitLab on [Kubernetes](https://kubernetes.io/) is
-to take advantage of GitLab's Helm charts. [Helm] is a package
-management tool for Kubernetes, allowing apps to be easily managed via their
-Charts. A [Chart] is a detailed description of the application including how it
-should be deployed, upgraded, and configured.
+to take advantage of GitLab's Helm charts. [Helm](https://github.com/kubernetes/helm/blob/master/README.md)
+is a package management tool for Kubernetes, allowing apps to be easily managed via their
+Charts. A [Chart](https://github.com/kubernetes/charts) is a detailed description
+of the application including how it should be deployed, upgraded, and configured.
## GitLab Chart
@@ -32,29 +40,3 @@ and you'd like to leverage the Runner's
it can be deployed with the GitLab Runner chart.
Learn more about [gitlab-runner chart](gitlab_runner_chart.md).
-
-## Deprecated Charts
-
-CAUTION: **Deprecated:**
-These charts are **deprecated**. We recommend using the [GitLab Chart](gitlab_chart.md)
-instead.
-
-### GitLab-Omnibus Chart
-
-This chart is based on the [GitLab Omnibus Docker images](https://docs.gitlab.com/omnibus/docker/).
-It deploys and configures nearly all features of GitLab, including:
-
-- a [GitLab Runner](https://docs.gitlab.com/runner/)
-- [Container Registry](../../user/project/container_registry.html#gitlab-container-registry)
-- [Mattermost](https://docs.gitlab.com/omnibus/gitlab-mattermost/)
-- [automatic SSL](https://github.com/kubernetes/charts/tree/master/stable/kube-lego)
-- and an [NGINX load balancer](https://github.com/kubernetes/ingress/tree/master/controllers/nginx).
-
-Learn more about the [gitlab-omnibus chart](gitlab_omnibus.md).
-
-### Community Contributed Charts
-
-The community has also contributed GitLab [CE](https://github.com/kubernetes/charts/tree/master/stable/gitlab-ce) and [EE](https://github.com/kubernetes/charts/tree/master/stable/gitlab-ee) charts to the [Helm Stable Repository](https://github.com/kubernetes/charts#repository-structure). These charts are [deprecated](https://github.com/kubernetes/charts/issues/1138) in favor of the [official Chart](gitlab_chart.md).
-
-[chart]: https://github.com/kubernetes/charts
-[helm]: https://github.com/kubernetes/helm/blob/master/README.md
diff --git a/doc/integration/img/github_app.png b/doc/integration/img/github_app.png
index 4a1523d41ac..b72cf03dd4d 100644
--- a/doc/integration/img/github_app.png
+++ b/doc/integration/img/github_app.png
Binary files differ
diff --git a/doc/integration/img/github_app_entry.png b/doc/integration/img/github_app_entry.png
index 9e151f8cdff..0a1fe0ca65a 100644
--- a/doc/integration/img/github_app_entry.png
+++ b/doc/integration/img/github_app_entry.png
Binary files differ
diff --git a/doc/integration/img/github_register_app.png b/doc/integration/img/github_register_app.png
index edd3f660f4e..5786b822f53 100644
--- a/doc/integration/img/github_register_app.png
+++ b/doc/integration/img/github_register_app.png
Binary files differ
diff --git a/doc/integration/recaptcha.md b/doc/integration/recaptcha.md
index 8fdadb008ec..825c3654492 100644
--- a/doc/integration/recaptcha.md
+++ b/doc/integration/recaptcha.md
@@ -9,9 +9,9 @@ to confirm that a real user, not a bot, is attempting to create an account.
To use reCAPTCHA, first you must create a site and private key.
1. Go to the URL: <https://www.google.com/recaptcha/admin>.
-1. Fill out the form necessary to obtain reCAPTCHA keys.
-1. Login to your GitLab server, with administrator credentials.
-1. Go to Applications Settings on Admin Area (`admin/application_settings`).
+1. Fill out the form necessary to obtain reCAPTCHA v2 keys.
+1. Log in to your GitLab server, with administrator credentials.
+1. Go to Reporting Applications Settings in the Admin Area (`admin/application_settings/reporting`).
1. Fill all recaptcha fields with keys from previous steps.
1. Check the `Enable reCAPTCHA` checkbox.
1. Save the configuration.
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index a63656fafef..57bc71d2903 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -657,6 +657,7 @@ Restoring database tables:
- Loading fixture wikis...[SKIPPING]
Restoring repositories:
- Restoring repository abcd... [DONE]
+- Object pool 1 ...
Deleting tmp directories...[DONE]
```
diff --git a/doc/raketasks/web_hooks.md b/doc/raketasks/web_hooks.md
index 5f3143f76cd..df3dab118b2 100644
--- a/doc/raketasks/web_hooks.md
+++ b/doc/raketasks/web_hooks.md
@@ -38,8 +38,6 @@
## List the webhooks from projects in a given **NAMESPACE**:
# omnibus-gitlab
- sudo gitlab-rake gitlab:web_hook:list NAMESPACE=/
+ sudo gitlab-rake gitlab:web_hook:list NAMESPACE=acme
# source installations
- bundle exec rake gitlab:web_hook:list NAMESPACE=/ RAILS_ENV=production
-
-> Note: `/` is the global namespace.
+ bundle exec rake gitlab:web_hook:list NAMESPACE=acme RAILS_ENV=production
diff --git a/doc/security/rack_attack.md b/doc/security/rack_attack.md
index dcdc9f42c22..ad83dc05a93 100644
--- a/doc/security/rack_attack.md
+++ b/doc/security/rack_attack.md
@@ -10,8 +10,7 @@ Rack Attack offers IP whitelisting, blacklisting, Fail2ban style filtering and
tracking.
**Note:** Starting with 11.2, Rack Attack is disabled by default. To continue
-using this feature, please enable it in your `gitlab.rb` by setting
-`gitlab_rails['rack_attack_git_basic_auth'] = true`.
+using this feature, please enable it by [configuring `gitlab.rb` as described in Settings](#settings).
By default, user sign-in, user sign-up (if enabled), and user password reset is
limited to 6 requests per minute. After trying for 6 times, the client will
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index d9ae3c08172..e570627bfc1 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -185,7 +185,26 @@ your terminal (replacing `gitlab.com` with your GitLab's instance domain):
ssh -T git@gitlab.com
```
-You should receive a _Welcome to GitLab, `@username`!_ message.
+The first time you connect to GitLab via SSH, you will be asked to verify the
+authenticity of the GitLab host you are connecting to.
+For example, when connecting to GitLab.com, answer `yes` to add GitLab.com to
+the list of trusted hosts:
+
+```
+The authenticity of host 'gitlab.com (35.231.145.151)' can't be established.
+ECDSA key fingerprint is SHA256:HbW3g8zUjNSksFbqTiUWPWg2Bq1x8xdGUrliXFzSnUw.
+Are you sure you want to continue connecting (yes/no)? yes
+Warning: Permanently added 'gitlab.com' (ECDSA) to the list of known hosts.
+```
+
+NOTE: **Note:**
+For GitLab.com, consult the
+[SSH host keys fingerprints](../user/gitlab_com/index.md#ssh-host-keys-fingerprints),
+to make sure you're connecting to the correct server.
+
+Once added to the list of known hosts, you won't be asked to validate the
+authenticity of GitLab's host again. Run the above command once more, and
+you should only receive a _Welcome to GitLab, `@username`!_ message.
If the welcome message doesn't appear, run SSH's verbose mode by replacing `-T`
with `-vvvT` to understand where the error is.
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 63e7497cbbc..b41f401e14c 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -132,7 +132,8 @@ in three places:
- either under the project's CI/CD settings while [enabling Auto DevOps](#enabling-auto-devops)
- or in instance-wide settings in the **admin area > Settings** under the "Continuous Integration and Delivery" section
-- or at the project or group level as a variable: `AUTO_DEVOPS_DOMAIN` (required if you want to use [multiple clusters](#using-multiple-kubernetes-clusters))
+- or at the project as a variable: `AUTO_DEVOPS_DOMAIN` (required if you want to use [multiple clusters](#using-multiple-kubernetes-clusters))
+- or at the group level as a variable: `AUTO_DEVOPS_DOMAIN`
A wildcard DNS A record matching the base domain(s) is required, for example,
given a base domain of `example.com`, you'd need a DNS entry like:
@@ -203,6 +204,12 @@ and verifying that your app is deployed as a review app in the Kubernetes
cluster with the `review/*` environment scope. Similarly, you can check the
other environments.
+NOTE: **Note:**
+Auto DevOps is not supported for a group with multiple clusters, as it
+is not possible to set `AUTO_DEVOPS_DOMAIN` per environment on the group
+level. This will be resolved in the future with the [following issue](
+https://gitlab.com/gitlab-org/gitlab-ce/issues/52363).
+
## Enabling/Disabling Auto DevOps
When first using Auto Devops, review the [requirements](#requirements) to ensure all necessary components to make
@@ -560,7 +567,7 @@ To view the metrics, open the
While Auto DevOps provides great defaults to get you started, you can customize
almost everything to fit your needs; from custom [buildpacks](#custom-buildpacks),
to [`Dockerfile`s](#custom-dockerfile), [Helm charts](#custom-helm-chart), or
-even copying the complete [CI/CD configuration](#customizing-gitlab-ci-yml)
+even copying the complete [CI/CD configuration](#customizing-gitlab-ciyml)
into your project to enable staging and canary deployments, and more.
### Custom buildpacks
diff --git a/doc/university/high-availability/aws/img/reference-arch2.png b/doc/university/high-availability/aws/img/reference-arch2.png
index 9f50b2f5171..a9cb6663103 100644
--- a/doc/university/high-availability/aws/img/reference-arch2.png
+++ b/doc/university/high-availability/aws/img/reference-arch2.png
Binary files differ
diff --git a/doc/update/upgrader.md b/doc/update/upgrader.md
deleted file mode 100644
index 746d6bf93e7..00000000000
--- a/doc/update/upgrader.md
+++ /dev/null
@@ -1,91 +0,0 @@
----
-comments: false
----
-
-# GitLab Upgrader (deprecated)
-
-*DEPRECATED* We recommend to [switch to the Omnibus package and repository server](https://about.gitlab.com/update/) instead of using this script.
-
-Although deprecated, if someone wants to make this script into a gem or otherwise improve it merge requests are welcome.
-
-*Make sure you view this [upgrade guide from the 'master' branch](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/upgrader.md) for the most up to date instructions.*
-
-GitLab Upgrader - a ruby script that allows you easily upgrade GitLab to latest minor version.
-
-For example it can update your application from 6.4 to latest GitLab 6 version (like 6.6.1).
-
-You still need to create a backup and manually restart GitLab after running the script but all other operations are done by this upgrade script.
-
-If you have local changes to your GitLab repository the script will stash them and you need to use `git stash pop` after running the script.
-
-**GitLab Upgrader is available only for GitLab version 6.4.2 or higher.**
-
-**This script does NOT update gitlab-shell, it needs manual update. See step 5 below.**
-
-## 0. Backup
-
- cd /home/git/gitlab
- sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
-
-## 1. Stop server
-
- sudo service gitlab stop
-
-## 2. Run GitLab upgrade tool
-
-Please replace X.X.X with the [latest GitLab release](https://packages.gitlab.com/gitlab/gitlab-ce).
-
-GitLab 7.9 adds `nodejs` as a dependency. GitLab 7.6 adds `libkrb5-dev` as a dependency (installed by default on Ubuntu and OSX). GitLab 7.2 adds `pkg-config` and `cmake` as dependency. Please check the dependencies in the [installation guide.](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies)
-
- cd /home/git/gitlab
- sudo -u git -H ruby -Ilib -e 'require "gitlab/upgrader"' -e 'class Gitlab::Upgrader' -e 'def latest_version_raw' -e '"vX.X.X"' -e 'end' -e 'end' -e 'Gitlab::Upgrader.new.execute'
-
- # to perform a non-interactive install (no user input required) you can add -y
- # sudo -u git -H ruby -Ilib -e 'require "gitlab/upgrader"' -e 'class Gitlab::Upgrader' -e 'def latest_version_raw' -e '"vX.X.X"' -e 'end' -e 'end' -e 'Gitlab::Upgrader.new.execute' -- -y
-
-## 3. Start application
-
- sudo service gitlab start
- sudo service nginx restart
-
-## 4. Check application status
-
-Check if GitLab and its dependencies are configured correctly:
-
- sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
-
-If all items are green, then congratulations upgrade is complete!
-
-## 5. Upgrade GitLab Shell
-
-GitLab Shell might be outdated, running the commands below ensures you're using a compatible version:
-
-```
-cd /home/git/gitlab-shell
-sudo -u git -H git fetch
-sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`
-sudo -u git -H sh -c 'if [ -x bin/compile ] ; then bin/compile ; fi'
-```
-
-## One line upgrade command
-
-You've read through the entire guide and probably already did all the steps one by one.
-
-Below is a one line command with step 1 to 5 for the next time you upgrade.
-
-Please replace X.X.X with the [latest GitLab release](https://packages.gitlab.com/gitlab/gitlab-ce).
-
-```bash
-cd /home/git/gitlab; \
- sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \
- sudo service gitlab stop; \
- sudo -u git -H ruby -Ilib -e 'require "gitlab/upgrader"' -e 'class Gitlab::Upgrader' -e 'def latest_version_raw' -e '"vX.X.X"' -e 'end' -e 'end' -e 'Gitlab::Upgrader.new.execute' -- -y; \
- cd /home/git/gitlab-shell; \
- sudo -u git -H git fetch; \
- sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`; \
- sudo -u git -H sh -c 'if [ -x bin/compile ] ; then bin/compile ; fi'; \
- cd /home/git/gitlab; \
- sudo service gitlab start; \
- sudo service nginx restart; \
- sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
-``` \ No newline at end of file
diff --git a/doc/user/abuse_reports.md b/doc/user/abuse_reports.md
new file mode 100644
index 00000000000..1f4f598b121
--- /dev/null
+++ b/doc/user/abuse_reports.md
@@ -0,0 +1,53 @@
+# Abuse reports
+
+Report abuse from users to GitLab administrators.
+
+You can report a user through their:
+
+- [Profile](#reporting-abuse-through-a-users-profile)
+- [Comments](#reporting-abuse-through-a-users-comment)
+- [Issues and Merge requests](#reporting-abuse-through-a-users-issue-or-merge-request)
+
+## Reporting abuse through a user's profile
+
+To report abuse from a user's profile page:
+
+1. Click on the exclamation point report abuse button at the top right of the user's profile.
+1. Complete an abuse report.
+1. Click the **Send report** button.
+
+## Reporting abuse through a user's comment
+
+To report abuse from a user's comment:
+
+1. Click on the vertical ellipsis (⋮) more actions button to open the dropdown.
+1. Select **Report as abuse**.
+1. Complete an abuse report.
+1. Click the **Send report** button.
+
+
+NOTE: **Note:**
+A URL to the reported user's comment will be
+pre-filled in the abuse report's **Message** field.
+
+## Reporting abuse through a user's issue or merge request
+
+The **Report abuse** button is displayed at the top right of the issue or merge request:
+
+- When **Report abuse** is selected from the menu that appears when the **Close issue** or **Close merge request** button is clicked, for users that have permission to close the issue or merge request.
+- When viewing the issue or merge request, for users that don't have permission to close the issue or merge request.
+
+With the **Report abuse** button displayed, to submit an abuse report:
+
+1. Click the **Report abuse** button.
+1. Submit an abuse report.
+1. Click the **Send report** button.
+
+NOTE: **Note:**
+A URL to the reported user's issue or merge request will be pre-filled
+in the abuse report's **Message** field.
+
+## Managing abuse reports
+
+Admins are able to view and resolve abuse reports.
+For more information, see [abuse reports administration documentation](admin_area/abuse_reports.md).
diff --git a/doc/user/admin_area/abuse_reports.md b/doc/user/admin_area/abuse_reports.md
new file mode 100644
index 00000000000..01c2d9607f5
--- /dev/null
+++ b/doc/user/admin_area/abuse_reports.md
@@ -0,0 +1,31 @@
+# Abuse reports
+
+View and resolve abuse reports from GitLab users.
+
+Admins can view abuse reports in the admin area and are able to
+resolve the reports by removing the reported user, blocking the reported user, or removing the report.
+
+## Reporting abuse
+
+To find out more about reporting abuse, see [abuse reports user documentation](../abuse_reports.md).
+
+## Resolving abuse reports
+
+To access abuse reports, go to **Admin area > Abuse Reports**.
+
+There are 3 ways to resolve an abuse report, with a button for each method:
+
+- Remove user & report: [Deletes the reported user](../profile/account/delete_account.md) from the instance and removes the abuse report from the list.
+- Block user: Blocks the reported user from the instance and does not remove the abuse report from the list.
+- Remove report: Removes the abuse report from the list and does not restrict the access for the reported user.
+
+![abuse-reports-page-image](img/abuse_reports_page.png)
+
+## Blocked users
+
+Blocking a user will not remove the abuse report from the list.
+
+Instead, the block button will be disabled and explain that the user is "Already blocked".
+You are still able to remove the user and/or report if necessary.
+
+![abuse-report-blocked-user-image](img/abuse_report_blocked_user.png)
diff --git a/doc/user/admin_area/img/abuse_report_blocked_user.png b/doc/user/admin_area/img/abuse_report_blocked_user.png
new file mode 100644
index 00000000000..0cb4c7bb8ac
--- /dev/null
+++ b/doc/user/admin_area/img/abuse_report_blocked_user.png
Binary files differ
diff --git a/doc/user/admin_area/img/abuse_reports_page.png b/doc/user/admin_area/img/abuse_reports_page.png
new file mode 100644
index 00000000000..81dbe976cda
--- /dev/null
+++ b/doc/user/admin_area/img/abuse_reports_page.png
Binary files differ
diff --git a/doc/user/admin_area/img/admin_area_settings_button.png b/doc/user/admin_area/img/admin_area_settings_button.png
index 315ef40a375..5b969ecd668 100644
--- a/doc/user/admin_area/img/admin_area_settings_button.png
+++ b/doc/user/admin_area/img/admin_area_settings_button.png
Binary files differ
diff --git a/doc/user/admin_area/settings/img/import_sources.png b/doc/user/admin_area/settings/img/import_sources.png
index 4257f02448f..20829a27dd7 100644
--- a/doc/user/admin_area/settings/img/import_sources.png
+++ b/doc/user/admin_area/settings/img/import_sources.png
Binary files differ
diff --git a/doc/user/discussions/img/insert_suggestion.png b/doc/user/discussions/img/insert_suggestion.png
new file mode 100644
index 00000000000..4bf293b8297
--- /dev/null
+++ b/doc/user/discussions/img/insert_suggestion.png
Binary files differ
diff --git a/doc/user/discussions/img/make_suggestion.png b/doc/user/discussions/img/make_suggestion.png
new file mode 100644
index 00000000000..20acc1417da
--- /dev/null
+++ b/doc/user/discussions/img/make_suggestion.png
Binary files differ
diff --git a/doc/user/discussions/img/suggestion.png b/doc/user/discussions/img/suggestion.png
new file mode 100644
index 00000000000..68a67e6ae5e
--- /dev/null
+++ b/doc/user/discussions/img/suggestion.png
Binary files differ
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index 0f89d261ff6..9379d047fca 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -293,6 +293,51 @@ Once you select one of the filters in a given issue or MR, GitLab will save
your preference, so that it will persist when you visit the same page again
from any device you're logged into.
+## Suggest Changes
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/18008) in GitLab 11.6.
+
+As a reviewer, you're able to suggest code changes with a simple
+markdown syntax in Merge Request Diff discussions. Then, the
+Merge Request author (or other users with appropriate
+[permission](../permissions.md)) is able to apply these
+suggestions with a click, which will generate a commit in
+the Merge Request authored by the user that applied them.
+
+1. Choose a line of code to be changed, add a new comment, then click
+on the **Insert suggestion** icon in the toolbar:
+
+ ![Add a new comment](img/insert_suggestion.png)
+
+ > **Note:**
+ The suggestion will only affect the commented line. Multi-line
+ suggestions are currently not supported. Will be introduced by
+ [#53310](https://gitlab.com/gitlab-org/gitlab-ce/issues/53310).
+
+1. In the comment, add your suggestion to the pre-populated code block:
+
+ ![Add a suggestion into a code block tagged properly](img/make_suggestion.png)
+
+1. Click **Comment**.
+
+ The suggestions in the comment can be applied by the merge request author
+ directly from the merge request:
+
+ ![Apply suggestions](img/suggestion.png)
+
+ > **Note:**
+ Discussions are _not_ automatically resolved. Will be introduced by
+ [#54405](https://gitlab.com/gitlab-org/gitlab-ce/issues/54405).
+
+Once the author applies a suggestion, it will be marked with the **Applied** label,
+and GitLab will create a new commit with the message `Apply suggestion to <file-name>`
+and push the suggested change directly into the codebase in the merge request's branch.
+[Developer permission](../permissions.md) is required to do so.
+
+> **Note:**
+Custom commit messages will be introduced by
+[#54404](https://gitlab.com/gitlab-org/gitlab-ce/issues/54404).
+
[ce-5022]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5022
[ce-7125]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7125
[ce-7527]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7527
diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md
new file mode 100644
index 00000000000..9f9b2da23e1
--- /dev/null
+++ b/doc/user/group/clusters/index.md
@@ -0,0 +1,132 @@
+# Group-level Kubernetes clusters
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/34758) in GitLab 11.6.
+> Group Cluster integration is currently in [Beta](https://about.gitlab.com/handbook/product/#alpha-beta-ga).
+
+## Overview
+
+Similar to [project Kubernetes
+clusters](../../project/clusters/index.md), Group-level Kubernetes
+clusters allow you to connect a Kubernetes cluster to your group,
+enabling you to use the same cluster across multiple projects.
+
+## Installing applications
+
+GitLab provides a one-click install for various applications that can be
+added directly to your cluster.
+
+NOTE: **Note:**
+Applications will be installed in a dedicated namespace called
+`gitlab-managed-apps`. If you have added an existing Kubernetes cluster
+with Tiller already installed, you should be careful as GitLab cannot
+detect it. In this event, installing Tiller via the applications will
+result in the cluster having it twice. This can lead to confusion during
+deployments.
+
+| Application | GitLab version | Description | Helm Chart |
+| ----------- | -------------- | ----------- | ---------- |
+| [Helm Tiller](https://docs.helm.sh) | 10.2+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. | n/a |
+| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps](../../../topics/autodevops/index.md) or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) |
+
+## RBAC compatibility
+
+For each project under a group with a Kubernetes cluster, GitLab will
+create a restricted service account with [`edit`
+privileges](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)
+in the project namespace.
+
+NOTE: **Note:**
+RBAC support was introduced in
+[GitLab 11.4](https://gitlab.com/gitlab-org/gitlab-ce/issues/29398), and
+Project namespace restriction was introduced in
+[GitLab 11.5](https://gitlab.com/gitlab-org/gitlab-ce/issues/51716).
+
+## Cluster precedence
+
+GitLab will use the project's cluster before using any cluster belonging
+to the group containing the project if the project's cluster is available and not disabled.
+
+In the case of sub-groups, GitLab will use the cluster of the closest ancestor group
+to the project, provided the cluster is not disabled.
+
+## Multiple Kubernetes clusters **[PREMIUM]**
+
+With GitLab Premium, you can associate more than one Kubernetes clusters to your
+group. That way you can have different clusters for different environments,
+like dev, staging, production, etc.
+
+Add another cluster similar to the first one and make sure to
+[set an environment scope](#environment-scopes) that will
+differentiate the new cluster from the rest.
+
+NOTE: **Note:**
+Auto DevOps is not supported for a group with multiple clusters, as it
+is not possible to set `AUTO_DEVOPS_DOMAIN` per environment on the group
+level. This will be resolved in the future with the [following issue](
+https://gitlab.com/gitlab-org/gitlab-ce/issues/52363).
+
+## Environment scopes **[PREMIUM]**
+
+When adding more than one Kubernetes cluster to your project, you need
+to differentiate them with an environment scope. The environment scope
+associates clusters with [environments](../../../ci/environments.md)
+similar to how the [environment-specific
+variables](../../../ci/variables/README.md#limiting-environment-scopes-of-variables)
+work.
+
+While evaluating which environment matches the environment scope of a
+cluster, [cluster precedence](#cluster-precedence) will take
+effect. The cluster at the project level will take precedence, followed
+by the closest ancestor group, followed by that groups' parent and so
+on.
+
+For example, let's say we have the following Kubernetes clusters:
+
+| Cluster | Environment scope | Where |
+| ---------- | ------------------- | ----------|
+| Project | `*` | Project |
+| Staging | `staging/*` | Project |
+| Production | `production/*` | Project |
+| Test | `test` | Group |
+| Development| `*` | Group |
+
+
+And the following environments are set in [`.gitlab-ci.yml`](../../../ci/yaml/README.md):
+
+```yaml
+stages:
+- test
+- deploy
+
+test:
+ stage: test
+ script: sh test
+
+deploy to staging:
+ stage: deploy
+ script: make deploy
+ environment:
+ name: staging/$CI_COMMIT_REF_NAME
+ url: https://staging.example.com/
+
+deploy to production:
+ stage: deploy
+ script: make deploy
+ environment:
+ name: production/$CI_COMMIT_REF_NAME
+ url: https://example.com/
+```
+
+The result will then be:
+
+- The Project cluster will be used for the `test` job.
+- The Staging cluster will be used for the `deploy to staging` job.
+- The Production cluster will be used for the `deploy to production` job.
+
+## Unavailable features
+
+The following features are not currently available for group-level clusters:
+
+1. Terminals (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55487)).
+1. Pod logs (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55488)).
+1. Deployment boards (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55488)).
diff --git a/doc/user/group/img/add_new_members.png b/doc/user/group/img/add_new_members.png
index 4431c9fbe0b..6d43e309e84 100644
--- a/doc/user/group/img/add_new_members.png
+++ b/doc/user/group/img/add_new_members.png
Binary files differ
diff --git a/doc/user/group/img/create_new_project_from_group.png b/doc/user/group/img/create_new_project_from_group.png
index b6286ac7800..df98091334c 100644
--- a/doc/user/group/img/create_new_project_from_group.png
+++ b/doc/user/group/img/create_new_project_from_group.png
Binary files differ
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 36b9318c0e0..5fea683a7fd 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -269,6 +269,7 @@ Define project templates at a group-level by setting a group as a template sourc
- **Projects**: view all projects within that group, add members to each project,
access each project's settings, and remove any project from the same screen.
- **Webhooks**: configure [webhooks](../project/integrations/webhooks.md) to your group.
+- **Kubernetes cluster integration**: connect your GitLab group with [Kubernetes clusters](clusters/index.md).
- **Audit Events**: view [Audit Events](https://docs.gitlab.com/ee/administration/audit_events.html#audit-events)
for the group. **[STARTER ONLY]**
-- **Pipelines quota**: keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group
+- **Pipelines quota**: keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group.
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index 8db36c4a0e8..943b0c693c0 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -167,7 +167,6 @@ Here's a list of what you can't do with subgroups:
- [GitLab Pages](../../project/pages/index.md) are not currently working for
projects hosted under a subgroup. That means that only projects hosted under
the first parent group will work.
-- Group level labels don't work in subgroups / sub projects
- It is not possible to share a project with a group that's an ancestor of
the group the project is in. That means you can only share as you walk down
the hierarchy. For example, `group/subgroup01/project` **cannot** be shared
diff --git a/doc/user/img/color_inline_colorchip_render_gfm.png b/doc/user/img/color_inline_colorchip_render_gfm.png
index 6a8a674d6e0..fed8ca5c34b 100644
--- a/doc/user/img/color_inline_colorchip_render_gfm.png
+++ b/doc/user/img/color_inline_colorchip_render_gfm.png
Binary files differ
diff --git a/doc/user/img/math_inline_sup_render_gfm.png b/doc/user/img/math_inline_sup_render_gfm.png
index bf1464457bc..3ee2abb14df 100644
--- a/doc/user/img/math_inline_sup_render_gfm.png
+++ b/doc/user/img/math_inline_sup_render_gfm.png
Binary files differ
diff --git a/doc/user/img/mermaid_diagram_render_gfm.png b/doc/user/img/mermaid_diagram_render_gfm.png
index 3b3eb3a738a..9d192a30a85 100644
--- a/doc/user/img/mermaid_diagram_render_gfm.png
+++ b/doc/user/img/mermaid_diagram_render_gfm.png
Binary files differ
diff --git a/doc/user/img/task_list_ordered_render_gfm.png b/doc/user/img/task_list_ordered_render_gfm.png
index fdff8a9886c..0905a8378be 100644
--- a/doc/user/img/task_list_ordered_render_gfm.png
+++ b/doc/user/img/task_list_ordered_render_gfm.png
Binary files differ
diff --git a/doc/user/img/unordered_check_list_render_gfm.png b/doc/user/img/unordered_check_list_render_gfm.png
index 2e3fb7cbb79..ccdeab6e62c 100644
--- a/doc/user/img/unordered_check_list_render_gfm.png
+++ b/doc/user/img/unordered_check_list_render_gfm.png
Binary files differ
diff --git a/doc/user/index.md b/doc/user/index.md
index 08995032cb1..fc68404d0c2 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -113,6 +113,7 @@ methods available in GitLab.
user type (guest, reporter, developer, maintainer, owner).
- [Feature highlight](feature_highlight.md): Learn more about the little blue dots
around the app that explain certain features
+- [Abuse reports](abuse_reports.md): Report abuse from users to GitLab administrators
## Groups
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index debebd4c081..893658290e5 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -5,11 +5,11 @@ It is not valid for the [GitLab documentation website](https://docs.gitlab.com)
nor [GitLab's main website](https://about.gitlab.com), as they both use
[Kramdown](https://kramdown.gettalong.org) as their markdown engine.
The documentation website uses an extended Kramdown gem, [GitLab Kramdown](https://gitlab.com/gitlab-org/gitlab_kramdown).
-Consult the [GitLab Kramdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/) for a complete Kramdown reference._
+Consult the [GitLab Kramdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/) for a complete Kramdown reference.
## GitLab Flavored Markdown (GFM)
-GitLab uses "GitLab Flavored Markdown" (GFM). It extends the [CommonMark specification][commonmark-spec] (which is based on standard Markdown) in a few significant ways to add some useful functionality. It was inspired by [GitHub Flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/).
+GitLab uses "GitLab Flavored Markdown" (GFM). It extends the [CommonMark specification][commonmark-spec] (which is based on standard Markdown) in a few significant ways to add additional useful functionality. It was inspired by [GitHub Flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/).
You can use GFM in the following areas:
@@ -26,8 +26,7 @@ dependency to do so. Please see the [`github-markup` gem readme](https://github.
> **Notes:**
>
-> For the best result, we encourage you to check this document out as [rendered
-> by GitLab itself](markdown.md).
+> We encourage you to view this document as [rendered by GitLab itself](markdown.md).
>
> As of 11.1, GitLab uses the [CommonMark Ruby Library][commonmarker] for Markdown
processing of all new issues, merge requests, comments, and other Markdown content
@@ -142,7 +141,7 @@ GFM will autolink almost any URL you copy and paste into your text:
* <a href="irc://irc.freenode.net/gitlab">irc://irc.freenode.net/gitlab</a>
* http://localhost:3000
-### Multiline Blockquote
+### Multiline blockquote
> If this is not rendered correctly, see
https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#multiline-blockquote
@@ -172,7 +171,7 @@ you can quote that without having to manually prepend `>` to every line!
<p>you can quote that without having to manually prepend <code>&gt;</code> to every line!</p>
</blockquote>
-### Code and Syntax Highlighting
+### Code and syntax highlighting
> If this is not rendered correctly, see
https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#code-and-syntax-highlighting
@@ -242,7 +241,7 @@ s = "There is no highlighting for this."
But let's throw in a <b>tag</b>.
```
-### Inline Diff
+### Inline diff
> If this is not rendered correctly, see
https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#inline-diff
@@ -309,7 +308,7 @@ On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/he
Ubuntu 18.04 (like many modern Linux distros) has this font installed by default.
-### Special GitLab References
+### Special GitLab references
GFM recognizes special references.
@@ -363,7 +362,7 @@ It also has a shorthand version to reference other projects from the same namesp
| `project@9ba12248...b19a04f5` | commit range comparison |
| `project~"Some label"` | issues with given label |
-### Task Lists
+### Task lists
> If this is not rendered correctly, see
https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#task-lists
@@ -505,6 +504,66 @@ Becomes:
For details see the [Mermaid official page][mermaid].
+### Front matter
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23331)
+ in GitLab 11.6.
+
+Front matter is metadata included at the beginning of a markdown document, preceding
+its content. This data can be used by static site generators such as [Jekyll](https://jekyllrb.com/docs/front-matter/) and [Hugo](https://gohugo.io/content-management/front-matter/),
+and many other applications.
+
+In GitLab, front matter is only used in Markdown files and wiki pages, not the other places where Markdown formatting is supported.
+When you view a Markdown file rendered by GitLab, any front matter is displayed as-is, in a box at the top of the document, before the rendered HTML content.
+To view an example, you can toggle between the source and rendered version of a [GitLab documentation file](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/README.md).
+
+The following delimeters are supported:
+
+- YAML (`---`):
+
+ ```
+ ---
+ title: About Front Matter
+ example:
+ language: yaml
+ ---
+ ```
+
+- TOML (`+++`):
+
+ ```
+ +++
+ title = "About Front Matter"
+ [example]
+ language = "toml"
+ +++
+ ```
+
+- JSON (`;;;`):
+
+ ```
+ ;;;
+ {
+ "title": "About Front Matter"
+ "example": {
+ "language": "json"
+ }
+ }
+ ;;;
+ ```
+
+Other languages are supported by adding a specifier to any of the existing
+delimiters. For example:
+
+```
+---php
+$title = "About Front Matter";
+$example = array(
+ 'language' => "php",
+);
+---
+```
+
## Standard Markdown
### Headers
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index c4a2d5f66e5..ed00f86f9de 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -79,6 +79,7 @@ The following table depicts the various user permission levels in a project.
| View approved/blacklisted licenses **[ULTIMATE]** | | | ✓ | ✓ | ✓ |
| Use security dashboard **[ULTIMATE]** | | | ✓ | ✓ | ✓ |
| Dismiss vulnerability **[ULTIMATE]** | | | ✓ | ✓ | ✓ |
+| Apply code change suggestions | | | ✓ | ✓ | ✓ |
| Use environment terminals | | | | ✓ | ✓ |
| Add new team members | | | | ✓ | ✓ |
| Push to protected branches | | | | ✓ | ✓ |
diff --git a/doc/user/profile/account/delete_account.md b/doc/user/profile/account/delete_account.md
index 49f0ce2cd79..b497cc414af 100644
--- a/doc/user/profile/account/delete_account.md
+++ b/doc/user/profile/account/delete_account.md
@@ -25,7 +25,7 @@ Here's a list of things that will not be deleted:
Instead of being deleted, these records will be moved to a system-wide
"Ghost User", whose sole purpose is to act as a container for such records.
-When a user is deleted from an abuse report or spam log, these associated
+When a user is deleted from an [abuse report](../../admin_area/abuse_reports.md) or spam log, these associated
records are not ghosted and will be removed, along with any groups the user
is a sole owner of. Administrators can also request this behaviour when
deleting users from the [API](../../../api/users.md#user-deletion) or the
@@ -35,4 +35,3 @@ admin area.
[ce-10273]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10273
[ce-10467]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10467
[ce-11853]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11853
-
diff --git a/doc/user/project/clusters/eks_and_gitlab/index.md b/doc/user/project/clusters/eks_and_gitlab/index.md
index fa2ed21f980..0e6d4bce153 100644
--- a/doc/user/project/clusters/eks_and_gitlab/index.md
+++ b/doc/user/project/clusters/eks_and_gitlab/index.md
@@ -49,7 +49,7 @@ A few details from the EKS cluster will be required to connect it to GitLab:
- Get the certificate with:
```sh
- kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 -D
+ kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode
```
1. **Create admin token**: A `cluster-admin` token is required to install and
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 66ad1843e93..6bdafc60949 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -17,6 +17,11 @@ your account with Google Kubernetes Engine (GKE) so that you can [create new
clusters](#adding-and-creating-a-new-gke-cluster-via-gitlab) from within GitLab,
or provide the credentials to an [existing Kubernetes cluster](#adding-an-existing-kubernetes-cluster).
+NOTE: **Note:**
+From [GitLab 11.6](https://gitlab.com/gitlab-org/gitlab-ce/issues/34758) you
+can also associate a Kubernetes cluster to your groups. Learn more about
+[group Kubernetes clusters](../../group/clusters/index.md).
+
## Adding and creating a new GKE cluster via GitLab
TIP: **Tip:**
@@ -135,13 +140,14 @@ To add an existing Kubernetes cluster to your project:
to grant access.
- **Project namespace** (optional) - You don't have to fill it in; by leaving
it blank, GitLab will create one for you. Also:
- - Each project should have a unique namespace.
- - The project namespace is not necessarily the namespace of the secret, if
- you're using a secret with broader permissions, like the secret from `default`.
- - You should **not** use `default` as the project namespace.
- - If you or someone created a secret specifically for the project, usually
- with limited permissions, the secret's namespace and project namespace may
- be the same.
+ - Each project should have a unique namespace.
+ - The project namespace is not necessarily the namespace of the secret, if
+ you're using a secret with broader permissions, like the secret from `default`.
+ - You should **not** use `default` as the project namespace.
+ - If you or someone created a secret specifically for the project, usually
+ with limited permissions, the secret's namespace and project namespace may
+ be the same.
+
1. Finally, click the **Create Kubernetes cluster** button.
After a couple of minutes, your cluster will be ready to go. You can now proceed
@@ -152,8 +158,8 @@ To determine the:
- API URL, run `kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}'`.
- Token:
1. List the secrets by running: `kubectl get secrets`. Note the name of the secret you need the token for.
- 1. Get the token for the appropriate secret by running: `kubectl get secret <SECRET_NAME> -o jsonpath="{['data']['token']}" | base64 -D`.
-- CA certificate, run `kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 -D`.
+ 1. Get the token for the appropriate secret by running: `kubectl get secret <SECRET_NAME> -o jsonpath="{['data']['token']}" | base64 --decode`.
+- CA certificate, run `kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode`.
## Security implications
@@ -163,7 +169,7 @@ are trusted, so **only trusted users should be allowed to control your clusters*
The default cluster configuration grants access to a wide set of
functionalities needed to successfully build and deploy a containerized
-application. Bare in mind that the same credentials are used for all the
+application. Bear in mind that the same credentials are used for all the
applications running on the cluster.
## Access controls
@@ -245,30 +251,41 @@ install it manually.
## Installing applications
-GitLab provides a one-click install for various applications which will be
-added directly to your configured cluster. Those applications are needed for
-[Review Apps](../../../ci/review_apps/index.md) and [deployments](../../../ci/environments.md).
+GitLab provides a one-click install for various applications which can
+be added directly to your configured cluster. Those applications are
+needed for [Review Apps](../../../ci/review_apps/index.md) and
+[deployments](../../../ci/environments.md). You can install them after you
+[create a cluster](#adding-and-creating-a-new-gke-cluster-via-gitlab).
+
+To see a list of available applications to install:
+
+1. Navigate to your project's **Operations > Kubernetes**.
+1. Select your cluster.
+
+Install Helm Tiller first because it's used to install other applications.
NOTE: **Note:**
-With the exception of Knative, the applications will be installed in a dedicated namespace called
-`gitlab-managed-apps`. In case you have added an existing Kubernetes cluster
-with Tiller already installed, you should be careful as GitLab cannot
-detect it. By installing it via the applications will result into having it
-twice, which can lead to confusion during deployments.
+As of GitLab 11.6, Helm Tiller will be upgraded to the latest version supported
+by GitLab before installing any of the applications.
| Application | GitLab version | Description | Helm Chart |
| ----------- | :------------: | ----------- | --------------- |
| [Helm Tiller](https://docs.helm.sh/) | 10.2+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. | n/a |
| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps] or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) |
-| [Cert Manager](http://docs.cert-manager.io/en/latest/) | 11.6+ | Cert Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert Manager on your cluster will issue a certificate by [Let's Encrypt](https://letsencrypt.org/) and ensure that certificates are valid and up to date. The email address used by Let's Encrypt registration will be taken from the GitLab user that installed Cert Manager on the cluster. | [stable/cert-manager](https://github.com/helm/charts/tree/master/stable/cert-manager) |
+| [Cert Manager](http://docs.cert-manager.io/en/latest/) | 11.6+ | Cert Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert Manager on your cluster will issue a certificate by [Let's Encrypt](https://letsencrypt.org/) and ensure that certificates are valid and up-to-date. | [stable/cert-manager](https://github.com/helm/charts/tree/master/stable/cert-manager) |
| [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications. | [stable/prometheus](https://github.com/helm/charts/tree/master/stable/prometheus) |
| [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) |
-| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use [this](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) custom Jupyter image that installs additional useful packages on top of the base Jupyter. You will also see ready-to-use DevOps Runbooks built with Nurtch's [Rubix library](https://github.com/amit1rrr/rubix). More information on creating executable runbooks can be found at [Nurtch Documentation](http://docs.nurtch.com/en/latest). **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) |
+| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use a [custom Jupyter image](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) that installs additional useful packages on top of the base Jupyter. You will also see ready-to-use DevOps Runbooks built with Nurtch's [Rubix library](https://github.com/amit1rrr/rubix). More information on creating executable runbooks can be found in [our Nurtch documentation](runbooks/index.md#nurtch-executable-runbooks). **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) |
| [Knative](https://cloud.google.com/knative) | 11.5+ | Knative provides a platform to create, deploy, and manage serverless workloads from a Kubernetes cluster. It is used in conjunction with, and includes [Istio](https://istio.io) to provide an external IP address for all programs hosted by Knative. You will be prompted to enter a wildcard domain where your applications will be exposed. Configure your DNS server to use the external IP address for that domain. For any application created and installed, they will be accessible as `<program_name>.<kubernetes_namespace>.<domain_name>`. This will require your kubernetes cluster to have [RBAC enabled](#role-based-access-control-rbac). | [knative/knative](https://storage.googleapis.com/triggermesh-charts)
-NOTE: **Note:**
-As of GitLab 11.6 Helm Tiller will be upgraded to the latest version supported
-by GitLab before installing any of the above applications.
+With the exception of Knative, the applications will be installed in a dedicated
+namespace called `gitlab-managed-apps`.
+
+CAUTION: **Caution:**
+If you have an existing Kubernetes cluster with Tiller already installed,
+you should be careful as GitLab cannot detect it. In this case, installing
+Tiller via the applications will result in the cluster having it twice, which
+can lead to confusion during deployments.
## Getting the external IP address
@@ -347,17 +364,25 @@ to reach your apps. This heavily depends on your domain provider, but in case
you aren't sure, just create an A record with a wildcard host like
`*.example.com.`.
-## Setting the environment scope
+## Multiple Kubernetes clusters **[PREMIUM]**
-NOTE: **Note:**
-This is only available for [GitLab Premium][ee] where you can add more than
-one Kubernetes cluster.
-
-When adding more than one Kubernetes clusters to your project, you need to
-differentiate them with an environment scope. The environment scope associates
-clusters and [environments](../../../ci/environments.md) in an 1:1 relationship
-similar to how the
-[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-variables)
+> Introduced in [GitLab Premium][ee] 10.3.
+
+With GitLab Premium, you can associate more than one Kubernetes clusters to your
+project. That way you can have different clusters for different environments,
+like dev, staging, production, etc.
+
+Simply add another cluster, like you did the first time, and make sure to
+[set an environment scope](#setting-the-environment-scope) that will
+differentiate the new cluster with the rest.
+
+## Setting the environment scope **[PREMIUM]**
+
+When adding more than one Kubernetes clusters to your project, you need
+to differentiate them with an environment scope. The environment scope
+associates clusters with [environments](../../../ci/environments.md)
+similar to how the [environment-specific
+variables](../../../ci/variables/README.md#limiting-environment-scopes-of-variables)
work.
The default environment scope is `*`, which means all jobs, regardless of their
@@ -369,11 +394,11 @@ Also, jobs that don't have an environment keyword set will not be able to access
For example, let's say the following Kubernetes clusters exist in a project:
-| Cluster | Environment scope |
-| ---------- | ------------------- |
-| Development| `*` |
-| Staging | `staging/*` |
-| Production | `production/*` |
+| Cluster | Environment scope |
+| ----------- | ----------------- |
+| Development | `*` |
+| Staging | `staging` |
+| Production | `production` |
And the following environments are set in [`.gitlab-ci.yml`](../../../ci/yaml/README.md):
@@ -390,14 +415,14 @@ deploy to staging:
stage: deploy
script: make deploy
environment:
- name: staging/$CI_COMMIT_REF_NAME
+ name: staging
url: https://staging.example.com/
deploy to production:
stage: deploy
script: make deploy
environment:
- name: production/$CI_COMMIT_REF_NAME
+ name: production
url: https://example.com/
```
@@ -407,18 +432,6 @@ The result will then be:
- The staging cluster will be used for the "deploy to staging" job.
- The production cluster will be used for the "deploy to production" job.
-## Multiple Kubernetes clusters
-
-> Introduced in [GitLab Premium][ee] 10.3.
-
-With GitLab Premium, you can associate more than one Kubernetes clusters to your
-project. That way you can have different clusters for different environments,
-like dev, staging, production, etc.
-
-Simply add another cluster, like you did the first time, and make sure to
-[set an environment scope](#setting-the-environment-scope) that will
-differentiate the new cluster with the rest.
-
## Deployment variables
The Kubernetes cluster integration exposes the following
@@ -460,6 +473,14 @@ builds is that they must have a matching
your build has no `environment:name` set, it will not be passed the Kubernetes
credentials.
+## Monitoring your Kubernetes cluster **[ULTIMATE]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4701) in [GitLab Ultimate][ee] 10.6.
+
+When [Prometheus is deployed](#installing-applications), GitLab will automatically monitor the cluster's health. At the top of the cluster settings page, CPU and Memory utilization is displayed, along with the total amount available. Keeping an eye on cluster resources can be important, if the cluster runs out of memory pods may be shutdown or fail to start.
+
+![Cluster Monitoring](https://docs.gitlab.com/ee/user/project/clusters/img/k8s_cluster_monitoring.png)
+
## Enabling or disabling the Kubernetes cluster integration
After you have successfully added your cluster information, you can enable the
@@ -486,13 +507,16 @@ To remove the Kubernetes cluster integration from your project, simply click the
**Remove integration** button. You will then be able to follow the procedure
and add a Kubernetes cluster again.
+## View Kubernetes pod logs from GitLab **[ULTIMATE]**
+
+Learn how to easily
+[view the logs of running pods in connected Kubernetes clusters](https://docs.gitlab.com/ee/user/project/clusters/kubernetes_pod_logs.html).
+
## What you can get with the Kubernetes integration
Here's what you can do with GitLab if you enable the Kubernetes integration.
-### Deploy Boards
-
-> Available in [GitLab Premium][ee].
+### Deploy Boards **[PREMIUM]**
GitLab's Deploy Boards offer a consolidated view of the current health and
status of each CI [environment](../../../ci/environments.md) running on Kubernetes,
@@ -500,24 +524,22 @@ displaying the status of the pods in the deployment. Developers and other
teammates can view the progress and status of a rollout, pod by pod, in the
workflow they already use without any need to access Kubernetes.
-[> Read more about Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html)
+[Read more about Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html)
-### Canary Deployments
-
-> Available in [GitLab Premium][ee].
+### Canary Deployments **[PREMIUM]**
Leverage [Kubernetes' Canary deployments](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments)
and visualize your canary deployments right inside the Deploy Board, without
the need to leave GitLab.
-[> Read more about Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html)
+[Read more about Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html)
### Kubernetes monitoring
Automatically detect and monitor Kubernetes metrics. Automatic monitoring of
[NGINX ingress](../integrations/prometheus_library/nginx.md) is also supported.
-[> Read more about Kubernetes monitoring](../integrations/prometheus_library/kubernetes.md)
+[Read more about Kubernetes monitoring](../integrations/prometheus_library/kubernetes.md)
### Auto DevOps
@@ -527,7 +549,7 @@ applications.
To make full use of Auto DevOps(Auto Deploy, Auto Review Apps, and Auto Monitoring)
you will need the Kubernetes project integration enabled.
-[> Read more about Auto DevOps](../../../topics/autodevops/index.md)
+[Read more about Auto DevOps](../../../topics/autodevops/index.md)
### Web terminals
@@ -543,8 +565,6 @@ containers. To use this integration, you should deploy to Kubernetes using
the deployment variables above, ensuring any pods you create are labelled with
`app=$CI_ENVIRONMENT_SLUG`. GitLab will do the rest!
-## Read more
-
### Integrating Amazon EKS cluster with GitLab
- Learn how to [connect and deploy to an Amazon EKS cluster](eks_and_gitlab/index.md).
diff --git a/doc/user/project/clusters/runbooks/img/authorize-jupyter.png b/doc/user/project/clusters/runbooks/img/authorize-jupyter.png
index 64f95ed45f0..84cce311483 100644
--- a/doc/user/project/clusters/runbooks/img/authorize-jupyter.png
+++ b/doc/user/project/clusters/runbooks/img/authorize-jupyter.png
Binary files differ
diff --git a/doc/user/project/clusters/runbooks/img/demo-runbook.png b/doc/user/project/clusters/runbooks/img/demo-runbook.png
index 25c9df4126d..37c110ed0d8 100644
--- a/doc/user/project/clusters/runbooks/img/demo-runbook.png
+++ b/doc/user/project/clusters/runbooks/img/demo-runbook.png
Binary files differ
diff --git a/doc/user/project/clusters/runbooks/img/gitlab-variables.png b/doc/user/project/clusters/runbooks/img/gitlab-variables.png
index f76ed21145f..1d338f063a9 100644
--- a/doc/user/project/clusters/runbooks/img/gitlab-variables.png
+++ b/doc/user/project/clusters/runbooks/img/gitlab-variables.png
Binary files differ
diff --git a/doc/user/project/clusters/runbooks/img/helm-install.png b/doc/user/project/clusters/runbooks/img/helm-install.png
index e39094bcbf7..003e482e756 100644
--- a/doc/user/project/clusters/runbooks/img/helm-install.png
+++ b/doc/user/project/clusters/runbooks/img/helm-install.png
Binary files differ
diff --git a/doc/user/project/clusters/runbooks/img/ingress-install.png b/doc/user/project/clusters/runbooks/img/ingress-install.png
index 093c61f2d0e..7edc11d5b45 100644
--- a/doc/user/project/clusters/runbooks/img/ingress-install.png
+++ b/doc/user/project/clusters/runbooks/img/ingress-install.png
Binary files differ
diff --git a/doc/user/project/clusters/runbooks/img/jupyterhub-install.png b/doc/user/project/clusters/runbooks/img/jupyterhub-install.png
index 2115ec9745b..75c6028a763 100644
--- a/doc/user/project/clusters/runbooks/img/jupyterhub-install.png
+++ b/doc/user/project/clusters/runbooks/img/jupyterhub-install.png
Binary files differ
diff --git a/doc/user/project/clusters/runbooks/img/postgres-query.png b/doc/user/project/clusters/runbooks/img/postgres-query.png
index 3880438c97a..04315d54d5e 100644
--- a/doc/user/project/clusters/runbooks/img/postgres-query.png
+++ b/doc/user/project/clusters/runbooks/img/postgres-query.png
Binary files differ
diff --git a/doc/user/project/clusters/runbooks/img/sample-runbook.png b/doc/user/project/clusters/runbooks/img/sample-runbook.png
index c12ce8990a4..70011202bf0 100644
--- a/doc/user/project/clusters/runbooks/img/sample-runbook.png
+++ b/doc/user/project/clusters/runbooks/img/sample-runbook.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/img/deploy-stage.png b/doc/user/project/clusters/serverless/img/deploy-stage.png
index dc2f8af9c63..a4a6b363b64 100644
--- a/doc/user/project/clusters/serverless/img/deploy-stage.png
+++ b/doc/user/project/clusters/serverless/img/deploy-stage.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/img/dns-entry.png b/doc/user/project/clusters/serverless/img/dns-entry.png
index 2e7655c6041..678743f256e 100644
--- a/doc/user/project/clusters/serverless/img/dns-entry.png
+++ b/doc/user/project/clusters/serverless/img/dns-entry.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/img/function-execution.png b/doc/user/project/clusters/serverless/img/function-execution.png
new file mode 100644
index 00000000000..93b0b6d802d
--- /dev/null
+++ b/doc/user/project/clusters/serverless/img/function-execution.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/img/install-knative.png b/doc/user/project/clusters/serverless/img/install-knative.png
index dd576a9df35..93b1cbe602f 100644
--- a/doc/user/project/clusters/serverless/img/install-knative.png
+++ b/doc/user/project/clusters/serverless/img/install-knative.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/img/knative-app.png b/doc/user/project/clusters/serverless/img/knative-app.png
index 54301e1786f..931830d83ae 100644
--- a/doc/user/project/clusters/serverless/img/knative-app.png
+++ b/doc/user/project/clusters/serverless/img/knative-app.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/img/serverless-page.png b/doc/user/project/clusters/serverless/img/serverless-page.png
new file mode 100644
index 00000000000..960d6e736d6
--- /dev/null
+++ b/doc/user/project/clusters/serverless/img/serverless-page.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md
index bdbc4f7f09d..a423212a879 100644
--- a/doc/user/project/clusters/serverless/index.md
+++ b/doc/user/project/clusters/serverless/index.md
@@ -1,6 +1,7 @@
# Serverless
> Introduced in GitLab 11.5.
+> Serverless is currently in [alpha](https://about.gitlab.com/handbook/product/#alpha).
Run serverless workloads on Kubernetes using [Knative](https://cloud.google.com/knative/).
@@ -8,104 +9,229 @@ Run serverless workloads on Kubernetes using [Knative](https://cloud.google.com/
Knative extends Kubernetes to provide a set of middleware components that are useful to build modern, source-centric, container-based applications. Knative brings some significant benefits out of the box through its main components:
-- [Build:](https://github.com/knative/build) Source-to-container build orchestration
-- [Eventing:](https://github.com/knative/eventing) Management and delivery of events
-- [Serving:](https://github.com/knative/serving) Request-driven compute that can scale to zero
+- [Build](https://github.com/knative/build): Source-to-container build orchestration.
+- [Eventing](https://github.com/knative/eventing): Management and delivery of events.
+- [Serving](https://github.com/knative/serving): Request-driven compute that can scale to zero.
For more information on Knative, visit the [Knative docs repo](https://github.com/knative/docs).
+With GitLab serverless, you can deploy both functions-as-a-service (FaaS) and serverless applications.
+
## Requirements
To run Knative on Gitlab, you will need:
-1. **Kubernetes:** An RBAC-enabled Kubernetes cluster is required to deploy Knative.
- The simplest way to get started is to add a cluster using [GitLab's GKE integration](https://docs.gitlab.com/ee/user/project/clusters/#adding-and-creating-a-new-gke-cluster-via-gitlab).
- GitLab recommends
-1. **Helm Tiller:** Helm is a package manager for Kubernetes and is required to install
- all the other applications.
-1. **Domain Name:** Knative will provide its own load balancer using Istio. It will provide an
- external IP address for all the applications served by Knative. You will be prompted to enter a
- wildcard domain where your applications will be served. Configure your DNS server to use the
+1. **Kubernetes Cluster:** An RBAC-enabled Kubernetes cluster is required to deploy Knative.
+ The simplest way to get started is to add a cluster using [GitLab's GKE integration](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab).
+1. **Helm Tiller:** Helm is a package manager for Kubernetes and is required to install
+ Knative.
+1. **Domain Name:** Knative will provide its own load balancer using Istio. It will provide an
+ external IP address for all the applications served by Knative. You will be prompted to enter a
+ wildcard domain where your applications will be served. Configure your DNS server to use the
external IP address for that domain.
-1. **Serverless `gitlab-ci.yml` Template:** GitLab uses [Kaniko](https://github.com/GoogleContainerTools/kaniko)
- to build the application and the [TriggerMesh CLI](https://github.com/triggermesh/tm), to simplify the
+1. **`.gitlab-ci.yml`:** GitLab uses [Kaniko](https://github.com/GoogleContainerTools/kaniko)
+ to build the application and the [TriggerMesh CLI](https://github.com/triggermesh/tm) to simplify the
deployment of knative services and functions.
-
- Add the following `.gitlab-ci.yml` to the root of your repository (you may skip this step if using the sample
- [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) mentioned below).
-
- ```yaml
- stages:
- - build
- - deploy
-
- build:
- stage: build
- image:
- name: gcr.io/kaniko-project/executor:debug
- entrypoint: [""]
- only:
- - master
- script:
- - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE
-
- deploy:
- stage: deploy
- image: gcr.io/triggermesh/tm@sha256:e3ee74db94d215bd297738d93577481f3e4db38013326c90d57f873df7ab41d5
- only:
- - master
- environment: production
- script:
- - echo "$CI_REGISTRY_IMAGE"
- - tm -n "$KUBE_NAMESPACE" --config "$KUBECONFIG" deploy service "$CI_PROJECT_NAME" --from-image "$CI_REGISTRY_IMAGE" --wait
- ```
-
-1. **Dockerfile:** Knative requires a Dockerfile in order to build your application. It should be included
- at the root of your project's repo and expose port 8080.
+1. **`serverless.yaml`** (for [functions only](#deploying-functions)): When using serverless to deploy functions, the `serverless.yaml` file
+ will contain the information for all the functions being hosted in the repository as well as a reference to the
+ runtime being used.
+1. **`Dockerfile`** (for [applications only](#deploying-serverless-applications): Knative requires a `Dockerfile` in order to build your application. It should be included
+ at the root of your project's repo and expose port `8080`.
## Installing Knative via GitLab's Kubernetes integration
NOTE: **Note:**
-Minimum recommended cluster size to run Knative is 3-nodes, 6 vCPUs, and 22.50 GB memory. RBAC must be enabled.
-
-You may download the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) to get started.
+The minimum recommended cluster size to run Knative is 3-nodes, 6 vCPUs, and 22.50 GB memory. **RBAC must be enabled.**
-1. [Add a Kubernetes cluster](https://docs.gitlab.com/ce/user/project/clusters/) and install Helm.
-
-1. Once Helm has been successfully installed, on the Knative app section, enter the domain to be used with
+1. [Add a Kubernetes cluster](../index.md) and install Helm.
+1. Once Helm has been successfully installed, on the Knative app section, enter the domain to be used with
your application and click "Install".
![install-knative](img/install-knative.png)
-1. After the Knative installation has finished, retrieve the Istio Ingress IP address by running the following command:
+1. After the Knative installation has finished, you can wait for the IP address to be displayed in the
+ **Knative IP Address** field or retrieve the Istio Ingress IP address by running the following command:
- ```bash
- kubectl get svc --namespace=istio-system knative-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip} '
- ```
+ ```bash
+ kubectl get svc --namespace=istio-system knative-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip} '
+ ```
- Output:
+ Output:
- ```bash
- 35.161.143.124 my-machine-name:~ my-user$
- ```
+ ```bash
+ 35.161.143.124 my-machine-name:~ my-user$
+ ```
-1. The ingress is now available at this address and will route incoming requests to the proper service based on the DNS
- name in the request. To support this, a wildcard DNS A record should be created for the desired domain name. For example,
- if your Knative base domain is `knative.example.com` then you need to create an A record with domain `*.knative.example.com`
- pointing the ip address of the ingress.
+1. The ingress is now available at this address and will route incoming requests to the proper service based on the DNS
+ name in the request. To support this, a wildcard DNS A record should be created for the desired domain name. For example,
+ if your Knative base domain is `knative.info` then you need to create an A record with domain `*.knative.info`
+ pointing the ip address of the ingress.
![dns entry](img/dns-entry.png)
+## Deploying Functions
+
+> Introduced in GitLab 11.6.
+
+Using functions is useful for dealing with independent
+events without needing to maintain a complex unified infrastructure. This allows
+you to focus on a single task that can be executed/scaled automatically and independently.
+
+Currently the following [runtimes](https://gitlab.com/triggermesh/runtimes) are offered:
+
+- node.js
+- kaniko
+
+In order to deploy functions to your Knative instance, the following files must be present:
+
+1. `.gitlab-ci.yml`: This template allows to define the stage, environment, and
+ image to be used for your functions. It must be included at the root of your repository:
+
+ ```yaml
+ stages:
+ - deploy
+
+ functions:
+ stage: deploy
+ environment: test
+ image: gcr.io/triggermesh/tm:v0.0.7
+ script:
+ - tm -n "$KUBE_NAMESPACE" set registry-auth gitlab-registry --registry "$CI_REGISTRY" --username "$CI_REGISTRY_USER" --password "$CI_JOB_TOKEN"
+ - tm -n "$KUBE_NAMESPACE" --registry-host "$CI_REGISTRY_IMAGE" deploy --wait
+ ```
+
+ The `gitlab-ci.yml` template creates a `Deploy` stage with a `functions` job that invokes the `tm` CLI with the required parameters.
+
+2. `serverless.yaml`: This file contains the metadata for your functions,
+ such as name, runtime, and environment. It must be included at the root of your repository. The following is a sample `echo` function which shows the required structure for the file.
+
+ NOTE: **Note:**
+ The file extension for the `serverless.yaml` file must be specified as `.yaml` in order to the file to be parsed properly. Specifying the extension as `.yml` will not work.
+
+ ```yaml
+ service: my-functions
+ description: "Deploying functions from GitLab using Knative"
+
+ provider:
+ name: triggermesh
+ registry-secret: gitlab-registry
+ environment:
+ FOO: BAR
+
+ functions:
+ echo:
+ handler: echo
+ runtime: https://gitlab.com/triggermesh/runtimes/raw/master/nodejs.yaml
+ description: "echo function using node.js runtime"
+ buildargs:
+ - DIRECTORY=echo
+ environment:
+ FUNCTION: echo
+ ```
+
+
+The `serverless.yaml` file contains three sections with distinct parameters:
+
+### `service`
+
+| Parameter | Description |
+|-----------|-------------|
+| `service` | Name for the Knative service which will serve the function. |
+| `description` | A short description of the `service`. |
+
+
+### `provider`
+
+| Parameter | Description |
+|-----------|-------------|
+| `name` | Indicates which provider is used to execute the `serverless.yaml` file. In this case, the TriggerMesh `tm` CLI. |
+| `registry-secret` | Indicates which registry will be used to store docker images. The sample function is using the GitLab Registry (`gitlab-registry`). A different registry host may be specified using `registry` key in the `provider` object. If changing the default, update the permission and the secret value on the `gitlab-ci.yml` file |
+| `environment` | Includes the environment variables to be passed as part of function execution for **all** functions in the file, where `FOO` is the variable name and `BAR` are he variable contents. You may replace this with you own variables. |
+
+### `functions`
+
+In the `serverless.yaml` example above, the function name is `echo` and the subsequent lines contain the function attributes.
+
+
+| Parameter | Description |
+|-----------|-------------|
+| `handler` | The function's file name. In the example above, both the function name and the handler are the same. |
+| `runtime` | The runtime to be used to execute the function. |
+| `description` | A short description of the function. |
+| `buildargs` | Pointer to the function file in the repo. In the sample the function is located in the `echo` directory. |
+| `environment` | Sets an environment variable for the specific function only. |
+
+After the `gitlab-ci.yml` template has been added and the `serverless.yaml` file has been
+created, each function must be defined as a single file in your repository. Committing a
+function to your project will result in a
+CI pipeline being executed which will deploy each function as a Knative service.
+Once the deploy stage has finished, additional details for the function will
+appear under **Operations > Serverless**.
+
+![serverless page](img/serverless-page.png)
+
+This page contains all functions available for the project, the URL for
+accessing the function, and if available, the function's runtime information.
+The details are derived from the Knative installation inside each of the project's
+Kubernetes cluster.
+
+The function details can be retrieved directly from Knative on the cluster:
+
+```bash
+kubectl -n "$KUBE_NAMESPACE" get services.serving.knative.dev
+```
+
+The sample function can now be triggered from any HTTP client using a simple `POST` call.
+
+![function exection](img/function-execution.png)
+
+Currently, the Serverless page presents all functions available in all clusters registered for the project with Knative installed.
+
+## Deploying Serverless applications
+
+> Introduced in GitLab 11.5.
+
+NOTE: **Note:**
+You can reference the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) to get started.
+
+Add the following `.gitlab-ci.yml` to the root of your repository
+(you may skip this step if using the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) mentioned above):
+
+```yaml
+stages:
+ - build
+ - deploy
+
+build:
+ stage: build
+ image:
+ name: gcr.io/kaniko-project/executor:debug
+ entrypoint: [""]
+ only:
+ - master
+ script:
+ - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
+ - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE
+
+deploy:
+ stage: deploy
+ image: gcr.io/triggermesh/tm@sha256:e3ee74db94d215bd297738d93577481f3e4db38013326c90d57f873df7ab41d5
+ only:
+ - master
+ environment: production
+ script:
+ - echo "$CI_REGISTRY_IMAGE"
+ - tm -n "$KUBE_NAMESPACE" --config "$KUBECONFIG" deploy service "$CI_PROJECT_NAME" --from-image "$CI_REGISTRY_IMAGE" --wait
+```
+
## Deploy the application with Knative
-With all the pieces in place, you can simply create a new CI pipeline to deploy the Knative application. Navigate to
-**CI/CD >> Pipelines** and click the **Run Pipeline** button at the upper-right part of the screen. Then, on the
-Pipelines page, click **Create pipeline**.
+With all the pieces in place, the next time a CI pipeline runs, the Knative application will be deployed. Navigate to
+**CI/CD > Pipelines** and click the most recent pipeline.
## Obtain the URL for the Knative deployment
-Once all the stages of the pipeline finish, click the **deploy** stage.
+Use the CI/CD deployment job output to obtain the deployment URL. Once all the stages of the pipeline finish, click the **deploy** stage.
![deploy stage](img/deploy-stage.png)
@@ -131,7 +257,7 @@ Service domain: knative.knative-4342902.knative.info
Job succeeded
```
-The second to last line, labeled **Service domain** contains the URL for the deployment. Copy and paste the domain into your
+The second to last line, labeled **Service domain** contains the URL for the deployment. Copy and paste the domain into your
browser to see the app live.
-![knative app](img/knative-app.png) \ No newline at end of file
+![knative app](img/knative-app.png)
diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md
index ea843054f8e..b98221bea61 100644
--- a/doc/user/project/cycle_analytics.md
+++ b/doc/user/project/cycle_analytics.md
@@ -146,7 +146,8 @@ A few notes:
The current permissions on the Cycle Analytics dashboard are:
- Public projects - anyone can access
-- Private/internal projects - any member (guest level and above) can access
+- Internal projects - any authenticated user can access
+- Private projects - any member Guest and above can access
You can [read more about permissions][permissions] in general.
diff --git a/doc/user/project/img/issue_board.png b/doc/user/project/img/issue_board.png
index b46b995d8bb..b753593d212 100644
--- a/doc/user/project/img/issue_board.png
+++ b/doc/user/project/img/issue_board.png
Binary files differ
diff --git a/doc/user/project/img/issue_board_summed_weights.png b/doc/user/project/img/issue_board_summed_weights.png
index 2288d767d8c..6035d7ca330 100644
--- a/doc/user/project/img/issue_board_summed_weights.png
+++ b/doc/user/project/img/issue_board_summed_weights.png
Binary files differ
diff --git a/doc/user/project/img/issue_boards_core.png b/doc/user/project/img/issue_boards_core.png
index 8bc187482ad..41ddbb24b14 100644
--- a/doc/user/project/img/issue_boards_core.png
+++ b/doc/user/project/img/issue_boards_core.png
Binary files differ
diff --git a/doc/user/project/img/issue_boards_premium.png b/doc/user/project/img/issue_boards_premium.png
index 4e238ea6983..ef9f5bbea32 100644
--- a/doc/user/project/img/issue_boards_premium.png
+++ b/doc/user/project/img/issue_boards_premium.png
Binary files differ
diff --git a/doc/user/project/img/releases.png b/doc/user/project/img/releases.png
new file mode 100644
index 00000000000..aec1db89a75
--- /dev/null
+++ b/doc/user/project/img/releases.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_api_token.png b/doc/user/project/integrations/img/jira_api_token.png
index 2c64f7bc44f..4fa7a46854e 100644
--- a/doc/user/project/integrations/img/jira_api_token.png
+++ b/doc/user/project/integrations/img/jira_api_token.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_api_token_menu.png b/doc/user/project/integrations/img/jira_api_token_menu.png
index 20655ba3c0e..55c8fb1bdb9 100644
--- a/doc/user/project/integrations/img/jira_api_token_menu.png
+++ b/doc/user/project/integrations/img/jira_api_token_menu.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_service_page.png b/doc/user/project/integrations/img/jira_service_page.png
index 869d562ed5b..3a27b4df841 100644
--- a/doc/user/project/integrations/img/jira_service_page.png
+++ b/doc/user/project/integrations/img/jira_service_page.png
Binary files differ
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 7d0e567cae7..d9a2eeb32ae 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -41,6 +41,7 @@ Once you have a connected Kubernetes cluster with Helm installed, deploying a ma
Prometheus is deployed into the `gitlab-managed-apps` namespace, using the [official Helm chart](https://github.com/kubernetes/charts/tree/master/stable/prometheus). Prometheus is only accessible within the cluster, with GitLab communicating through the [Kubernetes API](https://kubernetes.io/docs/concepts/overview/kubernetes-api/).
The Prometheus server will [automatically detect and monitor](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#%3Ckubernetes_sd_config%3E) nodes, pods, and endpoints. To configure a resource to be monitored by Prometheus, simply set the following [Kubernetes annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/):
+
* `prometheus.io/scrape` to `true` to enable monitoring of the resource.
* `prometheus.io/port` to define the port of the metrics endpoint.
* `prometheus.io/path` to define the path of the metrics endpoint. Defaults to `/metrics`.
diff --git a/doc/user/project/issues/img/issue_board.png b/doc/user/project/issues/img/issue_board.png
index c75c35a382e..dd40740aec5 100644
--- a/doc/user/project/issues/img/issue_board.png
+++ b/doc/user/project/issues/img/issue_board.png
Binary files differ
diff --git a/doc/user/project/issues/img/similar_issues.png b/doc/user/project/issues/img/similar_issues.png
new file mode 100644
index 00000000000..0dfb5b00e02
--- /dev/null
+++ b/doc/user/project/issues/img/similar_issues.png
Binary files differ
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index d71273ba970..200b3a642a1 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -155,3 +155,7 @@ Read through the [API documentation](../../../api/issues.md).
### Bulk editing issues
Find out about [bulk editing issues](../../project/bulk_editing.md).
+
+### Similar issues
+
+Find out about [similar issues](similar_issues.md).
diff --git a/doc/user/project/issues/similar_issues.md b/doc/user/project/issues/similar_issues.md
new file mode 100644
index 00000000000..e90ecd88ec6
--- /dev/null
+++ b/doc/user/project/issues/similar_issues.md
@@ -0,0 +1,16 @@
+# Similar issues
+
+> [Introduced][ce-22866] in GitLab 11.6.
+
+Similar issues suggests issues that are similar when new issues are being created.
+This features requires [GraphQL] to be enabled.
+
+![Similar issues](img/similar_issues.png)
+
+You can see the similar issues when typing in the title in the new issue form.
+This searches both titles and descriptions across all issues the user has access
+to in the current project. It then displays the first 5 issues sorted by most
+recently updated.
+
+[GraphQL]: ../../../api/graphql/index.md
+[ce-22866]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22866
diff --git a/doc/user/project/merge_requests/img/comment-on-any-diff-line.png b/doc/user/project/merge_requests/img/comment-on-any-diff-line.png
index 856ede41527..c2455c2d1e5 100644
--- a/doc/user/project/merge_requests/img/comment-on-any-diff-line.png
+++ b/doc/user/project/merge_requests/img/comment-on-any-diff-line.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/filter_wip_merge_requests.png b/doc/user/project/merge_requests/img/filter_wip_merge_requests.png
index 40913718385..81878709487 100644
--- a/doc/user/project/merge_requests/img/filter_wip_merge_requests.png
+++ b/doc/user/project/merge_requests/img/filter_wip_merge_requests.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/merge_request_pipeline.png b/doc/user/project/merge_requests/img/merge_request_pipeline.png
index 183d9cb910b..ce1d6bab536 100644
--- a/doc/user/project/merge_requests/img/merge_request_pipeline.png
+++ b/doc/user/project/merge_requests/img/merge_request_pipeline.png
Binary files differ
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 5b54b6ecdd5..d4f8cf929f6 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -150,6 +150,16 @@ in a Merge Request. To do so, click the **...** button in the gutter of the Merg
![Comment on any diff file line](img/comment-on-any-diff-line.png)
+## Suggest changes
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/18008) in GitLab 11.6.
+
+As a reviewer, you can add suggestions to change the content in
+merge request discussions, and users with appropriate [permission](../../permissions.md)
+can easily apply them to the codebase directly from the UI. Read
+through the documentation on [Suggest changes](../../discussions/index.md#suggest-changes)
+to learn more.
+
## Resolve conflicts
When a merge request has conflicts, GitLab may provide the option to resolve
@@ -259,6 +269,16 @@ all your changes will be available to preview by anyone with the Review Apps lin
[Read more about Review Apps.](../../../ci/review_apps/index.md)
+## Pipelines for merge requests
+
+When a developer updates a merge request, a pipeline should quickly report back
+its result to the developer, but often pipelines take long time to complete
+because general branch pipelines contain unnecessary jobs from the merge request standpoint.
+You can customize a specific pipeline structure for merge requests in order to
+speed the cycle up by running only important jobs.
+
+Learn more about [pipelines for merge requests](../../../ci/merge_request_pipelines/index.md).
+
## Pipeline status in merge requests
If you've set up [GitLab CI/CD](../../../ci/README.md) in your project,
diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md
index 247e8d2a6a0..cea9628966d 100644
--- a/doc/user/project/pages/getting_started_part_three.md
+++ b/doc/user/project/pages/getting_started_part_three.md
@@ -234,26 +234,25 @@ reiterating the importance of HTTPS.
## Issuing Certificates
-GitLab Pages accepts [PEM](https://support.quovadisglobal.com/kb/a37/what-is-pem-format.aspx) certificates issued by
-[Certificate Authorities (CA)](https://en.wikipedia.org/wiki/Certificate_authority)
-and self-signed certificates. Of course,
-[you'd rather issue a certificate than generate a self-signed](https://en.wikipedia.org/wiki/Self-signed_certificate),
-for security reasons and for having browsers trusting your
-site's certificate.
-
-There are several different kinds of certificates, each one
-with certain security level. A static personal website will
+GitLab Pages accepts certificates provided in the [PEM](https://support.quovadisglobal.com/kb/a37/what-is-pem-format.aspx) format, issued by
+[Certificate Authorities (CAs)](https://en.wikipedia.org/wiki/Certificate_authority) or as
+[self-signed certificates](https://en.wikipedia.org/wiki/Self-signed_certificate). Note that [self-signed certificates are typically not used](https://securingtomorrow.mcafee.com/other-blogs/mcafee-labs/self-signed-certificates-secure-so-why-ban/)
+for public websites for security reasons and to ensure that browsers trust your site's certificate.
+
+There are various kinds of certificates, each one
+with a certain security level. A static personal website will
not require the same security level as an online banking web app,
-for instance. There are a couple Certificate Authorities that
+for instance.
+
+There are some certificate authorities that
offer free certificates, aiming to make the internet more secure
to everyone. The most popular is [Let's Encrypt](https://letsencrypt.org/),
which issues certificates trusted by most of browsers, it's open
-source, and free to use. Please read through this tutorial to
-understand [how to secure your GitLab Pages website with Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/).
+source, and free to use. See our tutorial on [how to secure your GitLab Pages website with Let's Encrypt](lets_encrypt_for_gitlab_pages.md).
-With the same popularity, there are [certificates issued by CloudFlare](https://www.cloudflare.com/ssl/),
+Similarly popular are [certificates issued by CloudFlare](https://www.cloudflare.com/ssl/),
which also offers a [free CDN service](https://blog.cloudflare.com/cloudflares-free-cdn-and-you/).
-Their certs are valid up to 15 years. Read through the tutorial on
+Their certs are valid up to 15 years. See the tutorial on
[how to add a CloudFlare Certificate to your GitLab Pages website](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/).
### Adding certificates to your project
diff --git a/doc/user/project/pages/img/icons/click.png b/doc/user/project/pages/img/icons/click.png
index daaf760ec08..a534ae29e0f 100644
--- a/doc/user/project/pages/img/icons/click.png
+++ b/doc/user/project/pages/img/icons/click.png
Binary files differ
diff --git a/doc/user/project/pages/img/icons/cogs.png b/doc/user/project/pages/img/icons/cogs.png
index a12da1b5e8c..f37f8f361d1 100644
--- a/doc/user/project/pages/img/icons/cogs.png
+++ b/doc/user/project/pages/img/icons/cogs.png
Binary files differ
diff --git a/doc/user/project/pages/img/icons/fork.png b/doc/user/project/pages/img/icons/fork.png
index e2c9577e7ce..8a3aa46eb37 100644
--- a/doc/user/project/pages/img/icons/fork.png
+++ b/doc/user/project/pages/img/icons/fork.png
Binary files differ
diff --git a/doc/user/project/pages/img/icons/free.png b/doc/user/project/pages/img/icons/free.png
index 3b8f8f6863e..ae455033e94 100644
--- a/doc/user/project/pages/img/icons/free.png
+++ b/doc/user/project/pages/img/icons/free.png
Binary files differ
diff --git a/doc/user/project/pages/img/icons/lock.png b/doc/user/project/pages/img/icons/lock.png
index 1c1f0b4457b..f4c35c84112 100644
--- a/doc/user/project/pages/img/icons/lock.png
+++ b/doc/user/project/pages/img/icons/lock.png
Binary files differ
diff --git a/doc/user/project/pages/img/icons/monitor.png b/doc/user/project/pages/img/icons/monitor.png
index 7b99d430eef..8bad059a74c 100644
--- a/doc/user/project/pages/img/icons/monitor.png
+++ b/doc/user/project/pages/img/icons/monitor.png
Binary files differ
diff --git a/doc/user/project/pages/img/icons/terminal.png b/doc/user/project/pages/img/icons/terminal.png
index ab5ae11310c..377eeb4edc6 100644
--- a/doc/user/project/pages/img/icons/terminal.png
+++ b/doc/user/project/pages/img/icons/terminal.png
Binary files differ
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index 7de9ae0caea..ce4fccdaff3 100644
--- a/doc/user/project/pages/index.md
+++ b/doc/user/project/pages/index.md
@@ -170,7 +170,7 @@ optionally secure it with SSL/TLS certificates. You can read the following
tutorials to learn how to use these third-party certificates with GitLab Pages:
- [CloudFlare](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/)
-- [Let's Encrypt](https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/) (mind that although this article is out-of-date, it can still be useful to guide you through the basic steps)
+- [Let's Encrypt](lets_encrypt_for_gitlab_pages.md)
## Advanced use
diff --git a/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md b/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md
new file mode 100644
index 00000000000..f639188684b
--- /dev/null
+++ b/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md
@@ -0,0 +1,158 @@
+---
+description: "How to secure GitLab Pages websites with Let's Encrypt."
+---
+
+# Let's Encrypt for GitLab Pages
+
+If you have a GitLab Pages website served under your own domain,
+you might want to secure it with a SSL/TSL certificate.
+
+[Let's Encrypt](https://letsencrypt.org) is a free, automated, and
+open source Certificate Authority.
+
+## Requirements
+
+To follow along with this tutorial, we assume you already have:
+
+- Created a [project](getting_started_part_two.md) in GitLab which
+ contains your website's source code.
+- Acquired a domain (`example.com`) and added a [DNS entry](getting_started_part_three.md#dns-records)
+ pointing it to your Pages website.
+- [Added your domain to your Pages project](getting_started_part_three.md#add-your-custom-domain-to-gitlab-pages-settings)
+ and verified your ownership.
+- Cloned your project into your computer.
+- Your website up and running, served under HTTP protocol at `http://example.com`.
+
+## Obtaining a Let's Encrypt certificate
+
+Once you have the requirements addressed, follow the instructions
+below to learn how to obtain the certificate.
+
+NOTE: **Note:**
+The instructions below were tested on macOS Mojave. For other
+operating systems the steps might be slightly different. Follow the
+[CertBot instructions](https://certbot.eff.org/) according to your OS.
+
+1. On your computer, open a terminal and navigate to your repository's
+ root directory:
+
+ ```bash
+ cd path/to/dir
+ ```
+
+1. Install CertBot (the tool Let's Encrypt uses to issue certificates):
+
+ ```bash
+ brew install certbot
+ ```
+
+1. Request a certificate for your domain (`example.com`) and
+ provide an email account (`your@email.com`) to receive notifications:
+
+ ```bash
+ sudo certbot certonly -a manual -d example.com --email your@email.com
+ ```
+
+ Alternatively, you can register without adding an e-mail account,
+ but you won't be notified about the certificate expiration's date:
+
+ ```bash
+ sudo certbot certonly -a manual -d example.com --register-unsafely-without-email
+ ```
+
+ TIP: **Tip:**
+ Read through CertBot's documentation on their
+ [command line options](https://certbot.eff.org/docs/using.html#certbot-command-line-options).
+
+1. You'll be prompted with a message to agree with their terms.
+ Press `A` to agree and `Y` to let they log your IP.
+
+ CertBot will then prompt you with the following message:
+
+ ```bash
+ Create a file containing just this data:
+
+ Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP.HUGNKk82jlsmOOfphlt8Jy69iuglsn095nxOMH9j3Yb
+
+ And make it available on your web server at this URL:
+
+ http://example.com/.well-known/acme-challenge/Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP
+
+ Press Enter to Continue
+ ```
+
+1. **Do not press Enter yet.** Let's Encrypt will need to verify your
+ domain ownership before issuing the certificate. To do so, create 3
+ consecutive directories under your website's root:
+ `/.well-known/acme-challenge/Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP/`
+ and add to the last folder an `index.html` file containing the content
+ referred on the previous prompt message:
+
+ ```bash
+ Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP.HUGNKk82jlsmOOfphlt8Jy69iuglsn095nxOMH9j3Yb
+ ```
+
+ Note that this file needs to be accessed under
+ `http://example.com/.well-known/acme-challenge/Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP`
+ to allow Let's Encrypt to verify the ownership of your domain,
+ therefore, it needs to be part of the website content under the
+ repo's [`public`](index.md#how-it-works) folder.
+
+1. Add, commit, and push the file into your repo in GitLab. Once the pipeline
+ passes, press **Enter** on your terminal to continue issuing your
+ certificate. CertBot will then prompt you with the following message:
+
+ ```bash
+ Waiting for verification...
+ Cleaning up challenges
+
+ IMPORTANT NOTES:
+ - Congratulations! Your certificate and chain have been saved at:
+ /etc/letsencrypt/live/example.com/fullchain.pem
+ Your key file has been saved at:
+ /etc/letsencrypt/live/example.com/privkey.pem
+ Your cert will expire on 2019-03-12. To obtain a new or tweaked
+ version of this certificate in the future, simply run certbot
+ again. To non-interactively renew *all* of your certificates, run
+ "certbot renew"
+ - If you like Certbot, please consider supporting our work by:
+
+ Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
+ Donating to EFF: https://eff.org/donate-le
+ ```
+
+## Add your certificate to GitLab Pages
+
+Now that your certificate has been issued, let's add it to your Pages site:
+
+1. Back at GitLab, navigate to your project's **Settings > Pages**,
+ find your domain and click **Details** and **Edit** to add your certificate.
+1. From your terminal, copy and paste the certificate into the first field
+ **Certificate (PEM)**:
+
+ ```bash
+ sudo cat /etc/letsencrypt/live/example.com/fullchain.pem | pbcopy
+ ```
+
+1. Copy and paste the public key into the second field **Key (PEM)**:
+
+ ```bash
+ sudo cat /etc/letsencrypt/live/example.com/privkey.pem | pbcopy
+ ```
+
+1. Click **Save changes** to apply them to your website.
+1. Wait a few minutes for DNS propagation.
+1. Visit your website at `https://example.com`.
+
+To force `https` connections on your site, navigate to your
+project's **Settings > Pages** and check **Force domains with SSL
+certificates to use HTTPS**.
+
+## Renewal
+
+Let's Encrypt certificates expire every 90 days and you'll have to
+renew them periodically. To renew all your certificates at once, run:
+
+```bash
+sudo certbot renew
+```
diff --git a/doc/user/project/releases.md b/doc/user/project/releases.md
new file mode 100644
index 00000000000..8dad4240c91
--- /dev/null
+++ b/doc/user/project/releases.md
@@ -0,0 +1,12 @@
+# Releases
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41766) in GitLab 11.7.
+
+Releases mark specific points in a project's development history, communicate
+information about the type of change, and deliver on prepared, often compiled,
+versions of the software to be reused elsewhere. Currently, releases can only be
+created through the API.
+
+Navigate to **Project > Releases** in order to see the list of releases of a project.
+
+![Releases list](img/releases.png)
diff --git a/doc/user/project/repository/branches/img/branch_filter_search_box.png b/doc/user/project/repository/branches/img/branch_filter_search_box.png
index c4364ef39f4..5dc7eccf189 100644
--- a/doc/user/project/repository/branches/img/branch_filter_search_box.png
+++ b/doc/user/project/repository/branches/img/branch_filter_search_box.png
Binary files differ
diff --git a/doc/user/project/repository/img/repository_cleanup.png b/doc/user/project/repository/img/repository_cleanup.png
new file mode 100644
index 00000000000..bda40d3e193
--- /dev/null
+++ b/doc/user/project/repository/img/repository_cleanup.png
Binary files differ
diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md
index 1710bba2fd0..fac5975a0dc 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -29,7 +29,7 @@ You can either use the user interface (UI), or connect your local computer
with GitLab [through the command line](../../../gitlab-basics/command-line-commands.md#start-working-on-your-project).
To configure [GitLab CI/CD](../../../ci/README.md) to build, test, and deploy
-you code, add a file called [.`gitlab-ci.yml`](../../../ci/quick_start/README.md)
+you code, add a file called [`.gitlab-ci.yml`](../../../ci/quick_start/README.md)
to your repository's root.
**From the user interface:**
diff --git a/doc/user/project/repository/reducing_the_repo_size_using_git.md b/doc/user/project/repository/reducing_the_repo_size_using_git.md
index d534c8cbe4b..672567a8d7d 100644
--- a/doc/user/project/repository/reducing_the_repo_size_using_git.md
+++ b/doc/user/project/repository/reducing_the_repo_size_using_git.md
@@ -1,43 +1,105 @@
# Reducing the repository size using Git
A GitLab Enterprise Edition administrator can set a [repository size limit][admin-repo-size]
-which will prevent you to exceed it.
+which will prevent you from exceeding it.
When a project has reached its size limit, you will not be able to push to it,
create a new merge request, or merge existing ones. You will still be able to
create new issues, and clone the project though. Uploading LFS objects will
also be denied.
-In order to lift these restrictions, the administrator of the GitLab instance
-needs to increase the limit on the particular project that exceeded it or you
-need to instruct Git to rewrite changes.
-
If you exceed the repository size limit, your first thought might be to remove
-some data, make a new commit and push back to the repository. Unfortunately,
-it's not so easy and that workflow won't work. Deleting files in a commit doesn't
-actually reduce the size of the repo since the earlier commits and blobs are
-still around. What you need to do is rewrite history with Git's
-[`filter-branch` option][gitscm].
+some data, make a new commit and push back to the repository. Perhaps you can
+move some blobs to LFS, or remove some old dependency updates from history.
+Unfortunately, it's not so easy and that workflow won't work. Deleting files in
+a commit doesn't actually reduce the size of the repo since the earlier commits
+and blobs are still around. What you need to do is rewrite history with Git's
+[`filter-branch` option][gitscm], or a tool like the [BFG Repo-Cleaner][bfg].
Note that even with that method, until `git gc` runs on the GitLab side, the
-"removed" commits and blobs will still be around. And if a commit was ever
-included in an MR, or if a build was run for a commit, or if a user commented
-on it, it will be kept around too. So, in these cases the size will not decrease.
-
-The only fool proof way to actually decrease the repository size is to prune all
-the unneeded stuff locally, and then create a new project on GitLab and start
-using that instead.
+"removed" commits and blobs will still be around. You also need to be able to
+push the rewritten history to GitLab, which may be impossible if you've already
+exceeded the maximum size limit.
-With that being said, you can try reducing your repository size with the
-following method.
-
-## Using `git filter-branch` to purge files
+In order to lift these restrictions, the administrator of the GitLab instance
+needs to increase the limit on the particular project that exceeded it, so it's
+always better to spot that you're approaching the limit and act proactively to
+stay underneath it. If you hit the limit, and your admin can't - or won't -
+temporarily increase it for you, your only option is to prune all the unneeded
+stuff locally, and then create a new project on GitLab and start using that
+instead.
+
+If you can continue to use the original project, we recommend [using the
+BFG Repo-Cleaner](#using-the-bfg-repo-cleaner). It's faster and simpler than
+`git filter-branch`, and GitLab can use its account of what has changed to clean
+up its own internal state, maximizing the space saved.
> **Warning:**
> Make sure to first make a copy of your repository since rewriting history will
> purge the files and information you are about to delete. Also make sure to
> inform any collaborators to not use `pull` after your changes, but use `rebase`.
+> **Warning:**
+> This process is not suitable for removing sensitive data like password or keys
+> from your repository. Information about commits, including file content, is
+> cached in the database, and will remain visible even after they have been
+> removed from the repository.
+
+## Using the BFG Repo-Cleaner
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/19376) in GitLab 11.6.
+
+1. [Install BFG](https://rtyley.github.io/bfg-repo-cleaner/).
+
+1. Navigate to your repository:
+
+ ```
+ cd my_repository/
+ ```
+
+1. Change to the branch you want to remove the big file from:
+
+ ```
+ git checkout master
+ ```
+
+1. Create a commit removing the large file from the branch, if it still exists:
+
+ ```
+ git rm path/to/big_file.mpg
+ git commit -m 'Remove unneeded large file'
+ ```
+
+1. Rewrite history:
+
+ ```
+ bfg --delete-files path/to/big_file.mpg
+ ```
+
+ An object map file will be written to `object-id-map.old-new.txt`. Keep it
+ around - you'll need it for the final step!
+
+1. Force-push the changes to GitLab:
+
+ ```
+ git push --force-with-lease origin master
+ ```
+
+ If this step fails, someone has changed the `master` branch while you were
+ rewriting history. You could restore the branch and re-run BFG to preserve
+ their changes, or use `git push --force` to overwrite their changes.
+
+1. Navigate to **Project > Settings > Repository > Repository Cleanup**:
+
+ ![Repository settings cleanup form](img/repository_cleanup.png)
+
+ Upload the `object-id-map.old-new.txt` file and press **Start cleanup**.
+ This will remove any internal git references to the old commits, and run
+ `git gc` against the repository. You will receive an email once it has
+ completed.
+
+## Using `git filter-branch`
+
1. Navigate to your repository:
```
@@ -70,11 +132,6 @@ following method.
Your repository should now be below the size limit.
-> **Note:**
-> As an alternative to `filter-branch`, you can use the `bfg` tool with a
-> command like: `bfg --delete-files path/to/big_file.mpg`. Read the
-> [BFG Repo-Cleaner][bfg] documentation for more information.
-
[admin-repo-size]: https://docs.gitlab.com/ee/user/admin_area/settings/account_and_limit_settings.html#repository-size-limit
[bfg]: https://rtyley.github.io/bfg-repo-cleaner/
[gitscm]: https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History#The-Nuclear-Option:-filter-branch
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index f94671fcf87..cb68c9318bc 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -24,6 +24,10 @@
> Otherwise, a supplementary comment is left to mention the original author and
> the MRs, notes or issues will be owned by the importer.
> - Control project Import/Export with the [API](../../../api/project_import_export.md).
+> - If an imported project contains merge requests originated from forks,
+> then new branches associated with such merge requests will be created
+> within a project during the import/export. Thus, the number of branches
+> in the exported project could be bigger than in the original project.
Existing projects running on any GitLab instance or GitLab.com can be exported
with all their related data and be moved into a new GitLab instance.
diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md
index e6b1f6b6aae..55e53b865af 100644
--- a/doc/user/project/web_ide/index.md
+++ b/doc/user/project/web_ide/index.md
@@ -1,6 +1,6 @@
# Web IDE
-> [Introduced in](https://gitlab.com/gitlab-org/gitlab-ee/issues/4539) [GitLab Ultimate][ee] 10.4.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/4539) in [GitLab Ultimate][ee] 10.4.
> [Brought to GitLab Core](https://gitlab.com/gitlab-org/gitlab-ce/issues/44157) in 10.7.
The Web IDE makes it faster and easier to contribute changes to your projects
@@ -15,7 +15,7 @@ and from merge requests.
## File finder
-> [Introduced in](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18323) [GitLab Core][ce] 10.8.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18323) in [GitLab Core][ce] 10.8.
The file finder allows you to quickly open files in the current branch by
searching. The file finder is launched using the keyboard shortcut `Command-p`,
@@ -65,7 +65,7 @@ shows you a preview of the merge request diff if you commit your changes.
## View CI job logs
-> [Introduced in](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19279) [GitLab Core][ce] 11.0.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19279) in [GitLab Core][ce] 11.0.
The Web IDE can be used to quickly fix failing tests by opening the branch or
merge request in the Web IDE and opening the logs of the failed job. The status
@@ -77,7 +77,7 @@ left.
## Switching merge requests
-> [Introduced in](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19318) [GitLab Core][ce] 11.0.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19318) in [GitLab Core][ce] 11.0.
Switching between your authored and assigned merge requests can be done without
leaving the Web IDE. Click the dropdown in the top of the sidebar to open a list
@@ -86,7 +86,7 @@ switching to a different merge request.
## Switching branches
-> [Introduced in](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20850) [GitLab Core][ce] 11.2.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20850) in [GitLab Core][ce] 11.2.
Switching between branches of the current project repository can be done without
leaving the Web IDE. Click the dropdown in the top of the sidebar to open a list
@@ -95,7 +95,7 @@ switching to a different branch.
## Client Side Evaluation
-> [Introduced in](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19764) [GitLab Core][ce] 11.2.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19764) in [GitLab Core][ce] 11.2.
The Web IDE can be used to preview JavaScript projects right in the browser.
This feature uses CodeSandbox to compile and bundle the JavaScript used to
diff --git a/doc/user/search/img/dashboard_links.png b/doc/user/search/img/dashboard_links.png
index 2c472c7e464..d784ba8018e 100644
--- a/doc/user/search/img/dashboard_links.png
+++ b/doc/user/search/img/dashboard_links.png
Binary files differ
diff --git a/doc/user/search/img/issues_assigned_to_you.png b/doc/user/search/img/issues_assigned_to_you.png
index d2fff5e9a67..55986eedcba 100644
--- a/doc/user/search/img/issues_assigned_to_you.png
+++ b/doc/user/search/img/issues_assigned_to_you.png
Binary files differ
diff --git a/doc/workflow/img/repository_mirroring_force_update.png b/doc/workflow/img/repository_mirroring_force_update.png
index 8ba715d1ba3..1e6dcb9ea08 100644
--- a/doc/workflow/img/repository_mirroring_force_update.png
+++ b/doc/workflow/img/repository_mirroring_force_update.png
Binary files differ
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
index 020aa73f809..6ce789998a4 100644
--- a/doc/workflow/notifications.md
+++ b/doc/workflow/notifications.md
@@ -135,6 +135,7 @@ Notification emails include headers that provide extra content about the notific
| X-GitLab-Pipeline-Id | Only in pipeline emails, the ID of the pipeline the notification is for |
| X-GitLab-Reply-Key | A unique token to support reply by email |
| X-GitLab-NotificationReason | The reason for being notified. "mentioned", "assigned", etc |
+| List-Id | The path of the project in a RFC 2919 mailing list identifier useful for email organization, for example, with GMail filters |
#### X-GitLab-NotificationReason
diff --git a/config/jest.config.js b/jest.config.js
index 23e62f49be1..4dab7c2891a 100644
--- a/config/jest.config.js
+++ b/jest.config.js
@@ -14,8 +14,10 @@ if (process.env.CI) {
// eslint-disable-next-line import/no-commonjs
module.exports = {
testMatch: ['<rootDir>/spec/frontend/**/*_spec.js'],
+ moduleFileExtensions: ['js', 'json', 'vue'],
moduleNameMapper: {
'^~(.*)$': '<rootDir>/app/assets/javascripts$1',
+ '^helpers(.*)$': '<rootDir>/spec/frontend/helpers$1',
},
collectCoverageFrom: ['<rootDir>/app/assets/javascripts/**/*.{js,vue}'],
coverageDirectory: '<rootDir>/coverage-frontend/',
@@ -23,5 +25,10 @@ module.exports = {
cacheDirectory: '<rootDir>/tmp/cache/jest',
modulePathIgnorePatterns: ['<rootDir>/.yarn-cache/'],
reporters,
- rootDir: '..', // necessary because this file is in the config/ subdirectory
+ setupTestFrameworkScriptFile: '<rootDir>/spec/frontend/test_setup.js',
+ restoreMocks: true,
+ transform: {
+ '^.+\\.js$': 'babel-jest',
+ '^.+\\.vue$': 'vue-jest',
+ },
};
diff --git a/lib/api/api.rb b/lib/api/api.rb
index a4bf0d77eb1..f1448da7403 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -20,7 +20,8 @@ module API
Gitlab::GrapeLogging::Loggers::RouteLogger.new,
Gitlab::GrapeLogging::Loggers::UserLogger.new,
Gitlab::GrapeLogging::Loggers::QueueDurationLogger.new,
- Gitlab::GrapeLogging::Loggers::PerfLogger.new
+ Gitlab::GrapeLogging::Loggers::PerfLogger.new,
+ Gitlab::GrapeLogging::Loggers::CorrelationIdLogger.new
]
allow_access_with_scope :api
@@ -84,7 +85,6 @@ module API
content_type :txt, "text/plain"
# Ensure the namespace is right, otherwise we might load Grape::API::Helpers
- helpers ::SentryHelper
helpers ::API::Helpers
helpers ::API::Helpers::CommonHelpers
@@ -149,6 +149,7 @@ module API
mount ::API::Snippets
mount ::API::Submodules
mount ::API::Subscriptions
+ mount ::API::Suggestions
mount ::API::SystemHooks
mount ::API::Tags
mount ::API::Templates
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 62c966e06b4..08b4f8db8b0 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -116,7 +116,7 @@ module API
end
MergeRequest.where(source_project: @project, source_branch: ref)
- .update_all(head_pipeline_id: pipeline) if pipeline.latest?
+ .update_all(head_pipeline_id: pipeline.id) if pipeline.latest?
present status, with: Entities::CommitStatus
rescue StateMachines::InvalidTransition => e
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 5dbfbb85e9e..22403664c21 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -323,7 +323,7 @@ module API
expose :request_access_enabled
expose :full_name, :full_path
- if ::Group.supports_nested_groups?
+ if ::Group.supports_nested_objects?
expose :parent_id
end
@@ -1495,5 +1495,17 @@ module API
expose :label, using: Entities::LabelBasic
expose :action
end
+
+ class Suggestion < Grape::Entity
+ expose :id
+ expose :from_original_line
+ expose :to_original_line
+ expose :from_line
+ expose :to_line
+ expose :appliable?, as: :appliable
+ expose :applied
+ expose :from_content
+ expose :to_content
+ end
end
end
diff --git a/lib/api/events.rb b/lib/api/events.rb
index 44dae57770d..b98aa9f31e1 100644
--- a/lib/api/events.rb
+++ b/lib/api/events.rb
@@ -18,29 +18,15 @@ module API
desc: 'Return events sorted in ascending and descending order'
end
- RedactedEvent = OpenStruct.new(target_title: 'Confidential event').freeze
-
- def redact_events(events)
- events.map do |event|
- if event.visible_to_user?(current_user)
- event
- else
- RedactedEvent
- end
- end
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def present_events(events, redact: true)
- events = events.reorder(created_at: params[:sort])
- .with_associations
-
+ def present_events(events)
events = paginate(events)
- events = redact_events(events) if redact
present events, with: Entities::Event
end
- # rubocop: enable CodeReuse/ActiveRecord
+
+ def find_events(source)
+ EventsFinder.new(params.merge(source: source, current_user: current_user, with_associations: true)).execute
+ end
end
resource :events do
@@ -55,16 +41,14 @@ module API
use :event_filter_params
use :sort_params
end
- # rubocop: disable CodeReuse/ActiveRecord
+
get do
authenticate!
- events = EventsFinder.new(params.merge(source: current_user, current_user: current_user)).execute.preload(:author, :target)
+ events = find_events(current_user)
- # Since we're viewing our own events, redaction is unnecessary
- present_events(events, redact: false)
+ present_events(events)
end
- # rubocop: enable CodeReuse/ActiveRecord
end
params do
@@ -82,16 +66,15 @@ module API
use :event_filter_params
use :sort_params
end
- # rubocop: disable CodeReuse/ActiveRecord
+
get ':id/events' do
user = find_user(params[:id])
not_found!('User') unless user
- events = EventsFinder.new(params.merge(source: user, current_user: current_user)).execute.preload(:author, :target)
+ events = find_events(user)
present_events(events)
end
- # rubocop: enable CodeReuse/ActiveRecord
end
params do
@@ -106,13 +89,12 @@ module API
use :event_filter_params
use :sort_params
end
- # rubocop: disable CodeReuse/ActiveRecord
+
get ":id/events" do
- events = EventsFinder.new(params.merge(source: user_project, current_user: current_user)).execute.preload(:author, :target)
+ events = find_events(user_project)
present_events(events)
end
- # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 626a2008dee..64958ff982a 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -113,7 +113,7 @@ module API
requires :name, type: String, desc: 'The name of the group'
requires :path, type: String, desc: 'The path of the group'
- if ::Group.supports_nested_groups?
+ if ::Group.supports_nested_objects?
optional :parent_id, type: Integer, desc: 'The parent group id for creating nested group'
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 9fda73d5b92..c3eca713712 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -163,9 +163,11 @@ module API
end
def find_branch!(branch_name)
- user_project.repository.find_branch(branch_name) || not_found!('Branch')
- rescue Gitlab::Git::CommandError
- render_api_error!('The branch refname is invalid', 400)
+ if Gitlab::GitRefValidator.validate(branch_name)
+ user_project.repository.find_branch(branch_name) || not_found!('Branch')
+ else
+ render_api_error!('The branch refname is invalid', 400)
+ end
end
def find_project_label(id)
@@ -291,7 +293,7 @@ module API
end
end
permitted_attrs = ActionController::Parameters.new(attrs).permit!
- Gitlab.rails5? ? permitted_attrs.to_h : permitted_attrs
+ permitted_attrs.to_h
end
# rubocop: disable CodeReuse/ActiveRecord
@@ -368,10 +370,10 @@ module API
end
def handle_api_exception(exception)
- if sentry_enabled? && report_exception?(exception)
+ if report_exception?(exception)
define_params_for_grape_middleware
- sentry_context
- Raven.capture_exception(exception, extra: params)
+ Gitlab::Sentry.context(current_user)
+ Gitlab::Sentry.track_acceptable_exception(exception, extra: params)
end
# lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb
index 7c2d8ff11bf..a4068a200b3 100644
--- a/lib/api/job_artifacts.rb
+++ b/lib/api/job_artifacts.rb
@@ -35,6 +35,29 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
+ desc 'Download a specific file from artifacts archive from a ref' do
+ detail 'This feature was introduced in GitLab 11.5'
+ end
+ params do
+ requires :ref_name, type: String, desc: 'The ref from repository'
+ requires :job, type: String, desc: 'The name for the job'
+ requires :artifact_path, type: String, desc: 'Artifact path'
+ end
+ get ':id/jobs/artifacts/:ref_name/raw/*artifact_path',
+ format: false,
+ requirements: { ref_name: /.+/ } do
+ authorize_download_artifacts!
+
+ build = user_project.latest_successful_build_for(params[:job], params[:ref_name])
+
+ path = Gitlab::Ci::Build::Artifacts::Path
+ .new(params[:artifact_path])
+
+ bad_request! unless path.valid?
+
+ send_artifacts_entry(build, path)
+ end
+
desc 'Download the artifacts archive from a job' do
detail 'This feature was introduced in GitLab 8.5'
end
@@ -65,6 +88,7 @@ module API
path = Gitlab::Ci::Build::Artifacts::Path
.new(params[:artifact_path])
+
bad_request! unless path.valid?
send_artifacts_entry(build, path)
diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb
index 06a57e3cd6f..3cc09f6ac3f 100644
--- a/lib/api/namespaces.rb
+++ b/lib/api/namespaces.rb
@@ -6,20 +6,35 @@ module API
before { authenticate! }
+ helpers do
+ params :optional_list_params_ee do
+ # EE::API::Namespaces would override this helper
+ end
+
+ # EE::API::Namespaces would override this method
+ def custom_namespace_present_options
+ {}
+ end
+ end
+
resource :namespaces do
desc 'Get a namespaces list' do
success Entities::Namespace
end
params do
optional :search, type: String, desc: "Search query for namespaces"
+
use :pagination
+ use :optional_list_params_ee
end
get do
namespaces = current_user.admin ? Namespace.all : current_user.namespaces
namespaces = namespaces.search(params[:search]) if params[:search].present?
- present paginate(namespaces), with: Entities::Namespace, current_user: current_user
+ options = { with: Entities::Namespace, current_user: current_user }
+
+ present paginate(namespaces), options.reverse_merge(custom_namespace_present_options)
end
desc 'Get a namespace by ID' do
diff --git a/lib/api/search.rb b/lib/api/search.rb
index 5900e1cccc2..f5db692afe5 100644
--- a/lib/api/search.rb
+++ b/lib/api/search.rb
@@ -35,12 +35,7 @@ module API
end
def process_results(results)
- case params[:scope]
- when 'blobs', 'wiki_blobs'
- paginate(results).map { |blob| blob[1] }
- else
- paginate(results)
- end
+ paginate(results)
end
def snippets?
diff --git a/lib/api/suggestions.rb b/lib/api/suggestions.rb
new file mode 100644
index 00000000000..d008d1b9e97
--- /dev/null
+++ b/lib/api/suggestions.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module API
+ class Suggestions < Grape::API
+ before { authenticate! }
+
+ resource :suggestions do
+ desc 'Apply suggestion patch in the Merge Request it was created' do
+ success Entities::Suggestion
+ end
+ params do
+ requires :id, type: String, desc: 'The suggestion ID'
+ end
+ put ':id/apply' do
+ suggestion = Suggestion.find_by_id(params[:id])
+
+ not_found! unless suggestion
+ authorize! :apply_suggestion, suggestion
+
+ result = ::Suggestions::ApplyService.new(current_user).execute(suggestion)
+
+ if result[:status] == :success
+ present suggestion, with: Entities::Suggestion, current_user: current_user
+ else
+ http_status = result[:http_status] || 400
+ render_api_error!(result[:message], http_status)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/templates.rb b/lib/api/templates.rb
index 8dab19d50c2..51f357d9477 100644
--- a/lib/api/templates.rb
+++ b/lib/api/templates.rb
@@ -82,7 +82,7 @@ module API
params do
requires :name, type: String, desc: 'The name of the template'
end
- get "templates/#{template_type}/:name" do
+ get "templates/#{template_type}/:name", requirements: { name: /[\w\.-]+/ } do
finder = TemplateFinder.build(template_type, nil, name: declared(params)[:name])
new_template = finder.execute
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index c8a5377bfa0..184c7418e75 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -4,6 +4,7 @@ require 'yaml'
module Backup
class Repository
+ include Gitlab::ShellAdapter
attr_reader :progress
def initialize(progress)
@@ -75,7 +76,6 @@ module Backup
def restore
prepare_directories
- gitlab_shell = Gitlab::Shell.new
Project.find_each(batch_size: 1000) do |project|
progress.print " * #{project.full_path} ... "
@@ -118,6 +118,8 @@ module Backup
end
end
end
+
+ restore_object_pools
end
protected
@@ -159,5 +161,17 @@ module Backup
def display_repo_path(project)
project.hashed_storage?(:repository) ? "#{project.full_path} (#{project.disk_path})" : project.full_path
end
+
+ def restore_object_pools
+ PoolRepository.includes(:source_project).find_each do |pool|
+ progress.puts " - Object pool #{pool.disk_path}..."
+
+ pool.source_project ||= pool.member_projects.first.root_of_fork_network
+ pool.state = 'none'
+ pool.save
+
+ pool.schedule
+ end
+ end
end
end
diff --git a/lib/banzai/filter/front_matter_filter.rb b/lib/banzai/filter/front_matter_filter.rb
new file mode 100644
index 00000000000..a27d18facd1
--- /dev/null
+++ b/lib/banzai/filter/front_matter_filter.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ class FrontMatterFilter < HTML::Pipeline::Filter
+ DELIM_LANG = {
+ '---' => 'yaml',
+ '+++' => 'toml',
+ ';;;' => 'json'
+ }.freeze
+
+ DELIM = Regexp.union(DELIM_LANG.keys)
+
+ PATTERN = %r{
+ \A(?:[^\r\n]*coding:[^\r\n]*)? # optional encoding line
+ \s*
+ ^(?<delim>#{DELIM})[ \t]*(?<lang>\S*) # opening front matter marker (optional language specifier)
+ \s*
+ ^(?<front_matter>.*?) # front matter (not greedy)
+ \s*
+ ^\k<delim> # closing front matter marker
+ \s*
+ }mx
+
+ def call
+ html.sub(PATTERN) do |_match|
+ lang = $~[:lang].presence || DELIM_LANG[$~[:delim]]
+
+ ["```#{lang}", $~[:front_matter], "```", "\n"].join("\n")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index 328c8c1803b..c70c3f0c04e 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -4,6 +4,8 @@ module Banzai
module Filter
# HTML filter that replaces milestone references with links.
class MilestoneReferenceFilter < AbstractReferenceFilter
+ include Gitlab::Utils::StrongMemoize
+
self.reference_type = :milestone
def self.object_class
@@ -13,16 +15,34 @@ module Banzai
# Links to project milestones contain the IID, but when we're handling
# 'regular' references, we need to use the global ID to disambiguate
# between group and project milestones.
- def find_object(project, id)
- return unless project.is_a?(Project)
+ def find_object(parent, id)
+ return unless valid_context?(parent)
- find_milestone_with_finder(project, id: id)
+ find_milestone_with_finder(parent, id: id)
end
- def find_object_from_link(project, iid)
- return unless project.is_a?(Project)
+ def find_object_from_link(parent, iid)
+ return unless valid_context?(parent)
+
+ find_milestone_with_finder(parent, iid: iid)
+ end
+
+ def valid_context?(parent)
+ strong_memoize(:valid_context) do
+ group_context?(parent) || project_context?(parent)
+ end
+ end
+
+ def group_context?(parent)
+ strong_memoize(:group_context) do
+ parent.is_a?(Group)
+ end
+ end
- find_milestone_with_finder(project, iid: iid)
+ def project_context?(parent)
+ strong_memoize(:project_context) do
+ parent.is_a?(Project)
+ end
end
def references_in(text, pattern = Milestone.reference_pattern)
@@ -44,13 +64,15 @@ module Banzai
def find_milestone(project_ref, namespace_ref, milestone_id, milestone_name)
project_path = full_project_path(namespace_ref, project_ref)
- project = parent_from_ref(project_path)
- return unless project && project.is_a?(Project)
+ # Returns group if project is not found by path
+ parent = parent_from_ref(project_path)
+
+ return unless parent
milestone_params = milestone_params(milestone_id, milestone_name)
- find_milestone_with_finder(project, milestone_params)
+ find_milestone_with_finder(parent, milestone_params)
end
def milestone_params(iid, name)
@@ -61,16 +83,28 @@ module Banzai
end
end
- def find_milestone_with_finder(project, params)
- finder_params = { project_ids: [project.id], order: nil, state: 'all' }
+ def find_milestone_with_finder(parent, params)
+ finder_params = milestone_finder_params(parent, params[:iid].present?)
+
+ MilestonesFinder.new(finder_params).find_by(params)
+ end
- # We don't support IID lookups for group milestones, because IIDs can
- # clash between group and project milestones.
- if project.group && !params[:iid]
- finder_params[:group_ids] = project.group.self_and_ancestors_ids
+ def milestone_finder_params(parent, find_by_iid)
+ { order: nil, state: 'all' }.tap do |params|
+ params[:project_ids] = parent.id if project_context?(parent)
+
+ # We don't support IID lookups because IIDs can clash between
+ # group/project milestones and group/subgroup milestones.
+ params[:group_ids] = self_and_ancestors_ids(parent) unless find_by_iid
end
+ end
- MilestonesFinder.new(finder_params).find_by(params)
+ def self_and_ancestors_ids(parent)
+ if group_context?(parent)
+ parent.self_and_ancestors_ids
+ elsif project_context?(parent)
+ parent.group&.self_and_ancestors_ids
+ end
end
def url_for_object(milestone, project)
diff --git a/lib/banzai/filter/suggestion_filter.rb b/lib/banzai/filter/suggestion_filter.rb
new file mode 100644
index 00000000000..307ea449140
--- /dev/null
+++ b/lib/banzai/filter/suggestion_filter.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ class SuggestionFilter < HTML::Pipeline::Filter
+ # Class used for tagging elements that should be rendered
+ TAG_CLASS = 'js-render-suggestion'.freeze
+
+ def call
+ return doc unless suggestions_filter_enabled?
+
+ doc.search('pre.suggestion > code').each do |node|
+ node.add_class(TAG_CLASS)
+ end
+
+ doc
+ end
+
+ def suggestions_filter_enabled?
+ context[:suggestions_filter_enabled]
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb
index 8a7f9045c24..18e5e9185de 100644
--- a/lib/banzai/filter/syntax_highlight_filter.rb
+++ b/lib/banzai/filter/syntax_highlight_filter.rb
@@ -69,7 +69,7 @@ module Banzai
end
def use_rouge?(language)
- %w(math mermaid plantuml).exclude?(language)
+ %w(math mermaid plantuml suggestion).exclude?(language)
end
end
end
diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb
index 11960047e5b..8cda67867a8 100644
--- a/lib/banzai/filter/user_reference_filter.rb
+++ b/lib/banzai/filter/user_reference_filter.rb
@@ -106,7 +106,7 @@ module Banzai
end
def link_class
- reference_class(:project_member)
+ reference_class(:project_member, tooltip: false)
end
def link_to_all(link_content: nil)
diff --git a/lib/banzai/filter/yaml_front_matter_filter.rb b/lib/banzai/filter/yaml_front_matter_filter.rb
deleted file mode 100644
index 295964dd75d..00000000000
--- a/lib/banzai/filter/yaml_front_matter_filter.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-module Banzai
- module Filter
- class YamlFrontMatterFilter < HTML::Pipeline::Filter
- DELIM = '---'.freeze
-
- # Hat-tip to Middleman: https://git.io/v2e0z
- PATTERN = %r{
- \A(?:[^\r\n]*coding:[^\r\n]*\r?\n)?
- (?<start>#{DELIM})[ ]*\r?\n
- (?<frontmatter>.*?)[ ]*\r?\n?
- ^(?<stop>#{DELIM})[ ]*\r?\n?
- \r?\n?
- (?<content>.*)
- }mx.freeze
-
- def call
- match = PATTERN.match(html)
-
- return html unless match
-
- "```yaml\n#{match['frontmatter']}\n```\n\n#{match['content']}"
- end
- end
- end
-end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index 96bea7ca935..5f13a6d6cde 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -29,6 +29,7 @@ module Banzai
Filter::TableOfContentsFilter,
Filter::AutolinkFilter,
Filter::ExternalLinkFilter,
+ Filter::SuggestionFilter,
*reference_filters,
diff --git a/lib/banzai/pipeline/post_process_pipeline.rb b/lib/banzai/pipeline/post_process_pipeline.rb
index 63a998a2c1f..7eaad6d7560 100644
--- a/lib/banzai/pipeline/post_process_pipeline.rb
+++ b/lib/banzai/pipeline/post_process_pipeline.rb
@@ -14,7 +14,8 @@ module Banzai
[
Filter::RedactorFilter,
Filter::RelativeLinkFilter,
- Filter::IssuableStateFilter
+ Filter::IssuableStateFilter,
+ Filter::SuggestionFilter
]
end
diff --git a/lib/banzai/pipeline/pre_process_pipeline.rb b/lib/banzai/pipeline/pre_process_pipeline.rb
index c937f783180..4c2b4ca1665 100644
--- a/lib/banzai/pipeline/pre_process_pipeline.rb
+++ b/lib/banzai/pipeline/pre_process_pipeline.rb
@@ -5,7 +5,7 @@ module Banzai
class PreProcessPipeline < BasePipeline
def self.filters
FilterArray[
- Filter::YamlFrontMatterFilter,
+ Filter::FrontMatterFilter,
Filter::BlockquoteFenceFilter,
]
end
diff --git a/lib/banzai/suggestions_parser.rb b/lib/banzai/suggestions_parser.rb
new file mode 100644
index 00000000000..09f36635020
--- /dev/null
+++ b/lib/banzai/suggestions_parser.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Banzai
+ module SuggestionsParser
+ # Returns the content of each suggestion code block.
+ #
+ def self.parse(text)
+ html = Banzai.render(text, project: nil, no_original_data: true)
+ doc = Nokogiri::HTML(html)
+
+ doc.search('pre.suggestion').map { |node| node.text }
+ end
+ end
+end
diff --git a/lib/constraints/feature_constrainer.rb b/lib/constraints/feature_constrainer.rb
index ca4376a9d38..cd246cf37a4 100644
--- a/lib/constraints/feature_constrainer.rb
+++ b/lib/constraints/feature_constrainer.rb
@@ -2,14 +2,14 @@
module Constraints
class FeatureConstrainer
- attr_reader :feature
+ attr_reader :args
- def initialize(feature)
- @feature = feature
+ def initialize(*args)
+ @args = args
end
def matches?(_request)
- Feature.enabled?(feature)
+ Feature.enabled?(*args)
end
end
end
diff --git a/lib/declarative_policy.rb b/lib/declarative_policy.rb
index 5e22523e45a..7ba48ae9c79 100644
--- a/lib/declarative_policy.rb
+++ b/lib/declarative_policy.rb
@@ -22,14 +22,10 @@ module DeclarativePolicy
key = Cache.policy_key(user, subject)
cache[key] ||=
- if Gitlab.rails5?
- # to avoid deadlocks in multi-threaded environment when
- # autoloading is enabled, we allow concurrent loads,
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/48263
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
- class_for(subject).new(user, subject, opts)
- end
- else
+ # to avoid deadlocks in multi-threaded environment when
+ # autoloading is enabled, we allow concurrent loads,
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/48263
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
class_for(subject).new(user, subject, opts)
end
end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 6eb5f9e2300..7aa02009aa0 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -199,7 +199,7 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
- def lfs_token_check(login, password, project)
+ def lfs_token_check(login, encoded_token, project)
deploy_key_matches = login.match(/\Alfs\+deploy-key-(\d+)\z/)
actor =
@@ -222,7 +222,7 @@ module Gitlab
read_authentication_abilities
end
- if Devise.secure_compare(token_handler.token, password)
+ if token_handler.token_valid?(encoded_token)
Gitlab::Auth::Result.new(actor, nil, token_handler.type, authentication_abilities)
end
end
diff --git a/lib/gitlab/background_migration/backfill_hashed_project_repositories.rb b/lib/gitlab/background_migration/backfill_hashed_project_repositories.rb
new file mode 100644
index 00000000000..a6194616663
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_hashed_project_repositories.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Class that will fill the project_repositories table for projects that
+ # are on hashed storage and an entry is is missing in this table.
+ class BackfillHashedProjectRepositories < BackfillProjectRepositories
+ private
+
+ def projects
+ Project.on_hashed_storage
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/backfill_legacy_project_repositories.rb b/lib/gitlab/background_migration/backfill_legacy_project_repositories.rb
new file mode 100644
index 00000000000..6dc92672929
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_legacy_project_repositories.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Class that will fill the project_repositories table for projects that
+ # are on legacy storage and an entry is is missing in this table.
+ class BackfillLegacyProjectRepositories < BackfillProjectRepositories
+ private
+
+ def projects
+ Project.with_parent.on_legacy_storage
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/backfill_project_repositories.rb b/lib/gitlab/background_migration/backfill_project_repositories.rb
new file mode 100644
index 00000000000..aaf520d70f6
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_project_repositories.rb
@@ -0,0 +1,219 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Class that will create fill the project_repositories table
+ # for projects an entry is is missing in this table.
+ class BackfillProjectRepositories
+ OrphanedNamespaceError = Class.new(StandardError)
+
+ # Shard model
+ class Shard < ActiveRecord::Base
+ self.table_name = 'shards'
+ end
+
+ # Class that will find or create the shard by name.
+ # There is only a small set of shards, which would
+ # not change quickly, so look them up from memory
+ # instead of hitting the DB each time.
+ class ShardFinder
+ def find_shard_id(name)
+ shard_id = shards.fetch(name, nil)
+ return shard_id if shard_id.present?
+
+ Shard.transaction(requires_new: true) do
+ create!(name)
+ end
+ rescue ActiveRecord::RecordNotUnique
+ reload!
+ retry
+ end
+
+ private
+
+ def create!(name)
+ Shard.create!(name: name).tap { |shard| @shards[name] = shard.id }
+ end
+
+ def shards
+ @shards ||= reload!
+ end
+
+ def reload!
+ @shards = Hash[*Shard.all.map { |shard| [shard.name, shard.id] }.flatten]
+ end
+ end
+
+ module Storage
+ # Class that returns the disk path for a project using hashed storage
+ class HashedProject
+ attr_accessor :project
+
+ ROOT_PATH_PREFIX = '@hashed'
+
+ def initialize(project)
+ @project = project
+ end
+
+ def disk_path
+ "#{ROOT_PATH_PREFIX}/#{disk_hash[0..1]}/#{disk_hash[2..3]}/#{disk_hash}"
+ end
+
+ def disk_hash
+ @disk_hash ||= Digest::SHA2.hexdigest(project.id.to_s)
+ end
+ end
+
+ # Class that returns the disk path for a project using legacy storage
+ class LegacyProject
+ attr_accessor :project
+
+ def initialize(project)
+ @project = project
+ end
+
+ def disk_path
+ project.full_path
+ end
+ end
+ end
+
+ # Concern used by Project and Namespace to determine the full route to the project
+ module Routable
+ extend ActiveSupport::Concern
+
+ def full_path
+ @full_path ||= build_full_path
+ end
+
+ def build_full_path
+ return path unless has_parent?
+
+ raise OrphanedNamespaceError if parent.nil?
+
+ parent.full_path + '/' + path
+ end
+
+ def has_parent?
+ read_attribute(association(:parent).reflection.foreign_key)
+ end
+ end
+
+ # Namespace model.
+ class Namespace < ActiveRecord::Base
+ self.table_name = 'namespaces'
+ self.inheritance_column = nil
+
+ include Routable
+
+ belongs_to :parent, class_name: 'Namespace', inverse_of: 'namespaces'
+
+ has_many :projects, inverse_of: :parent
+ has_many :namespaces, inverse_of: :parent
+ end
+
+ # ProjectRegistry model
+ class ProjectRepository < ActiveRecord::Base
+ self.table_name = 'project_repositories'
+
+ belongs_to :project, inverse_of: :project_repository
+ end
+
+ # Project model
+ class Project < ActiveRecord::Base
+ self.table_name = 'projects'
+
+ include Routable
+
+ HASHED_STORAGE_FEATURES = {
+ repository: 1,
+ attachments: 2
+ }.freeze
+
+ scope :with_parent, -> { includes(:parent) }
+
+ belongs_to :parent, class_name: 'Namespace', foreign_key: :namespace_id, inverse_of: 'projects'
+
+ has_one :project_repository, inverse_of: :project
+
+ delegate :disk_path, to: :storage
+
+ class << self
+ def on_hashed_storage
+ where(Project.arel_table[:storage_version]
+ .gteq(HASHED_STORAGE_FEATURES[:repository]))
+ end
+
+ def on_legacy_storage
+ where(Project.arel_table[:storage_version].eq(nil)
+ .or(Project.arel_table[:storage_version].eq(0)))
+ end
+
+ def without_project_repository
+ joins(left_outer_join_project_repository)
+ .where(ProjectRepository.arel_table[:project_id].eq(nil))
+ end
+
+ def left_outer_join_project_repository
+ projects_table = Project.arel_table
+ repository_table = ProjectRepository.arel_table
+
+ projects_table
+ .join(repository_table, Arel::Nodes::OuterJoin)
+ .on(projects_table[:id].eq(repository_table[:project_id]))
+ .join_sources
+ end
+ end
+
+ def storage
+ @storage ||=
+ if hashed_storage?
+ Storage::HashedProject.new(self)
+ else
+ Storage::LegacyProject.new(self)
+ end
+ end
+
+ def hashed_storage?
+ self.storage_version &&
+ self.storage_version >= HASHED_STORAGE_FEATURES[:repository]
+ end
+ end
+
+ def perform(start_id, stop_id)
+ Gitlab::Database.bulk_insert(:project_repositories, project_repositories(start_id, stop_id))
+ end
+
+ private
+
+ def projects
+ raise NotImplementedError,
+ "#{self.class} does not implement #{__method__}"
+ end
+
+ def project_repositories(start_id, stop_id)
+ projects
+ .without_project_repository
+ .where(id: start_id..stop_id)
+ .map { |project| build_attributes_for_project(project) }
+ .compact
+ end
+
+ def build_attributes_for_project(project)
+ {
+ project_id: project.id,
+ shard_id: find_shard_id(project.repository_storage),
+ disk_path: project.disk_path
+ }
+ end
+
+ def find_shard_id(repository_storage)
+ shard_finder.find_shard_id(repository_storage)
+ end
+
+ def shard_finder
+ @shard_finder ||= ShardFinder.new
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_improved.rb b/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_improved.rb
new file mode 100644
index 00000000000..37592d67dd9
--- /dev/null
+++ b/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_improved.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+# rubocop:disable Style/Documentation
+
+module Gitlab
+ module BackgroundMigration
+ class PopulateMergeRequestMetricsWithEventsDataImproved
+ CLOSED_EVENT_ACTION = 3
+ MERGED_EVENT_ACTION = 7
+
+ def perform(min_merge_request_id, max_merge_request_id)
+ insert_metrics_for_range(min_merge_request_id, max_merge_request_id)
+ update_metrics_with_events_data(min_merge_request_id, max_merge_request_id)
+ end
+
+ # Inserts merge_request_metrics records for merge_requests without it for
+ # a given merge request batch.
+ def insert_metrics_for_range(min, max)
+ metrics_not_exists_clause =
+ <<-SQL.strip_heredoc
+ NOT EXISTS (SELECT 1 FROM merge_request_metrics
+ WHERE merge_request_metrics.merge_request_id = merge_requests.id)
+ SQL
+
+ MergeRequest.where(metrics_not_exists_clause).where(id: min..max).each_batch do |batch|
+ select_sql = batch.select(:id, :created_at, :updated_at).to_sql
+
+ execute("INSERT INTO merge_request_metrics (merge_request_id, created_at, updated_at) #{select_sql}")
+ end
+ end
+
+ def update_metrics_with_events_data(min, max)
+ if Gitlab::Database.postgresql?
+ psql_update_metrics_with_events_data(min, max)
+ else
+ mysql_update_metrics_with_events_data(min, max)
+ end
+ end
+
+ def psql_update_metrics_with_events_data(min, max)
+ update_sql = <<-SQL.strip_heredoc
+ UPDATE merge_request_metrics
+ SET (latest_closed_at,
+ latest_closed_by_id) =
+ ( SELECT updated_at,
+ author_id
+ FROM events
+ WHERE target_id = merge_request_id
+ AND target_type = 'MergeRequest'
+ AND action = #{CLOSED_EVENT_ACTION}
+ ORDER BY id DESC
+ LIMIT 1 ),
+ merged_by_id =
+ ( SELECT author_id
+ FROM events
+ WHERE target_id = merge_request_id
+ AND target_type = 'MergeRequest'
+ AND action = #{MERGED_EVENT_ACTION}
+ ORDER BY id DESC
+ LIMIT 1 )
+ WHERE merge_request_id BETWEEN #{min} AND #{max}
+ SQL
+
+ execute(update_sql)
+ end
+
+ def mysql_update_metrics_with_events_data(min, max)
+ closed_updated_at_subquery = mysql_events_select(:updated_at, CLOSED_EVENT_ACTION)
+ closed_author_id_subquery = mysql_events_select(:author_id, CLOSED_EVENT_ACTION)
+ merged_author_id_subquery = mysql_events_select(:author_id, MERGED_EVENT_ACTION)
+
+ update_sql = <<-SQL.strip_heredoc
+ UPDATE merge_request_metrics
+ SET latest_closed_at = (#{closed_updated_at_subquery}),
+ latest_closed_by_id = (#{closed_author_id_subquery}),
+ merged_by_id = (#{merged_author_id_subquery})
+ WHERE merge_request_id BETWEEN #{min} AND #{max}
+ SQL
+
+ execute(update_sql)
+ end
+
+ def mysql_events_select(column, action)
+ <<-SQL.strip_heredoc
+ SELECT #{column} FROM events
+ WHERE target_id = merge_request_id
+ AND target_type = 'MergeRequest'
+ AND action = #{action}
+ ORDER BY id DESC
+ LIMIT 1
+ SQL
+ end
+
+ def execute(sql)
+ @connection ||= ActiveRecord::Base.connection
+ @connection.execute(sql)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/bitbucket_server_import/importer.rb b/lib/gitlab/bitbucket_server_import/importer.rb
index d4080536d81..28cfb46e2d4 100644
--- a/lib/gitlab/bitbucket_server_import/importer.rb
+++ b/lib/gitlab/bitbucket_server_import/importer.rb
@@ -3,8 +3,6 @@
module Gitlab
module BitbucketServerImport
class Importer
- include Gitlab::ShellAdapter
-
attr_reader :recover_missing_commits
attr_reader :project, :project_key, :repository_slug, :client, :errors, :users
attr_accessor :logger
diff --git a/lib/gitlab/branch_push_merge_commit_analyzer.rb b/lib/gitlab/branch_push_merge_commit_analyzer.rb
new file mode 100644
index 00000000000..a8f601f2451
--- /dev/null
+++ b/lib/gitlab/branch_push_merge_commit_analyzer.rb
@@ -0,0 +1,132 @@
+# frozen_string_literal: true
+
+module Gitlab
+ # Analyse a graph of commits from a push to a branch,
+ # for each commit, analyze that if it is the head of a merge request,
+ # then what should its merge_commit be, relative to the branch.
+ #
+ # A----->B----->C----->D target branch
+ # | ^
+ # | |
+ # +-->E----->F--+ merged branch
+ # | ^
+ # | |
+ # +->G--+
+ #
+ # (See merge-commit-analyze-after branch in gitlab-test)
+ #
+ # Assuming
+ # - A is already in remote
+ # - B~D are all in its own branch with its own merge request, targeting the target branch
+ #
+ # When D is finally pushed to the target branch,
+ # what are the merge commits for all the other merge requests?
+ #
+ # We can walk backwards from the HEAD commit D,
+ # and find status of its parents.
+ # First we determine if commit belongs to the target branch (i.e. A, B, C, D),
+ # and then determine its merge commit.
+ #
+ # +--------+-----------------+--------------+
+ # | Commit | Direct ancestor | Merge commit |
+ # +--------+-----------------+--------------+
+ # | D | Y | D |
+ # +--------+-----------------+--------------+
+ # | C | Y | C |
+ # +--------+-----------------+--------------+
+ # | F | | C |
+ # +--------+-----------------+--------------+
+ # | B | Y | B |
+ # +--------+-----------------+--------------+
+ # | E | | C |
+ # +--------+-----------------+--------------+
+ # | G | | C |
+ # +--------+-----------------+--------------+
+ #
+ # By examining the result, it can be said that
+ #
+ # - If commit is direct ancestor of HEAD, its merge commit is itself.
+ # - Otherwise, the merge commit is the same as its child's merge commit.
+ #
+ class BranchPushMergeCommitAnalyzer
+ class CommitDecorator < SimpleDelegator
+ attr_accessor :merge_commit
+ attr_writer :direct_ancestor # boolean
+
+ def direct_ancestor?
+ @direct_ancestor
+ end
+
+ # @param child_commit [CommitDecorator]
+ # @param first_parent [Boolean] whether `self` is the first parent of `child_commit`
+ def set_merge_commit(child_commit:)
+ @merge_commit ||= direct_ancestor? ? self : child_commit.merge_commit
+ end
+ end
+
+ # @param commits [Array] list of commits, must be ordered from the child (tip) of the graph back to the ancestors
+ def initialize(commits, relevant_commit_ids: nil)
+ @commits = commits
+ @id_to_commit = {}
+ @commits.each do |commit|
+ @id_to_commit[commit.id] = CommitDecorator.new(commit)
+
+ if relevant_commit_ids
+ relevant_commit_ids.delete(commit.id)
+ break if relevant_commit_ids.empty? # Only limit the analyze up to relevant_commit_ids
+ end
+ end
+
+ analyze
+ end
+
+ def get_merge_commit(id)
+ get_commit(id).merge_commit.id
+ end
+
+ private
+
+ def analyze
+ head_commit = get_commit(@commits.first.id)
+ head_commit.direct_ancestor = true
+ head_commit.merge_commit = head_commit
+
+ mark_all_direct_ancestors(head_commit)
+
+ # Analyzing a commit requires its child commit be analyzed first,
+ # which is the case here since commits are ordered from child to parent.
+ @id_to_commit.each_value do |commit|
+ analyze_parents(commit)
+ end
+ end
+
+ def analyze_parents(commit)
+ commit.parent_ids.each do |parent_commit_id|
+ parent_commit = get_commit(parent_commit_id)
+
+ next unless parent_commit # parent commit may not be part of new commits
+
+ parent_commit.set_merge_commit(child_commit: commit)
+ end
+ end
+
+ # Mark all direct ancestors.
+ # If child commit is a direct ancestor, its first parent is also a direct ancestor.
+ # We assume direct ancestors matches the trail of the target branch over time,
+ # This assumption is correct most of the time, especially for gitlab managed merges,
+ # but there are exception cases which can't be solved (https://stackoverflow.com/a/49754723/474597)
+ def mark_all_direct_ancestors(commit)
+ loop do
+ commit = get_commit(commit.parent_ids.first)
+
+ break unless commit
+
+ commit.direct_ancestor = true
+ end
+ end
+
+ def get_commit(id)
+ @id_to_commit[id]
+ end
+ end
+end
diff --git a/lib/gitlab/checks/diff_check.rb b/lib/gitlab/checks/diff_check.rb
index 49d361fcef7..8ee345ab45a 100644
--- a/lib/gitlab/checks/diff_check.rb
+++ b/lib/gitlab/checks/diff_check.rb
@@ -11,6 +11,7 @@ module Gitlab
}.freeze
def validate!
+ return if deletion? || newrev.nil?
return unless should_run_diff_validations?
return if commits.empty?
return unless uses_raw_delta_validations?
@@ -28,7 +29,7 @@ module Gitlab
private
def should_run_diff_validations?
- newrev && oldrev && !deletion? && validate_lfs_file_locks?
+ validate_lfs_file_locks?
end
def validate_lfs_file_locks?
diff --git a/lib/gitlab/ci/build/policy/refs.rb b/lib/gitlab/ci/build/policy/refs.rb
index 10934536536..0e9bb5c94bb 100644
--- a/lib/gitlab/ci/build/policy/refs.rb
+++ b/lib/gitlab/ci/build/policy/refs.rb
@@ -32,10 +32,14 @@ module Gitlab
return true if pipeline.source == pattern
return true if pipeline.source&.pluralize == pattern
- if pattern.first == "/" && pattern.last == "/"
- Regexp.new(pattern[1...-1]) =~ pipeline.ref
- else
- pattern == pipeline.ref
+ # patterns can be matched only when branch or tag is used
+ # the pattern matching does not work for merge requests pipelines
+ if pipeline.branch? || pipeline.tag?
+ if pattern.first == "/" && pattern.last == "/"
+ Regexp.new(pattern[1...-1]) =~ pipeline.ref
+ else
+ pattern == pipeline.ref
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/except_policy.rb b/lib/gitlab/ci/config/entry/except_policy.rb
deleted file mode 100644
index 46ded35325d..00000000000
--- a/lib/gitlab/ci/config/entry/except_policy.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- class Config
- module Entry
- ##
- # Entry that represents an only/except trigger policy for the job.
- #
- class ExceptPolicy < Policy
- def self.default
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index 085be5da08d..50942fbdb40 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -16,6 +16,13 @@ module Gitlab
dependencies before_script after_script variables
environment coverage retry parallel extends].freeze
+ DEFAULT_ONLY_POLICY = {
+ refs: %w(branches tags)
+ }.freeze
+
+ DEFAULT_EXCEPT_POLICY = {
+ }.freeze
+
validations do
validates :config, allowed_keys: ALLOWED_KEYS
validates :config, presence: true
@@ -65,10 +72,10 @@ module Gitlab
entry :services, Entry::Services,
description: 'Services that will be used to execute this job.'
- entry :only, Entry::OnlyPolicy,
+ entry :only, Entry::Policy,
description: 'Refs policy this job will be executed for.'
- entry :except, Entry::ExceptPolicy,
+ entry :except, Entry::Policy,
description: 'Refs policy this job will be executed for.'
entry :variables, Entry::Variables,
@@ -154,8 +161,8 @@ module Gitlab
services: services_value,
stage: stage_value,
cache: cache_value,
- only: only_value,
- except: except_value,
+ only: DEFAULT_ONLY_POLICY.deep_merge(only_value.to_h),
+ except: DEFAULT_EXCEPT_POLICY.deep_merge(except_value.to_h),
variables: variables_defined? ? variables_value : nil,
environment: environment_defined? ? environment_value : nil,
environment_name: environment_defined? ? environment_value[:name] : nil,
diff --git a/lib/gitlab/ci/config/entry/only_policy.rb b/lib/gitlab/ci/config/entry/only_policy.rb
deleted file mode 100644
index 9a581b8e97e..00000000000
--- a/lib/gitlab/ci/config/entry/only_policy.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- class Config
- module Entry
- ##
- # Entry that represents an only/except trigger policy for the job.
- #
- class OnlyPolicy < Policy
- def self.default
- { refs: %w[branches tags] }
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/config/entry/policy.rb b/lib/gitlab/ci/config/entry/policy.rb
index 81e74a639fc..998da1f6837 100644
--- a/lib/gitlab/ci/config/entry/policy.rb
+++ b/lib/gitlab/ci/config/entry/policy.rb
@@ -5,9 +5,12 @@ module Gitlab
class Config
module Entry
##
- # Base class for OnlyPolicy and ExceptPolicy
+ # Entry that represents an only/except trigger policy for the job.
#
class Policy < ::Gitlab::Config::Entry::Simplifiable
+ strategy :RefsPolicy, if: -> (config) { config.is_a?(Array) }
+ strategy :ComplexPolicy, if: -> (config) { config.is_a?(Hash) }
+
class RefsPolicy < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
@@ -63,16 +66,6 @@ module Gitlab
def self.default
end
-
- ##
- # Class-level execution won't be inherited by subclasses by default.
- # Therefore, we need to explicitly execute that for OnlyPolicy and ExceptPolicy
- def self.inherited(klass)
- super
-
- klass.strategy :RefsPolicy, if: -> (config) { config.is_a?(Array) }
- klass.strategy :ComplexPolicy, if: -> (config) { config.is_a?(Hash) }
- end
end
end
end
diff --git a/lib/gitlab/ci/parsers.rb b/lib/gitlab/ci/parsers.rb
new file mode 100644
index 00000000000..eb63e6c8363
--- /dev/null
+++ b/lib/gitlab/ci/parsers.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Parsers
+ ParserNotFoundError = Class.new(ParserError)
+
+ def self.parsers
+ {
+ junit: ::Gitlab::Ci::Parsers::Test::Junit
+ }
+ end
+
+ def self.fabricate!(file_type)
+ parsers.fetch(file_type.to_sym).new
+ rescue KeyError
+ raise ParserNotFoundError, "Cannot find any parser matching file type '#{file_type}'"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/parsers/parser_error.rb b/lib/gitlab/ci/parsers/parser_error.rb
new file mode 100644
index 00000000000..ef327737cdb
--- /dev/null
+++ b/lib/gitlab/ci/parsers/parser_error.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Parsers
+ ParserError = Class.new(StandardError)
+ end
+ end
+end
diff --git a/lib/gitlab/ci/parsers/test.rb b/lib/gitlab/ci/parsers/test.rb
deleted file mode 100644
index c6bc9662b07..00000000000
--- a/lib/gitlab/ci/parsers/test.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module Parsers
- module Test
- ParserNotFoundError = Class.new(StandardError)
-
- PARSERS = {
- junit: ::Gitlab::Ci::Parsers::Test::Junit
- }.freeze
-
- def self.fabricate!(file_type)
- PARSERS.fetch(file_type.to_sym).new
- rescue KeyError
- raise ParserNotFoundError, "Cannot find any parser matching file type '#{file_type}'"
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/parsers/test/junit.rb b/lib/gitlab/ci/parsers/test/junit.rb
index 2791730fd26..dca60eabc1c 100644
--- a/lib/gitlab/ci/parsers/test/junit.rb
+++ b/lib/gitlab/ci/parsers/test/junit.rb
@@ -5,7 +5,7 @@ module Gitlab
module Parsers
module Test
class Junit
- JunitParserError = Class.new(StandardError)
+ JunitParserError = Class.new(Gitlab::Ci::Parsers::ParserError)
def parse!(xml_data, test_suite)
root = Hash.from_xml(xml_data)
diff --git a/lib/gitlab/ci/status/bridge/common.rb b/lib/gitlab/ci/status/bridge/common.rb
new file mode 100644
index 00000000000..c6cb620f7a0
--- /dev/null
+++ b/lib/gitlab/ci/status/bridge/common.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Status
+ module Bridge
+ module Common
+ def label
+ subject.description
+ end
+
+ def has_details?
+ false
+ end
+
+ def has_action?
+ false
+ end
+
+ def details_path
+ raise NotImplementedError
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/bridge/factory.rb b/lib/gitlab/ci/status/bridge/factory.rb
new file mode 100644
index 00000000000..910de865483
--- /dev/null
+++ b/lib/gitlab/ci/status/bridge/factory.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Status
+ module Bridge
+ class Factory < Status::Factory
+ def self.common_helpers
+ Status::Bridge::Common
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index 3b2cae07c12..a9e361b0b32 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -149,10 +149,10 @@ performance:
only:
refs:
- branches
+ kubernetes: active
except:
variables:
- $PERFORMANCE_DISABLED
- - $KUBECONFIG == null
sast:
stage: test
@@ -164,7 +164,8 @@ sast:
- setup_docker
- sast
artifacts:
- paths: [gl-sast-report.json]
+ reports:
+ sast: gl-sast-report.json
only:
refs:
- branches
@@ -227,6 +228,7 @@ dast:
only:
refs:
- branches
+ kubernetes: active
variables:
- $GITLAB_FEATURES =~ /\bdast\b/
except:
@@ -234,7 +236,6 @@ dast:
- master
variables:
- $DAST_DISABLED
- - $KUBECONFIG == null
review:
stage: review
@@ -256,12 +257,12 @@ review:
only:
refs:
- branches
+ kubernetes: active
except:
refs:
- master
variables:
- $REVIEW_DISABLED
- - $KUBECONFIG == null
stop_review:
stage: cleanup
@@ -279,12 +280,12 @@ stop_review:
only:
refs:
- branches
+ kubernetes: active
except:
refs:
- master
variables:
- $REVIEW_DISABLED
- - $KUBECONFIG == null
# Staging deploys are disabled by default since
# continuous deployment to production is enabled by default
@@ -308,11 +309,9 @@ staging:
only:
refs:
- master
+ kubernetes: active
variables:
- $STAGING_ENABLED
- except:
- variables:
- - $KUBECONFIG == null
# Canaries are also disabled by default, but if you want them,
# and know what the downsides are, you can enable this by setting
@@ -335,11 +334,9 @@ canary:
only:
refs:
- master
+ kubernetes: active
variables:
- $CANARY_ENABLED
- except:
- variables:
- - $KUBECONFIG == null
.production: &production_template
stage: production
@@ -365,13 +362,13 @@ production:
only:
refs:
- master
+ kubernetes: active
except:
variables:
- $STAGING_ENABLED
- $CANARY_ENABLED
- $INCREMENTAL_ROLLOUT_ENABLED
- $INCREMENTAL_ROLLOUT_MODE
- - $KUBECONFIG == null
production_manual:
<<: *production_template
@@ -380,6 +377,7 @@ production_manual:
only:
refs:
- master
+ kubernetes: active
variables:
- $STAGING_ENABLED
- $CANARY_ENABLED
@@ -387,7 +385,6 @@ production_manual:
variables:
- $INCREMENTAL_ROLLOUT_ENABLED
- $INCREMENTAL_ROLLOUT_MODE
- - $KUBECONFIG == null
# This job implements incremental rollout on for every push to `master`.
@@ -417,13 +414,13 @@ production_manual:
only:
refs:
- master
+ kubernetes: active
variables:
- $INCREMENTAL_ROLLOUT_MODE == "manual"
- $INCREMENTAL_ROLLOUT_ENABLED
except:
variables:
- $INCREMENTAL_ROLLOUT_MODE == "timed"
- - $KUBECONFIG == null
.timed_rollout_template: &timed_rollout_template
<<: *rollout_template
@@ -432,11 +429,9 @@ production_manual:
only:
refs:
- master
+ kubernetes: active
variables:
- $INCREMENTAL_ROLLOUT_MODE == "timed"
- except:
- variables:
- - $KUBECONFIG == null
timed rollout 10%:
<<: *timed_rollout_template
diff --git a/lib/gitlab/color_schemes.rb b/lib/gitlab/color_schemes.rb
index a5e4065cf09..881e5dbc923 100644
--- a/lib/gitlab/color_schemes.rb
+++ b/lib/gitlab/color_schemes.rb
@@ -12,7 +12,8 @@ module Gitlab
Scheme.new(2, 'Dark', 'dark'),
Scheme.new(3, 'Solarized Light', 'solarized-light'),
Scheme.new(4, 'Solarized Dark', 'solarized-dark'),
- Scheme.new(5, 'Monokai', 'monokai')
+ Scheme.new(5, 'Monokai', 'monokai'),
+ Scheme.new(6, 'None', 'none')
].freeze
# Convenience method to get a space-separated String of all the color scheme
diff --git a/lib/gitlab/correlation_id.rb b/lib/gitlab/correlation_id.rb
new file mode 100644
index 00000000000..0f9bde4390e
--- /dev/null
+++ b/lib/gitlab/correlation_id.rb
@@ -0,0 +1,40 @@
+# 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/current_settings.rb b/lib/gitlab/current_settings.rb
index 477f9101e98..552aad83dd4 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -7,10 +7,6 @@ module Gitlab
Gitlab::SafeRequestStore.fetch(:current_application_settings) { ensure_application_settings! }
end
- def fake_application_settings(attributes = {})
- Gitlab::FakeApplicationSettings.new(::ApplicationSetting.defaults.merge(attributes || {}))
- end
-
def clear_in_memory_application_settings!
@in_memory_application_settings = nil
end
@@ -50,28 +46,21 @@ module Gitlab
# and other callers from failing, use any loaded settings and return
# defaults for missing columns.
if ActiveRecord::Migrator.needs_migration?
- return fake_application_settings(current_settings&.attributes)
- end
-
- return current_settings if current_settings.present?
-
- with_fallback_to_fake_application_settings do
- ::ApplicationSetting.create_from_defaults || in_memory_application_settings
+ db_attributes = current_settings&.attributes || {}
+ ::ApplicationSetting.build_from_defaults(db_attributes)
+ elsif current_settings.present?
+ current_settings
+ else
+ ::ApplicationSetting.create_from_defaults
end
end
- def in_memory_application_settings
- with_fallback_to_fake_application_settings do
- @in_memory_application_settings ||= ::ApplicationSetting.build_from_defaults
- end
+ def fake_application_settings(attributes = {})
+ Gitlab::FakeApplicationSettings.new(::ApplicationSetting.defaults.merge(attributes || {}))
end
- def with_fallback_to_fake_application_settings(&block)
- yield
- rescue
- # In case the application_settings table is not created yet, or if a new
- # ApplicationSetting column is not yet migrated we fallback to a simple OpenStruct
- fake_application_settings
+ def in_memory_application_settings
+ @in_memory_application_settings ||= ::ApplicationSetting.build_from_defaults
end
def connect_to_db?
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index 6d40e00c035..b6ca777e029 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -232,11 +232,7 @@ module Gitlab
end
def self.cached_table_exists?(table_name)
- if Gitlab.rails5?
- connection.schema_cache.data_source_exists?(table_name)
- else
- connection.schema_cache.table_exists?(table_name)
- end
+ connection.schema_cache.data_source_exists?(table_name)
end
private_class_method :connection
diff --git a/lib/gitlab/database/arel_methods.rb b/lib/gitlab/database/arel_methods.rb
deleted file mode 100644
index 991e4152dcb..00000000000
--- a/lib/gitlab/database/arel_methods.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Database
- module ArelMethods
- private
-
- # In Arel 7.0.0 (Arel 7.1.4 is used in Rails 5.0) the `engine` parameter of `Arel::UpdateManager#initializer`
- # was removed.
- # Remove this file and inline this method when removing rails5? code.
- def arel_update_manager
- if Gitlab.rails5?
- Arel::UpdateManager.new
- else
- Arel::UpdateManager.new(ActiveRecord::Base)
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/database/count.rb b/lib/gitlab/database/count.rb
index c996d786909..f3d37ccd72a 100644
--- a/lib/gitlab/database/count.rb
+++ b/lib/gitlab/database/count.rb
@@ -40,7 +40,7 @@ module Gitlab
if strategy.enabled?
models_with_missing_counts = models - counts_by_model.keys
- break if models_with_missing_counts.empty?
+ break counts_by_model if models_with_missing_counts.empty?
counts = strategy.new(models_with_missing_counts).count
diff --git a/lib/gitlab/database/count/exact_count_strategy.rb b/lib/gitlab/database/count/exact_count_strategy.rb
index 0276fe2b54f..fa6951eda22 100644
--- a/lib/gitlab/database/count/exact_count_strategy.rb
+++ b/lib/gitlab/database/count/exact_count_strategy.rb
@@ -20,6 +20,8 @@ module Gitlab
models.each_with_object({}) do |model, data|
data[model] = model.count
end
+ rescue *CONNECTION_ERRORS
+ {}
end
def self.enabled?
diff --git a/lib/gitlab/database/median.rb b/lib/gitlab/database/median.rb
index 0da5119a3ed..1455e410d4b 100644
--- a/lib/gitlab/database/median.rb
+++ b/lib/gitlab/database/median.rb
@@ -35,13 +35,7 @@ module Gitlab
end
def mysql_median_datetime_sql(arel_table, query_so_far, column_sym)
- arel_from = if Gitlab.rails5?
- arel_table.from
- else
- arel_table
- end
-
- query = arel_from
+ query = arel_table.from
.from(arel_table.project(Arel.sql('*')).order(arel_table[column_sym]).as(arel_table.table_name))
.project(average([arel_table[column_sym]], 'median'))
.where(
@@ -151,13 +145,8 @@ module Gitlab
.order(arel_table[column_sym])
).as('row_id')
- arel_from = if Gitlab.rails5?
- arel_table.from.from(arel_table.alias)
- else
- arel_table.from(arel_table.alias)
- end
-
- count = arel_from.project('COUNT(*)')
+ count = arel_table.from.from(arel_table.alias)
+ .project('COUNT(*)')
.where(arel_table[partition_column].eq(arel_table.alias[partition_column]))
.as('ct')
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 134d1e7a724..3abd0600e9d 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -3,8 +3,6 @@
module Gitlab
module Database
module MigrationHelpers
- include Gitlab::Database::ArelMethods
-
BACKGROUND_MIGRATION_BATCH_SIZE = 1000 # Number of rows to process per job
BACKGROUND_MIGRATION_JOB_BUFFER_SIZE = 1000 # Number of jobs to bulk queue at a time
@@ -361,7 +359,7 @@ module Gitlab
stop_arel = yield table, stop_arel if block_given?
stop_row = exec_query(stop_arel.to_sql).to_hash.first
- update_arel = arel_update_manager
+ update_arel = Arel::UpdateManager.new
.table(table)
.set([[table[column], value]])
.where(table[:id].gteq(start_id))
@@ -975,9 +973,10 @@ into similar problems in the future (e.g. when new tables are created).
raise "#{model_class} does not have an ID to use for batch ranges" unless model_class.column_names.include?('id')
jobs = []
+ table_name = model_class.quoted_table_name
model_class.each_batch(of: batch_size) do |relation|
- start_id, end_id = relation.pluck('MIN(id), MAX(id)').first
+ start_id, end_id = relation.pluck("MIN(#{table_name}.id), MAX(#{table_name}.id)").first
if jobs.length >= BACKGROUND_MIGRATION_JOB_BUFFER_SIZE
# Note: This code path generally only helps with many millions of rows
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
index a5b42bbfdd9..60afa4bcd52 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
@@ -5,8 +5,6 @@ module Gitlab
module RenameReservedPathsMigration
module V1
class RenameBase
- include Gitlab::Database::ArelMethods
-
attr_reader :paths, :migration
delegate :update_column_in_batches,
@@ -66,7 +64,7 @@ module Gitlab
old_full_path,
new_full_path)
- update = arel_update_manager
+ update = Arel::UpdateManager.new
.table(routes)
.set([[routes[:path], replace_statement]])
.where(Arel::Nodes::SqlLiteral.new(filter))
diff --git a/lib/gitlab/database/sha_attribute.rb b/lib/gitlab/database/sha_attribute.rb
index 6516d6e648d..8d97adaff99 100644
--- a/lib/gitlab/database/sha_attribute.rb
+++ b/lib/gitlab/database/sha_attribute.rb
@@ -8,14 +8,7 @@ module Gitlab
# behaviour from the default Binary type.
ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Bytea
else
- # In Rails 5.0 `Type` has been moved from `ActiveRecord` to `ActiveModel`
- # https://github.com/rails/rails/commit/9cc8c6f3730df3d94c81a55be9ee1b7b4ffd29f6#diff-f8ba7983a51d687976e115adcd95822b
- # Remove this method and leave just `ActiveModel::Type::Binary` when removing Gitlab.rails5? code.
- if Gitlab.rails5?
- ActiveModel::Type::Binary
- else
- ActiveRecord::Type::Binary
- end
+ ActiveModel::Type::Binary
end
# Class for casting binary data to hexadecimal SHA1 hashes (and vice-versa).
@@ -26,31 +19,9 @@ module Gitlab
class ShaAttribute < BINARY_TYPE
PACK_FORMAT = 'H*'.freeze
- # It is called from activerecord-4.2.10/lib/active_record internal methods.
- # Remove this method when removing Gitlab.rails5? code.
- def type_cast_from_database(value)
- unpack_sha(super)
- end
-
- # It is called from activerecord-4.2.10/lib/active_record internal methods.
- # Remove this method when removing Gitlab.rails5? code.
- def type_cast_for_database(value)
- serialize(value)
- end
-
- # It is called from activerecord-5.0.6/lib/active_record/attribute.rb
- # Remove this method when removing Gitlab.rails5? code..
- def deserialize(value)
- value = Gitlab.rails5? ? super : method(:type_cast_from_database).super_method.call(value)
-
- unpack_sha(value)
- end
-
- # Rename this method to `deserialize(value)` removing Gitlab.rails5? code.
# Casts binary data to a SHA1 in hexadecimal.
- def unpack_sha(value)
- # Uncomment this line when removing Gitlab.rails5? code.
- # value = super
+ def deserialize(value)
+ value = super(value)
value ? value.unpack(PACK_FORMAT)[0] : nil
end
@@ -58,7 +29,7 @@ module Gitlab
def serialize(value)
arg = value ? [value].pack(PACK_FORMAT) : nil
- Gitlab.rails5? ? super(arg) : method(:type_cast_for_database).super_method.call(arg)
+ super(arg)
end
end
end
diff --git a/lib/gitlab/database/subquery.rb b/lib/gitlab/database/subquery.rb
index 36e4559b554..10971d2b274 100644
--- a/lib/gitlab/database/subquery.rb
+++ b/lib/gitlab/database/subquery.rb
@@ -6,15 +6,11 @@ module Gitlab
class << self
def self_join(relation)
t = relation.arel_table
- t2 = if !Gitlab.rails5?
- relation.arel.as('t2')
- else
- # Work around a bug in Rails 5, where LIMIT causes trouble
- # See https://gitlab.com/gitlab-org/gitlab-ce/issues/51729
- r = relation.limit(nil).arel
- r.take(relation.limit_value) if relation.limit_value
- r.as('t2')
- end
+ # Work around a bug in Rails 5, where LIMIT causes trouble
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/51729
+ r = relation.limit(nil).arel
+ r.take(relation.limit_value) if relation.limit_value
+ t2 = r.as('t2')
relation.unscoped.joins(t.join(t2).on(t[:id].eq(t2[:id])).join_sources.first)
end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index 8ba44dff06f..bcbded6be9a 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -3,7 +3,7 @@
module Gitlab
module Diff
class File
- attr_reader :diff, :repository, :diff_refs, :fallback_diff_refs
+ attr_reader :diff, :repository, :diff_refs, :fallback_diff_refs, :unique_identifier
delegate :new_file?, :deleted_file?, :renamed_file?,
:old_path, :new_path, :a_mode, :b_mode, :mode_changed?,
@@ -22,12 +22,20 @@ module Gitlab
DiffViewer::Image
].sort_by { |v| v.binary? ? 0 : 1 }.freeze
- def initialize(diff, repository:, diff_refs: nil, fallback_diff_refs: nil, stats: nil)
+ def initialize(
+ diff,
+ repository:,
+ diff_refs: nil,
+ fallback_diff_refs: nil,
+ stats: nil,
+ unique_identifier: nil)
+
@diff = diff
@stats = stats
@repository = repository
@diff_refs = diff_refs
@fallback_diff_refs = fallback_diff_refs
+ @unique_identifier = unique_identifier
@unfolded = false
# Ensure items are collected in the the batch
@@ -67,7 +75,15 @@ module Gitlab
def line_for_position(pos)
return nil unless pos.position_type == 'text'
- diff_lines.find { |line| line.old_line == pos.old_line && line.new_line == pos.new_line }
+ # This method is normally used to find which line the diff was
+ # commented on, and in this context, it's normally the raw diff persisted
+ # at `note_diff_files`, which is a fraction of the entire diff
+ # (it goes from the first line, to the commented line, or
+ # one line below). Therefore it's more performant to fetch
+ # from bottom to top instead of the other way around.
+ diff_lines
+ .reverse_each
+ .find { |line| line.old_line == pos.old_line && line.new_line == pos.new_line }
end
def position_for_line_code(code)
@@ -122,6 +138,16 @@ module Gitlab
old_blob_lazy&.itself
end
+ def new_blob_lines_between(from_line, to_line)
+ return [] unless new_blob
+
+ from_index = from_line - 1
+ to_index = to_line - 1
+
+ new_blob.load_all_data!
+ new_blob.data.lines[from_index..to_index]
+ end
+
def content_sha
new_content_sha || old_content_sha
end
@@ -156,6 +182,10 @@ module Gitlab
@unfolded
end
+ def highlight_loaded?
+ @highlighted_diff_lines.present?
+ end
+
def highlighted_diff_lines
@highlighted_diff_lines ||=
Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight
@@ -245,6 +275,10 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
+ def empty?
+ valid_blobs.map(&:empty?).all?
+ end
+
def raw_binary?
try_blobs(:raw_binary?)
end
diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb
index f0c4977fc50..001748afb41 100644
--- a/lib/gitlab/diff/line.rb
+++ b/lib/gitlab/diff/line.rb
@@ -73,6 +73,10 @@ module Gitlab
!meta?
end
+ def suggestible?
+ !removed?
+ end
+
def rich_text
@parent_file.try(:highlight_lines!) if @parent_file && !@rich_text
diff --git a/lib/gitlab/discussions_diff/file_collection.rb b/lib/gitlab/discussions_diff/file_collection.rb
new file mode 100644
index 00000000000..4ab7314f509
--- /dev/null
+++ b/lib/gitlab/discussions_diff/file_collection.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module DiscussionsDiff
+ class FileCollection
+ include Gitlab::Utils::StrongMemoize
+
+ def initialize(collection)
+ @collection = collection
+ end
+
+ # Returns a Gitlab::Diff::File with the given ID (`unique_identifier` in
+ # Gitlab::Diff::File).
+ def find_by_id(id)
+ diff_files_indexed_by_id[id]
+ end
+
+ # Writes cache and preloads highlighted diff lines for
+ # object IDs, in @collection.
+ #
+ # highlightable_ids - Diff file `Array` responding to ID. The ID will be used
+ # to generate the cache key.
+ #
+ # - Highlight cache is written just for uncached diff files
+ # - The cache content is not updated (there's no need to do so)
+ def load_highlight(highlightable_ids)
+ preload_highlighted_lines(highlightable_ids)
+ end
+
+ private
+
+ def preload_highlighted_lines(ids)
+ cached_content = read_cache(ids)
+
+ uncached_ids = ids.select.each_with_index { |_, i| cached_content[i].nil? }
+ mapping = highlighted_lines_by_ids(uncached_ids)
+
+ HighlightCache.write_multiple(mapping)
+
+ diffs = diff_files_indexed_by_id.values_at(*ids)
+
+ diffs.zip(cached_content).each do |diff, cached_lines|
+ next unless diff && cached_lines
+
+ diff.highlighted_diff_lines = cached_lines
+ end
+ end
+
+ def read_cache(ids)
+ HighlightCache.read_multiple(ids)
+ end
+
+ def diff_files_indexed_by_id
+ strong_memoize(:diff_files_indexed_by_id) do
+ diff_files.index_by(&:unique_identifier)
+ end
+ end
+
+ def diff_files
+ strong_memoize(:diff_files) do
+ @collection.map(&:raw_diff_file)
+ end
+ end
+
+ # Processes the diff lines highlighting for diff files matching the given
+ # IDs.
+ #
+ # Returns a Hash with { id => [Array of Gitlab::Diff::line], ...]
+ def highlighted_lines_by_ids(ids)
+ diff_files_indexed_by_id.slice(*ids).each_with_object({}) do |(id, file), hash|
+ hash[id] = file.highlighted_diff_lines.map(&:to_hash)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/discussions_diff/highlight_cache.rb b/lib/gitlab/discussions_diff/highlight_cache.rb
new file mode 100644
index 00000000000..270cfb89488
--- /dev/null
+++ b/lib/gitlab/discussions_diff/highlight_cache.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+#
+module Gitlab
+ module DiscussionsDiff
+ class HighlightCache
+ class << self
+ VERSION = 1
+ EXPIRATION = 1.week
+
+ # Sets multiple keys to a given value. The value
+ # is serialized as JSON.
+ #
+ # mapping - Write multiple cache values at once
+ def write_multiple(mapping)
+ Redis::Cache.with do |redis|
+ redis.multi do |multi|
+ mapping.each do |raw_key, value|
+ key = cache_key_for(raw_key)
+
+ multi.set(key, value.to_json, ex: EXPIRATION)
+ end
+ end
+ end
+ end
+
+ # Reads multiple cache keys at once.
+ #
+ # raw_keys - An Array of unique cache keys, without namespaces.
+ #
+ # It returns a list of deserialized diff lines. Ex.:
+ # [[Gitlab::Diff::Line, ...], [Gitlab::Diff::Line]]
+ def read_multiple(raw_keys)
+ return [] if raw_keys.empty?
+
+ keys = raw_keys.map { |id| cache_key_for(id) }
+
+ content =
+ Redis::Cache.with do |redis|
+ redis.mget(keys)
+ end
+
+ content.map! do |lines|
+ next unless lines
+
+ JSON.parse(lines).map! do |line|
+ line = line.with_indifferent_access
+ rich_text = line[:rich_text]
+ line[:rich_text] = rich_text&.html_safe
+
+ Gitlab::Diff::Line.init_from_hash(line)
+ end
+ end
+ end
+
+ def cache_key_for(raw_key)
+ "#{cache_key_prefix}:#{raw_key}"
+ end
+
+ private
+
+ def cache_key_prefix
+ "#{Redis::Cache::CACHE_NAMESPACE}:#{VERSION}:discussion-highlight"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/fake_application_settings.rb b/lib/gitlab/fake_application_settings.rb
index db1aeeea8d3..bd806269bf0 100644
--- a/lib/gitlab/fake_application_settings.rb
+++ b/lib/gitlab/fake_application_settings.rb
@@ -37,5 +37,9 @@ module Gitlab
def pick_repository_storage
repository_storages.sample
end
+
+ def commit_email_hostname
+ super.presence || ApplicationSetting.default_commit_email_hostname
+ end
end
end
diff --git a/lib/gitlab/file_finder.rb b/lib/gitlab/file_finder.rb
index b4db3f93c9c..3958814208c 100644
--- a/lib/gitlab/file_finder.rb
+++ b/lib/gitlab/file_finder.rb
@@ -4,8 +4,6 @@
# the result is joined and sorted by file name
module Gitlab
class FileFinder
- BATCH_SIZE = 100
-
attr_reader :project, :ref
delegate :repository, to: :project
@@ -16,60 +14,35 @@ module Gitlab
end
def find(query)
- query = Gitlab::Search::Query.new(query) do
- filter :filename, matcher: ->(filter, blob) { blob.filename =~ /#{filter[:regex_value]}$/i }
- filter :path, matcher: ->(filter, blob) { blob.filename =~ /#{filter[:regex_value]}/i }
- filter :extension, matcher: ->(filter, blob) { blob.filename =~ /\.#{filter[:regex_value]}$/i }
+ query = Gitlab::Search::Query.new(query, encode_binary: true) do
+ filter :filename, matcher: ->(filter, blob) { blob.binary_filename =~ /#{filter[:regex_value]}$/i }
+ filter :path, matcher: ->(filter, blob) { blob.binary_filename =~ /#{filter[:regex_value]}/i }
+ filter :extension, matcher: ->(filter, blob) { blob.binary_filename =~ /\.#{filter[:regex_value]}$/i }
end
- by_content = find_by_content(query.term)
-
- already_found = Set.new(by_content.map(&:filename))
- by_filename = find_by_filename(query.term, except: already_found)
+ files = find_by_filename(query.term) + find_by_content(query.term)
- files = (by_content + by_filename)
- .sort_by(&:filename)
+ files = query.filter_results(files) if query.filters.any?
- query.filter_results(files).map { |blob| [blob.filename, blob] }
+ files
end
private
def find_by_content(query)
- results = repository.search_files_by_content(query, ref).first(BATCH_SIZE)
- results.map { |result| Gitlab::ProjectSearchResults.parse_search_result(result, project) }
- end
-
- def find_by_filename(query, except: [])
- filenames = search_filenames(query, except)
-
- blobs(filenames).map do |blob|
- Gitlab::SearchResults::FoundBlob.new(
- id: blob.id,
- filename: blob.path,
- basename: File.basename(blob.path, File.extname(blob.path)),
- ref: ref,
- startline: 1,
- data: blob.data,
- project: project
- )
+ repository.search_files_by_content(query, ref).map do |result|
+ Gitlab::Search::FoundBlob.new(content_match: result, project: project, ref: ref, repository: repository)
end
end
- def search_filenames(query, except)
- filenames = repository.search_files_by_name(query, ref).first(BATCH_SIZE)
-
- filenames.delete_if { |filename| except.include?(filename) } unless except.empty?
-
- filenames
- end
-
- def blob_refs(filenames)
- filenames.map { |filename| [ref, filename] }
+ def find_by_filename(query)
+ search_filenames(query).map do |filename|
+ Gitlab::Search::FoundBlob.new(blob_filename: filename, project: project, ref: ref, repository: repository)
+ end
end
- def blobs(filenames)
- Gitlab::Git::Blob.batch(repository, blob_refs(filenames), blob_size_limit: 1024)
+ def search_filenames(query)
+ repository.search_files_by_name(query, ref)
end
end
end
diff --git a/lib/gitlab/git/object_pool.rb b/lib/gitlab/git/object_pool.rb
new file mode 100644
index 00000000000..1c6242b444a
--- /dev/null
+++ b/lib/gitlab/git/object_pool.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Git
+ class ObjectPool
+ # GL_REPOSITORY has to be passed for Gitlab::Git::Repositories, but not
+ # used for ObjectPools.
+ GL_REPOSITORY = ""
+
+ delegate :exists?, :size, to: :repository
+ delegate :unlink_repository, :delete, to: :object_pool_service
+
+ attr_reader :storage, :relative_path, :source_repository
+
+ def initialize(storage, relative_path, source_repository)
+ @storage = storage
+ @relative_path = relative_path
+ @source_repository = source_repository
+ end
+
+ def create
+ object_pool_service.create(source_repository)
+ end
+
+ def link(to_link_repo)
+ object_pool_service.link_repository(to_link_repo)
+ end
+
+ def gitaly_object_pool
+ Gitaly::ObjectPool.new(repository: to_gitaly_repository)
+ end
+
+ def to_gitaly_repository
+ Gitlab::GitalyClient::Util.repository(storage, relative_path, GL_REPOSITORY)
+ end
+
+ # Allows for reusing other RPCs by 'tricking' Gitaly to think its a repository
+ def repository
+ @repository ||= Gitlab::Git::Repository.new(storage, relative_path, GL_REPOSITORY)
+ end
+
+ private
+
+ def object_pool_service
+ @object_pool_service ||= Gitlab::GitalyClient::ObjectPoolService.new(self)
+ end
+
+ def relative_path_to(pool_member_path)
+ pool_path = Pathname.new("#{relative_path}#{File::SEPARATOR}")
+
+ Pathname.new(pool_member_path).relative_path_from(pool_path).to_s
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 0a541031884..5bbedc9d5e3 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -69,6 +69,13 @@ module Gitlab
attr_reader :storage, :gl_repository, :relative_path
+ # This remote name has to be stable for all types of repositories that
+ # can join an object pool. If it's structure ever changes, a migration
+ # has to be performed on the object pools to update the remote names.
+ # Else the pool can't be updated anymore and is left in an inconsistent
+ # state.
+ alias_method :object_pool_remote_name, :gl_repository
+
# This initializer method is only used on the client side (gitlab-ce).
# Gitaly-ruby uses a different initializer.
def initialize(storage, relative_path, gl_repository)
diff --git a/lib/gitlab/git/repository_cleaner.rb b/lib/gitlab/git/repository_cleaner.rb
new file mode 100644
index 00000000000..2d1d8435cf3
--- /dev/null
+++ b/lib/gitlab/git/repository_cleaner.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Git
+ class RepositoryCleaner
+ include Gitlab::Git::WrapsGitalyErrors
+
+ attr_reader :repository
+
+ # 'repository' is a Gitlab::Git::Repository
+ def initialize(repository)
+ @repository = repository
+ end
+
+ def apply_bfg_object_map(io)
+ wrapped_gitaly_errors do
+ gitaly_cleanup_client.apply_bfg_object_map(io)
+ end
+ end
+
+ private
+
+ def gitaly_cleanup_client
+ @gitaly_cleanup_client ||= Gitlab::GitalyClient::CleanupService.new(repository)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 9be553a8b86..8bf8a3b53cd 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -26,6 +26,7 @@ module Gitlab
end
end
+ PEM_REGEX = /\-+BEGIN CERTIFICATE\-+.+?\-+END CERTIFICATE\-+/m
SERVER_VERSION_FILE = 'GITALY_SERVER_VERSION'
MAXIMUM_GITALY_CALLS = 35
CLIENT_NAME = (Sidekiq.server? ? 'gitlab-sidekiq' : 'gitlab-web').freeze
@@ -50,11 +51,42 @@ module Gitlab
@stubs[storage][name] ||= begin
klass = stub_class(name)
addr = stub_address(storage)
- klass.new(addr, :this_channel_is_insecure)
+ creds = stub_creds(storage)
+ klass.new(addr, creds)
end
end
end
+ def self.stub_cert_paths
+ cert_paths = Dir["#{OpenSSL::X509::DEFAULT_CERT_DIR}/*"]
+ cert_paths << OpenSSL::X509::DEFAULT_CERT_FILE if File.exist? OpenSSL::X509::DEFAULT_CERT_FILE
+ cert_paths
+ end
+
+ def self.stub_certs
+ return @certs if @certs
+
+ @certs = stub_cert_paths.flat_map do |cert_file|
+ File.read(cert_file).scan(PEM_REGEX).map do |cert|
+ begin
+ OpenSSL::X509::Certificate.new(cert).to_pem
+ rescue OpenSSL::OpenSSLError => e
+ Rails.logger.error "Could not load certificate #{cert_file} #{e}"
+ Gitlab::Sentry.track_exception(e, extra: { cert_file: cert_file })
+ nil
+ end
+ end.compact
+ end.uniq.join("\n")
+ end
+
+ def self.stub_creds(storage)
+ if URI(address(storage)).scheme == 'tls'
+ GRPC::Core::ChannelCredentials.new stub_certs
+ else
+ :this_channel_is_insecure
+ end
+ end
+
def self.stub_class(name)
if name == :health_check
Grpc::Health::V1::Health::Stub
@@ -64,9 +96,7 @@ module Gitlab
end
def self.stub_address(storage)
- addr = address(storage)
- addr = addr.sub(%r{^tcp://}, '') if URI(addr).scheme == 'tcp'
- addr
+ address(storage).sub(%r{^tcp://|^tls://}, '')
end
def self.clear_stubs!
@@ -88,8 +118,8 @@ module Gitlab
raise "storage #{storage.inspect} is missing a gitaly_address"
end
- unless URI(address).scheme.in?(%w(tcp unix))
- raise "Unsupported Gitaly address: #{address.inspect} does not use URL scheme 'tcp' or 'unix'"
+ unless URI(address).scheme.in?(%w(tcp unix tls))
+ raise "Unsupported Gitaly address: #{address.inspect} does not use URL scheme 'tcp' or 'unix' or 'tls'"
end
address
@@ -193,6 +223,7 @@ module Gitlab
feature = feature_stack && feature_stack[0]
metadata['call_site'] = feature.to_s if feature
metadata['gitaly-servers'] = address_metadata(remote_storage) if remote_storage
+ metadata['x-gitlab-correlation-id'] = Gitlab::CorrelationId.current_id if Gitlab::CorrelationId.current_id
metadata.merge!(server_feature_flags)
diff --git a/lib/gitlab/gitaly_client/cleanup_service.rb b/lib/gitlab/gitaly_client/cleanup_service.rb
new file mode 100644
index 00000000000..3e8d6a773ca
--- /dev/null
+++ b/lib/gitlab/gitaly_client/cleanup_service.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GitalyClient
+ class CleanupService
+ attr_reader :repository, :gitaly_repo, :storage
+
+ # 'repository' is a Gitlab::Git::Repository
+ def initialize(repository)
+ @repository = repository
+ @gitaly_repo = repository.gitaly_repository
+ @storage = repository.storage
+ end
+
+ def apply_bfg_object_map(io)
+ first_request = Gitaly::ApplyBfgObjectMapRequest.new(repository: gitaly_repo)
+
+ enum = Enumerator.new do |y|
+ y.yield first_request
+
+ while data = io.read(RepositoryService::MAX_MSG_SIZE)
+ y.yield Gitaly::ApplyBfgObjectMapRequest.new(object_map: data)
+ break if io&.eof?
+ end
+ end
+
+ GitalyClient.call(
+ storage,
+ :cleanup_service,
+ :apply_bfg_object_map,
+ enum,
+ timeout: GitalyClient.no_timeout
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitaly_client/object_pool_service.rb b/lib/gitlab/gitaly_client/object_pool_service.rb
new file mode 100644
index 00000000000..6e7ede5fd18
--- /dev/null
+++ b/lib/gitlab/gitaly_client/object_pool_service.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GitalyClient
+ class ObjectPoolService
+ attr_reader :object_pool, :storage
+
+ def initialize(object_pool)
+ @object_pool = object_pool.gitaly_object_pool
+ @storage = object_pool.storage
+ end
+
+ def create(repository)
+ request = Gitaly::CreateObjectPoolRequest.new(
+ object_pool: object_pool,
+ origin: repository.gitaly_repository)
+
+ GitalyClient.call(storage, :object_pool_service, :create_object_pool, request)
+ end
+
+ def delete
+ request = Gitaly::DeleteObjectPoolRequest.new(object_pool: object_pool)
+
+ GitalyClient.call(storage, :object_pool_service, :delete_object_pool, request)
+ end
+
+ def link_repository(repository)
+ request = Gitaly::LinkRepositoryToObjectPoolRequest.new(
+ object_pool: object_pool,
+ repository: repository.gitaly_repository
+ )
+
+ GitalyClient.call(storage, :object_pool_service, :link_repository_to_object_pool,
+ request, timeout: GitalyClient.fast_timeout)
+ end
+
+ def unlink_repository(repository)
+ request = Gitaly::UnlinkRepositoryFromObjectPoolRequest.new(
+ object_pool: object_pool,
+ repository: repository.gitaly_repository
+ )
+
+ GitalyClient.call(storage, :object_pool_service, :unlink_repository_from_object_pool,
+ request, timeout: GitalyClient.fast_timeout)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gpg.rb b/lib/gitlab/gpg.rb
index e53c2d00743..32f61b1d65c 100644
--- a/lib/gitlab/gpg.rb
+++ b/lib/gitlab/gpg.rb
@@ -73,13 +73,7 @@ module Gitlab
if MUTEX.locked? && MUTEX.owned?
optimistic_using_tmp_keychain(&block)
else
- if Gitlab.rails5?
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
- MUTEX.synchronize do
- optimistic_using_tmp_keychain(&block)
- end
- end
- else
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
MUTEX.synchronize do
optimistic_using_tmp_keychain(&block)
end
diff --git a/lib/gitlab/gpg/commit.rb b/lib/gitlab/gpg/commit.rb
index 31bab20b044..4fbb87385c3 100644
--- a/lib/gitlab/gpg/commit.rb
+++ b/lib/gitlab/gpg/commit.rb
@@ -44,9 +44,8 @@ module Gitlab
def update_signature!(cached_signature)
using_keychain do |gpg_key|
cached_signature.update!(attributes(gpg_key))
+ @signature = cached_signature
end
-
- @signature = cached_signature
end
private
@@ -59,11 +58,15 @@ module Gitlab
# the proper signature.
# NOTE: the invoked method is #fingerprint but it's only returning
# 16 characters (the format used by keyid) instead of 40.
- gpg_key = find_gpg_key(verified_signature.fingerprint)
+ fingerprint = verified_signature&.fingerprint
+
+ break unless fingerprint
+
+ gpg_key = find_gpg_key(fingerprint)
if gpg_key
Gitlab::Gpg::CurrentKeyChain.add(gpg_key.key)
- @verified_signature = nil
+ clear_memoization(:verified_signature)
end
yield gpg_key
@@ -71,9 +74,16 @@ module Gitlab
end
def verified_signature
- @verified_signature ||= GPGME::Crypto.new.verify(signature_text, signed_text: signed_text) do |verified_signature|
+ strong_memoize(:verified_signature) { gpgme_signature }
+ end
+
+ def gpgme_signature
+ GPGME::Crypto.new.verify(signature_text, signed_text: signed_text) do |verified_signature|
+ # Return the first signature for now: https://gitlab.com/gitlab-org/gitlab-ce/issues/54932
break verified_signature
end
+ rescue GPGME::Error
+ nil
end
def create_cached_signature!
@@ -92,7 +102,7 @@ module Gitlab
commit_sha: @commit.sha,
project: @commit.project,
gpg_key: gpg_key,
- gpg_key_primary_keyid: gpg_key&.keyid || verified_signature.fingerprint,
+ gpg_key_primary_keyid: gpg_key&.keyid || verified_signature&.fingerprint,
gpg_key_user_name: user_infos[:name],
gpg_key_user_email: user_infos[:email],
verification_status: verification_status
@@ -102,7 +112,7 @@ module Gitlab
def verification_status(gpg_key)
return :unknown_key unless gpg_key
return :unverified_key unless gpg_key.verified?
- return :unverified unless verified_signature.valid?
+ return :unverified unless verified_signature&.valid?
if gpg_key.verified_and_belongs_to_email?(@commit.committer_email)
:verified
diff --git a/lib/gitlab/grape_logging/loggers/correlation_id_logger.rb b/lib/gitlab/grape_logging/loggers/correlation_id_logger.rb
new file mode 100644
index 00000000000..fa4c5d86d44
--- /dev/null
+++ b/lib/gitlab/grape_logging/loggers/correlation_id_logger.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+# This module adds additional correlation id the grape logger
+module Gitlab
+ module GrapeLogging
+ module Loggers
+ class CorrelationIdLogger < ::GrapeLogging::Loggers::Base
+ def parameters(_, _)
+ { Gitlab::CorrelationId::LOG_KEY => Gitlab::CorrelationId.current_id }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql.rb b/lib/gitlab/graphql.rb
index 74c04e5380e..8a59e83974f 100644
--- a/lib/gitlab/graphql.rb
+++ b/lib/gitlab/graphql.rb
@@ -3,5 +3,9 @@
module Gitlab
module Graphql
StandardGraphqlError = Class.new(StandardError)
+
+ def self.enabled?
+ Feature.enabled?(:graphql, default_enabled: true)
+ end
end
end
diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb
index c9e2a6a78d9..bdecff0931c 100644
--- a/lib/gitlab/import_export/command_line_util.rb
+++ b/lib/gitlab/import_export/command_line_util.rb
@@ -3,7 +3,8 @@
module Gitlab
module ImportExport
module CommandLineUtil
- DEFAULT_MODE = 0700
+ UNTAR_MASK = 'u+rwX,go+rX,go-w'
+ DEFAULT_DIR_MODE = 0700
def tar_czf(archive:, dir:)
tar_with_options(archive: archive, dir: dir, options: 'czf')
@@ -14,8 +15,8 @@ module Gitlab
end
def mkdir_p(path)
- FileUtils.mkdir_p(path, mode: DEFAULT_MODE)
- FileUtils.chmod(DEFAULT_MODE, path)
+ FileUtils.mkdir_p(path, mode: DEFAULT_DIR_MODE)
+ FileUtils.chmod(DEFAULT_DIR_MODE, path)
end
private
@@ -41,6 +42,7 @@ module Gitlab
def untar_with_options(archive:, dir:, options:)
execute(%W(tar -#{options} #{archive} -C #{dir}))
+ execute(%W(chmod -R #{UNTAR_MASK} #{dir}))
end
def execute(cmd)
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 93065879ec6..3cd8ede830c 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -27,7 +27,8 @@ project_tree:
- :award_emoji
- notes:
:author
- - :releases
+ - releases:
+ :author
- project_members:
- :user
- merge_requests:
@@ -115,6 +116,7 @@ excluded_attributes:
- :remote_mirror_available_overridden
- :description_html
- :repository_languages
+ - :bfg_object_map
namespaces:
- :runners_token
- :runners_token_encrypted
@@ -142,6 +144,7 @@ excluded_attributes:
statuses:
- :trace
- :token
+ - :token_encrypted
- :when
- :artifacts_file
- :artifacts_metadata
diff --git a/lib/gitlab/import_export/repo_restorer.rb b/lib/gitlab/import_export/repo_restorer.rb
index 921a06b4023..91167a9c4fb 100644
--- a/lib/gitlab/import_export/repo_restorer.rb
+++ b/lib/gitlab/import_export/repo_restorer.rb
@@ -4,7 +4,6 @@ module Gitlab
module ImportExport
class RepoRestorer
include Gitlab::ImportExport::CommandLineUtil
- include Gitlab::ShellAdapter
def initialize(project:, shared:, path_to_bundle:)
@project = project
diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb
index f46bb837cf7..67952ca0f2d 100644
--- a/lib/gitlab/import_sources.rb
+++ b/lib/gitlab/import_sources.rb
@@ -10,7 +10,7 @@ module Gitlab
ImportSource = Struct.new(:name, :title, :importer)
# We exclude `bare_repository` here as it has no import class associated
- ImportTable = [
+ IMPORT_TABLE = [
ImportSource.new('github', 'GitHub', Gitlab::GithubImport::ParallelImporter),
ImportSource.new('bitbucket', 'Bitbucket Cloud', Gitlab::BitbucketImport::Importer),
ImportSource.new('bitbucket_server', 'Bitbucket Server', Gitlab::BitbucketServerImport::Importer),
@@ -45,7 +45,7 @@ module Gitlab
end
def import_table
- ImportTable
+ IMPORT_TABLE
end
end
end
diff --git a/lib/gitlab/json_cache.rb b/lib/gitlab/json_cache.rb
new file mode 100644
index 00000000000..1adf83739ad
--- /dev/null
+++ b/lib/gitlab/json_cache.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class JsonCache
+ attr_reader :backend, :cache_key_with_version, :namespace
+
+ def initialize(options = {})
+ @backend = options.fetch(:backend, Rails.cache)
+ @namespace = options.fetch(:namespace, nil)
+ @cache_key_with_version = options.fetch(:cache_key_with_version, true)
+ end
+
+ def active?
+ if backend.respond_to?(:active?)
+ backend.active?
+ else
+ true
+ end
+ end
+
+ def cache_key(key)
+ expanded_cache_key = [namespace, key].compact
+
+ if cache_key_with_version
+ expanded_cache_key << Rails.version
+ end
+
+ expanded_cache_key.join(':')
+ end
+
+ def expire(key)
+ backend.delete(cache_key(key))
+ end
+
+ def read(key, klass = nil)
+ value = backend.read(cache_key(key))
+ value = parse_value(value, klass) if value
+ value
+ end
+
+ def write(key, value, options = nil)
+ backend.write(cache_key(key), value.to_json, options)
+ end
+
+ def fetch(key, options = {}, &block)
+ klass = options.delete(:as)
+ value = read(key, klass)
+
+ return value unless value.nil?
+
+ value = yield
+
+ write(key, value, options)
+
+ value
+ end
+
+ private
+
+ def parse_value(raw, klass)
+ value = ActiveSupport::JSON.decode(raw)
+
+ case value
+ when Hash then parse_entry(value, klass)
+ when Array then parse_entries(value, klass)
+ else
+ value
+ end
+ rescue ActiveSupport::JSON.parse_error
+ nil
+ end
+
+ def parse_entry(raw, klass)
+ klass.new(raw) if valid_entry?(raw, klass)
+ end
+
+ def valid_entry?(raw, klass)
+ return false unless klass && raw.is_a?(Hash)
+
+ (raw.keys - klass.attribute_names).empty?
+ end
+
+ def parse_entries(values, klass)
+ values.map { |value| parse_entry(value, klass) }.compact
+ end
+ end
+end
diff --git a/lib/gitlab/json_logger.rb b/lib/gitlab/json_logger.rb
index 3bff77731f6..a5a5759cc89 100644
--- a/lib/gitlab/json_logger.rb
+++ b/lib/gitlab/json_logger.rb
@@ -10,6 +10,7 @@ module Gitlab
data = {}
data[:severity] = severity
data[:time] = timestamp.utc.iso8601(3)
+ data[Gitlab::CorrelationId::LOG_KEY] = Gitlab::CorrelationId.current_id
case message
when String
diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb
index 05d3096a208..c09d3ebc7be 100644
--- a/lib/gitlab/lfs_token.rb
+++ b/lib/gitlab/lfs_token.rb
@@ -2,10 +2,21 @@
module Gitlab
class LfsToken
- attr_accessor :actor
+ module LfsTokenHelper
+ def user?
+ actor.is_a?(User)
+ end
+
+ def actor_name
+ user? ? actor.username : "lfs+deploy-key-#{actor.id}"
+ end
+ end
+
+ include LfsTokenHelper
- TOKEN_LENGTH = 50
- EXPIRY_TIME = 1800
+ DEFAULT_EXPIRE_TIME = 1800
+
+ attr_accessor :actor
def initialize(actor)
@actor =
@@ -19,36 +30,108 @@ module Gitlab
end
end
- def token
- Gitlab::Redis::SharedState.with do |redis|
- token = redis.get(redis_shared_state_key)
- token ||= Devise.friendly_token(TOKEN_LENGTH)
- redis.set(redis_shared_state_key, token, ex: EXPIRY_TIME)
+ def token(expire_time: DEFAULT_EXPIRE_TIME)
+ HMACToken.new(actor).token(expire_time)
+ end
- token
- end
+ def token_valid?(token_to_check)
+ HMACToken.new(actor).token_valid?(token_to_check) ||
+ LegacyRedisDeviseToken.new(actor).token_valid?(token_to_check)
end
def deploy_key_pushable?(project)
actor.is_a?(DeployKey) && actor.can_push_to?(project)
end
- def user?
- actor.is_a?(User)
- end
-
def type
user? ? :lfs_token : :lfs_deploy_token
end
- def actor_name
- actor.is_a?(User) ? actor.username : "lfs+deploy-key-#{actor.id}"
+ private # rubocop:disable Lint/UselessAccessModifier
+
+ class HMACToken
+ include LfsTokenHelper
+
+ def initialize(actor)
+ @actor = actor
+ end
+
+ def token(expire_time)
+ hmac_token = JSONWebToken::HMACToken.new(secret)
+ hmac_token.expire_time = Time.now + expire_time
+ hmac_token[:data] = { actor: actor_name }
+ hmac_token.encoded
+ end
+
+ def token_valid?(token_to_check)
+ decoded_token = JSONWebToken::HMACToken.decode(token_to_check, secret).first
+ decoded_token.dig('data', 'actor') == actor_name
+ rescue JWT::DecodeError
+ false
+ end
+
+ private
+
+ attr_reader :actor
+
+ def secret
+ salt + key
+ end
+
+ def salt
+ case actor
+ when DeployKey, Key
+ actor.fingerprint.delete(':').first(16)
+ when User
+ # Take the last 16 characters as they're more unique than the first 16
+ actor.id.to_s + actor.encrypted_password.last(16)
+ end
+ end
+
+ def key
+ # Take 16 characters of attr_encrypted_db_key_base, as that's what the
+ # cipher needs exactly
+ Settings.attr_encrypted_db_key_base.first(16)
+ end
end
- private
+ # TODO: LegacyRedisDeviseToken and references need to be removed after
+ # next released milestone
+ #
+ class LegacyRedisDeviseToken
+ TOKEN_LENGTH = 50
+ DEFAULT_EXPIRY_TIME = 1800 * 1000 # 30 mins
+
+ def initialize(actor)
+ @actor = actor
+ end
+
+ def token_valid?(token_to_check)
+ Devise.secure_compare(stored_token, token_to_check)
+ end
+
+ def stored_token
+ Gitlab::Redis::SharedState.with { |redis| redis.get(state_key) }
+ end
+
+ # This method exists purely to facilitate legacy testing to ensure the
+ # same redis key is used.
+ #
+ def store_new_token(expiry_time_in_ms = DEFAULT_EXPIRY_TIME)
+ Gitlab::Redis::SharedState.with do |redis|
+ new_token = Devise.friendly_token(TOKEN_LENGTH)
+ redis.set(state_key, new_token, px: expiry_time_in_ms)
+ new_token
+ end
+ end
+
+ private
- def redis_shared_state_key
- "gitlab:lfs_token:#{actor.class.name.underscore}_#{actor.id}" if actor
+ attr_reader :actor
+
+ def state_key
+ "gitlab:lfs_token:#{actor.class.name.underscore}_#{actor.id}"
+ end
end
end
end
diff --git a/lib/gitlab/middleware/correlation_id.rb b/lib/gitlab/middleware/correlation_id.rb
new file mode 100644
index 00000000000..80dddc41c12
--- /dev/null
+++ b/lib/gitlab/middleware/correlation_id.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+# A dumb middleware that steals correlation id
+# and sets it as a global context for the request
+module Gitlab
+ module Middleware
+ class CorrelationId
+ include ActionView::Helpers::TagHelper
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ ::Gitlab::CorrelationId.use_id(correlation_id(env)) do
+ @app.call(env)
+ end
+ end
+
+ private
+
+ def correlation_id(env)
+ request(env).request_id
+ end
+
+ def request(env)
+ ActionDispatch::Request.new(env)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/group_hierarchy.rb b/lib/gitlab/object_hierarchy.rb
index 97cbdc6cb39..f2772c733c7 100644
--- a/lib/gitlab/group_hierarchy.rb
+++ b/lib/gitlab/object_hierarchy.rb
@@ -1,16 +1,16 @@
# frozen_string_literal: true
module Gitlab
- # Retrieving of parent or child groups based on a base ActiveRecord relation.
+ # Retrieving of parent or child objects based on a base ActiveRecord relation.
#
# This class uses recursive CTEs and as a result will only work on PostgreSQL.
- class GroupHierarchy
+ class ObjectHierarchy
attr_reader :ancestors_base, :descendants_base, :model
# ancestors_base - An instance of ActiveRecord::Relation for which to
- # get parent groups.
+ # get parent objects.
# descendants_base - An instance of ActiveRecord::Relation for which to
- # get child groups. If omitted, ancestors_base is used.
+ # get child objects. If omitted, ancestors_base is used.
def initialize(ancestors_base, descendants_base = ancestors_base)
raise ArgumentError.new("Model of ancestors_base does not match model of descendants_base") if ancestors_base.model != descendants_base.model
@@ -39,7 +39,7 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
- # Returns a relation that includes the ancestors_base set of groups
+ # Returns a relation that includes the ancestors_base set of objects
# and all their ancestors (recursively).
#
# Passing an `upto` will stop the recursion once the specified parent_id is
@@ -47,13 +47,13 @@ module Gitlab
# included.
#
# Passing a `hierarchy_order` with either `:asc` or `:desc` will cause the
- # recursive query order from most nested group to root or from the root
- # ancestor to most nested group respectively. This uses a `depth` column
+ # recursive query order from most nested object to root or from the root
+ # ancestor to most nested object respectively. This uses a `depth` column
# where `1` is defined as the depth for the base and increment as we go up
# each parent.
# rubocop: disable CodeReuse/ActiveRecord
def base_and_ancestors(upto: nil, hierarchy_order: nil)
- return ancestors_base unless Group.supports_nested_groups?
+ return ancestors_base unless hierarchy_supported?
recursive_query = base_and_ancestors_cte(upto, hierarchy_order).apply_to(model.all)
recursive_query = recursive_query.order(depth: hierarchy_order) if hierarchy_order
@@ -62,16 +62,16 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
- # Returns a relation that includes the descendants_base set of groups
+ # Returns a relation that includes the descendants_base set of objects
# and all their descendants (recursively).
def base_and_descendants
- return descendants_base unless Group.supports_nested_groups?
+ return descendants_base unless hierarchy_supported?
read_only(base_and_descendants_cte.apply_to(model.all))
end
- # Returns a relation that includes the base groups, their ancestors,
- # and the descendants of the base groups.
+ # Returns a relation that includes the base objects, their ancestors,
+ # and the descendants of the base objects.
#
# The resulting query will roughly look like the following:
#
@@ -91,16 +91,16 @@ module Gitlab
# Using this approach allows us to further add criteria to the relation with
# Rails thinking it's selecting data the usual way.
#
- # If nested groups are not supported, ancestors_base is returned.
+ # If nested objects are not supported, ancestors_base is returned.
# rubocop: disable CodeReuse/ActiveRecord
- def all_groups
- return ancestors_base unless Group.supports_nested_groups?
+ def all_objects
+ return ancestors_base unless hierarchy_supported?
ancestors = base_and_ancestors_cte
descendants = base_and_descendants_cte
- ancestors_table = ancestors.alias_to(groups_table)
- descendants_table = descendants.alias_to(groups_table)
+ ancestors_table = ancestors.alias_to(objects_table)
+ descendants_table = descendants.alias_to(objects_table)
relation = model
.unscoped
@@ -117,23 +117,27 @@ module Gitlab
private
+ def hierarchy_supported?
+ Gitlab::Database.postgresql?
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def base_and_ancestors_cte(stop_id = nil, hierarchy_order = nil)
cte = SQL::RecursiveCTE.new(:base_and_ancestors)
depth_column = :depth
base_query = ancestors_base.except(:order)
- base_query = base_query.select("1 as #{depth_column}", groups_table[Arel.star]) if hierarchy_order
+ base_query = base_query.select("1 as #{depth_column}", objects_table[Arel.star]) if hierarchy_order
cte << base_query
# Recursively get all the ancestors of the base set.
parent_query = model
- .from([groups_table, cte.table])
- .where(groups_table[:id].eq(cte.table[:parent_id]))
+ .from([objects_table, cte.table])
+ .where(objects_table[:id].eq(cte.table[:parent_id]))
.except(:order)
- parent_query = parent_query.select(cte.table[depth_column] + 1, groups_table[Arel.star]) if hierarchy_order
+ parent_query = parent_query.select(cte.table[depth_column] + 1, objects_table[Arel.star]) if hierarchy_order
parent_query = parent_query.where(cte.table[:parent_id].not_eq(stop_id)) if stop_id
cte << parent_query
@@ -149,15 +153,15 @@ module Gitlab
# Recursively get all the descendants of the base set.
cte << model
- .from([groups_table, cte.table])
- .where(groups_table[:parent_id].eq(cte.table[:id]))
+ .from([objects_table, cte.table])
+ .where(objects_table[:parent_id].eq(cte.table[:id]))
.except(:order)
cte
end
# rubocop: enable CodeReuse/ActiveRecord
- def groups_table
+ def objects_table
model.arel_table
end
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index 04df881bf03..a68f8801c2a 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -17,9 +17,9 @@ module Gitlab
when 'notes'
notes.page(page).per(per_page)
when 'blobs'
- Kaminari.paginate_array(blobs).page(page).per(per_page)
+ paginated_blobs(blobs, page)
when 'wiki_blobs'
- Kaminari.paginate_array(wiki_blobs).page(page).per(per_page)
+ paginated_blobs(wiki_blobs, page)
when 'commits'
Kaminari.paginate_array(commits).page(page).per(per_page)
else
@@ -55,37 +55,6 @@ module Gitlab
@commits_count ||= commits.count
end
- def self.parse_search_result(result, project = nil)
- ref = nil
- filename = nil
- basename = nil
-
- data = []
- startline = 0
-
- result.each_line.each_with_index do |line, index|
- prefix ||= line.match(/^(?<ref>[^:]*):(?<filename>[^\x00]*)\x00(?<startline>\d+)\x00/)&.tap do |matches|
- ref = matches[:ref]
- filename = matches[:filename]
- startline = matches[:startline]
- startline = startline.to_i - index
- extname = Regexp.escape(File.extname(filename))
- basename = filename.sub(/#{extname}$/, '')
- end
-
- data << line.sub(prefix.to_s, '')
- end
-
- FoundBlob.new(
- filename: filename,
- basename: basename,
- ref: ref,
- startline: startline,
- data: data.join,
- project: project
- )
- end
-
def single_commit_result?
return false if commits_count != 1
@@ -97,6 +66,14 @@ module Gitlab
private
+ def paginated_blobs(blobs, page)
+ results = Kaminari.paginate_array(blobs).page(page).per(per_page)
+
+ Gitlab::Search::FoundBlob.preload_blobs(results)
+
+ results
+ end
+
def blobs
return [] unless Ability.allowed?(@current_user, :download_code, @project)
diff --git a/lib/gitlab/safe_request_store.rb b/lib/gitlab/safe_request_store.rb
index 4e82353adb6..d146913bdb3 100644
--- a/lib/gitlab/safe_request_store.rb
+++ b/lib/gitlab/safe_request_store.rb
@@ -19,5 +19,13 @@ module Gitlab
NULL_STORE
end
end
+
+ # This method accept an options hash to be compatible with
+ # ActiveSupport::Cache::Store#write method. The options are
+ # not passed to the underlying cache implementation because
+ # RequestStore#write accepts only a key, and value params.
+ def self.write(key, value, options = nil)
+ store.write(key, value)
+ end
end
end
diff --git a/lib/gitlab/search/found_blob.rb b/lib/gitlab/search/found_blob.rb
new file mode 100644
index 00000000000..a62ab1521a7
--- /dev/null
+++ b/lib/gitlab/search/found_blob.rb
@@ -0,0 +1,162 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Search
+ class FoundBlob
+ include EncodingHelper
+ include Presentable
+ include BlobLanguageFromGitAttributes
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :project, :content_match, :blob_filename
+
+ FILENAME_REGEXP = /\A(?<ref>[^:]*):(?<filename>[^\x00]*)\x00/.freeze
+ CONTENT_REGEXP = /^(?<ref>[^:]*):(?<filename>[^\x00]*)\x00(?<startline>\d+)\x00/.freeze
+
+ def self.preload_blobs(blobs)
+ to_fetch = blobs.select { |blob| blob.is_a?(self) && blob.blob_filename }
+
+ to_fetch.each { |blob| blob.fetch_blob }
+ end
+
+ def initialize(opts = {})
+ @id = opts.fetch(:id, nil)
+ @binary_filename = opts.fetch(:filename, nil)
+ @binary_basename = opts.fetch(:basename, nil)
+ @ref = opts.fetch(:ref, nil)
+ @startline = opts.fetch(:startline, nil)
+ @binary_data = opts.fetch(:data, nil)
+ @per_page = opts.fetch(:per_page, 20)
+ @project = opts.fetch(:project, nil)
+ # Some caller does not have project object (e.g. elastic search),
+ # yet they can trigger many calls in one go,
+ # causing duplicated queries.
+ # Allow those to just pass project_id instead.
+ @project_id = opts.fetch(:project_id, nil)
+ @content_match = opts.fetch(:content_match, nil)
+ @blob_filename = opts.fetch(:blob_filename, nil)
+ @repository = opts.fetch(:repository, nil)
+ end
+
+ def id
+ @id ||= parsed_content[:id]
+ end
+
+ def ref
+ @ref ||= parsed_content[:ref]
+ end
+
+ def startline
+ @startline ||= parsed_content[:startline]
+ end
+
+ # binary_filename is used for running filters on all matches,
+ # for grepped results (which use content_match), we get
+ # filename from the beginning of the grepped result which is faster
+ # then parsing whole snippet
+ def binary_filename
+ @binary_filename ||= content_match ? search_result_filename : parsed_content[:binary_filename]
+ end
+
+ def filename
+ @filename ||= encode_utf8(@binary_filename || parsed_content[:binary_filename])
+ end
+
+ def basename
+ @basename ||= encode_utf8(@binary_basename || parsed_content[:binary_basename])
+ end
+
+ def data
+ @data ||= encode_utf8(@binary_data || parsed_content[:binary_data])
+ end
+
+ def path
+ filename
+ end
+
+ def project_id
+ @project_id || @project&.id
+ end
+
+ def present
+ super(presenter_class: BlobPresenter)
+ end
+
+ def fetch_blob
+ path = [ref, blob_filename]
+ missing_blob = { binary_filename: blob_filename }
+
+ BatchLoader.for(path).batch(default_value: missing_blob) do |refs, loader|
+ Gitlab::Git::Blob.batch(repository, refs, blob_size_limit: 1024).each do |blob|
+ # if the blob couldn't be fetched for some reason,
+ # show at least the blob filename
+ data = {
+ id: blob.id,
+ binary_filename: blob.path,
+ binary_basename: File.basename(blob.path, File.extname(blob.path)),
+ ref: ref,
+ startline: 1,
+ binary_data: blob.data,
+ project: project
+ }
+
+ loader.call([ref, blob.path], data)
+ end
+ end
+ end
+
+ private
+
+ def search_result_filename
+ content_match.match(FILENAME_REGEXP) { |matches| matches[:filename] }
+ end
+
+ def parsed_content
+ strong_memoize(:parsed_content) do
+ if content_match
+ parse_search_result
+ elsif blob_filename
+ fetch_blob
+ else
+ {}
+ end
+ end
+ end
+
+ def parse_search_result
+ ref = nil
+ filename = nil
+ basename = nil
+
+ data = []
+ startline = 0
+
+ content_match.each_line.each_with_index do |line, index|
+ prefix ||= line.match(CONTENT_REGEXP)&.tap do |matches|
+ ref = matches[:ref]
+ filename = matches[:filename]
+ startline = matches[:startline]
+ startline = startline.to_i - index
+ extname = Regexp.escape(File.extname(filename))
+ basename = filename.sub(/#{extname}$/, '')
+ end
+
+ data << line.sub(prefix.to_s, '')
+ end
+
+ {
+ binary_filename: filename,
+ binary_basename: basename,
+ ref: ref,
+ startline: startline,
+ binary_data: data.join,
+ project: project
+ }
+ end
+
+ def repository
+ @repository ||= project.repository
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/search/query.rb b/lib/gitlab/search/query.rb
index 7f69083a492..ba0e16607a6 100644
--- a/lib/gitlab/search/query.rb
+++ b/lib/gitlab/search/query.rb
@@ -3,6 +3,8 @@
module Gitlab
module Search
class Query < SimpleDelegator
+ include EncodingHelper
+
def initialize(query, filter_opts = {}, &block)
@raw_query = query.dup
@filters = []
@@ -50,7 +52,9 @@ module Gitlab
end
def parse_filter(filter, input)
- filter[:parser].call(input)
+ result = filter[:parser].call(input)
+
+ @filter_options[:encode_binary] ? encode_binary(result) : result
end
end
end
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 458737f31eb..491148ec1a6 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -2,42 +2,6 @@
module Gitlab
class SearchResults
- class FoundBlob
- include EncodingHelper
- include Presentable
- include BlobLanguageFromGitAttributes
-
- attr_reader :id, :filename, :basename, :ref, :startline, :data, :project
-
- def initialize(opts = {})
- @id = opts.fetch(:id, nil)
- @filename = encode_utf8(opts.fetch(:filename, nil))
- @basename = encode_utf8(opts.fetch(:basename, nil))
- @ref = opts.fetch(:ref, nil)
- @startline = opts.fetch(:startline, nil)
- @data = encode_utf8(opts.fetch(:data, nil))
- @per_page = opts.fetch(:per_page, 20)
- @project = opts.fetch(:project, nil)
- # Some caller does not have project object (e.g. elastic search),
- # yet they can trigger many calls in one go,
- # causing duplicated queries.
- # Allow those to just pass project_id instead.
- @project_id = opts.fetch(:project_id, nil)
- end
-
- def path
- filename
- end
-
- def project_id
- @project_id || @project&.id
- end
-
- def present
- super(presenter_class: BlobPresenter)
- end
- end
-
attr_reader :current_user, :query, :per_page
# Limit search results by passed projects
diff --git a/lib/gitlab/sentry.rb b/lib/gitlab/sentry.rb
index 8079c5882c4..46d01964eac 100644
--- a/lib/gitlab/sentry.rb
+++ b/lib/gitlab/sentry.rb
@@ -3,7 +3,8 @@
module Gitlab
module Sentry
def self.enabled?
- Rails.env.production? && Gitlab::CurrentSettings.sentry_enabled?
+ (Rails.env.production? || Rails.env.development?) &&
+ Gitlab::CurrentSettings.sentry_enabled?
end
def self.context(current_user = nil)
@@ -31,7 +32,7 @@ module Gitlab
def self.track_exception(exception, issue_url: nil, extra: {})
track_acceptable_exception(exception, issue_url: issue_url, extra: extra)
- raise exception if should_raise?
+ raise exception if should_raise_for_dev?
end
# This should be used when you do not want to raise an exception in
@@ -43,7 +44,11 @@ module Gitlab
extra[:issue_url] = issue_url if issue_url
context # Make sure we've set everything we know in the context
- Raven.capture_exception(exception, extra: extra)
+ tags = {
+ Gitlab::CorrelationId::LOG_KEY.to_sym => Gitlab::CorrelationId.current_id
+ }
+
+ Raven.capture_exception(exception, tags: tags, extra: extra)
end
end
@@ -55,7 +60,7 @@ module Gitlab
end
end
- def self.should_raise?
+ def self.should_raise_for_dev?
Rails.env.development? || Rails.env.test?
end
end
diff --git a/lib/gitlab/sidekiq_middleware/correlation_injector.rb b/lib/gitlab/sidekiq_middleware/correlation_injector.rb
new file mode 100644
index 00000000000..b807b3a03ed
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/correlation_injector.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ class CorrelationInjector
+ def call(worker_class, job, queue, redis_pool)
+ job[Gitlab::CorrelationId::LOG_KEY] ||=
+ Gitlab::CorrelationId.current_or_new_id
+
+ yield
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/correlation_logger.rb b/lib/gitlab/sidekiq_middleware/correlation_logger.rb
new file mode 100644
index 00000000000..cb8ff4a6284
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/correlation_logger.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ class CorrelationLogger
+ def call(worker, job, queue)
+ correlation_id = job[Gitlab::CorrelationId::LOG_KEY]
+
+ Gitlab::CorrelationId.use_id(correlation_id) do
+ yield
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ssh_public_key.rb b/lib/gitlab/ssh_public_key.rb
index 47571239b5c..6df54852d02 100644
--- a/lib/gitlab/ssh_public_key.rb
+++ b/lib/gitlab/ssh_public_key.rb
@@ -4,7 +4,7 @@ module Gitlab
class SSHPublicKey
Technology = Struct.new(:name, :key_class, :supported_sizes)
- Technologies = [
+ TECHNOLOGIES = [
Technology.new(:rsa, OpenSSL::PKey::RSA, [1024, 2048, 3072, 4096]),
Technology.new(:dsa, OpenSSL::PKey::DSA, [1024, 2048, 3072]),
Technology.new(:ecdsa, OpenSSL::PKey::EC, [256, 384, 521]),
@@ -12,11 +12,11 @@ module Gitlab
].freeze
def self.technology(name)
- Technologies.find { |tech| tech.name.to_s == name.to_s }
+ TECHNOLOGIES.find { |tech| tech.name.to_s == name.to_s }
end
def self.technology_for_key(key)
- Technologies.find { |tech| key.is_a?(tech.key_class) }
+ TECHNOLOGIES.find { |tech| key.is_a?(tech.key_class) }
end
def self.supported_sizes(name)
diff --git a/lib/gitlab/template/finders/global_template_finder.rb b/lib/gitlab/template/finders/global_template_finder.rb
index 76bb9eb611e..2dd4b7a4092 100644
--- a/lib/gitlab/template/finders/global_template_finder.rb
+++ b/lib/gitlab/template/finders/global_template_finder.rb
@@ -18,6 +18,10 @@ module Gitlab
def find(key)
file_name = "#{key}#{@extension}"
+ # The key is untrusted input, so ensure we can't be directed outside
+ # of base_dir
+ Gitlab::Utils.check_path_traversal!(file_name)
+
directory = select_directory(file_name)
directory ? File.join(category_directory(directory), file_name) : nil
end
diff --git a/lib/gitlab/template/finders/repo_template_finder.rb b/lib/gitlab/template/finders/repo_template_finder.rb
index b92cefefb8f..8e234148a63 100644
--- a/lib/gitlab/template/finders/repo_template_finder.rb
+++ b/lib/gitlab/template/finders/repo_template_finder.rb
@@ -26,6 +26,11 @@ module Gitlab
def find(key)
file_name = "#{key}#{@extension}"
+
+ # The key is untrusted input, so ensure we can't be directed outside
+ # of base_dir inside the repository
+ Gitlab::Utils.check_path_traversal!(file_name)
+
directory = select_directory(file_name)
raise FileNotFoundError if directory.nil?
diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb
deleted file mode 100644
index ccab0e4dd73..00000000000
--- a/lib/gitlab/upgrader.rb
+++ /dev/null
@@ -1,111 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- class Upgrader
- def execute
- puts "GitLab #{current_version.major} upgrade tool"
- puts "Your version is #{current_version}"
- puts "Latest available version for GitLab #{current_version.major} is #{latest_version}"
-
- if latest_version?
- puts "You are using the latest GitLab version"
- else
- puts "Newer GitLab version is available"
-
- answer = if ARGV.first == "-y"
- "yes"
- else
- prompt("Do you want to upgrade (yes/no)? ", %w{yes no})
- end
-
- if answer == "yes"
- upgrade
- else
- exit 0
- end
- end
- end
-
- def latest_version?
- current_version >= latest_version
- end
-
- def current_version
- @current_version ||= Gitlab::VersionInfo.parse(current_version_raw)
- end
-
- def latest_version
- @latest_version ||= Gitlab::VersionInfo.parse(latest_version_raw)
- end
-
- def current_version_raw
- File.read(File.join(gitlab_path, "VERSION")).strip
- end
-
- def latest_version_raw
- git_tags = fetch_git_tags
- git_tags = git_tags.select { |version| version =~ /v\d+\.\d+\.\d+\Z/ }
- git_versions = git_tags.map { |tag| Gitlab::VersionInfo.parse(tag.match(/v\d+\.\d+\.\d+/).to_s) }
- "v#{git_versions.sort.last}"
- end
-
- def fetch_git_tags
- remote_tags, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} ls-remote --tags https://gitlab.com/gitlab-org/gitlab-ce.git))
- remote_tags.split("\n").grep(%r{tags/v#{current_version.major}})
- end
-
- def update_commands
- {
- "Stash changed files" => %W(#{Gitlab.config.git.bin_path} stash),
- "Get latest code" => %W(#{Gitlab.config.git.bin_path} fetch),
- "Switch to new version" => %W(#{Gitlab.config.git.bin_path} checkout v#{latest_version}),
- "Install gems" => %w(bundle),
- "Migrate DB" => %w(bundle exec rake db:migrate),
- "Recompile assets" => %w(bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile),
- "Clear cache" => %w(bundle exec rake cache:clear)
- }
- end
-
- def env
- {
- 'RAILS_ENV' => 'production',
- 'NODE_ENV' => 'production'
- }
- end
-
- def upgrade
- update_commands.each do |title, cmd|
- puts title
- puts " -> #{cmd.join(' ')}"
-
- if system(env, *cmd)
- puts " -> OK"
- else
- puts " -> FAILED"
- puts "Failed to upgrade. Try to repeat task or proceed with upgrade manually "
- exit 1
- end
- end
-
- puts "Done"
- end
-
- def gitlab_path
- File.expand_path(File.join(File.dirname(__FILE__), '../..'))
- end
-
- # Prompt the user to input something
- #
- # message - the message to display before input
- # choices - array of strings of acceptable answers or nil for any answer
- #
- # Returns the user's answer
- def prompt(message, choices = nil)
- begin
- print(message)
- answer = STDIN.gets.chomp
- end while !choices.include?(answer)
- answer
- end
- end
-end
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index b8040f73cee..44c71f8431d 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -8,7 +8,7 @@ module Gitlab
BlockedUrlError = Class.new(StandardError)
class << self
- def validate!(url, allow_localhost: false, allow_local_network: true, enforce_user: false, ports: [], protocols: [])
+ def validate!(url, ports: [], protocols: [], allow_localhost: false, allow_local_network: true, ascii_only: false, enforce_user: false)
return true if url.nil?
# Param url can be a string, URI or Addressable::URI
@@ -22,6 +22,7 @@ module Gitlab
validate_port!(port, ports) if ports.any?
validate_user!(uri.user) if enforce_user
validate_hostname!(uri.hostname)
+ validate_unicode_restriction!(uri) if ascii_only
begin
addrs_info = Addrinfo.getaddrinfo(uri.hostname, port, nil, :STREAM).map do |addr|
@@ -91,6 +92,12 @@ module Gitlab
raise BlockedUrlError, "Hostname or IP address invalid"
end
+ def validate_unicode_restriction!(uri)
+ return if uri.to_s.ascii_only?
+
+ raise BlockedUrlError, "URI must be ascii only #{uri.to_s.dump}"
+ end
+
def validate_localhost!(addrs_info)
local_ips = ["::", "0.0.0.0"]
local_ips.concat(Socket.ip_address_list.map(&:ip_address))
diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb
index 035268bc4f2..880712de5fe 100644
--- a/lib/gitlab/url_sanitizer.rb
+++ b/lib/gitlab/url_sanitizer.rb
@@ -14,6 +14,7 @@ module Gitlab
def self.valid?(url)
return false unless url.present?
+ return false unless url.is_a?(String)
uri = Addressable::URI.parse(url.strip)
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index bfcc8efdc96..083c620267a 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -2,6 +2,8 @@
module Gitlab
class UsageData
+ APPROXIMATE_COUNT_MODELS = [Label, MergeRequest, Note, Todo].freeze
+
class << self
def data(force_refresh: false)
Rails.cache.fetch('usage_data', force: force_refresh, expires_in: 2.weeks) { uncached_data }
@@ -73,12 +75,9 @@ module Gitlab
issues: count(Issue),
keys: count(Key),
label_lists: count(List.label),
- labels: count(Label),
lfs_objects: count(LfsObject),
- merge_requests: count(MergeRequest),
milestone_lists: count(List.milestone),
milestones: count(Milestone),
- notes: count(Note),
pages_domains: count(PagesDomain),
projects: count(Project),
projects_imported_from_github: count(Project.where(import_type: 'github')),
@@ -86,10 +85,11 @@ module Gitlab
releases: count(Release),
remote_mirrors: count(RemoteMirror),
snippets: count(Snippet),
+ suggestions: count(Suggestion),
todos: count(Todo),
uploads: count(Upload),
web_hooks: count(WebHook)
- }.merge(services_usage)
+ }.merge(services_usage).merge(approximate_counts)
}
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -164,6 +164,16 @@ module Gitlab
fallback
end
# rubocop: enable CodeReuse/ActiveRecord
+
+ def approximate_counts
+ approx_counts = Gitlab::Database::Count.approximate_counts(APPROXIMATE_COUNT_MODELS)
+
+ APPROXIMATE_COUNT_MODELS.each_with_object({}) do |model, result|
+ key = model.name.underscore.pluralize.to_sym
+
+ result[key] = approx_counts[model] || -1
+ end
+ end
end
end
end
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index e0e8f598ba4..a81cee0d6d2 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -4,6 +4,15 @@ module Gitlab
module Utils
extend self
+ # Ensure that the relative path will not traverse outside the base directory
+ def check_path_traversal!(path)
+ raise StandardError.new("Invalid path") if path.start_with?("..#{File::SEPARATOR}") ||
+ path.include?("#{File::SEPARATOR}..#{File::SEPARATOR}") ||
+ path.end_with?("#{File::SEPARATOR}..")
+
+ path
+ end
+
# Run system command without outputting to stdout.
#
# @param cmd [Array<String>]
@@ -51,7 +60,7 @@ module Gitlab
# Converts newlines into HTML line break elements
def nlbr(str)
- ActionView::Base.full_sanitizer.sanitize(str, tags: []).gsub(/\r?\n/, '<br>').html_safe
+ ActionView::Base.full_sanitizer.sanitize(+str, tags: []).gsub(/\r?\n/, '<br>').html_safe
end
def remove_line_breaks(str)
diff --git a/lib/gitlab/utils/override.rb b/lib/gitlab/utils/override.rb
index c412961ea3f..c87e97d0213 100644
--- a/lib/gitlab/utils/override.rb
+++ b/lib/gitlab/utils/override.rb
@@ -4,16 +4,11 @@ module Gitlab
module Utils
module Override
class Extension
- def self.verify_class!(klass, method_name)
- instance_method_defined?(klass, method_name) ||
- raise(
- NotImplementedError.new(
- "#{klass}\##{method_name} doesn't exist!"))
- end
-
- def self.instance_method_defined?(klass, name, include_super: true)
- klass.instance_methods(include_super).include?(name) ||
- klass.private_instance_methods(include_super).include?(name)
+ def self.verify_class!(klass, method_name, arity)
+ extension = new(klass)
+ parents = extension.parents_for(klass)
+ extension.verify_method!(
+ klass: klass, parents: parents, method_name: method_name, sub_method_arity: arity)
end
attr_reader :subject
@@ -22,35 +17,77 @@ module Gitlab
@subject = subject
end
- def add_method_name(method_name)
- method_names << method_name
- end
-
- def add_class(klass)
- classes << klass
+ def parents_for(klass)
+ index = klass.ancestors.index(subject)
+ klass.ancestors.drop(index + 1)
end
def verify!
classes.each do |klass|
- index = klass.ancestors.index(subject)
- parents = klass.ancestors.drop(index + 1)
-
- method_names.each do |method_name|
- parents.any? do |parent|
- self.class.instance_method_defined?(
- parent, method_name, include_super: false)
- end ||
- raise(
- NotImplementedError.new(
- "#{klass}\##{method_name} doesn't exist!"))
+ parents = parents_for(klass)
+
+ method_names.each_pair do |method_name, arity|
+ verify_method!(
+ klass: klass,
+ parents: parents,
+ method_name: method_name,
+ sub_method_arity: arity)
end
end
end
+ def verify_method!(klass:, parents:, method_name:, sub_method_arity:)
+ overridden_parent = parents.find do |parent|
+ instance_method_defined?(parent, method_name)
+ end
+
+ raise NotImplementedError.new("#{klass}\##{method_name} doesn't exist!") unless overridden_parent
+
+ super_method_arity = find_direct_method(overridden_parent, method_name).arity
+
+ unless arity_compatible?(sub_method_arity, super_method_arity)
+ raise NotImplementedError.new("#{subject}\##{method_name} has arity of #{sub_method_arity}, but #{overridden_parent}\##{method_name} has arity of #{super_method_arity}")
+ end
+ end
+
+ def add_method_name(method_name, arity = nil)
+ method_names[method_name] = arity
+ end
+
+ def add_class(klass)
+ classes << klass
+ end
+
+ def verify_override?(method_name)
+ method_names.has_key?(method_name)
+ end
+
private
+ def instance_method_defined?(klass, name)
+ klass.instance_methods(false).include?(name) ||
+ klass.private_instance_methods(false).include?(name)
+ end
+
+ def find_direct_method(klass, name)
+ method = klass.instance_method(name)
+ method = method.super_method until method && klass == method.owner
+ method
+ end
+
+ def arity_compatible?(sub_method_arity, super_method_arity)
+ if sub_method_arity >= 0 && super_method_arity >= 0
+ # Regular arguments
+ sub_method_arity == super_method_arity
+ else
+ # It's too complex to check this case, just allow sub-method having negative arity
+ # But we don't allow sub_method_arity > 0 yet super_method_arity < 0
+ sub_method_arity < 0
+ end
+ end
+
def method_names
- @method_names ||= []
+ @method_names ||= {}
end
def classes
@@ -80,11 +117,21 @@ module Gitlab
def override(method_name)
return unless ENV['STATIC_VERIFICATION']
+ Override.extensions[self] ||= Extension.new(self)
+ Override.extensions[self].add_method_name(method_name)
+ end
+
+ def method_added(method_name)
+ super
+
+ return unless ENV['STATIC_VERIFICATION']
+ return unless Override.extensions[self]&.verify_override?(method_name)
+
+ method_arity = instance_method(method_name).arity
if is_a?(Class)
- Extension.verify_class!(self, method_name)
+ Extension.verify_class!(self, method_name, method_arity)
else # We delay the check for modules
- Override.extensions[self] ||= Extension.new(self)
- Override.extensions[self].add_method_name(method_name)
+ Override.extensions[self].add_method_name(method_name, method_arity)
end
end
diff --git a/lib/gitlab/wiki_file_finder.rb b/lib/gitlab/wiki_file_finder.rb
index a00cd65594c..5303b3582ab 100644
--- a/lib/gitlab/wiki_file_finder.rb
+++ b/lib/gitlab/wiki_file_finder.rb
@@ -2,6 +2,8 @@
module Gitlab
class WikiFileFinder < FileFinder
+ BATCH_SIZE = 100
+
attr_reader :repository
def initialize(project, ref)
@@ -12,13 +14,11 @@ module Gitlab
private
- def search_filenames(query, except)
+ def search_filenames(query)
safe_query = Regexp.escape(query.tr(' ', '-'))
safe_query = Regexp.new(safe_query, Regexp::IGNORECASE)
filenames = repository.ls_files(ref)
- filenames.delete_if { |filename| except.include?(filename) } unless except.empty?
-
filenames.grep(safe_query).first(BATCH_SIZE)
end
end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index e1f777e9cd1..265f6213a99 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -13,6 +13,7 @@ module Gitlab
INTERNAL_API_REQUEST_HEADER = 'Gitlab-Workhorse-Api-Request'.freeze
NOTIFICATION_CHANNEL = 'workhorse:notifications'.freeze
ALLOWED_GIT_HTTP_ACTIONS = %w[git_receive_pack git_upload_pack info_refs].freeze
+ DETECT_HEADER = 'Gitlab-Workhorse-Detect-Content-Type'.freeze
# Supposedly the effective key size for HMAC-SHA256 is 256 bits, i.e. 32
# bytes https://tools.ietf.org/html/rfc4868#section-2.6
@@ -30,7 +31,6 @@ module Gitlab
GL_USERNAME: user&.username,
ShowAllRefs: show_all_refs,
Repository: repository.gitaly_repository.to_h,
- RepoPath: 'ignored but not allowed to be empty in gitlab-workhorse',
GitConfigOptions: [],
GitalyServer: {
address: Gitlab::GitalyClient.address(project.repository_storage),
diff --git a/lib/mysql_zero_date.rb b/lib/mysql_zero_date.rb
index 216560148fa..f36610abf8f 100644
--- a/lib/mysql_zero_date.rb
+++ b/lib/mysql_zero_date.rb
@@ -17,4 +17,4 @@ module MysqlZeroDate
end
end
-ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.prepend(MysqlZeroDate) if Gitlab.rails5?
+ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.prepend(MysqlZeroDate)
diff --git a/lib/omni_auth/strategies/jwt.rb b/lib/omni_auth/strategies/jwt.rb
index a792903fde7..2f3d477a591 100644
--- a/lib/omni_auth/strategies/jwt.rb
+++ b/lib/omni_auth/strategies/jwt.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'omniauth'
+require 'openssl'
require 'jwt'
module OmniAuth
@@ -37,7 +38,19 @@ module OmniAuth
end
def decoded
- @decoded ||= ::JWT.decode(request.params['jwt'], options.secret, options.algorithm).first
+ secret =
+ case options.algorithm
+ when *%w[RS256 RS384 RS512]
+ OpenSSL::PKey::RSA.new(options.secret).public_key
+ when *%w[ES256 ES384 ES512]
+ OpenSSL::PKey::EC.new(options.secret).tap { |key| key.private_key = nil }
+ when *%w(HS256 HS384 HS512)
+ options.secret
+ else
+ raise NotImplementedError, "Unsupported algorithm: #{options.algorithm}"
+ end
+
+ @decoded ||= ::JWT.decode(request.params['jwt'], secret, true, { algorithm: options.algorithm }).first
(options.required_claims || []).each do |field|
raise ClaimInvalid, "Missing required '#{field}' claim" unless @decoded.key?(field.to_s)
@@ -45,7 +58,7 @@ module OmniAuth
raise ClaimInvalid, "Missing required 'iat' claim" if options.valid_within && !@decoded["iat"]
- if options.valid_within && (Time.now.to_i - @decoded["iat"]).abs > options.valid_within
+ if options.valid_within && (Time.now.to_i - @decoded["iat"]).abs > options.valid_within.to_i
raise ClaimInvalid, "'iat' timestamp claim is too skewed from present"
end
diff --git a/lib/rails4_migration_version.rb b/lib/rails4_migration_version.rb
deleted file mode 100644
index ae48734dfad..00000000000
--- a/lib/rails4_migration_version.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# rubocop:disable Naming/FileName
-# frozen_string_literal: true
-
-# When switching to rails 5, we added migration version to all migration
-# classes. This patch makes it possible to run versioned migrations
-# also with rails 4
-
-unless Gitlab.rails5?
- module ActiveRecord
- class Migration
- def self.[](version)
- Migration
- end
- end
- end
-end
diff --git a/lib/tasks/gitlab/storage.rake b/lib/tasks/gitlab/storage.rake
index f539b1df955..09dc3aa9882 100644
--- a/lib/tasks/gitlab/storage.rake
+++ b/lib/tasks/gitlab/storage.rake
@@ -2,6 +2,12 @@ namespace :gitlab do
namespace :storage do
desc 'GitLab | Storage | Migrate existing projects to Hashed Storage'
task migrate_to_hashed: :environment do
+ if Gitlab::Database.read_only?
+ warn 'This task requires database write access. Exiting.'
+
+ next
+ end
+
storage_migrator = Gitlab::HashedStorage::Migrator.new
helper = Gitlab::HashedStorage::RakeHelper
@@ -9,7 +15,7 @@ namespace :gitlab do
project = Project.with_unmigrated_storage.find_by(id: helper.range_from)
unless project
- puts "There are no projects requiring storage migration with ID=#{helper.range_from}"
+ warn "There are no projects requiring storage migration with ID=#{helper.range_from}"
next
end
@@ -23,7 +29,7 @@ namespace :gitlab do
legacy_projects_count = Project.with_unmigrated_storage.count
if legacy_projects_count == 0
- puts 'There are no projects requiring storage migration. Nothing to do!'
+ warn 'There are no projects requiring storage migration. Nothing to do!'
next
end
diff --git a/lib/tasks/gitlab/web_hook.rake b/lib/tasks/gitlab/web_hook.rake
index 5a1c8006052..15cec80b6a6 100644
--- a/lib/tasks/gitlab/web_hook.rake
+++ b/lib/tasks/gitlab/web_hook.rake
@@ -25,11 +25,22 @@ namespace :gitlab do
web_hook_url = ENV['URL']
namespace_path = ENV['NAMESPACE']
- projects = find_projects(namespace_path)
- project_ids = projects.pluck(:id)
+ web_hooks = find_web_hooks(namespace_path)
puts "Removing webhooks with the url '#{web_hook_url}' ... "
- count = WebHook.where(url: web_hook_url, project_id: project_ids, type: 'ProjectHook').delete_all
+
+ # FIXME: Hook URLs are now encrypted, so there is no way to efficiently
+ # find them all in SQL. For now, check them in Ruby. If this is too slow,
+ # we could consider storing a hash of the URL alongside the encrypted
+ # value to speed up searches
+ count = 0
+ web_hooks.find_each do |hook|
+ next unless hook.url == web_hook_url
+
+ hook.destroy!
+ count += 1
+ end
+
puts "#{count} webhooks were removed."
end
@@ -37,29 +48,37 @@ namespace :gitlab do
task list: :environment do
namespace_path = ENV['NAMESPACE']
- projects = find_projects(namespace_path)
- web_hooks = projects.all.map(&:hooks).flatten
- web_hooks.each do |hook|
+ web_hooks = find_web_hooks(namespace_path)
+ web_hooks.find_each do |hook|
puts "#{hook.project.name.truncate(20).ljust(20)} -> #{hook.url}"
end
- puts "\n#{web_hooks.size} webhooks found."
+ puts "\n#{web_hooks.count} webhooks found."
end
end
def find_projects(namespace_path)
if namespace_path.blank?
Project
- elsif namespace_path == '/'
- Project.in_namespace(nil)
else
- namespace = Namespace.where(path: namespace_path).first
- if namespace
- Project.in_namespace(namespace.id)
- else
+ namespace = Namespace.find_by_full_path(namespace_path)
+
+ unless namespace
puts "Namespace not found: #{namespace_path}".color(:red)
exit 2
end
+
+ Project.in_namespace(namespace.id)
+ end
+ end
+
+ def find_web_hooks(namespace_path)
+ if namespace_path.blank?
+ ProjectHook
+ else
+ project_ids = find_projects(namespace_path).select(:id)
+
+ ProjectHook.where(project_id: project_ids)
end
end
end
diff --git a/lib/version_check.rb b/lib/version_check.rb
index ccf7bb493db..c9f102f6b19 100644
--- a/lib/version_check.rb
+++ b/lib/version_check.rb
@@ -5,16 +5,17 @@ require "base64"
# This class is used to build image URL to
# check if it is a new version for update
class VersionCheck
- def data
+ def self.data
{ version: Gitlab::VERSION }
end
- def url
+ def self.url
encoded_data = Base64.urlsafe_encode64(data.to_json)
+
"#{host}?gitlab_info=#{encoded_data}"
end
- def host
+ def self.host
'https://version.gitlab.com/check.svg'
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 47463f4894e..226ca69000e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -141,6 +141,24 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
+msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{strong_start}%{commit_count}%{strong_end} Commit"
+msgid_plural "%{strong_start}%{commit_count}%{strong_end} Commits"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "%{strong_start}%{human_size}%{strong_end} Files"
+msgstr ""
+
+msgid "%{strong_start}%{tag_count}%{strong_end} Tag"
+msgid_plural "%{strong_start}%{tag_count}%{strong_end} Tags"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%{text} %{files}"
msgid_plural "%{text} %{files} files"
msgstr[0] ""
@@ -225,7 +243,7 @@ msgstr ""
msgid "2FA enabled"
msgstr ""
-msgid "403|Please contact your GitLab administrator to get the permission."
+msgid "403|Please contact your GitLab administrator to get permission."
msgstr ""
msgid "403|You don't have the permission to access this page."
@@ -333,16 +351,16 @@ msgstr ""
msgid "Activity"
msgstr ""
-msgid "Add Changelog"
+msgid "Add CHANGELOG"
msgstr ""
-msgid "Add Contribution guide"
+msgid "Add CONTRIBUTING"
msgstr ""
msgid "Add Kubernetes cluster"
msgstr ""
-msgid "Add Readme"
+msgid "Add README"
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
@@ -384,9 +402,6 @@ msgstr ""
msgid "Admin Overview"
msgstr ""
-msgid "Admin area"
-msgstr ""
-
msgid "AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide \"Ghost-user\". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -420,9 +435,15 @@ msgstr ""
msgid "AdminProjects|Delete project"
msgstr ""
+msgid "AdminSettings|Environment variables are protected by default"
+msgstr ""
+
msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
msgstr ""
+msgid "AdminSettings|When creating a new environment variable it will be protected by default."
+msgstr ""
+
msgid "AdminUsers|Block user"
msgstr ""
@@ -507,6 +528,9 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
+msgid "An error occured while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred creating the new branch."
msgstr ""
@@ -639,6 +663,12 @@ msgstr ""
msgid "Applications"
msgstr ""
+msgid "Applied"
+msgstr ""
+
+msgid "Apply suggestion"
+msgstr ""
+
msgid "Apr"
msgstr ""
@@ -687,6 +717,9 @@ msgstr ""
msgid "Ask your group maintainer to set up a group Runner."
msgstr ""
+msgid "Assets"
+msgstr ""
+
msgid "Assign custom color like #FF0000"
msgstr ""
@@ -828,6 +861,9 @@ msgstr ""
msgid "Available specific runners"
msgstr ""
+msgid "Avatar for %{assigneeName}"
+msgstr ""
+
msgid "Avatar will be removed. Are you sure?"
msgstr ""
@@ -945,11 +981,6 @@ msgstr ""
msgid "Branch %{branchName} was not found in this project's repository."
msgstr ""
-msgid "Branch (%{branch_count})"
-msgid_plural "Branches (%{branch_count})"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}"
msgstr ""
@@ -1103,6 +1134,9 @@ msgstr ""
msgid "ByAuthor|by"
msgstr ""
+msgid "CHANGELOG"
+msgstr ""
+
msgid "CI / CD"
msgstr ""
@@ -1160,6 +1194,9 @@ msgstr ""
msgid "CICD|instance enabled"
msgstr ""
+msgid "CONTRIBUTING"
+msgstr ""
+
msgid "Callback URL"
msgstr ""
@@ -1202,9 +1239,6 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
-msgid "Changelog"
-msgstr ""
-
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr ""
@@ -1244,6 +1278,9 @@ msgstr ""
msgid "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."
msgstr ""
+msgid "Choose a file"
+msgstr ""
+
msgid "Choose a template..."
msgstr ""
@@ -1385,9 +1422,18 @@ msgstr ""
msgid "Clients"
msgstr ""
+msgid "Clone"
+msgstr ""
+
msgid "Clone repository"
msgstr ""
+msgid "Clone with %{http_label}"
+msgstr ""
+
+msgid "Clone with SSH"
+msgstr ""
+
msgid "Close"
msgstr ""
@@ -1445,6 +1491,9 @@ msgstr ""
msgid "ClusterIntegration|Cert-Manager"
msgstr ""
+msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
+msgstr ""
+
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
msgstr ""
@@ -1454,6 +1503,9 @@ msgstr ""
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr ""
+msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
+msgstr ""
+
msgid "ClusterIntegration|Copy API URL"
msgstr ""
@@ -1496,6 +1548,15 @@ msgstr ""
msgid "ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab's Google Kubernetes Engine Integration."
msgstr ""
+msgid "ClusterIntegration|Failed to configure Google Kubernetes Engine Cluster: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to request to Google Cloud Platform: %{message}"
+msgstr ""
+
+msgid "ClusterIntegration|Failed to run Kubeclient: %{message}"
+msgstr ""
+
msgid "ClusterIntegration|Fetching machine types"
msgstr ""
@@ -1559,6 +1620,12 @@ msgstr ""
msgid "ClusterIntegration|Integration status"
msgstr ""
+msgid "ClusterIntegration|Issuer Email"
+msgstr ""
+
+msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
+msgstr ""
+
msgid "ClusterIntegration|Jupyter Hostname"
msgstr ""
@@ -1778,9 +1845,6 @@ msgstr ""
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr ""
-msgid "ClusterIntegration|cert-manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing cert-manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up to date."
-msgstr ""
-
msgid "ClusterIntegration|check the pricing here"
msgstr ""
@@ -1808,6 +1872,9 @@ msgstr ""
msgid "Collapse sidebar"
msgstr ""
+msgid "Command line instructions"
+msgstr ""
+
msgid "Comment"
msgstr ""
@@ -1828,11 +1895,6 @@ msgid_plural "Commits"
msgstr[0] ""
msgstr[1] ""
-msgid "Commit (%{commit_count})"
-msgid_plural "Commits (%{commit_count})"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "Commit Message"
msgstr ""
@@ -2016,9 +2078,6 @@ msgstr ""
msgid "Contribution Charts"
msgstr ""
-msgid "Contribution guide"
-msgstr ""
-
msgid "Contributions for <strong>%{calendar_date}</strong>"
msgstr ""
@@ -2043,10 +2102,10 @@ msgstr ""
msgid "ConvDev Index"
msgstr ""
-msgid "Copy %{protocol} clone URL"
+msgid "Copy %{http_label} clone URL"
msgstr ""
-msgid "Copy HTTPS clone URL"
+msgid "Copy %{protocol} clone URL"
msgstr ""
msgid "Copy ID to clipboard"
@@ -2109,6 +2168,9 @@ msgstr ""
msgid "Create a new issue"
msgstr ""
+msgid "Create a new repository"
+msgstr ""
+
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
@@ -2546,6 +2608,9 @@ msgstr ""
msgid "Download"
msgstr ""
+msgid "Download asset"
+msgstr ""
+
msgid "Download tar"
msgstr ""
@@ -2621,6 +2686,9 @@ msgstr ""
msgid "Embed"
msgstr ""
+msgid "Empty file"
+msgstr ""
+
msgid "Enable"
msgstr ""
@@ -2681,6 +2749,15 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
+msgid "Environment variables"
+msgstr ""
+
+msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
+msgstr ""
+
+msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
+msgstr ""
+
msgid "Environments"
msgstr ""
@@ -2867,6 +2944,12 @@ msgstr ""
msgid "Everyone can contribute"
msgstr ""
+msgid "Existing Git repository"
+msgstr ""
+
+msgid "Existing folder"
+msgstr ""
+
msgid "Expand"
msgstr ""
@@ -2879,6 +2962,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
+msgid "Expired %{expiredOn}"
+msgstr ""
+
msgid "Expires in %{expires_at}"
msgstr ""
@@ -2939,6 +3025,9 @@ msgstr ""
msgid "Failed to update issues, please try again."
msgstr ""
+msgid "Failed to upload object map file"
+msgstr ""
+
msgid "Failure"
msgstr ""
@@ -2972,9 +3061,6 @@ msgstr ""
msgid "Files"
msgstr ""
-msgid "Files (%{human_size})"
-msgstr ""
-
msgid "Filter"
msgstr ""
@@ -3050,6 +3136,9 @@ msgstr ""
msgid "Forking in progress"
msgstr ""
+msgid "Forks"
+msgstr ""
+
msgid "Format"
msgstr ""
@@ -3098,9 +3187,15 @@ msgstr ""
msgid "Geo"
msgstr ""
+msgid "Getting started with releases"
+msgstr ""
+
msgid "Git"
msgstr ""
+msgid "Git global setup"
+msgstr ""
+
msgid "Git repository URL"
msgstr ""
@@ -3370,6 +3465,9 @@ msgid_plural "Hide values"
msgstr[0] ""
msgstr[1] ""
+msgid "Hide values"
+msgstr ""
+
msgid "Hide whitespace changes"
msgstr ""
@@ -3544,6 +3642,9 @@ msgstr ""
msgid "Input your repository URL"
msgstr ""
+msgid "Insert suggestion"
+msgstr ""
+
msgid "Install GitLab Runner"
msgstr ""
@@ -3673,7 +3774,7 @@ msgstr ""
msgid "Job|The artifacts were removed"
msgstr ""
-msgid "Job|The artifacts will be removed in"
+msgid "Job|The artifacts will be removed"
msgstr ""
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
@@ -3798,12 +3899,18 @@ msgstr ""
msgid "Latest changes"
msgstr ""
+msgid "Latest pipeline for this branch"
+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 ""
+
msgid "Learn more about Kubernetes"
msgstr ""
@@ -3863,6 +3970,9 @@ msgstr ""
msgid "Loading..."
msgstr ""
+msgid "Loading…"
+msgstr ""
+
msgid "Lock"
msgstr ""
@@ -3998,9 +4108,6 @@ msgstr ""
msgid "Merge Request"
msgstr ""
-msgid "Merge Request:"
-msgstr ""
-
msgid "Merge Requests"
msgstr ""
@@ -4040,6 +4147,9 @@ msgstr ""
msgid "MergeRequests|started a discussion on %{linkStart}the diff%{linkEnd}"
msgstr ""
+msgid "MergeRequests|started a discussion on an outdated change in commit %{linkStart}%{commitId}%{linkEnd}"
+msgstr ""
+
msgid "MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}"
msgstr ""
@@ -4309,6 +4419,9 @@ msgstr ""
msgid "No changes"
msgstr ""
+msgid "No changes between %{ref_start}%{source_branch}%{ref_end} and %{ref_start}%{target_branch}%{ref_end}"
+msgstr ""
+
msgid "No connection could be made to a Gitaly Server, please check your logs!"
msgstr ""
@@ -4327,6 +4440,9 @@ msgstr ""
msgid "No file chosen"
msgstr ""
+msgid "No file selected"
+msgstr ""
+
msgid "No files found."
msgstr ""
@@ -4423,6 +4539,12 @@ msgstr ""
msgid "Notification events"
msgstr ""
+msgid "Notification setting"
+msgstr ""
+
+msgid "Notification setting - %{notification_title}"
+msgstr ""
+
msgid "NotificationEvent|Close issue"
msgstr ""
@@ -4524,6 +4646,9 @@ msgstr ""
msgid "Open"
msgstr ""
+msgid "Open Documentation"
+msgstr ""
+
msgid "Open in Xcode"
msgstr ""
@@ -4902,6 +5027,9 @@ msgstr ""
msgid "Profiles| You are going to change the username %{currentUsernameBold} to %{newUsernameBold}. Profile and projects will be redirected to the %{newUsername} namespace but this redirect will expire once the %{currentUsername} namespace is registered by another user or group. Please update your Git repository remotes as soon as possible."
msgstr ""
+msgid "Profiles|@username"
+msgstr ""
+
msgid "Profiles|Account scheduled for removal."
msgstr ""
@@ -4923,7 +5051,10 @@ msgstr ""
msgid "Profiles|Choose file..."
msgstr ""
-msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information."
+msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information"
+msgstr ""
+
+msgid "Profiles|City, country"
msgstr ""
msgid "Profiles|Clear status"
@@ -4956,6 +5087,9 @@ msgstr ""
msgid "Profiles|Edit Profile"
msgstr ""
+msgid "Profiles|Enter your name, so people you know can recognize you"
+msgstr ""
+
msgid "Profiles|Invalid password"
msgstr ""
@@ -4995,7 +5129,7 @@ msgstr ""
msgid "Profiles|Some options are unavailable for LDAP accounts"
msgstr ""
-msgid "Profiles|Tell us about yourself in fewer than 250 characters."
+msgid "Profiles|Tell us about yourself in fewer than 250 characters"
msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
@@ -5004,7 +5138,7 @@ msgstr ""
msgid "Profiles|This doesn't look like a public SSH key, are you sure you want to add it?"
msgstr ""
-msgid "Profiles|This email will be displayed on your public profile."
+msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
@@ -5013,10 +5147,10 @@ msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This feature is experimental and translations are not complete yet."
+msgid "Profiles|This feature is experimental and translations are not complete yet"
msgstr ""
-msgid "Profiles|This information will appear on your profile."
+msgid "Profiles|This information will appear on your profile"
msgstr ""
msgid "Profiles|Type your %{confirmationValue} to confirm:"
@@ -5043,10 +5177,10 @@ msgstr ""
msgid "Profiles|Username successfully changed"
msgstr ""
-msgid "Profiles|Website"
+msgid "Profiles|What's your status?"
msgstr ""
-msgid "Profiles|What's your status?"
+msgid "Profiles|Who you represent or work for"
msgstr ""
msgid "Profiles|You can change your avatar here"
@@ -5067,16 +5201,19 @@ msgstr ""
msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account."
msgstr ""
+msgid "Profiles|Your LinkedIn profile name from linkedin.com/in/profilename"
+msgstr ""
+
msgid "Profiles|Your account is currently an owner in these groups:"
msgstr ""
-msgid "Profiles|Your email address was automatically set based on your %{provider_label} account."
+msgid "Profiles|Your email address was automatically set based on your %{provider_label} account"
msgstr ""
-msgid "Profiles|Your location was automatically set based on your %{provider_label} account."
+msgid "Profiles|Your location was automatically set based on your %{provider_label} account"
msgstr ""
-msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you."
+msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you"
msgstr ""
msgid "Profiles|Your status"
@@ -5085,6 +5222,12 @@ msgstr ""
msgid "Profiles|e.g. My MacBook key"
msgstr ""
+msgid "Profiles|username"
+msgstr ""
+
+msgid "Profiles|website.com"
+msgstr ""
+
msgid "Profiles|your account"
msgstr ""
@@ -5334,6 +5477,9 @@ msgstr ""
msgid "Quick actions can be used in the issues description and comment boxes."
msgstr ""
+msgid "README"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -5343,9 +5489,6 @@ msgstr ""
msgid "Read more about project permissions <strong>%{link_to_help}</strong>"
msgstr ""
-msgid "Readme"
-msgstr ""
-
msgid "Real-time features"
msgstr ""
@@ -5402,6 +5545,12 @@ msgstr ""
msgid "Related merge requests"
msgstr ""
+msgid "Releases"
+msgstr ""
+
+msgid "Releases mark specific points in a project's development history, communicate information about the type of change, and deliver on prepared, often compiled, versions of the software to be reused elsewhere. Currently, releases can only be created through the API."
+msgstr ""
+
msgid "Remind later"
msgstr ""
@@ -5483,6 +5632,12 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository cleanup"
+msgstr ""
+
+msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
+msgstr ""
+
msgid "Repository maintenance"
msgstr ""
@@ -5537,6 +5692,9 @@ msgstr ""
msgid "Response metrics (HA Proxy)"
msgstr ""
+msgid "Response metrics (NGINX Ingress VTS)"
+msgstr ""
+
msgid "Response metrics (NGINX Ingress)"
msgstr ""
@@ -5555,14 +5713,14 @@ msgstr ""
msgid "Retry verification"
msgstr ""
-msgid "Reveal Variables"
-msgstr ""
-
msgid "Reveal value"
msgid_plural "Reveal values"
msgstr[0] ""
msgstr[1] ""
+msgid "Reveal values"
+msgstr ""
+
msgid "Revert this commit"
msgstr ""
@@ -5809,6 +5967,45 @@ msgstr ""
msgid "Server version"
msgstr ""
+msgid "Serverless"
+msgstr ""
+
+msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
+msgstr ""
+
+msgid "Serverless|An error occurred while retrieving serverless components"
+msgstr ""
+
+msgid "Serverless|Domain"
+msgstr ""
+
+msgid "Serverless|Function"
+msgstr ""
+
+msgid "Serverless|Getting started with serverless"
+msgstr ""
+
+msgid "Serverless|If you believe none of these apply, please check back later as the function data may be in the process of becoming available."
+msgstr ""
+
+msgid "Serverless|Install Knative"
+msgstr ""
+
+msgid "Serverless|Last Update"
+msgstr ""
+
+msgid "Serverless|Learn more about Serverless"
+msgstr ""
+
+msgid "Serverless|No functions available"
+msgstr ""
+
+msgid "Serverless|Runtime"
+msgstr ""
+
+msgid "Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:"
+msgstr ""
+
msgid "Service Templates"
msgstr ""
@@ -5961,6 +6158,9 @@ msgstr ""
msgid "Something went wrong when toggling the button"
msgstr ""
+msgid "Something went wrong while applying the suggestion. Please try again."
+msgstr ""
+
msgid "Something went wrong while closing the %{issuable}. Please try again later"
msgstr ""
@@ -6150,12 +6350,18 @@ msgstr ""
msgid "Starred projects"
msgstr ""
+msgid "Stars"
+msgstr ""
+
msgid "Start a %{new_merge_request} with these changes"
msgstr ""
msgid "Start and due date"
msgstr ""
+msgid "Start cleanup"
+msgstr ""
+
msgid "Start date"
msgstr ""
@@ -6165,6 +6371,12 @@ msgstr ""
msgid "Started"
msgstr ""
+msgid "Started %{startsIn}"
+msgstr ""
+
+msgid "Starts %{startsIn}"
+msgstr ""
+
msgid "Starts at (UTC)"
msgstr ""
@@ -6216,6 +6428,9 @@ msgstr ""
msgid "Subscribed"
msgstr ""
+msgid "Suggested change"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
@@ -6231,10 +6446,8 @@ msgstr ""
msgid "System metrics (Kubernetes)"
msgstr ""
-msgid "Tag (%{tag_count})"
-msgid_plural "Tags (%{tag_count})"
-msgstr[0] ""
-msgstr[1] ""
+msgid "Tag"
+msgstr ""
msgid "Tags"
msgstr ""
@@ -6371,6 +6584,9 @@ msgstr ""
msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
msgstr ""
+msgid "The maximum file size allowed is %{max_attachment_size}mb"
+msgstr ""
+
msgid "The maximum file size allowed is 200KB."
msgstr ""
@@ -6605,6 +6821,9 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
+msgid "This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>"
+msgstr ""
+
msgid "This project"
msgstr ""
@@ -6942,10 +7161,13 @@ msgstr ""
msgid "Trending"
msgstr ""
-msgid "Trigger"
+msgid "Trigger this manual action"
+msgstr ""
+
+msgid "Trigger token:"
msgstr ""
-msgid "Trigger this manual action"
+msgid "Trigger variables:"
msgstr ""
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
@@ -7050,6 +7272,9 @@ msgstr ""
msgid "Upload file"
msgstr ""
+msgid "Upload object map"
+msgstr ""
+
msgid "UploadLink|click to upload"
msgstr ""
@@ -7140,12 +7365,6 @@ msgstr ""
msgid "Users requesting access to"
msgstr ""
-msgid "Variables"
-msgstr ""
-
-msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want."
-msgstr ""
-
msgid "Various container registry settings."
msgstr ""
@@ -7678,6 +7897,9 @@ msgstr ""
msgid "importing"
msgstr ""
+msgid "in"
+msgstr ""
+
msgid "issue boards"
msgstr ""
@@ -7926,6 +8148,9 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
+msgid "should be higher than %{access} inherited membership from group %{group_name}"
+msgstr ""
+
msgid "source"
msgstr ""
diff --git a/package.json b/package.json
index 680a5bb1cde..1dc9797c59b 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
"eslint": "eslint --max-warnings 0 --report-unused-disable-directives --ext .js,.vue .",
"eslint-fix": "eslint --max-warnings 0 --report-unused-disable-directives --ext .js,.vue --fix .",
"eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html --no-inline-config .",
- "jest": "BABEL_ENV=jest jest --config=config/jest.config.js",
+ "jest": "BABEL_ENV=jest jest",
"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",
@@ -25,8 +25,9 @@
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-syntax-import-meta": "^7.0.0",
"@babel/preset-env": "^7.1.0",
- "@gitlab/svgs": "^1.40.0",
- "@gitlab/ui": "^1.11.0",
+ "@gitlab/csslab": "^1.8.0",
+ "@gitlab/svgs": "^1.43.0",
+ "@gitlab/ui": "^1.18.0",
"apollo-boost": "^0.1.20",
"apollo-client": "^2.4.5",
"autosize": "^4.0.0",
@@ -57,6 +58,7 @@
"diff": "^3.4.0",
"document-register-element": "1.3.0",
"dropzone": "^4.2.0",
+ "echarts": "^4.2.0-rc.2",
"emoji-unicode-version": "^0.2.1",
"exports-loader": "^0.7.0",
"file-loader": "^2.0.0",
@@ -116,7 +118,7 @@
"xterm": "^3.5.0"
},
"devDependencies": {
- "@gitlab/eslint-config": "^1.2.0",
+ "@gitlab/eslint-config": "^1.4.0",
"@vue/test-utils": "^1.0.0-beta.25",
"axios-mock-adapter": "^1.15.0",
"babel-core": "^7.0.0-bridge",
@@ -129,10 +131,10 @@
"babel-types": "^6.26.0",
"chalk": "^2.4.1",
"commander": "^2.18.0",
- "eslint": "~5.6.0",
+ "eslint": "~5.9.0",
"eslint-import-resolver-jest": "^2.1.1",
"eslint-import-resolver-webpack": "^0.10.1",
- "eslint-plugin-html": "4.0.5",
+ "eslint-plugin-html": "5.0.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jasmine": "^2.10.1",
"eslint-plugin-jest": "^22.1.0",
@@ -155,6 +157,11 @@
"karma-webpack": "^4.0.0-beta.0",
"nodemon": "^1.18.4",
"prettier": "1.15.2",
- "webpack-dev-server": "^3.1.10"
+ "vue-jest": "^3.0.1",
+ "webpack-dev-server": "^3.1.10",
+ "yarn-deduplicate": "^1.0.5"
+ },
+ "engines": {
+ "yarn": "^1.10.0"
}
}
diff --git a/qa/qa.rb b/qa/qa.rb
index aa0b78b37e8..bf05b6b53ca 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -158,6 +158,10 @@ module QA
autoload :Activity, 'qa/page/project/activity'
autoload :Menu, 'qa/page/project/menu'
+ module Commit
+ autoload :Show, 'qa/page/project/commit/show'
+ end
+
module Import
autoload :Github, 'qa/page/project/import/github'
end
@@ -184,6 +188,7 @@ module QA
autoload :Runners, 'qa/page/project/settings/runners'
autoload :MergeRequest, 'qa/page/project/settings/merge_request'
autoload :Members, 'qa/page/project/settings/members'
+ autoload :MirroringRepositories, 'qa/page/project/settings/mirroring_repositories'
end
module Issue
@@ -272,6 +277,7 @@ module QA
#
module Component
autoload :ClonePanel, 'qa/page/component/clone_panel'
+ autoload :LegacyClonePanel, 'qa/page/component/legacy_clone_panel'
autoload :Dropzone, 'qa/page/component/dropzone'
autoload :GroupsFilter, 'qa/page/component/groups_filter'
autoload :Select2, 'qa/page/component/select2'
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 91e229c4c8c..5788dceaaae 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -15,7 +15,7 @@ module QA
def_delegators :evaluator, :view, :views
def refresh
- visit current_url
+ page.refresh
end
def wait(max: 60, time: 0.1, reload: true)
@@ -80,8 +80,8 @@ module QA
page.evaluate_script('xhr.status') == 200
end
- def find_element(name)
- find(element_selector_css(name))
+ def find_element(name, text_filter = nil, wait: Capybara.default_max_wait_time)
+ find(element_selector_css(name), wait: wait, text: text_filter)
end
def all_elements(name)
@@ -100,8 +100,20 @@ module QA
find_element(name).set(content)
end
- def has_element?(name)
- has_css?(element_selector_css(name))
+ def select_element(name, value)
+ element = find_element(name)
+
+ return if element.text.downcase.to_s == value.to_s
+
+ element.select value.to_s.capitalize
+ end
+
+ def has_element?(name, wait: Capybara.default_max_wait_time)
+ has_css?(element_selector_css(name), wait: wait)
+ end
+
+ def has_no_text?(text)
+ page.has_no_text? text
end
def within_element(name)
@@ -110,6 +122,12 @@ module QA
end
end
+ def within_element_by_index(name, index)
+ page.within all_elements(name)[index] do
+ yield
+ end
+ end
+
def scroll_to_element(name, *args)
scroll_to(element_selector_css(name), *args)
end
@@ -118,6 +136,10 @@ module QA
Page::Element.new(name).selector_css
end
+ def click_link_with_text(text)
+ click_link text
+ end
+
def self.path
raise NotImplementedError
end
diff --git a/qa/qa/page/component/clone_panel.rb b/qa/qa/page/component/clone_panel.rb
index 94e761b0e0c..d37b63c716a 100644
--- a/qa/qa/page/component/clone_panel.rb
+++ b/qa/qa/page/component/clone_panel.rb
@@ -5,26 +5,20 @@ module QA
module Component
module ClonePanel
def self.included(base)
- base.view 'app/views/shared/_clone_panel.html.haml' do
+ base.view 'app/views/projects/buttons/_clone.html.haml' do
element :clone_dropdown
- element :clone_options_dropdown, '.clone-options-dropdown' # rubocop:disable QA/ElementWithPattern
- element :project_repository_location, 'text_field_tag :project_clone' # rubocop:disable QA/ElementWithPattern
+ element :clone_options
+ element :ssh_clone_url
+ element :http_clone_url
end
end
- def choose_repository_clone_http
- choose_repository_clone('HTTP', 'http')
+ def repository_clone_http_location
+ repository_clone_location(:http_clone_url)
end
- def choose_repository_clone_ssh
- # It's not always beginning with ssh:// so detecting with @
- # would be more reliable because ssh would always contain it.
- # We can't use .git because HTTP also contain that part.
- choose_repository_clone('SSH', '@')
- end
-
- def repository_location
- Git::Location.new(find('#project_clone').value)
+ def repository_clone_ssh_location
+ repository_clone_location(:ssh_clone_url)
end
def wait_for_push
@@ -34,16 +28,13 @@ module QA
private
- def choose_repository_clone(kind, detect_text)
+ def repository_clone_location(kind)
wait(reload: false) do
click_element :clone_dropdown
- page.within('.clone-options-dropdown') do
- click_link(kind)
+ within_element :clone_options do
+ Git::Location.new(find_element(kind).value)
end
-
- # Ensure git clone textbox was updated
- repository_location.git_uri.include?(detect_text)
end
end
end
diff --git a/qa/qa/page/component/legacy_clone_panel.rb b/qa/qa/page/component/legacy_clone_panel.rb
new file mode 100644
index 00000000000..99132190f3f
--- /dev/null
+++ b/qa/qa/page/component/legacy_clone_panel.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module LegacyClonePanel
+ def self.included(base)
+ base.view 'app/views/shared/_clone_panel.html.haml' do
+ element :clone_dropdown
+ element :clone_options_dropdown, '.clone-options-dropdown' # rubocop:disable QA/ElementWithPattern
+ element :project_repository_location, 'text_field_tag :project_clone' # rubocop:disable QA/ElementWithPattern
+ end
+ end
+
+ def choose_repository_clone_http
+ choose_repository_clone('HTTP', 'http')
+ end
+
+ def choose_repository_clone_ssh
+ # It's not always beginning with ssh:// so detecting with @
+ # would be more reliable because ssh would always contain it.
+ # We can't use .git because HTTP also contain that part.
+ choose_repository_clone('SSH', '@')
+ end
+
+ def repository_location
+ Git::Location.new(find('#project_clone').value)
+ end
+
+ def wait_for_push
+ sleep 5
+ refresh
+ end
+
+ private
+
+ def choose_repository_clone(kind, detect_text)
+ wait(reload: false) do
+ click_element :clone_dropdown
+
+ page.within('.clone-options-dropdown') do
+ click_link(kind)
+ end
+
+ # Ensure git clone textbox was updated
+ repository_location.git_uri.include?(detect_text)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index 97ffe0e5716..cb83ace20b6 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -35,13 +35,21 @@ module QA
element :saml_login_button
end
+ view 'app/views/layouts/devise.html.haml' do
+ element :login_page
+ end
+
def initialize
# The login page is usually the entry point for all the scenarios so
# we need to wait for the instance to start. That said, in some cases
# we are already logged-in so we check both cases here.
+ # Check if we're already logged in first. If we don't then we have to
+ # wait 10 seconds for the check for the login page to fail every time
+ # we use this class when we're already logged in (E.g., whenever we
+ # create a personal access token to use for API access).
wait(max: 500) do
- has_css?('.login-page') ||
- Page::Main::Menu.act { has_personal_area?(wait: 0) }
+ Page::Main::Menu.act { has_personal_area?(wait: 0) } ||
+ has_element?(:login_page)
end
end
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index cc2724618e9..6804cc8fb20 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -63,15 +63,11 @@ module QA
end
def has_personal_area?(wait: Capybara.default_max_wait_time)
- using_wait_time(wait) do
- page.has_selector?(element_selector_css(:user_avatar))
- end
+ has_element?(:user_avatar, wait: wait)
end
def has_admin_area_link?(wait: Capybara.default_max_wait_time)
- using_wait_time(wait) do
- page.has_selector?(element_selector_css(:admin_area_link))
- end
+ has_element?(:admin_area_link, wait: wait)
end
private
diff --git a/qa/qa/page/merge_request/new.rb b/qa/qa/page/merge_request/new.rb
index 1f8f1fbca8e..20d9c336367 100644
--- a/qa/qa/page/merge_request/new.rb
+++ b/qa/qa/page/merge_request/new.rb
@@ -26,6 +26,10 @@ module QA
element :issuable_label
end
+ view 'app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml' do
+ element :assign_to_me_link
+ end
+
def create_merge_request
click_element :issuable_create_button
end
@@ -50,6 +54,10 @@ module QA
click_link label.title
end
+
+ def assign_to_me
+ click_element :assign_to_me_link
+ end
end
end
end
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 2fd30e15ffb..869dc0b9d21 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -52,6 +52,7 @@ module QA
end
view 'app/views/shared/issuable/_sidebar.html.haml' do
+ element :assignee_block
element :labels_block
end
@@ -100,6 +101,12 @@ module QA
end
end
+ def has_assignee?(username)
+ page.within(element_selector_css(:assignee_block)) do
+ has_text?(username)
+ end
+ end
+
def has_label?(label)
page.within(element_selector_css(:labels_block)) do
element = find('span', text: label)
diff --git a/qa/qa/page/project/commit/show.rb b/qa/qa/page/project/commit/show.rb
new file mode 100644
index 00000000000..9770b8a657c
--- /dev/null
+++ b/qa/qa/page/project/commit/show.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Commit
+ class Show < Page::Base
+ view 'app/views/projects/commit/_commit_box.html.haml' do
+ element :options_button
+ element :email_patches
+ element :plain_diff
+ end
+
+ def select_email_patches
+ click_element :options_button
+ click_element :email_patches
+ end
+
+ def select_plain_diff
+ click_element :options_button
+ click_element :plain_diff
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index 23def93c7dd..9ec6d90719e 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -37,17 +37,17 @@ module QA
def select_comments_only_filter
click_element :discussion_filter
- all_elements(:filter_options)[1].click
+ find_element(:filter_options, "Show comments only").click
end
def select_history_only_filter
click_element :discussion_filter
- all_elements(:filter_options).last.click
+ find_element(:filter_options, "Show history only").click
end
def select_all_activities_filter
click_element :discussion_filter
- all_elements(:filter_options).first.click
+ find_element(:filter_options, "Show all activity").click
end
end
end
diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb
index cb4a10e1b6a..835e1ed00b5 100644
--- a/qa/qa/page/project/menu.rb
+++ b/qa/qa/page/project/menu.rb
@@ -29,11 +29,9 @@ module QA
element :fly_out, "classList.add('fly-out-list')" # rubocop:disable QA/ElementWithPattern
end
- def click_repository_settings
- hover_settings do
- within_submenu do
- click_link('Repository')
- end
+ def click_ci_cd_pipelines
+ within_sidebar do
+ click_element :link_pipelines
end
end
@@ -45,11 +43,9 @@ module QA
end
end
- def click_operations_environments
- hover_operations do
- within_submenu do
- click_element(:operations_environments_link)
- end
+ def click_issues
+ within_sidebar do
+ click_link('Issues')
end
end
@@ -61,61 +57,71 @@ module QA
end
end
- def click_operations_kubernetes
+ def click_merge_requests
+ within_sidebar do
+ click_link('Merge Requests')
+ end
+ end
+
+ def click_operations_environments
hover_operations do
within_submenu do
- click_link('Kubernetes')
+ click_element(:operations_environments_link)
end
end
end
- def click_ci_cd_pipelines
- within_sidebar do
- click_element :link_pipelines
+ def click_operations_kubernetes
+ hover_operations do
+ within_submenu do
+ click_link('Kubernetes')
+ end
end
end
- def go_to_settings
+ def click_milestones
within_sidebar do
- click_on 'Settings'
+ click_element :milestones_link
end
end
- def click_issues
+ def click_repository
within_sidebar do
- click_link('Issues')
+ click_link('Repository')
end
end
- def go_to_labels
- hover_issues do
+ def click_repository_settings
+ hover_settings do
within_submenu do
- click_element(:labels_link)
+ click_link('Repository')
end
end
end
- def click_merge_requests
+ def click_wiki
within_sidebar do
- click_link('Merge Requests')
+ click_link('Wiki')
end
end
- def click_milestones
+ def go_to_activity
within_sidebar do
- click_element :milestones_link
+ click_on 'Activity'
end
end
- def click_wiki
- within_sidebar do
- click_link('Wiki')
+ def go_to_labels
+ hover_issues do
+ within_submenu do
+ click_element(:labels_link)
+ end
end
end
- def click_repository
+ def go_to_settings
within_sidebar do
- click_link('Repository')
+ click_on 'Settings'
end
end
@@ -129,17 +135,17 @@ module QA
end
end
- def hover_settings
+ def hover_operations
within_sidebar do
- find('.qa-settings-item').hover
+ find('.shortcuts-operations').hover
yield
end
end
- def hover_operations
+ def hover_settings
within_sidebar do
- find('.shortcuts-operations').hover
+ find('.qa-settings-item').hover
yield
end
@@ -151,12 +157,6 @@ module QA
end
end
- def go_to_activity
- within_sidebar do
- click_on 'Activity'
- end
- end
-
def within_submenu
page.within('.fly-out-list') do
yield
diff --git a/qa/qa/page/project/settings/mirroring_repositories.rb b/qa/qa/page/project/settings/mirroring_repositories.rb
new file mode 100644
index 00000000000..a73be7dfeda
--- /dev/null
+++ b/qa/qa/page/project/settings/mirroring_repositories.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Settings
+ class MirroringRepositories < Page::Base
+ view 'app/views/projects/mirrors/_authentication_method.html.haml' do
+ element :authentication_method
+ element :password
+ end
+
+ view 'app/views/projects/mirrors/_mirror_repos.html.haml' do
+ element :mirror_repository_url_input
+ element :mirror_repository_button
+ element :mirror_repository_url
+ element :mirror_last_update_at
+ element :mirrored_repository_row
+ end
+
+ view 'app/views/projects/mirrors/_mirror_repos_form.html.haml' do
+ element :mirror_direction
+ end
+
+ view 'app/views/shared/_remote_mirror_update_button.html.haml' do
+ element :update_now_button
+ end
+
+ def repository_url=(value)
+ fill_element :mirror_repository_url_input, value
+ end
+
+ def password=(value)
+ fill_element :password, value
+ end
+
+ def mirror_direction=(value)
+ raise ArgumentError, "Mirror direction must be :push or :pull" unless [:push, :pull].include? value
+
+ select_element(:mirror_direction, value)
+ end
+
+ def authentication_method=(value)
+ raise ArgumentError, "Authentication method must be :password or :none" unless [:password, :none].include? value
+
+ select_element(:authentication_method, value)
+ end
+
+ def mirror_repository
+ click_element :mirror_repository_button
+ end
+
+ def update(url)
+ row_index = find_repository_row_index url
+
+ within_element_by_index(:mirrored_repository_row, row_index) do
+ click_element :update_now_button
+ end
+
+ # Wait a few seconds for the sync to occur and then refresh the page
+ # so that 'last update' shows 'just now' or a period in seconds
+ sleep 5
+ refresh
+
+ wait(time: 1) do
+ within_element_by_index(:mirrored_repository_row, row_index) do
+ last_update = find_element(:mirror_last_update_at, wait: 0)
+ last_update.has_text?('just now') || last_update.has_text?('seconds')
+ end
+ end
+
+ # Fail early if the page still shows that there has been no update
+ within_element_by_index(:mirrored_repository_row, row_index) do
+ find_element(:mirror_last_update_at, wait: 0).assert_no_text('Never')
+ end
+ end
+
+ private
+
+ def find_repository_row_index(target_url)
+ all_elements(:mirror_repository_url).index do |url|
+ # The url might be a sanitized url but the target_url won't be so
+ # we compare just the paths instead of the full url
+ URI.parse(url.text).path == target_url.path
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/settings/repository.rb b/qa/qa/page/project/settings/repository.rb
index 53ebe28970b..ac0b87aca5e 100644
--- a/qa/qa/page/project/settings/repository.rb
+++ b/qa/qa/page/project/settings/repository.rb
@@ -13,6 +13,10 @@ module QA
element :protected_branches_settings
end
+ view 'app/views/projects/mirrors/_mirror_repos.html.haml' do
+ element :mirroring_repositories_settings
+ end
+
def expand_deploy_keys(&block)
expand_section(:deploy_keys_settings) do
DeployKeys.perform(&block)
@@ -30,6 +34,12 @@ module QA
DeployTokens.perform(&block)
end
end
+
+ def expand_mirroring_repositories(&block)
+ expand_section(:mirroring_repositories_settings) do
+ MirroringRepositories.perform(&block)
+ end
+ end
end
end
end
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index d6dddf03ffb..945b244df15 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -6,6 +6,11 @@ module QA
class Show < Page::Base
include Page::Component::ClonePanel
+ view 'app/views/layouts/header/_new_dropdown.haml' do
+ element :new_menu_toggle
+ element :new_issue_link, "link_to _('New issue'), new_project_issue_path(@project)" # rubocop:disable QA/ElementWithPattern
+ end
+
view 'app/views/projects/_last_push.html.haml' do
element :create_merge_request
end
@@ -14,14 +19,12 @@ module QA
element :project_name
end
- view 'app/views/layouts/header/_new_dropdown.haml' do
- element :new_menu_toggle
- element :new_issue_link, "link_to _('New issue'), new_project_issue_path(@project)" # rubocop:disable QA/ElementWithPattern
+ view 'app/views/projects/_files.html.haml' do
+ element :tree_holder, '.tree-holder' # rubocop:disable QA/ElementWithPattern
end
- view 'app/views/shared/_ref_switcher.html.haml' do
- element :branches_select
- element :branches_dropdown
+ view 'app/views/projects/buttons/_dropdown.html.haml' do
+ element :create_new_dropdown
end
view 'app/views/projects/buttons/_fork.html.haml' do
@@ -29,46 +32,57 @@ module QA
element :fork_link, "link_to new_project_fork_path(@project)" # rubocop:disable QA/ElementWithPattern
end
- view 'app/views/projects/_files.html.haml' do
- element :tree_holder, '.tree-holder' # rubocop:disable QA/ElementWithPattern
+ view 'app/views/projects/empty.html.haml' do
+ element :quick_actions
end
- view 'app/views/projects/buttons/_dropdown.html.haml' do
- element :create_new_dropdown
- element :new_file_option
+ view 'app/views/projects/tree/_tree_content.html.haml' do
+ element :file_tree
end
view 'app/views/projects/tree/_tree_header.html.haml' do
+ element :add_to_tree
+ element :new_file_option
element :web_ide_button
end
- view 'app/views/projects/tree/_tree_content.html.haml' do
- element :file_tree
+ view 'app/views/shared/_ref_switcher.html.haml' do
+ element :branches_select
+ element :branches_dropdown
end
- def project_name
- find('.qa-project-name').text
+ def create_first_new_file!
+ within_element(:quick_actions) do
+ click_link_with_text 'New file'
+ end
end
def create_new_file!
- click_element :create_new_dropdown
+ click_element :add_to_tree
click_element :new_file_option
end
+ def fork_project
+ click_on 'Fork'
+ end
+
def go_to_file(filename)
within_element(:file_tree) do
click_on filename
end
end
- def switch_to_branch(branch_name)
- find_element(:branches_select).click
-
- within_element(:branches_dropdown) do
- click_on branch_name
+ def go_to_commit(commit_msg)
+ within_element(:file_tree) do
+ click_on commit_msg
end
end
+ def go_to_new_issue
+ click_element :new_menu_toggle
+ click_link 'New issue'
+ end
+
def last_commit_content
find_element(:commit_content).text
end
@@ -81,24 +95,26 @@ module QA
click_element :create_merge_request
end
- def wait_for_import
- wait(reload: true) do
- has_css?('.tree-holder')
- end
+ def open_web_ide!
+ click_element :web_ide_button
end
- def go_to_new_issue
- click_element :new_menu_toggle
-
- click_link 'New issue'
+ def project_name
+ find('.qa-project-name').text
end
- def fork_project
- click_on 'Fork'
+ def switch_to_branch(branch_name)
+ find_element(:branches_select).click
+
+ within_element(:branches_dropdown) do
+ click_on branch_name
+ end
end
- def open_web_ide!
- click_element :web_ide_button
+ def wait_for_import
+ wait(reload: true) do
+ has_css?('.tree-holder')
+ end
end
end
end
diff --git a/qa/qa/page/project/wiki/show.rb b/qa/qa/page/project/wiki/show.rb
index a7c4455d080..dffbc5d60a2 100644
--- a/qa/qa/page/project/wiki/show.rb
+++ b/qa/qa/page/project/wiki/show.rb
@@ -5,7 +5,7 @@ module QA
module Project
module Wiki
class Show < Page::Base
- include Page::Component::ClonePanel
+ include Page::Component::LegacyClonePanel
view 'app/views/projects/wikis/pages.html.haml' do
element :clone_repository_link, 'Clone repository' # rubocop:disable QA/ElementWithPattern
diff --git a/qa/qa/resource/file.rb b/qa/qa/resource/file.rb
index effc5a7940b..57e82ac19ad 100644
--- a/qa/qa/resource/file.rb
+++ b/qa/qa/resource/file.rb
@@ -22,7 +22,7 @@ module QA
def fabricate!
project.visit!
- Page::Project::Show.perform(&:create_new_file!)
+ Page::Project::Show.perform(&:create_first_new_file!)
Page::File::Form.perform do |page|
page.add_name(@name)
diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb
index 77afb3cfcba..7150098a00a 100644
--- a/qa/qa/resource/merge_request.rb
+++ b/qa/qa/resource/merge_request.rb
@@ -58,11 +58,15 @@ module QA
populate(:target, :source)
project.visit!
- Page::Project::Show.perform(&:new_merge_request)
+ Page::Project::Show.perform do |project|
+ project.wait_for_push
+ project.new_merge_request
+ end
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'
labels.each do |label|
page.select_label(label)
end
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 7fdf69278f9..1fafbf5d73e 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -14,15 +14,13 @@ module QA
attribute :repository_ssh_location do
Page::Project::Show.perform do |page|
- page.choose_repository_clone_ssh
- page.repository_location
+ page.repository_clone_ssh_location
end
end
attribute :repository_http_location do
Page::Project::Show.perform do |page|
- page.choose_repository_clone_http
- page.repository_location
+ page.repository_clone_http_location
end
end
diff --git a/qa/qa/resource/repository/project_push.rb b/qa/qa/resource/repository/project_push.rb
index c9fafe3419f..f4692c3dd4d 100644
--- a/qa/qa/resource/repository/project_push.rb
+++ b/qa/qa/resource/repository/project_push.rb
@@ -20,23 +20,16 @@ module QA
end
def repository_http_uri
- @repository_http_uri ||= begin
- project.visit!
- Page::Project::Show.act do
- choose_repository_clone_http
- repository_location.uri
- end
- end
+ @repository_http_uri ||= project.repository_http_location.uri
end
def repository_ssh_uri
- @repository_ssh_uri ||= begin
- project.visit!
- Page::Project::Show.act do
- choose_repository_clone_ssh
- repository_location.uri
- end
- end
+ @repository_ssh_uri ||= project.repository_ssh_location.uri
+ end
+
+ def fabricate!
+ super
+ project.visit!
end
end
end
diff --git a/qa/qa/resource/repository/wiki_push.rb b/qa/qa/resource/repository/wiki_push.rb
index f1c39d507fe..77c4c8a514d 100644
--- a/qa/qa/resource/repository/wiki_push.rb
+++ b/qa/qa/resource/repository/wiki_push.rb
@@ -30,6 +30,11 @@ module QA
end
end
end
+
+ def fabricate!
+ super
+ wiki.visit!
+ end
end
end
end
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
index 7fd2ba25527..b706d6565d2 100644
--- a/qa/qa/runtime/browser.rb
+++ b/qa/qa/runtime/browser.rb
@@ -70,6 +70,13 @@ module QA
options.add_argument("disable-gpu")
end
+ # Use the same profile on QA runs if CHROME_REUSE_PROFILE is true.
+ # Useful to speed up local QA.
+ if QA::Runtime::Env.reuse_chrome_profile?
+ qa_profile_dir = ::File.expand_path('../../tmp/qa-profile', __dir__)
+ options.add_argument("user-data-dir=#{qa_profile_dir}")
+ end
+
# Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab-ee/issues/4252
options.add_argument("disable-dev-shm-usage") if QA::Runtime::Env.running_in_ci?
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 3bc2b44ccd8..dae5aa3f794 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -30,6 +30,11 @@ module QA
enabled?(ENV['CHROME_HEADLESS'])
end
+ # set to 'true' to have Chrome use a fixed profile directory
+ def reuse_chrome_profile?
+ enabled?(ENV['CHROME_REUSE_PROFILE'], default: false)
+ end
+
def accept_insecure_certs?
enabled?(ENV['ACCEPT_INSECURE_CERTS'])
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
index 275de3d332c..d4cedc9362d 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
@@ -5,17 +5,17 @@ module QA
describe 'Project activity' do
it 'user creates an event in the activity page upon Git push' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
- Resource::Repository::ProjectPush.fabricate! do |push|
+ project_push = Resource::Repository::ProjectPush.fabricate! do |push|
push.file_name = 'README.md'
push.file_content = '# This is a test project'
push.commit_message = 'Add README.md'
end
+ project_push.project.visit!
- Page::Project::Menu.act { go_to_activity }
-
- Page::Project::Activity.act { go_to_push_events }
+ Page::Project::Menu.perform(&:go_to_activity)
+ Page::Project::Activity.perform(&:go_to_push_events)
expect(page).to have_content('pushed new branch master')
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
new file mode 100644
index 00000000000..7e8b42e286f
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Plan' do
+ describe 'issue suggestions' do
+ let(:issue_title) { 'Issue Lists are awesome' }
+
+ it 'user sees issue suggestions when creating a new issue' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ project = Resource::Project.fabricate! do |resource|
+ resource.name = 'project-for-issue-suggestions'
+ resource.description = 'project for issue suggestions'
+ end
+
+ Resource::Issue.fabricate! do |issue|
+ issue.title = issue_title
+ issue.project = project
+ end
+
+ project.visit!
+
+ Page::Project::Show.perform(&:go_to_new_issue)
+ Page::Project::Issue::New.perform do |new_issue_page|
+ new_issue_page.add_title("issue")
+ expect(new_issue_page).to have_content(issue_title)
+
+ new_issue_page.add_title("Issue Board")
+ expect(new_issue_page).not_to have_content(issue_title)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
index d33947f41da..6ddd7dde2cf 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
@@ -4,6 +4,8 @@ module QA
context 'Create' do
describe 'Merge request creation' do
it 'user creates a new merge request' do
+ gitlab_account_username = "@#{Runtime::User.username}"
+
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
@@ -27,6 +29,7 @@ module QA
merge_request.description = 'Great feature with milestone'
merge_request.project = current_project
merge_request.milestone = current_milestone
+ merge_request.assignee = 'me'
merge_request.labels.push(new_label)
end
@@ -34,6 +37,7 @@ module QA
expect(merge_request).to have_content('This is a merge request with a milestone')
expect(merge_request).to have_content('Great feature with milestone')
expect(merge_request).to have_content(/Opened [\w\s]+ ago/)
+ expect(merge_request).to have_assignee(gitlab_account_username)
expect(merge_request).to have_label(new_label.title)
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
index 6ff7360c413..4126f967ee2 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
@@ -5,7 +5,7 @@ module QA
describe 'Merge request squashing' do
it 'user squashes commits while merging' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
project = Resource::Project.fabricate! do |project|
project.name = "squash-before-merge"
@@ -38,13 +38,12 @@ module QA
Git::Repository.perform do |repository|
repository.uri = Page::Project::Show.act do
- choose_repository_clone_http
- repository_location.uri
+ repository_clone_http_location.uri
end
repository.use_default_credentials
- repository.act { clone }
+ repository.clone
expect(repository.commits.size).to eq 3
end
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 297485dd81e..de5c535c757 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
@@ -7,7 +7,7 @@ module QA
def login
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
end
before(:all) do
@@ -18,7 +18,15 @@ module QA
project.description = 'Add file templates via the Files view'
end
- Page::Main::Menu.act { sign_out }
+ # There's no 'New File' dropdown when the project is blank, so we first
+ # add a dummy file so that the dropdown will appear
+ Resource::File.fabricate! do |file|
+ file.project = @project
+ file.name = 'README.md'
+ file.content = '# Readme'
+ end
+
+ Page::Main::Menu.perform(&:sign_out)
end
templates = [
@@ -55,7 +63,7 @@ module QA
login
@project.visit!
- Page::Project::Show.act { create_new_file! }
+ Page::Project::Show.perform(&:create_new_file!)
Page::File::Form.perform do |page|
page.select_template template[:file_name], template[:name]
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb
index 6a0add56fe0..571cae4a3c5 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb
@@ -4,15 +4,12 @@ module QA
context 'Create' do
describe 'Git clone over HTTP', :ldap_no_tls do
let(:location) do
- Page::Project::Show.act do
- choose_repository_clone_http
- repository_location
- end
+ Page::Project::Show.perform(&:repository_clone_http_location).uri
end
before do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
project = Resource::Project.fabricate! do |scenario|
scenario.name = 'project-with-code'
@@ -21,7 +18,7 @@ module QA
project.visit!
Git::Repository.perform do |repository|
- repository.uri = location.uri
+ repository.uri = location
repository.use_default_credentials
repository.act do
@@ -32,14 +29,15 @@ module QA
push_changes
end
end
+ Page::Project::Show.perform(&:wait_for_push)
end
it 'user performs a deep clone' do
Git::Repository.perform do |repository|
- repository.uri = location.uri
+ repository.uri = location
repository.use_default_credentials
- repository.act { clone }
+ repository.clone
expect(repository.commits.size).to eq 2
end
@@ -47,10 +45,10 @@ module QA
it 'user performs a shallow clone' do
Git::Repository.perform do |repository|
- repository.uri = location.uri
+ repository.uri = location
repository.use_default_credentials
- repository.act { shallow_clone }
+ repository.shallow_clone
expect(repository.commits.size).to eq 1
expect(repository.commits.first).to include 'Add Readme'
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb
new file mode 100644
index 00000000000..2d0e281ab59
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ describe 'Push mirror a repository over HTTP' do
+ it 'configures and syncs a (push) mirrored repository' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ target_project = Resource::Project.fabricate! do |project|
+ project.name = 'push-mirror-target-project'
+ end
+ target_project_uri = target_project.repository_http_location.uri
+ target_project_uri.user = Runtime::User.username
+
+ source_project_push = Resource::Repository::ProjectPush.fabricate! do |push|
+ push.file_name = 'README.md'
+ push.file_content = '# This is a test project'
+ push.commit_message = 'Add README.md'
+ end
+ source_project_push.project.visit!
+
+ Page::Project::Show.perform(&:wait_for_push)
+
+ Page::Project::Menu.perform(&:click_repository_settings)
+ Page::Project::Settings::Repository.perform do |settings|
+ settings.expand_mirroring_repositories do |mirror_settings|
+ # Configure the source project to push to the target project
+ mirror_settings.repository_url = target_project_uri
+ mirror_settings.mirror_direction = :push
+ mirror_settings.authentication_method = :password
+ mirror_settings.password = Runtime::User.password
+ mirror_settings.mirror_repository
+ mirror_settings.update target_project_uri
+ end
+ end
+
+ # Check that the target project has the commit from the source
+ target_project.visit!
+ expect(page).to have_content('README.md')
+ expect(page).to have_content('This is a test project')
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
index 92f596a44d9..ad6426df420 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb
@@ -7,12 +7,12 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
- Resource::Repository::ProjectPush.fabricate! do |push|
+ project_push = Resource::Repository::ProjectPush.fabricate! do |push|
push.file_name = 'README.md'
push.file_content = '# This is a test project'
push.commit_message = 'Add README.md'
end
-
+ project_push.project.visit!
Page::Project::Show.act { wait_for_push }
expect(page).to have_content('README.md')
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb
index 9c764424129..509a639c130 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb
@@ -16,13 +16,14 @@ module QA
resource.title = key_title
end
- Resource::Repository::ProjectPush.fabricate! do |push|
+ project_push = Resource::Repository::ProjectPush.fabricate! do |push|
push.ssh_key = key
push.file_name = 'README.md'
push.file_content = '# Test Use SSH Key'
push.commit_message = 'Add README.md'
end
+ project_push.project.visit!
Page::Project::Show.act { wait_for_push }
expect(page).to have_content('README.md')
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb
new file mode 100644
index 00000000000..203338ddf77
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_raw_diff_patch_requests_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ describe 'Commit data' do
+ before(:context) do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ project_push = Resource::Repository::ProjectPush.fabricate! do |push|
+ push.file_name = 'README.md'
+ push.file_content = '# This is a test project'
+ push.commit_message = 'Add README.md'
+ end
+ @project = project_push.project
+
+ # first file added has no parent commit, thus no diff data
+ # add second file to repo to enable diff from initial commit
+ @commit_message = 'Add second file'
+
+ Page::Project::Show.perform(&:create_new_file!)
+ Page::File::Form.perform do |f|
+ f.add_name('second')
+ f.add_content('second file content')
+ f.add_commit_message(@commit_message)
+ f.commit_changes
+ end
+ end
+
+ def view_commit
+ @project.visit!
+ Page::Project::Show.perform do |page|
+ page.go_to_commit(@commit_message)
+ end
+ end
+
+ def raw_content
+ find('pre').text
+ end
+
+ it 'user views raw email patch' do
+ view_commit
+
+ Page::Project::Commit::Show.perform(&:select_email_patches)
+
+ expect(page).to have_content('From: Administrator <admin@example.com>')
+ expect(page).to have_content('Subject: [PATCH] Add second file')
+ expect(page).to have_content('diff --git a/second b/second')
+ end
+
+ it 'user views raw commit diff' do
+ view_commit
+
+ Page::Project::Commit::Show.perform(&:select_plain_diff)
+
+ expect(raw_content).to start_with('diff --git a/second b/second')
+ expect(page).to have_content('+second file content')
+ end
+ end
+ end
+end
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 e7374377104..f176ec31abd 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
@@ -7,7 +7,7 @@ module QA
def login
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
end
before(:all) do
@@ -21,14 +21,14 @@ module QA
# Add a file via the regular Files view because the Web IDE isn't
# available unless there is a file present
- Page::Project::Show.act { create_new_file! }
+ Page::Project::Show.perform(&:create_first_new_file!)
Page::File::Form.perform do |page|
page.add_name('dummy')
page.add_content('Enable the Web IDE')
page.commit_changes
end
- Page::Main::Menu.act { sign_out }
+ Page::Main::Menu.perform(&:sign_out)
end
templates = [
@@ -65,7 +65,7 @@ module QA
login
@project.visit!
- Page::Project::Show.act { open_web_ide! }
+ Page::Project::Show.perform(&:open_web_ide!)
Page::Project::WebIDE::Edit.perform do |page|
page.create_new_file_from_template template[:file_name], template[:name]
@@ -75,9 +75,7 @@ module QA
expect(page).to have_button('Undo')
expect(page).to have_content(content[0..100])
- Page::Project::WebIDE::Edit.perform do |page|
- page.commit_changes
- end
+ Page::Project::WebIDE::Edit.perform(&:commit_changes)
expect(page).to have_content(template[:file_name])
expect(page).to have_content(content[0..100])
diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb
index cf5cd3a79f8..df3b794b14b 100644
--- a/qa/qa/support/page/logging.rb
+++ b/qa/qa/support/page/logging.rb
@@ -37,8 +37,8 @@ module QA
exists
end
- def find_element(name)
- log("finding :#{name}")
+ def find_element(name, wait: Capybara.default_max_wait_time)
+ log("finding :#{name} (wait: #{wait})")
element = super
@@ -71,7 +71,13 @@ module QA
super
end
- def has_element?(name)
+ def select_element(name, value)
+ log(%Q(selecting "#{value}" in :#{name}))
+
+ super
+ end
+
+ def has_element?(name, wait: Capybara.default_max_wait_time)
found = super
log("has_element? :#{name} returned #{found}")
@@ -79,6 +85,14 @@ module QA
found
end
+ def has_no_text?(text)
+ found = super
+
+ log(%Q{has_no_text?('#{text}') returned #{found}})
+
+ found
+ end
+
def within_element(name)
log("within element :#{name}")
@@ -89,6 +103,16 @@ module QA
element
end
+ def within_element_by_index(name, index)
+ log("within elements :#{name} at index #{index}")
+
+ element = super
+
+ log("end within elements :#{name} at index #{index}")
+
+ element
+ end
+
private
def log(msg)
diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb
index 9d56353062b..a54ff424f53 100644
--- a/qa/spec/page/logging_spec.rb
+++ b/qa/spec/page/logging_spec.rb
@@ -65,6 +65,13 @@ describe QA::Support::Page::Logging do
.to output(/has_element\? :element returned true/).to_stdout_from_any_process
end
+ it 'logs has_no_text?' do
+ allow(page).to receive(:has_no_text?).with('foo').and_return(true)
+
+ expect { subject.has_no_text? 'foo' }
+ .to output(/has_no_text\?\('foo'\) returned true/).to_stdout_from_any_process
+ end
+
it 'logs within_element' do
expect { subject.within_element(:element) }
.to output(/within element :element/).to_stdout_from_any_process
diff --git a/rubocop/cop/inject_enterprise_edition_module.rb b/rubocop/cop/inject_enterprise_edition_module.rb
new file mode 100644
index 00000000000..c8b8aca51ab
--- /dev/null
+++ b/rubocop/cop/inject_enterprise_edition_module.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module RuboCop
+ module Cop
+ # Cop that blacklists the injecting of EE specific modules anywhere but on
+ # the last line of a file. Injecting a module in the middle of a file will
+ # cause merge conflicts, while placing it on the last line will not.
+ class InjectEnterpriseEditionModule < RuboCop::Cop::Cop
+ MSG = 'Injecting EE modules must be done on the last line of this file' \
+ ', outside of any class or module definitions'
+
+ METHODS = Set.new(%i[include extend prepend]).freeze
+
+ def_node_matcher :ee_const?, <<~PATTERN
+ (const (const _ :EE) _)
+ PATTERN
+
+ def on_send(node)
+ return unless METHODS.include?(node.children[1])
+ return unless ee_const?(node.children[2])
+
+ line = node.location.line
+ buffer = node.location.expression.source_buffer
+ last_line = buffer.last_line
+
+ # Parser treats the final newline (if present) as a separate line,
+ # meaning that a simple `line < last_line` would yield true even though
+ # the expression is the last line _of code_.
+ last_line -= 1 if buffer.source.end_with?("\n")
+
+ add_offense(node) if line < last_line
+ end
+
+ # Automatically correcting these offenses is not always possible, as
+ # sometimes code needs to be refactored to make this work. As such, we
+ # only allow developers to easily blacklist existing offenses.
+ def autocorrect(node)
+ lambda do |corrector|
+ corrector.insert_after(
+ node.source_range,
+ " # rubocop: disable #{cop_name}"
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 4489159f422..3e33419eb2e 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -41,3 +41,4 @@ require_relative 'cop/code_reuse/presenter'
require_relative 'cop/code_reuse/serializer'
require_relative 'cop/code_reuse/active_record'
require_relative 'cop/group_public_or_visible_to_user'
+require_relative 'cop/inject_enterprise_edition_module'
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index 75a3cea0448..d85c53090a4 100644
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -1,12 +1,11 @@
. scripts/utils.sh
export SETUP_DB=${SETUP_DB:-true}
-export CREATE_DB_USER=${CREATE_DB_USER:-$SETUP_DB}
export USE_BUNDLE_INSTALL=${USE_BUNDLE_INSTALL:-true}
export BUNDLE_INSTALL_FLAGS="--without=production --jobs=$(nproc) --path=vendor --retry=3 --quiet"
if [ "$USE_BUNDLE_INSTALL" != "false" ]; then
- bundle install --clean $BUNDLE_INSTALL_FLAGS && bundle check
+ bundle install --clean $BUNDLE_INSTALL_FLAGS && bundle check
fi
# Only install knapsack after bundle install! Otherwise oddly some native
@@ -23,18 +22,30 @@ export GITLAB_DATABASE=$(echo $CI_JOB_NAME | cut -f1 -d' ' | cut -f2 -d-)
# This would make the default database postgresql, and we could also use
# pg to mean postgresql.
if [ "$GITLAB_DATABASE" != 'mysql' ]; then
- export GITLAB_DATABASE='postgresql'
+ export GITLAB_DATABASE='postgresql'
fi
cp config/database.yml.$GITLAB_DATABASE config/database.yml
+if [ -f config/database_geo.yml.$GITLAB_DATABASE ]; then
+ cp config/database_geo.yml.$GITLAB_DATABASE config/database_geo.yml
+fi
+
# Set user to a non-superuser to ensure we test permissions
sed -i 's/username: root/username: gitlab/g' config/database.yml
if [ "$GITLAB_DATABASE" = 'postgresql' ]; then
- sed -i 's/localhost/postgres/g' config/database.yml
+ sed -i 's/localhost/postgres/g' config/database.yml
+
+ if [ -f config/database_geo.yml ]; then
+ sed -i 's/localhost/postgres/g' config/database_geo.yml
+ fi
else # Assume it's mysql
- sed -i 's/localhost/mysql/g' config/database.yml
+ sed -i 's/localhost/mysql/g' config/database.yml
+
+ if [ -f config/database_geo.yml ]; then
+ sed -i 's/localhost/mysql/g' config/database_geo.yml
+ fi
fi
cp config/resque.yml.example config/resque.yml
@@ -50,7 +61,7 @@ cp config/redis.shared_state.yml.example config/redis.shared_state.yml
sed -i 's/localhost/redis/g' config/redis.shared_state.yml
if [ "$SETUP_DB" != "false" ]; then
- setup_db
+ setup_db
elif getent hosts postgres || getent hosts mysql; then
- setup_db_user_only
+ setup_db_user_only
fi
diff --git a/scripts/rails4-gemfile-lock-check b/scripts/rails4-gemfile-lock-check
deleted file mode 100755
index a74a49874e1..00000000000
--- a/scripts/rails4-gemfile-lock-check
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/usr/bin/env bash
-
-echo -e "=> Checking if Gemfile.rails4.lock is up-to-date...\\n"
-
-cp Gemfile.rails4.lock Gemfile.rails4.lock.orig
-BUNDLE_GEMFILE=Gemfile.rails4 bundle install "$BUNDLE_INSTALL_FLAGS"
-diff -u Gemfile.rails4.lock.orig Gemfile.rails4.lock >/dev/null 2>&1
-
-if [ $? == 1 ]
-then
- diff -u Gemfile.rails4.lock.orig Gemfile.rails4.lock
-
- echo -e "\\n✖ ERROR: Gemfile.rails4.lock is not up-to-date!
- Please run 'BUNDLE_GEMFILE=Gemfile.rails4 bundle install'\\n" >&2
- exit 1
-fi
-
-echo "✔ Gemfile.rails4.lock is up-to-date"
-exit 0
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index f3f788e0217..9e52366f800 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -227,7 +227,8 @@ function install_external_dns() {
--set aws.zoneType="public" \
--set domainFilters[0]="${domain}" \
--set txtOwnerId="${KUBE_NAMESPACE}" \
- --set rbac.create="true"
+ --set rbac.create="true" \
+ --set policy="sync"
fi
}
@@ -289,10 +290,10 @@ function get_job_id() {
local url="https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/pipelines/${CI_PIPELINE_ID}/jobs?per_page=100&page=${page}${query_string}"
echoerr "GET ${url}"
- local job_id=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${API_TOKEN}" "${url}" | jq ".[] | select(.name == \"${job_name}\") | .id")
- [[ "${job_id}" == "" && "${page}" -lt "$max_page" ]] || break
+ local job_id=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${API_TOKEN}" "${url}" | jq "map(select(.name == \"${job_name}\")) | map(.id) | last")
+ [[ "${job_id}" == "null" && "${page}" -lt "$max_page" ]] || break
- ((page++))
+ let "page++"
done
if [[ "${job_id}" == "" ]]; then
@@ -328,17 +329,18 @@ function wait_for_job_to_be_done() {
# In case the job hasn't finished yet. Keep trying until the job times out.
local interval=30
- local elapsed=0
+ local elapsed_seconds=0
while true; do
local job_status=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${API_TOKEN}" "${url}" | jq ".status" | sed -e s/\"//g)
[[ "${job_status}" == "pending" || "${job_status}" == "running" ]] || break
printf "."
- ((elapsed+=$interval))
+ let "elapsed_seconds+=interval"
sleep ${interval}
done
- echoerr "Waited '${job_name}' for ${elapsed} seconds."
+ local elapsed_minutes=$((elapsed_seconds / 60))
+ echoerr "Waited '${job_name}' for ${elapsed_minutes} minutes."
if [[ "${job_status}" == "failed" ]]; then
echo "The '${job_name}' failed."
diff --git a/scripts/trigger-build b/scripts/trigger-build
index 14af3281106..4032ba853e6 100755
--- a/scripts/trigger-build
+++ b/scripts/trigger-build
@@ -68,7 +68,7 @@ module Trigger
def base_variables
{
- 'GITLAB_REF_SLUG' => ref_slug,
+ 'GITLAB_REF_SLUG' => ENV['CI_COMMIT_REF_SLUG'],
'TRIGGERED_USER' => ENV['TRIGGERED_USER'] || ENV['GITLAB_USER_NAME'],
'TRIGGER_SOURCE' => ENV['CI_JOB_URL'],
'TOP_UPSTREAM_SOURCE_PROJECT' => ENV['CI_PROJECT_PATH'],
@@ -77,12 +77,6 @@ module Trigger
}
end
- def ref_slug
- return 'master' if ENV['CI_COMMIT_REF_SLUG'] =~ %r{(\Aqa[/-]|-qa\z)}
-
- ENV['CI_COMMIT_REF_SLUG']
- end
-
# Read version files from all components
def version_file_variables
Dir.glob("*_VERSION").each_with_object({}) do |version_file, params|
diff --git a/spec/controllers/abuse_reports_controller_spec.rb b/spec/controllers/abuse_reports_controller_spec.rb
index ada011e7595..7104305e9d2 100644
--- a/spec/controllers/abuse_reports_controller_spec.rb
+++ b/spec/controllers/abuse_reports_controller_spec.rb
@@ -19,7 +19,7 @@ describe AbuseReportsController do
user_id = user.id
user.destroy
- get :new, { user_id: user_id }
+ get :new, params: { user_id: user_id }
expect(response).to redirect_to root_path
expect(flash[:alert]).to eq('Cannot create the abuse report. The user has been deleted.')
@@ -30,7 +30,7 @@ describe AbuseReportsController do
it 'redirects the reporter to the user\'s profile' do
user.block
- get :new, { user_id: user.id }
+ get :new, params: { user_id: user.id }
expect(response).to redirect_to user
expect(flash[:alert]).to eq('Cannot create the abuse report. This user has been blocked.')
@@ -42,18 +42,18 @@ describe AbuseReportsController do
context 'with valid attributes' do
it 'saves the abuse report' do
expect do
- post :create, abuse_report: attrs
+ post :create, params: { abuse_report: attrs }
end.to change { AbuseReport.count }.by(1)
end
it 'calls notify' do
expect_any_instance_of(AbuseReport).to receive(:notify)
- post :create, abuse_report: attrs
+ post :create, params: { abuse_report: attrs }
end
it 'redirects back to the reported user' do
- post :create, abuse_report: attrs
+ post :create, params: { abuse_report: attrs }
expect(response).to redirect_to user
end
@@ -62,7 +62,7 @@ describe AbuseReportsController do
context 'with invalid attributes' do
it 'renders new' do
attrs.delete(:user_id)
- post :create, abuse_report: attrs
+ post :create, params: { abuse_report: attrs }
expect(response).to render_template(:new)
end
diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb
index 2e0f79cd313..9af472df74e 100644
--- a/spec/controllers/admin/application_settings_controller_spec.rb
+++ b/spec/controllers/admin/application_settings_controller_spec.rb
@@ -52,35 +52,35 @@ describe Admin::ApplicationSettingsController do
end
it 'updates the password_authentication_enabled_for_git setting' do
- put :update, application_setting: { password_authentication_enabled_for_git: "0" }
+ put :update, params: { application_setting: { password_authentication_enabled_for_git: "0" } }
expect(response).to redirect_to(admin_application_settings_path)
expect(ApplicationSetting.current.password_authentication_enabled_for_git).to eq(false)
end
it 'updates the default_project_visibility for string value' do
- put :update, application_setting: { default_project_visibility: "20" }
+ put :update, params: { application_setting: { default_project_visibility: "20" } }
expect(response).to redirect_to(admin_application_settings_path)
expect(ApplicationSetting.current.default_project_visibility).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
it 'update the restricted levels for string values' do
- put :update, application_setting: { restricted_visibility_levels: %w[10 20] }
+ put :update, params: { application_setting: { restricted_visibility_levels: %w[10 20] } }
expect(response).to redirect_to(admin_application_settings_path)
expect(ApplicationSetting.current.restricted_visibility_levels).to eq([10, 20])
end
it 'updates the restricted_visibility_levels when empty array is passed' do
- put :update, application_setting: { restricted_visibility_levels: [""] }
+ put :update, params: { application_setting: { restricted_visibility_levels: [""] } }
expect(response).to redirect_to(admin_application_settings_path)
expect(ApplicationSetting.current.restricted_visibility_levels).to be_empty
end
it 'updates the receive_max_input_size setting' do
- put :update, application_setting: { receive_max_input_size: "1024" }
+ put :update, params: { application_setting: { receive_max_input_size: "1024" } }
expect(response).to redirect_to(admin_application_settings_path)
expect(ApplicationSetting.current.receive_max_input_size).to eq(1024)
diff --git a/spec/controllers/admin/applications_controller_spec.rb b/spec/controllers/admin/applications_controller_spec.rb
index 7bd6c0e6117..7e1ce70dc7d 100644
--- a/spec/controllers/admin/applications_controller_spec.rb
+++ b/spec/controllers/admin/applications_controller_spec.rb
@@ -19,7 +19,7 @@ describe Admin::ApplicationsController do
describe 'GET #edit' do
it 'renders the application form' do
- get :edit, id: application.id
+ get :edit, params: { id: application.id }
expect(response).to render_template :edit
expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes)
@@ -31,7 +31,7 @@ describe Admin::ApplicationsController do
create_params = attributes_for(:application, trusted: true)
expect do
- post :create, doorkeeper_application: create_params
+ post :create, params: { doorkeeper_application: create_params }
end.to change { Doorkeeper::Application.count }.by(1)
application = Doorkeeper::Application.last
@@ -42,7 +42,7 @@ describe Admin::ApplicationsController do
it 'renders the application form on errors' do
expect do
- post :create, doorkeeper_application: attributes_for(:application).merge(redirect_uri: nil)
+ post :create, params: { doorkeeper_application: attributes_for(:application).merge(redirect_uri: nil) }
end.not_to change { Doorkeeper::Application.count }
expect(response).to render_template :new
@@ -52,7 +52,7 @@ describe Admin::ApplicationsController do
describe 'PATCH #update' do
it 'updates the application' do
- patch :update, id: application.id, doorkeeper_application: { redirect_uri: 'http://example.com/', trusted: true }
+ patch :update, params: { id: application.id, doorkeeper_application: { redirect_uri: 'http://example.com/', trusted: true } }
application.reload
@@ -61,7 +61,7 @@ describe Admin::ApplicationsController do
end
it 'renders the application form on errors' do
- patch :update, id: application.id, doorkeeper_application: { redirect_uri: nil }
+ patch :update, params: { id: application.id, doorkeeper_application: { redirect_uri: nil } }
expect(response).to render_template :edit
expect(assigns[:scopes]).to be_kind_of(Doorkeeper::OAuth::Scopes)
diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb
index 0dad95e418f..647fce0ecef 100644
--- a/spec/controllers/admin/groups_controller_spec.rb
+++ b/spec/controllers/admin/groups_controller_spec.rb
@@ -12,12 +12,12 @@ describe Admin::GroupsController do
describe 'DELETE #destroy' do
it 'schedules a group destroy' do
Sidekiq::Testing.fake! do
- expect { delete :destroy, id: project.group.path }.to change(GroupDestroyWorker.jobs, :size).by(1)
+ expect { delete :destroy, params: { id: project.group.path } }.to change(GroupDestroyWorker.jobs, :size).by(1)
end
end
it 'redirects to the admin group path' do
- delete :destroy, id: project.group.path
+ delete :destroy, params: { id: project.group.path }
expect(response).to redirect_to(admin_groups_path)
end
@@ -27,9 +27,11 @@ describe Admin::GroupsController do
let(:group_user) { create(:user) }
it 'adds user to members' do
- put :members_update, id: group,
- user_ids: group_user.id,
- access_level: Gitlab::Access::GUEST
+ put :members_update, params: {
+ id: group,
+ user_ids: group_user.id,
+ access_level: Gitlab::Access::GUEST
+ }
expect(response).to set_flash.to 'Users were successfully added.'
expect(response).to redirect_to(admin_group_path(group))
@@ -37,18 +39,22 @@ describe Admin::GroupsController do
end
it 'can add unlimited members' do
- put :members_update, id: group,
- user_ids: 1.upto(1000).to_a.join(','),
- access_level: Gitlab::Access::GUEST
+ put :members_update, params: {
+ id: group,
+ user_ids: 1.upto(1000).to_a.join(','),
+ access_level: Gitlab::Access::GUEST
+ }
expect(response).to set_flash.to 'Users were successfully added.'
expect(response).to redirect_to(admin_group_path(group))
end
it 'adds no user to members' do
- put :members_update, id: group,
- user_ids: '',
- access_level: Gitlab::Access::GUEST
+ put :members_update, params: {
+ id: group,
+ user_ids: '',
+ access_level: Gitlab::Access::GUEST
+ }
expect(response).to set_flash.to 'No users specified.'
expect(response).to redirect_to(admin_group_path(group))
diff --git a/spec/controllers/admin/hooks_controller_spec.rb b/spec/controllers/admin/hooks_controller_spec.rb
index d2c1e634930..9bc58344e4e 100644
--- a/spec/controllers/admin/hooks_controller_spec.rb
+++ b/spec/controllers/admin/hooks_controller_spec.rb
@@ -20,7 +20,7 @@ describe Admin::HooksController do
merge_requests_events: true
}
- post :create, hook: hook_params
+ post :create, params: { hook: hook_params }
expect(response).to have_gitlab_http_status(302)
expect(SystemHook.all.size).to eq(1)
diff --git a/spec/controllers/admin/identities_controller_spec.rb b/spec/controllers/admin/identities_controller_spec.rb
index a29853bf8df..e5428c8ddeb 100644
--- a/spec/controllers/admin/identities_controller_spec.rb
+++ b/spec/controllers/admin/identities_controller_spec.rb
@@ -13,7 +13,7 @@ describe Admin::IdentitiesController do
it 'repairs ldap blocks' do
expect_any_instance_of(RepairLdapBlockedUserService).to receive(:execute)
- put :update, user_id: user.username, id: user.ldap_identity.id, identity: { provider: 'twitter' }
+ put :update, params: { user_id: user.username, id: user.ldap_identity.id, identity: { provider: 'twitter' } }
end
end
@@ -23,7 +23,7 @@ describe Admin::IdentitiesController do
it 'repairs ldap blocks' do
expect_any_instance_of(RepairLdapBlockedUserService).to receive(:execute)
- delete :destroy, user_id: user.username, id: user.ldap_identity.id
+ delete :destroy, params: { user_id: user.username, id: user.ldap_identity.id }
end
end
end
diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb
index ee1aff09bdf..8166657f674 100644
--- a/spec/controllers/admin/projects_controller_spec.rb
+++ b/spec/controllers/admin/projects_controller_spec.rb
@@ -11,13 +11,13 @@ describe Admin::ProjectsController do
render_views
it 'retrieves the project for the given visibility level' do
- get :index, visibility_level: [Gitlab::VisibilityLevel::PUBLIC]
+ get :index, params: { visibility_level: [Gitlab::VisibilityLevel::PUBLIC] }
expect(response.body).to match(project.name)
end
it 'does not retrieve the project' do
- get :index, visibility_level: [Gitlab::VisibilityLevel::INTERNAL]
+ get :index, params: { visibility_level: [Gitlab::VisibilityLevel::INTERNAL] }
expect(response.body).not_to match(project.name)
end
@@ -47,7 +47,7 @@ describe Admin::ProjectsController do
render_views
it 'renders show page' do
- get :show, namespace_id: project.namespace.path, id: project.path
+ get :show, params: { namespace_id: project.namespace.path, id: project.path }
expect(response).to have_gitlab_http_status(200)
expect(response.body).to match(project.name)
diff --git a/spec/controllers/admin/runners_controller_spec.rb b/spec/controllers/admin/runners_controller_spec.rb
index 312dbdd0624..4cf14030ca1 100644
--- a/spec/controllers/admin/runners_controller_spec.rb
+++ b/spec/controllers/admin/runners_controller_spec.rb
@@ -17,13 +17,13 @@ describe Admin::RunnersController do
describe '#show' do
it 'shows a particular runner' do
- get :show, id: runner.id
+ get :show, params: { id: runner.id }
expect(response).to have_gitlab_http_status(200)
end
it 'shows 404 for unknown runner' do
- get :show, id: 0
+ get :show, params: { id: 0 }
expect(response).to have_gitlab_http_status(404)
end
@@ -34,7 +34,7 @@ describe Admin::RunnersController do
new_desc = runner.description.swapcase
expect do
- post :update, id: runner.id, runner: { description: new_desc }
+ post :update, params: { id: runner.id, runner: { description: new_desc } }
end.to change { runner.ensure_runner_queue_value }
runner.reload
@@ -46,7 +46,7 @@ describe Admin::RunnersController do
describe '#destroy' do
it 'destroys the runner' do
- delete :destroy, id: runner.id
+ delete :destroy, params: { id: runner.id }
expect(response).to have_gitlab_http_status(302)
expect(Ci::Runner.find_by(id: runner.id)).to be_nil
@@ -58,7 +58,7 @@ describe Admin::RunnersController do
runner.update(active: false)
expect do
- post :resume, id: runner.id
+ post :resume, params: { id: runner.id }
end.to change { runner.ensure_runner_queue_value }
runner.reload
@@ -73,7 +73,7 @@ describe Admin::RunnersController do
runner.update(active: true)
expect do
- post :pause, id: runner.id
+ post :pause, params: { id: runner.id }
end.to change { runner.ensure_runner_queue_value }
runner.reload
diff --git a/spec/controllers/admin/services_controller_spec.rb b/spec/controllers/admin/services_controller_spec.rb
index 4439ea4a533..ec161b92245 100644
--- a/spec/controllers/admin/services_controller_spec.rb
+++ b/spec/controllers/admin/services_controller_spec.rb
@@ -18,7 +18,7 @@ describe Admin::ServicesController do
end
it 'successfully displays the template' do
- get :edit, id: service.id
+ get :edit, params: { id: service.id }
expect(response).to have_gitlab_http_status(200)
end
@@ -44,7 +44,7 @@ describe Admin::ServicesController do
it 'calls the propagation worker when service is active' do
expect(PropagateServiceTemplateWorker).to receive(:perform_async).with(service.id)
- put :update, id: service.id, service: { active: true }
+ put :update, params: { id: service.id, service: { active: true } }
expect(response).to have_gitlab_http_status(302)
end
@@ -52,7 +52,7 @@ describe Admin::ServicesController do
it 'does not call the propagation worker when service is not active' do
expect(PropagateServiceTemplateWorker).not_to receive(:perform_async)
- put :update, id: service.id, service: { properties: {} }
+ put :update, params: { id: service.id, service: { properties: {} } }
expect(response).to have_gitlab_http_status(302)
end
diff --git a/spec/controllers/admin/spam_logs_controller_spec.rb b/spec/controllers/admin/spam_logs_controller_spec.rb
index 7a96ef6a5cc..2b946ec1c68 100644
--- a/spec/controllers/admin/spam_logs_controller_spec.rb
+++ b/spec/controllers/admin/spam_logs_controller_spec.rb
@@ -20,13 +20,13 @@ describe Admin::SpamLogsController do
describe '#destroy' do
it 'removes only the spam log when removing log' do
- expect { delete :destroy, id: first_spam.id }.to change { SpamLog.count }.by(-1)
+ expect { delete :destroy, params: { id: first_spam.id } }.to change { SpamLog.count }.by(-1)
expect(User.find(user.id)).to be_truthy
expect(response).to have_gitlab_http_status(200)
end
it 'removes user and his spam logs when removing the user' do
- delete :destroy, id: first_spam.id, remove_user: true
+ delete :destroy, params: { id: first_spam.id, remove_user: true }
expect(flash[:notice]).to eq "User #{user.username} was successfully removed."
expect(response).to have_gitlab_http_status(302)
@@ -40,7 +40,7 @@ describe Admin::SpamLogsController do
allow_any_instance_of(AkismetService).to receive(:submit_ham).and_return(true)
end
it 'submits the log as ham' do
- post :mark_as_ham, id: first_spam.id
+ post :mark_as_ham, params: { id: first_spam.id }
expect(response).to have_gitlab_http_status(302)
expect(SpamLog.find(first_spam.id).submitted_as_ham).to be_truthy
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 3dd0b2623ac..6b66cbd2651 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -17,7 +17,7 @@ describe Admin::UsersController do
end
it 'deletes user and ghosts their contributions' do
- delete :destroy, id: user.username, format: :json
+ delete :destroy, params: { id: user.username }, format: :json
expect(response).to have_gitlab_http_status(200)
expect(User.exists?(user.id)).to be_falsy
@@ -25,7 +25,7 @@ describe Admin::UsersController do
end
it 'deletes the user and their contributions when hard delete is specified' do
- delete :destroy, id: user.username, hard_delete: true, format: :json
+ delete :destroy, params: { id: user.username, hard_delete: true }, format: :json
expect(response).to have_gitlab_http_status(200)
expect(User.exists?(user.id)).to be_falsy
@@ -35,7 +35,7 @@ describe Admin::UsersController do
describe 'PUT block/:id' do
it 'blocks user' do
- put :block, id: user.username
+ put :block, params: { id: user.username }
user.reload
expect(user.blocked?).to be_truthy
expect(flash[:notice]).to eq 'Successfully blocked'
@@ -51,7 +51,7 @@ describe Admin::UsersController do
end
it 'does not unblock user' do
- put :unblock, id: user.username
+ put :unblock, params: { id: user.username }
user.reload
expect(user.blocked?).to be_truthy
expect(flash[:alert]).to eq 'This user cannot be unlocked manually from GitLab'
@@ -64,7 +64,7 @@ describe Admin::UsersController do
end
it 'unblocks user' do
- put :unblock, id: user.username
+ put :unblock, params: { id: user.username }
user.reload
expect(user.blocked?).to be_falsey
expect(flash[:notice]).to eq 'Successfully unblocked'
@@ -79,7 +79,7 @@ describe Admin::UsersController do
end
it 'unlocks user' do
- put :unlock, id: user.username
+ put :unlock, params: { id: user.username }
user.reload
expect(user.access_locked?).to be_falsey
end
@@ -93,7 +93,7 @@ describe Admin::UsersController do
end
it 'confirms user' do
- put :confirm, id: user.username
+ put :confirm, params: { id: user.username }
user.reload
expect(user.confirmed?).to be_truthy
end
@@ -121,17 +121,17 @@ describe Admin::UsersController do
end
def go
- patch :disable_two_factor, id: user.to_param
+ patch :disable_two_factor, params: { id: user.to_param }
end
end
describe 'POST create' do
it 'creates the user' do
- expect { post :create, user: attributes_for(:user) }.to change { User.count }.by(1)
+ expect { post :create, params: { user: attributes_for(:user) } }.to change { User.count }.by(1)
end
it 'shows only one error message for an invalid email' do
- post :create, user: attributes_for(:user, email: 'bogus')
+ post :create, params: { user: attributes_for(:user, email: 'bogus') }
expect(assigns[:user].errors).to contain_exactly("Email is invalid")
end
end
@@ -147,7 +147,7 @@ describe Admin::UsersController do
}
}
- post :update, params
+ post :update, params: params
end
context 'when the admin changes his own password' do
@@ -227,13 +227,13 @@ describe Admin::UsersController do
end
it "shows a notice" do
- post :impersonate, id: user.username
+ post :impersonate, params: { id: user.username }
expect(flash[:alert]).to eq("You cannot impersonate a blocked user")
end
it "doesn't sign us in as the user" do
- post :impersonate, id: user.username
+ post :impersonate, params: { id: user.username }
expect(warden.user).to eq(admin)
end
@@ -241,25 +241,25 @@ describe Admin::UsersController do
context "when the user is not blocked" do
it "stores the impersonator in the session" do
- post :impersonate, id: user.username
+ post :impersonate, params: { id: user.username }
expect(session[:impersonator_id]).to eq(admin.id)
end
it "signs us in as the user" do
- post :impersonate, id: user.username
+ post :impersonate, params: { id: user.username }
expect(warden.user).to eq(user)
end
it "redirects to root" do
- post :impersonate, id: user.username
+ post :impersonate, params: { id: user.username }
expect(response).to redirect_to(root_path)
end
it "shows a notice" do
- post :impersonate, id: user.username
+ post :impersonate, params: { id: user.username }
expect(flash[:alert]).to eq("You are now impersonating #{user.username}")
end
@@ -271,7 +271,7 @@ describe Admin::UsersController do
end
it "shows error page" do
- post :impersonate, id: user.username
+ post :impersonate, params: { id: user.username }
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index ac92b2ca657..43f561f7a25 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -460,6 +460,14 @@ describe ApplicationController do
expect(controller.last_payload.has_key?(:response)).to be_falsey
end
+ it 'does log correlation id' do
+ Gitlab::CorrelationId.use_id('new-id') do
+ get :index
+ end
+
+ expect(controller.last_payload).to include('correlation_id' => 'new-id')
+ end
+
context '422 errors' do
it 'logs a response with a string' do
response = spy(ActionDispatch::Response, status: 422, body: 'Hello world', content_type: 'application/json', cookies: {})
@@ -514,13 +522,13 @@ describe ApplicationController do
end
it 'renders a 403 when a message is passed to access denied' do
- get :index, message: 'None shall pass'
+ get :index, params: { message: 'None shall pass' }
expect(response).to have_gitlab_http_status(403)
end
it 'renders a status passed to access denied' do
- get :index, status: 401
+ get :index, params: { status: 401 }
expect(response).to have_gitlab_http_status(401)
end
@@ -540,34 +548,18 @@ describe ApplicationController do
end
context 'html' do
- subject { get :index, text: "hi \255" }
+ subject { get :index, params: { text: "hi \255" } }
it 'renders 412' do
- if Gitlab.rails5?
- expect { subject }.to raise_error(ActionController::BadRequest)
- else
- subject
-
- expect(response).to have_gitlab_http_status(412)
- expect(response).to render_template :precondition_failed
- end
+ expect { subject }.to raise_error(ActionController::BadRequest)
end
end
context 'js' do
- subject { get :index, text: "hi \255", format: :js }
+ subject { get :index, format: :js, params: { text: "hi \255" } }
it 'renders 412' do
- if Gitlab.rails5?
- expect { subject }.to raise_error(ActionController::BadRequest)
- else
- subject
-
- json_response = JSON.parse(response.body)
-
- expect(response).to have_gitlab_http_status(412)
- expect(json_response['error']).to eq('Invalid UTF-8')
- end
+ expect { subject }.to raise_error(ActionController::BadRequest)
end
end
end
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 883bb35f396..4458a7223bf 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -15,7 +15,7 @@ describe AutocompleteController do
describe 'GET #users with project ID' do
before do
- get(:users, project_id: project.id)
+ get(:users, params: { project_id: project.id })
end
it 'returns the project members' do
@@ -27,7 +27,7 @@ describe AutocompleteController do
describe 'GET #users with unknown project' do
before do
- get(:users, project_id: 'unknown')
+ get(:users, params: { project_id: 'unknown' })
end
it { expect(response).to have_gitlab_http_status(404) }
@@ -44,7 +44,7 @@ describe AutocompleteController do
describe 'GET #users with group ID' do
before do
- get(:users, group_id: group.id)
+ get(:users, params: { group_id: group.id })
end
it 'returns the group members' do
@@ -56,7 +56,7 @@ describe AutocompleteController do
describe 'GET #users with unknown group ID' do
before do
- get(:users, group_id: 'unknown')
+ get(:users, params: { group_id: 'unknown' })
end
it { expect(response).to have_gitlab_http_status(404) }
@@ -72,7 +72,7 @@ describe AutocompleteController do
describe 'GET #users with project ID' do
before do
- get(:users, project_id: project.id, current_user: true)
+ get(:users, params: { project_id: project.id, current_user: true })
end
it 'returns the project members and non-members' do
@@ -100,7 +100,7 @@ describe AutocompleteController do
user1 = create(:user, username: 'user1', name: 'Ian')
sign_in(user)
- get(:users, search: 'user')
+ get(:users, params: { search: 'user' })
response_usernames = json_response.map { |user| user['username'] }
@@ -128,7 +128,7 @@ describe AutocompleteController do
describe 'GET #users with public project' do
before do
public_project.add_guest(user)
- get(:users, project_id: public_project.id)
+ get(:users, params: { project_id: public_project.id })
end
it { expect(json_response).to be_kind_of(Array) }
@@ -137,7 +137,7 @@ describe AutocompleteController do
describe 'GET #users with project' do
before do
- get(:users, project_id: project.id)
+ get(:users, params: { project_id: project.id })
end
it { expect(response).to have_gitlab_http_status(404) }
@@ -145,7 +145,7 @@ describe AutocompleteController do
describe 'GET #users with unknown project' do
before do
- get(:users, project_id: 'unknown')
+ get(:users, params: { project_id: 'unknown' })
end
it { expect(response).to have_gitlab_http_status(404) }
@@ -154,7 +154,7 @@ describe AutocompleteController do
describe 'GET #users with inaccessible group' do
before do
project.add_guest(user)
- get(:users, group_id: user.namespace.id)
+ get(:users, params: { group_id: user.namespace.id })
end
it { expect(response).to have_gitlab_http_status(404) }
@@ -171,7 +171,7 @@ describe AutocompleteController do
describe 'GET #users with todo filter' do
it 'gives an array of users' do
- get :users, todo_filter: true
+ get :users, params: { todo_filter: true }
expect(response.status).to eq 200
expect(json_response).to be_kind_of(Array)
@@ -186,13 +186,13 @@ describe AutocompleteController do
end
it 'includes the author' do
- get(:users, author_id: non_member.id)
+ get(:users, params: { author_id: non_member.id })
expect(json_response.first["username"]).to eq non_member.username
end
it 'rejects non existent user ids' do
- get(:users, author_id: 99999)
+ get(:users, params: { author_id: 99999 })
expect(json_response.collect { |u| u['id'] }).not_to include(99999)
end
@@ -200,7 +200,7 @@ describe AutocompleteController do
context 'without authenticating' do
it 'returns empty result' do
- get(:users, author_id: non_member.id)
+ get(:users, params: { author_id: non_member.id })
expect(json_response).to be_empty
end
@@ -213,7 +213,7 @@ describe AutocompleteController do
end
it 'skips the user IDs passed' do
- get(:users, skip_users: [user, user2].map(&:id))
+ get(:users, params: { skip_users: [user, user2].map(&:id) })
response_user_ids = json_response.map { |user| user['id'] }
@@ -238,7 +238,7 @@ describe AutocompleteController do
describe 'GET #projects with project ID' do
before do
- get(:projects, project_id: project.id)
+ get(:projects, params: { project_id: project.id })
end
it 'returns projects' do
@@ -259,7 +259,7 @@ describe AutocompleteController do
describe 'GET #projects with project ID and search' do
before do
- get(:projects, project_id: project.id, search: 'rugged')
+ get(:projects, params: { project_id: project.id, search: 'rugged' })
end
it 'returns projects' do
@@ -283,7 +283,7 @@ describe AutocompleteController do
describe 'GET #projects with project ID' do
before do
- get(:projects, project_id: project.id)
+ get(:projects, params: { project_id: project.id })
end
it 'returns projects' do
@@ -305,7 +305,7 @@ describe AutocompleteController do
describe 'GET #projects with project ID and offset_id' do
before do
- get(:projects, project_id: project.id, offset_id: authorized_project.id)
+ get(:projects, params: { project_id: project.id, offset_id: authorized_project.id })
end
it 'returns projects' do
@@ -324,7 +324,7 @@ describe AutocompleteController do
describe 'GET #projects with project ID' do
before do
- get(:projects, project_id: project.id)
+ get(:projects, params: { project_id: project.id })
end
it 'returns no projects' do
diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb
index 6d0483f0032..8657fc2ebc0 100644
--- a/spec/controllers/boards/issues_controller_spec.rb
+++ b/spec/controllers/boards/issues_controller_spec.rb
@@ -153,7 +153,7 @@ describe Boards::IssuesController do
params[:project_id] = project
end
- get :index, params.compact
+ get :index, params: params.compact
end
end
@@ -230,9 +230,11 @@ describe Boards::IssuesController do
def create_issue(user:, board:, list:, title:)
sign_in(user)
- post :create, board_id: board.to_param,
- list_id: list.to_param,
- issue: { title: title, project_id: project.id },
+ post :create, params: {
+ board_id: board.to_param,
+ list_id: list.to_param,
+ issue: { title: title, project_id: project.id }
+ },
format: :json
end
end
@@ -291,12 +293,14 @@ describe Boards::IssuesController do
def move(user:, board:, issue:, from_list_id:, to_list_id:)
sign_in(user)
- patch :update, namespace_id: project.namespace.to_param,
- project_id: project.id,
- board_id: board.to_param,
- id: issue.id,
- from_list_id: from_list_id,
- to_list_id: to_list_id,
+ patch :update, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project.id,
+ board_id: board.to_param,
+ id: issue.id,
+ from_list_id: from_list_id,
+ to_list_id: to_list_id
+ },
format: :json
end
end
diff --git a/spec/controllers/boards/lists_controller_spec.rb b/spec/controllers/boards/lists_controller_spec.rb
index 80631d2efb0..70033857168 100644
--- a/spec/controllers/boards/lists_controller_spec.rb
+++ b/spec/controllers/boards/lists_controller_spec.rb
@@ -46,9 +46,11 @@ describe Boards::ListsController do
def read_board_list(user:, board:)
sign_in(user)
- get :index, namespace_id: project.namespace.to_param,
- project_id: project,
- board_id: board.to_param,
+ get :index, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ board_id: board.to_param
+ },
format: :json
end
end
@@ -103,10 +105,12 @@ describe Boards::ListsController do
def create_board_list(user:, board:, label_id:)
sign_in(user)
- post :create, namespace_id: project.namespace.to_param,
- project_id: project,
- board_id: board.to_param,
- list: { label_id: label_id },
+ post :create, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ board_id: board.to_param,
+ list: { label_id: label_id }
+ },
format: :json
end
end
@@ -163,11 +167,7 @@ describe Boards::ListsController do
list: { position: position },
format: :json }
- if Gitlab.rails5?
- patch :update, params: params, as: :json
- else
- patch :update, params
- end
+ patch :update, params: params, as: :json
end
end
@@ -205,10 +205,12 @@ describe Boards::ListsController do
def remove_board_list(user:, board:, list:)
sign_in(user)
- delete :destroy, namespace_id: project.namespace.to_param,
- project_id: project,
- board_id: board.to_param,
- id: list.to_param,
+ delete :destroy, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ board_id: board.to_param,
+ id: list.to_param
+ },
format: :json
end
end
@@ -249,9 +251,11 @@ describe Boards::ListsController do
def generate_default_lists(user:, board:)
sign_in(user)
- post :generate, namespace_id: project.namespace.to_param,
- project_id: project,
- board_id: board.to_param,
+ post :generate, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ board_id: board.to_param
+ },
format: :json
end
end
diff --git a/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb b/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb
index 3c9452cc42a..9b2d054f4fc 100644
--- a/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb
+++ b/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb
@@ -70,7 +70,7 @@ describe ControllerWithCrossProjectAccessCheck do
end
it 'correctly renders an action that does not require cross project access' do
- get :show, id: 'nothing'
+ get :show, params: { id: 'nothing' }
expect(response).to have_gitlab_http_status(200)
end
@@ -131,13 +131,13 @@ describe ControllerWithCrossProjectAccessCheck do
end
it 'does not skip the check on an action that is not skipped' do
- get :show, id: 'hello'
+ get :show, params: { id: 'hello' }
expect(response).to have_gitlab_http_status(403)
end
it 'does not skip the check on an action that was not defined to skip' do
- get :edit, id: 'hello'
+ get :edit, params: { id: 'hello' }
expect(response).to have_gitlab_http_status(403)
end
diff --git a/spec/controllers/concerns/group_tree_spec.rb b/spec/controllers/concerns/group_tree_spec.rb
index 503eb416962..9fe17210d50 100644
--- a/spec/controllers/concerns/group_tree_spec.rb
+++ b/spec/controllers/concerns/group_tree_spec.rb
@@ -23,7 +23,7 @@ describe GroupTree do
other_group = create(:group, name: 'filter')
other_group.add_owner(user)
- get :index, filter: 'filt', format: :json
+ get :index, params: { filter: 'filt' }, format: :json
expect(assigns(:groups)).to contain_exactly(other_group)
end
@@ -40,7 +40,7 @@ describe GroupTree do
it 'contains only the subgroup when a parent was given' do
subgroup = create(:group, :public, parent: group)
- get :index, parent_id: group.id, format: :json
+ get :index, params: { parent_id: group.id }, format: :json
expect(assigns(:groups)).to contain_exactly(subgroup)
end
@@ -48,7 +48,7 @@ describe GroupTree do
it 'allows filtering for subgroups and includes the parents for rendering' do
subgroup = create(:group, :public, parent: group, name: 'filter')
- get :index, filter: 'filt', format: :json
+ get :index, params: { filter: 'filt' }, format: :json
expect(assigns(:groups)).to contain_exactly(group, subgroup)
end
@@ -59,7 +59,7 @@ describe GroupTree do
subgroup.add_developer(user)
_other_subgroup = create(:group, :private, parent: parent, name: 'filte')
- get :index, filter: 'filt', format: :json
+ get :index, params: { filter: 'filt' }, format: :json
expect(assigns(:groups)).to contain_exactly(parent, subgroup)
end
@@ -70,7 +70,7 @@ describe GroupTree do
subgroup = create(:group, :public, parent: group)
search_result = create(:group, :public, name: 'result', parent: subgroup)
- get :index, filter: 'resu', format: :json
+ get :index, params: { filter: 'resu' }, format: :json
expect(assigns(:groups)).to contain_exactly(group, subgroup, search_result)
end
@@ -87,7 +87,7 @@ describe GroupTree do
it 'expands the tree when filtering' do
subgroup = create(:group, :public, parent: group, name: 'filter')
- get :index, filter: 'filt', format: :json
+ get :index, params: { filter: 'filt' }, format: :json
children_response = json_response.first['children']
diff --git a/spec/controllers/concerns/issuable_collections_spec.rb b/spec/controllers/concerns/issuable_collections_spec.rb
index f87eed6ff9f..5a3a7a15f5a 100644
--- a/spec/controllers/concerns/issuable_collections_spec.rb
+++ b/spec/controllers/concerns/issuable_collections_spec.rb
@@ -90,7 +90,7 @@ describe IssuableCollections do
finder_options = controller.send(:finder_options)
- expect(finder_options).to eq({
+ expect(finder_options).to eq(ActionController::Parameters.new({
'assignee_id' => '1',
'assignee_username' => 'user1',
'author_id' => '2',
@@ -103,7 +103,7 @@ describe IssuableCollections do
'search' => 'baz',
'sort' => 'priority',
'state' => 'opened'
- })
+ }).permit!)
end
end
end
diff --git a/spec/controllers/concerns/lfs_request_spec.rb b/spec/controllers/concerns/lfs_request_spec.rb
index 76c878ec5d7..7b49d4b6a3a 100644
--- a/spec/controllers/concerns/lfs_request_spec.rb
+++ b/spec/controllers/concerns/lfs_request_spec.rb
@@ -34,7 +34,7 @@ describe LfsRequest do
describe '#storage_project' do
it 'assigns the project as storage project' do
- get :show, id: project.id
+ get :show, params: { id: project.id }
expect(assigns(:storage_project)).to eq(project)
end
@@ -42,7 +42,7 @@ describe LfsRequest do
it 'assigns the source of a forked project' do
forked_project = fork_project(project)
- get :show, id: forked_project.id
+ get :show, params: { id: forked_project.id }
expect(assigns(:storage_project)).to eq(project)
end
diff --git a/spec/controllers/dashboard/groups_controller_spec.rb b/spec/controllers/dashboard/groups_controller_spec.rb
index 9068c1a792e..c8d99f79277 100644
--- a/spec/controllers/dashboard/groups_controller_spec.rb
+++ b/spec/controllers/dashboard/groups_controller_spec.rb
@@ -33,7 +33,7 @@ describe Dashboard::GroupsController do
end
it 'renders only groups the user is a member of when searching hierarchy correctly' do
- get :index, filter: 'chef', format: :json
+ get :index, params: { filter: 'chef' }, format: :json
expect(response).to have_gitlab_http_status(200)
all_groups = [top_level_result, top_level_a, sub_level_result_a]
diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb
index 278b980b6d8..8a8cc14fd4c 100644
--- a/spec/controllers/dashboard/milestones_controller_spec.rb
+++ b/spec/controllers/dashboard/milestones_controller_spec.rb
@@ -34,7 +34,7 @@ describe Dashboard::MilestonesController do
render_views
def view_milestone
- get :show, id: milestone.safe_title, title: milestone.title
+ get :show, params: { id: milestone.safe_title, title: milestone.title }
end
it 'shows milestone page' do
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb
index e2c799f5205..d88beaff0e1 100644
--- a/spec/controllers/dashboard/todos_controller_spec.rb
+++ b/spec/controllers/dashboard/todos_controller_spec.rb
@@ -16,19 +16,19 @@ describe Dashboard::TodosController do
it 'renders 404 when user does not have read access on given project' do
unauthorized_project = create(:project, :private)
- get :index, project_id: unauthorized_project.id
+ get :index, params: { project_id: unauthorized_project.id }
expect(response).to have_gitlab_http_status(404)
end
it 'renders 404 when given project does not exists' do
- get :index, project_id: 999
+ get :index, params: { project_id: 999 }
expect(response).to have_gitlab_http_status(404)
end
it 'renders 200 when filtering for "any project" todos' do
- get :index, project_id: ''
+ get :index, params: { project_id: '' }
expect(response).to have_gitlab_http_status(200)
end
@@ -36,7 +36,7 @@ describe Dashboard::TodosController do
it 'renders 200 when user has access on given project' do
authorized_project = create(:project, :public)
- get :index, project_id: authorized_project.id
+ get :index, params: { project_id: authorized_project.id }
expect(response).to have_gitlab_http_status(200)
end
@@ -46,7 +46,7 @@ describe Dashboard::TodosController do
it 'renders 404 when user does not have read access on given group' do
unauthorized_group = create(:group, :private)
- get :index, group_id: unauthorized_group.id
+ get :index, params: { group_id: unauthorized_group.id }
expect(response).to have_gitlab_http_status(404)
end
@@ -62,13 +62,13 @@ describe Dashboard::TodosController do
end
it 'redirects to last_page if page number is larger than number of pages' do
- get :index, page: (last_page + 1).to_param
+ get :index, params: { page: (last_page + 1).to_param }
expect(response).to redirect_to(dashboard_todos_path(page: last_page))
end
it 'goes to the correct page' do
- get :index, page: last_page
+ get :index, params: { page: last_page }
expect(assigns(:todos).current_page).to eq(last_page)
expect(response).to have_gitlab_http_status(200)
@@ -76,7 +76,7 @@ describe Dashboard::TodosController do
it 'does not redirect to external sites when provided a host field' do
external_host = "www.example.com"
- get :index, page: (last_page + 1).to_param, host: external_host
+ get :index, params: { page: (last_page + 1).to_param, host: external_host }
expect(response).to redirect_to(dashboard_todos_path(page: last_page))
end
@@ -87,7 +87,7 @@ describe Dashboard::TodosController do
expect(user).to receive(:todos_pending_count).and_call_original
- get :index, page: (last_page + 1).to_param, sort: :created_asc
+ get :index, params: { page: (last_page + 1).to_param, sort: :created_asc }
expect(response).to redirect_to(dashboard_todos_path(page: last_page, sort: :created_asc))
end
@@ -99,7 +99,7 @@ describe Dashboard::TodosController do
expect(user).not_to receive(:todos_pending_count)
- get :index, page: (last_page + 1).to_param, project_id: project.id
+ get :index, params: { page: (last_page + 1).to_param, project_id: project.id }
expect(response).to redirect_to(dashboard_todos_path(page: last_page, project_id: project.id))
end
@@ -111,7 +111,7 @@ describe Dashboard::TodosController do
let(:todo) { create(:todo, :done, user: user, project: project, author: author) }
it 'restores the todo to pending state' do
- patch :restore, id: todo.id
+ patch :restore, params: { id: todo.id }
expect(todo.reload).to be_pending
expect(response).to have_gitlab_http_status(200)
@@ -123,7 +123,7 @@ describe Dashboard::TodosController do
let(:todos) { create_list(:todo, 2, :done, user: user, project: project, author: author) }
it 'restores the todos to pending state' do
- patch :bulk_restore, ids: todos.map(&:id)
+ patch :bulk_restore, params: { ids: todos.map(&:id) }
todos.each do |todo|
expect(todo.reload).to be_pending
diff --git a/spec/controllers/explore/projects_controller_spec.rb b/spec/controllers/explore/projects_controller_spec.rb
index 2845f258f6f..d57367e931e 100644
--- a/spec/controllers/explore/projects_controller_spec.rb
+++ b/spec/controllers/explore/projects_controller_spec.rb
@@ -12,13 +12,13 @@ describe Explore::ProjectsController do
end
it 'sorts by last updated' do
- get :trending, sort: 'updated_desc'
+ get :trending, params: { sort: 'updated_desc' }
expect(assigns(:projects)).to eq [project2, project1]
end
it 'sorts by oldest updated' do
- get :trending, sort: 'updated_asc'
+ get :trending, params: { sort: 'updated_asc' }
expect(assigns(:projects)).to eq [project1, project2]
end
diff --git a/spec/controllers/google_api/authorizations_controller_spec.rb b/spec/controllers/google_api/authorizations_controller_spec.rb
index 80d553f0f34..1e8e82da4f3 100644
--- a/spec/controllers/google_api/authorizations_controller_spec.rb
+++ b/spec/controllers/google_api/authorizations_controller_spec.rb
@@ -6,7 +6,7 @@ describe GoogleApi::AuthorizationsController do
let(:token) { 'token' }
let(:expires_at) { 1.hour.since.strftime('%s') }
- subject { get :callback, code: 'xxx', state: @state }
+ subject { get :callback, params: { code: 'xxx', state: @state } }
before do
sign_in(user)
diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb
index 949ad532365..a0f40874db1 100644
--- a/spec/controllers/graphql_controller_spec.rb
+++ b/spec/controllers/graphql_controller_spec.rb
@@ -103,7 +103,7 @@ describe GraphqlController do
}
QUERY
- post :execute, query: query, operationName: 'Echo', variables: variables, private_token: private_token
+ post :execute, params: { query: query, operationName: 'Echo', variables: variables, private_token: private_token }
end
def query_response
diff --git a/spec/controllers/groups/avatars_controller_spec.rb b/spec/controllers/groups/avatars_controller_spec.rb
index 7feecd0c380..772d1d0c1dd 100644
--- a/spec/controllers/groups/avatars_controller_spec.rb
+++ b/spec/controllers/groups/avatars_controller_spec.rb
@@ -10,7 +10,7 @@ describe Groups::AvatarsController do
end
it 'removes avatar from DB calling destroy' do
- delete :destroy, group_id: group.path
+ delete :destroy, params: { group_id: group.path }
@group = assigns(:group)
expect(@group.avatar.present?).to be_falsey
expect(@group).to be_valid
diff --git a/spec/controllers/groups/boards_controller_spec.rb b/spec/controllers/groups/boards_controller_spec.rb
index 99429c93b82..4228e727b52 100644
--- a/spec/controllers/groups/boards_controller_spec.rb
+++ b/spec/controllers/groups/boards_controller_spec.rb
@@ -105,7 +105,7 @@ describe Groups::BoardsController do
end
def list_boards(format: :html)
- get :index, group_id: group, format: format
+ get :index, params: { group_id: group }, format: format
end
end
@@ -183,8 +183,10 @@ describe Groups::BoardsController do
end
def read_board(board:, format: :html)
- get :show, group_id: group,
- id: board.to_param,
+ get :show, params: {
+ group_id: group,
+ id: board.to_param
+ },
format: format
end
end
diff --git a/spec/controllers/groups/children_controller_spec.rb b/spec/controllers/groups/children_controller_spec.rb
index 22d3076c269..4d5bb1488ab 100644
--- a/spec/controllers/groups/children_controller_spec.rb
+++ b/spec/controllers/groups/children_controller_spec.rb
@@ -16,7 +16,7 @@ describe Groups::ChildrenController do
end
it 'shows all children' do
- get :index, group_id: group.to_param, format: :json
+ get :index, params: { group_id: group.to_param }, format: :json
expect(assigns(:children)).to contain_exactly(public_project, private_project)
end
@@ -26,7 +26,7 @@ describe Groups::ChildrenController do
group_member.destroy!
private_project.add_guest(user)
- get :index, group_id: group.to_param, format: :json
+ get :index, params: { group_id: group.to_param }, format: :json
expect(assigns(:children)).to contain_exactly(public_project, private_project)
end
@@ -35,7 +35,7 @@ describe Groups::ChildrenController do
context 'as a guest' do
it 'shows the public children' do
- get :index, group_id: group.to_param, format: :json
+ get :index, params: { group_id: group.to_param }, format: :json
expect(assigns(:children)).to contain_exactly(public_project)
end
@@ -54,7 +54,7 @@ describe Groups::ChildrenController do
end
it 'shows all children' do
- get :index, group_id: group.to_param, format: :json
+ get :index, params: { group_id: group.to_param }, format: :json
expect(assigns(:children)).to contain_exactly(public_subgroup, private_subgroup, public_project, private_project)
end
@@ -65,7 +65,7 @@ describe Groups::ChildrenController do
private_subgroup.add_guest(user)
private_project.add_guest(user)
- get :index, group_id: group.to_param, format: :json
+ get :index, params: { group_id: group.to_param }, format: :json
expect(assigns(:children)).to contain_exactly(public_subgroup, private_subgroup, public_project, private_project)
end
@@ -74,7 +74,7 @@ describe Groups::ChildrenController do
context 'as a guest' do
it 'shows the public children' do
- get :index, group_id: group.to_param, format: :json
+ get :index, params: { group_id: group.to_param }, format: :json
expect(assigns(:children)).to contain_exactly(public_subgroup, public_project)
end
@@ -84,7 +84,7 @@ describe Groups::ChildrenController do
it 'expands the tree for matching projects' do
project = create(:project, :public, namespace: public_subgroup, name: 'filterme')
- get :index, group_id: group.to_param, filter: 'filter', format: :json
+ get :index, params: { group_id: group.to_param, filter: 'filter' }, format: :json
group_json = json_response.first
project_json = group_json['children'].first
@@ -96,7 +96,7 @@ describe Groups::ChildrenController do
it 'expands the tree for matching subgroups' do
matched_group = create(:group, :public, parent: public_subgroup, name: 'filterme')
- get :index, group_id: group.to_param, filter: 'filter', format: :json
+ get :index, params: { group_id: group.to_param, filter: 'filter' }, format: :json
group_json = json_response.first
matched_group_json = group_json['children'].first
@@ -113,7 +113,7 @@ describe Groups::ChildrenController do
l3_subgroup = create(:group, :public, parent: l2_subgroup, path: 'wifi-group')
matched_project_2 = create(:project, :public, namespace: l3_subgroup, name: 'mobile')
- get :index, group_id: group.to_param, filter: 'mobile', format: :json
+ get :index, params: { group_id: group.to_param, filter: 'mobile' }, format: :json
shared_group_json = json_response.first
expect(shared_group_json['id']).to eq(shared_subgroup.id)
@@ -136,7 +136,7 @@ describe Groups::ChildrenController do
l2_subgroup = create(:group, :public, parent: subgroup)
create(:project, :public, namespace: l2_subgroup, name: 'test')
- get :index, group_id: subgroup.to_param, filter: 'test', format: :json
+ get :index, params: { group_id: subgroup.to_param, filter: 'test' }, format: :json
expect(response).to have_http_status(200)
end
@@ -144,7 +144,7 @@ describe Groups::ChildrenController do
it 'returns an array with one element when only one result is matched' do
create(:project, :public, namespace: group, name: 'match')
- get :index, group_id: group.to_param, filter: 'match', format: :json
+ get :index, params: { group_id: group.to_param, filter: 'match' }, format: :json
expect(json_response).to be_kind_of(Array)
expect(json_response.size).to eq(1)
@@ -155,7 +155,7 @@ describe Groups::ChildrenController do
l2_subgroup = create(:group, :public, parent: subgroup)
create(:project, :public, namespace: l2_subgroup, name: 'no-match')
- get :index, group_id: subgroup.to_param, filter: 'test', format: :json
+ get :index, params: { group_id: subgroup.to_param, filter: 'test' }, format: :json
expect(json_response).to eq([])
end
@@ -179,7 +179,7 @@ describe Groups::ChildrenController do
end
group_to_nest.update!(parent: subgroup)
- get :index, group_id: group.to_param, filter: 'filter', per_page: 3, format: :json
+ get :index, params: { group_id: group.to_param, filter: 'filter', per_page: 3 }, format: :json
expect(response).to have_gitlab_http_status(200)
end
@@ -187,7 +187,7 @@ describe Groups::ChildrenController do
it 'includes pagination headers' do
2.times { |i| create(:group, :public, parent: public_subgroup, name: "filterme#{i}") }
- get :index, group_id: group.to_param, filter: 'filter', per_page: 1, format: :json
+ get :index, params: { group_id: group.to_param, filter: 'filter', per_page: 1 }, format: :json
expect(response).to include_pagination_headers
end
@@ -203,7 +203,7 @@ describe Groups::ChildrenController do
let(:expected_queries_per_project) { 0 }
def get_list
- get :index, group_id: group.to_param, format: :json
+ get :index, params: { group_id: group.to_param }, format: :json
end
it 'queries the expected amount for a group row' do
@@ -227,7 +227,7 @@ describe Groups::ChildrenController do
let(:extra_queries_for_hierarchies) { 1 }
def get_filtered_list
- get :index, group_id: group.to_param, filter: 'filter', format: :json
+ get :index, params: { group_id: group.to_param, filter: 'filter' }, format: :json
end
it 'queries the expected amount when nested rows are increased for a group' do
@@ -276,13 +276,13 @@ describe Groups::ChildrenController do
let!(:first_page_projects) { create_list(:project, per_page, :public, namespace: group ) }
it 'has projects on the first page' do
- get :index, group_id: group.to_param, sort: 'id_desc', format: :json
+ get :index, params: { group_id: group.to_param, sort: 'id_desc' }, format: :json
expect(assigns(:children)).to contain_exactly(*first_page_projects)
end
it 'has projects on the second page' do
- get :index, group_id: group.to_param, sort: 'id_desc', page: 2, format: :json
+ get :index, params: { group_id: group.to_param, sort: 'id_desc', page: 2 }, format: :json
expect(assigns(:children)).to contain_exactly(other_project)
end
@@ -294,13 +294,13 @@ describe Groups::ChildrenController do
let!(:next_page_projects) { create_list(:project, per_page, :public, namespace: group) }
it 'contains all subgroups' do
- get :index, group_id: group.to_param, sort: 'id_asc', format: :json
+ get :index, params: { group_id: group.to_param, sort: 'id_asc' }, format: :json
expect(assigns(:children)).to contain_exactly(*first_page_subgroups)
end
it 'contains the project and group on the second page' do
- get :index, group_id: group.to_param, sort: 'id_asc', page: 2, format: :json
+ get :index, params: { group_id: group.to_param, sort: 'id_asc', page: 2 }, format: :json
expect(assigns(:children)).to contain_exactly(other_subgroup, *next_page_projects.take(per_page - 1))
end
@@ -310,7 +310,7 @@ describe Groups::ChildrenController do
let!(:first_page_projects) { create_list(:project, per_page, :public, namespace: group) }
it 'correctly calculates the counts' do
- get :index, group_id: group.to_param, sort: 'id_asc', page: 2, format: :json
+ get :index, params: { group_id: group.to_param, sort: 'id_asc', page: 2 }, format: :json
expect(response).to have_gitlab_http_status(200)
end
diff --git a/spec/controllers/groups/clusters/applications_controller_spec.rb b/spec/controllers/groups/clusters/applications_controller_spec.rb
index 68a798542b6..dd5263b077c 100644
--- a/spec/controllers/groups/clusters/applications_controller_spec.rb
+++ b/spec/controllers/groups/clusters/applications_controller_spec.rb
@@ -81,7 +81,7 @@ describe Groups::Clusters::ApplicationsController do
end
def go
- post :create, params.merge(group_id: group)
+ post :create, params: params.merge(group_id: group)
end
end
end
diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb
index 6e130f830a2..0f28499194e 100644
--- a/spec/controllers/groups/clusters_controller_spec.rb
+++ b/spec/controllers/groups/clusters_controller_spec.rb
@@ -17,7 +17,7 @@ describe Groups::ClustersController do
describe 'GET index' do
def go(params = {})
- get :index, params.reverse_merge(group_id: group)
+ get :index, params: params.reverse_merge(group_id: group)
end
context 'when feature flag is not enabled' do
@@ -104,7 +104,7 @@ describe Groups::ClustersController do
describe 'GET new' do
def go
- get :new, group_id: group
+ get :new, params: { group_id: group }
end
describe 'functionality for new cluster' do
@@ -198,7 +198,7 @@ describe Groups::ClustersController do
end
def go
- post :create_gcp, params.merge(group_id: group)
+ post :create_gcp, params: params.merge(group_id: group)
end
describe 'functionality' do
@@ -287,7 +287,7 @@ describe Groups::ClustersController do
end
def go
- post :create_user, params.merge(group_id: group)
+ post :create_user, params: params.merge(group_id: group)
end
describe 'functionality' do
@@ -353,8 +353,10 @@ describe Groups::ClustersController do
def go
get :cluster_status,
- group_id: group.to_param,
- id: cluster,
+ params: {
+ group_id: group.to_param,
+ id: cluster
+ },
format: :json
end
@@ -390,8 +392,10 @@ describe Groups::ClustersController do
def go
get :show,
- group_id: group,
- id: cluster
+ params: {
+ group_id: group,
+ id: cluster
+ }
end
describe 'functionality' do
@@ -417,7 +421,7 @@ describe Groups::ClustersController do
describe 'PUT update' do
def go(format: :html)
- put :update, params.merge(
+ put :update, params: params.merge(
group_id: group.to_param,
id: cluster,
format: format
@@ -505,8 +509,10 @@ describe Groups::ClustersController do
def go
delete :destroy,
- group_id: group,
- id: cluster
+ params: {
+ group_id: group,
+ id: cluster
+ }
end
describe 'functionality' do
diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb
index 362d5cc4514..ed38dadfd6b 100644
--- a/spec/controllers/groups/group_members_controller_spec.rb
+++ b/spec/controllers/groups/group_members_controller_spec.rb
@@ -6,7 +6,7 @@ describe Groups::GroupMembersController do
describe 'GET index' do
it 'renders index with 200 status code' do
- get :index, group_id: group
+ get :index, params: { group_id: group }
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template(:index)
@@ -26,9 +26,11 @@ describe Groups::GroupMembersController do
end
it 'returns 403' do
- post :create, group_id: group,
- user_ids: group_user.id,
- access_level: Gitlab::Access::GUEST
+ post :create, params: {
+ group_id: group,
+ user_ids: group_user.id,
+ access_level: Gitlab::Access::GUEST
+ }
expect(response).to have_gitlab_http_status(403)
expect(group.users).not_to include group_user
@@ -41,9 +43,11 @@ describe Groups::GroupMembersController do
end
it 'adds user to members' do
- post :create, group_id: group,
- user_ids: group_user.id,
- access_level: Gitlab::Access::GUEST
+ post :create, params: {
+ group_id: group,
+ user_ids: group_user.id,
+ access_level: Gitlab::Access::GUEST
+ }
expect(response).to set_flash.to 'Users were successfully added.'
expect(response).to redirect_to(group_group_members_path(group))
@@ -51,9 +55,11 @@ describe Groups::GroupMembersController do
end
it 'adds no user to members' do
- post :create, group_id: group,
- user_ids: '',
- access_level: Gitlab::Access::GUEST
+ post :create, params: {
+ group_id: group,
+ user_ids: '',
+ access_level: Gitlab::Access::GUEST
+ }
expect(response).to set_flash.to 'No users specified.'
expect(response).to redirect_to(group_group_members_path(group))
@@ -72,9 +78,11 @@ describe Groups::GroupMembersController do
Gitlab::Access.options.each do |label, value|
it "can change the access level to #{label}" do
- xhr :put, :update, group_member: { access_level: value },
- group_id: group,
- id: requester
+ put :update, params: {
+ group_member: { access_level: value },
+ group_id: group,
+ id: requester
+ }, xhr: true
expect(requester.reload.human_access).to eq(label)
end
@@ -90,7 +98,7 @@ describe Groups::GroupMembersController do
context 'when member is not found' do
it 'returns 403' do
- delete :destroy, group_id: group, id: 42
+ delete :destroy, params: { group_id: group, id: 42 }
expect(response).to have_gitlab_http_status(403)
end
@@ -103,7 +111,7 @@ describe Groups::GroupMembersController do
end
it 'returns 403' do
- delete :destroy, group_id: group, id: member
+ delete :destroy, params: { group_id: group, id: member }
expect(response).to have_gitlab_http_status(403)
expect(group.members).to include member
@@ -116,7 +124,7 @@ describe Groups::GroupMembersController do
end
it '[HTML] removes user from members' do
- delete :destroy, group_id: group, id: member
+ delete :destroy, params: { group_id: group, id: member }
expect(response).to set_flash.to 'User was successfully removed from group.'
expect(response).to redirect_to(group_group_members_path(group))
@@ -124,7 +132,7 @@ describe Groups::GroupMembersController do
end
it '[JS] removes user from members' do
- xhr :delete, :destroy, group_id: group, id: member
+ delete :destroy, params: { group_id: group, id: member }, xhr: true
expect(response).to be_success
expect(group.members).not_to include member
@@ -140,7 +148,7 @@ describe Groups::GroupMembersController do
context 'when member is not found' do
it 'returns 404' do
- delete :leave, group_id: group
+ delete :leave, params: { group_id: group }
expect(response).to have_gitlab_http_status(404)
end
@@ -153,7 +161,7 @@ describe Groups::GroupMembersController do
end
it 'removes user from members' do
- delete :leave, group_id: group
+ delete :leave, params: { group_id: group }
expect(response).to set_flash.to "You left the \"#{group.name}\" group."
expect(response).to redirect_to(dashboard_groups_path)
@@ -161,7 +169,7 @@ describe Groups::GroupMembersController do
end
it 'supports json request' do
- delete :leave, group_id: group, format: :json
+ delete :leave, params: { group_id: group }, format: :json
expect(response).to have_gitlab_http_status(200)
expect(json_response['notice']).to eq "You left the \"#{group.name}\" group."
@@ -174,7 +182,7 @@ describe Groups::GroupMembersController do
end
it 'cannot removes himself from the group' do
- delete :leave, group_id: group
+ delete :leave, params: { group_id: group }
expect(response).to have_gitlab_http_status(403)
end
@@ -186,7 +194,7 @@ describe Groups::GroupMembersController do
end
it 'removes user from members' do
- delete :leave, group_id: group
+ delete :leave, params: { group_id: group }
expect(response).to set_flash.to 'Your access request to the group has been withdrawn.'
expect(response).to redirect_to(group_path(group))
@@ -203,7 +211,7 @@ describe Groups::GroupMembersController do
end
it 'creates a new GroupMember that is not a team member' do
- post :request_access, group_id: group
+ post :request_access, params: { group_id: group }
expect(response).to set_flash.to 'Your request for access has been queued for review.'
expect(response).to redirect_to(group_path(group))
@@ -221,7 +229,7 @@ describe Groups::GroupMembersController do
context 'when member is not found' do
it 'returns 403' do
- post :approve_access_request, group_id: group, id: 42
+ post :approve_access_request, params: { group_id: group, id: 42 }
expect(response).to have_gitlab_http_status(403)
end
@@ -234,7 +242,7 @@ describe Groups::GroupMembersController do
end
it 'returns 403' do
- post :approve_access_request, group_id: group, id: member
+ post :approve_access_request, params: { group_id: group, id: member }
expect(response).to have_gitlab_http_status(403)
expect(group.members).not_to include member
@@ -247,7 +255,7 @@ describe Groups::GroupMembersController do
end
it 'adds user to members' do
- post :approve_access_request, group_id: group, id: member
+ post :approve_access_request, params: { group_id: group, id: member }
expect(response).to redirect_to(group_group_members_path(group))
expect(group.members).to include member
diff --git a/spec/controllers/groups/labels_controller_spec.rb b/spec/controllers/groups/labels_controller_spec.rb
index 185b6b4ce57..fa664a29066 100644
--- a/spec/controllers/groups/labels_controller_spec.rb
+++ b/spec/controllers/groups/labels_controller_spec.rb
@@ -16,7 +16,7 @@ describe Groups::LabelsController do
set(:group_label_1) { create(:group_label, group: group, title: 'group_label_1') }
it 'returns group and project labels by default' do
- get :index, group_id: group, format: :json
+ get :index, params: { group_id: group }, format: :json
label_ids = json_response.map {|label| label['title']}
expect(label_ids).to match_array([label_1.title, group_label_1.title])
@@ -31,7 +31,7 @@ describe Groups::LabelsController do
end
it 'returns ancestor group labels', :nested_groups do
- get :index, group_id: subgroup, include_ancestor_groups: true, only_group_labels: true, format: :json
+ get :index, params: { group_id: subgroup, include_ancestor_groups: true, only_group_labels: true }, format: :json
label_ids = json_response.map {|label| label['title']}
expect(label_ids).to match_array([group_label_1.title, subgroup_label_1.title])
@@ -43,7 +43,7 @@ describe Groups::LabelsController do
it 'allows user to toggle subscription on group labels' do
label = create(:group_label, group: group)
- post :toggle_subscription, group_id: group.to_param, id: label.to_param
+ post :toggle_subscription, params: { group_id: group.to_param, id: label.to_param }
expect(response).to have_gitlab_http_status(200)
end
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb
index 42723bb3820..b8e1e08cff7 100644
--- a/spec/controllers/groups/milestones_controller_spec.rb
+++ b/spec/controllers/groups/milestones_controller_spec.rb
@@ -33,7 +33,7 @@ describe Groups::MilestonesController do
describe '#index' do
it 'shows group milestones page' do
- get :index, group_id: group.to_param
+ get :index, params: { group_id: group.to_param }
expect(response).to have_gitlab_http_status(200)
end
@@ -44,7 +44,7 @@ describe Groups::MilestonesController do
let!(:legacy_milestone2) { create(:milestone, project: project2, title: 'legacy') }
it 'lists legacy group milestones and group milestones' do
- get :index, group_id: group.to_param, format: :json
+ get :index, params: { group_id: group.to_param }, format: :json
milestones = JSON.parse(response.body)
@@ -67,7 +67,7 @@ describe Groups::MilestonesController do
expect(GlobalMilestone).to receive(:build)
expect(Milestone).not_to receive(:find_by_iid)
- get :show, group_id: group.to_param, id: title, title: milestone1.safe_title
+ get :show, params: { group_id: group.to_param, id: title, title: milestone1.safe_title }
end
end
@@ -76,7 +76,7 @@ describe Groups::MilestonesController do
expect(GlobalMilestone).not_to receive(:build)
expect(Milestone).to receive(:find_by_iid)
- get :show, group_id: group.to_param, id: group_milestone.id
+ get :show, params: { group_id: group.to_param, id: group_milestone.id }
end
end
end
@@ -86,8 +86,10 @@ describe Groups::MilestonesController do
describe "#create" do
it "creates group milestone with Chinese title" do
post :create,
- group_id: group.to_param,
- milestone: milestone_params
+ params: {
+ group_id: group.to_param,
+ milestone: milestone_params
+ }
milestone = Milestone.find_by_title(title)
@@ -105,9 +107,11 @@ describe Groups::MilestonesController do
milestone_params[:title] = "title changed"
put :update,
- id: milestone.iid,
- group_id: group.to_param,
- milestone: milestone_params
+ params: {
+ id: milestone.iid,
+ group_id: group.to_param,
+ milestone: milestone_params
+ }
milestone.reload
expect(response).to redirect_to(group_milestone_path(group, milestone.iid))
@@ -124,10 +128,12 @@ describe Groups::MilestonesController do
milestone_params[:state_event] = "close"
put :update,
- id: milestone1.title.to_slug.to_s,
- group_id: group.to_param,
- milestone: milestone_params,
- title: milestone1.title
+ params: {
+ id: milestone1.title.to_slug.to_s,
+ group_id: group.to_param,
+ milestone: milestone_params,
+ title: milestone1.title
+ }
expect(response).to redirect_to(group_milestone_path(group, milestone1.safe_title, title: milestone1.title))
@@ -145,7 +151,7 @@ describe Groups::MilestonesController do
let(:milestone) { create(:milestone, group: group) }
it "removes milestone" do
- delete :destroy, group_id: group.to_param, id: milestone.iid, format: :js
+ delete :destroy, params: { group_id: group.to_param, id: milestone.iid }, format: :js
expect(response).to be_success
expect { Milestone.find(milestone.id) }.to raise_exception(ActiveRecord::RecordNotFound)
@@ -162,7 +168,7 @@ describe Groups::MilestonesController do
context 'non-show path' do
context 'with exactly matching casing' do
it 'does not redirect' do
- get :index, group_id: group.to_param
+ get :index, params: { group_id: group.to_param }
expect(response).not_to have_gitlab_http_status(301)
end
@@ -170,7 +176,7 @@ describe Groups::MilestonesController do
context 'with different casing' do
it 'redirects to the correct casing' do
- get :index, group_id: group.to_param.upcase
+ get :index, params: { group_id: group.to_param.upcase }
expect(response).to redirect_to(group_milestones_path(group.to_param))
expect(controller).not_to set_flash[:notice]
@@ -181,7 +187,7 @@ describe Groups::MilestonesController do
context 'show path' do
context 'with exactly matching casing' do
it 'does not redirect' do
- get :show, group_id: group.to_param, id: title
+ get :show, params: { group_id: group.to_param, id: title }
expect(response).not_to have_gitlab_http_status(301)
end
@@ -189,7 +195,7 @@ describe Groups::MilestonesController do
context 'with different casing' do
it 'redirects to the correct casing' do
- get :show, group_id: group.to_param.upcase, id: title
+ get :show, params: { group_id: group.to_param.upcase, id: title }
expect(response).to redirect_to(group_milestone_path(group.to_param, title))
expect(controller).not_to set_flash[:notice]
@@ -202,7 +208,7 @@ describe Groups::MilestonesController do
let(:redirect_route) { group.redirect_routes.create(path: 'old-path') }
it 'redirects to the canonical path' do
- get :merge_requests, group_id: redirect_route.path, id: title
+ get :merge_requests, params: { group_id: redirect_route.path, id: title }
expect(response).to redirect_to(merge_requests_group_milestone_path(group.to_param, title))
expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
@@ -212,7 +218,7 @@ describe Groups::MilestonesController do
let(:redirect_route) { group.redirect_routes.create(path: 'http') }
it 'does not modify the requested host' do
- get :merge_requests, group_id: redirect_route.path, id: title
+ get :merge_requests, params: { group_id: redirect_route.path, id: title }
expect(response).to redirect_to(merge_requests_group_milestone_path(group.to_param, title))
expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
@@ -224,7 +230,7 @@ describe Groups::MilestonesController do
let(:redirect_route) { group.redirect_routes.create(path: 'oups') }
it 'does not modify the /groups part of the path' do
- get :merge_requests, group_id: redirect_route.path, id: title
+ get :merge_requests, params: { group_id: redirect_route.path, id: title }
expect(response).to redirect_to(merge_requests_group_milestone_path(group.to_param, title))
expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
@@ -236,7 +242,7 @@ describe Groups::MilestonesController do
let(:redirect_route) { group.redirect_routes.create(path: 'oups/oup') }
it 'does not modify the /groups part of the path' do
- get :merge_requests, group_id: redirect_route.path, id: title
+ get :merge_requests, params: { group_id: redirect_route.path, id: title }
expect(response).to redirect_to(merge_requests_group_milestone_path(group.to_param, title))
expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
@@ -250,16 +256,20 @@ describe Groups::MilestonesController do
context 'when requesting the canonical path with different casing' do
it 'does not 404' do
post :create,
- group_id: group.to_param,
- milestone: { title: title }
+ params: {
+ group_id: group.to_param,
+ milestone: { title: title }
+ }
expect(response).not_to have_gitlab_http_status(404)
end
it 'does not redirect to the correct casing' do
post :create,
- group_id: group.to_param,
- milestone: { title: title }
+ params: {
+ group_id: group.to_param,
+ milestone: { title: title }
+ }
expect(response).not_to have_gitlab_http_status(301)
end
@@ -270,8 +280,10 @@ describe Groups::MilestonesController do
it 'returns not found' do
post :create,
- group_id: redirect_route.path,
- milestone: { title: title }
+ params: {
+ group_id: redirect_route.path,
+ milestone: { title: title }
+ }
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/controllers/groups/runners_controller_spec.rb b/spec/controllers/groups/runners_controller_spec.rb
index 598fb84552f..469459bfc02 100644
--- a/spec/controllers/groups/runners_controller_spec.rb
+++ b/spec/controllers/groups/runners_controller_spec.rb
@@ -22,7 +22,7 @@ describe Groups::RunnersController do
new_desc = runner.description.swapcase
expect do
- post :update, params.merge(runner: { description: new_desc } )
+ post :update, params: params.merge(runner: { description: new_desc } )
end.to change { runner.ensure_runner_queue_value }
runner.reload
@@ -34,7 +34,7 @@ describe Groups::RunnersController do
describe '#destroy' do
it 'destroys the runner' do
- delete :destroy, params
+ delete :destroy, params: params
expect(response).to have_gitlab_http_status(302)
expect(Ci::Runner.find_by(id: runner.id)).to be_nil
@@ -46,7 +46,7 @@ describe Groups::RunnersController do
runner.update(active: false)
expect do
- post :resume, params
+ post :resume, params: params
end.to change { runner.ensure_runner_queue_value }
runner.reload
@@ -61,7 +61,7 @@ describe Groups::RunnersController do
runner.update(active: true)
expect do
- post :pause, params
+ post :pause, params: params
end.to change { runner.ensure_runner_queue_value }
runner.reload
diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
index 06ccace8242..b7f04f732b9 100644
--- a/spec/controllers/groups/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
@@ -11,7 +11,7 @@ describe Groups::Settings::CiCdController do
describe 'GET #show' do
it 'renders show with 200 status code' do
- get :show, group_id: group
+ get :show, params: { group_id: group }
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template(:show)
@@ -19,7 +19,7 @@ describe Groups::Settings::CiCdController do
end
describe 'PUT #reset_registration_token' do
- subject { put :reset_registration_token, group_id: group }
+ subject { put :reset_registration_token, params: { group_id: group } }
it 'resets runner registration token' do
expect { subject }.to change { group.reload.runners_token }
diff --git a/spec/controllers/groups/shared_projects_controller_spec.rb b/spec/controllers/groups/shared_projects_controller_spec.rb
index 003c8c262e7..dab7700cf64 100644
--- a/spec/controllers/groups/shared_projects_controller_spec.rb
+++ b/spec/controllers/groups/shared_projects_controller_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Groups::SharedProjectsController do
def get_shared_projects(params = {})
- get :index, params.reverse_merge(format: :json, group_id: group.full_path)
+ get :index, params: params.reverse_merge(format: :json, group_id: group.full_path)
end
def share_project(project)
diff --git a/spec/controllers/groups/uploads_controller_spec.rb b/spec/controllers/groups/uploads_controller_spec.rb
index 5a7281ed704..0104ba827da 100644
--- a/spec/controllers/groups/uploads_controller_spec.rb
+++ b/spec/controllers/groups/uploads_controller_spec.rb
@@ -15,6 +15,6 @@ describe Groups::UploadsController do
def post_authorize(verified: true)
request.headers.merge!(workhorse_internal_api_request_header) if verified
- post :authorize, group_id: model.full_path, format: :json
+ post :authorize, params: { group_id: model.full_path }, format: :json
end
end
diff --git a/spec/controllers/groups/variables_controller_spec.rb b/spec/controllers/groups/variables_controller_spec.rb
index e5ac5634f95..29ec3588316 100644
--- a/spec/controllers/groups/variables_controller_spec.rb
+++ b/spec/controllers/groups/variables_controller_spec.rb
@@ -13,7 +13,7 @@ describe Groups::VariablesController do
let!(:variable) { create(:ci_group_variable, group: group) }
subject do
- get :show, group_id: group, format: :json
+ get :show, params: { group_id: group }, format: :json
end
include_examples 'GET #show lists all variables'
@@ -25,8 +25,10 @@ describe Groups::VariablesController do
subject do
patch :update,
- group_id: group,
- variables_attributes: variables_attributes,
+ params: {
+ group_id: group,
+ variables_attributes: variables_attributes
+ },
format: :json
end
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index f6c85102830..7d87b33e503 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -15,7 +15,7 @@ describe GroupsController do
it 'renders the new page' do
sign_in(member)
- get :new, parent_id: group.id
+ get :new, params: { parent_id: group.id }
expect(response).to render_template(:new)
end
@@ -25,7 +25,7 @@ describe GroupsController do
it 'renders the 404 page' do
sign_in(member)
- get :new, parent_id: group.id
+ get :new, params: { parent_id: group.id }
expect(response).not_to render_template(:new)
expect(response.status).to eq(404)
@@ -42,7 +42,7 @@ describe GroupsController do
it 'assigns events for all the projects in the group' do
create(:event, project: project)
- get :show, id: group.to_param, format: :atom
+ get :show, params: { id: group.to_param }, format: :atom
expect(assigns(:events)).not_to be_empty
end
@@ -53,7 +53,7 @@ describe GroupsController do
it 'sets the badge API endpoint' do
sign_in(owner)
- get :edit, id: group.to_param
+ get :edit, params: { id: group.to_param }
expect(assigns(:badge_api_endpoint)).not_to be_nil
end
@@ -102,7 +102,7 @@ describe GroupsController do
create(:event, project: project)
end
- get :activity, id: group.to_param, format: :json
+ get :activity, params: { id: group.to_param }, format: :json
expect(response).to have_gitlab_http_status(200)
expect(json_response['count']).to eq(3)
@@ -120,7 +120,7 @@ describe GroupsController do
owner.update_attribute(:can_create_group, can_create_group_status)
sign_in(owner)
- post :create, group: { parent_id: group.id, path: 'subgroup' }
+ post :create, params: { group: { parent_id: group.id, path: 'subgroup' } }
expect(response).to be_redirect
expect(response.body).to match(%r{http://test.host/#{group.path}/subgroup})
@@ -134,7 +134,7 @@ describe GroupsController do
previous_group_count = Group.count
- post :create, group: { parent_id: group.id, path: 'subgroup' }
+ post :create, params: { group: { parent_id: group.id, path: 'subgroup' } }
expect(response).to render_template(:new)
expect(Group.count).to eq(previous_group_count)
@@ -157,7 +157,7 @@ describe GroupsController do
it 'creates the Group' do
original_group_count = Group.count
- post :create, group: { path: 'subgroup' }
+ post :create, params: { group: { path: 'subgroup' } }
expect(Group.count).to eq(original_group_count + 1)
expect(response).to be_redirect
@@ -172,7 +172,7 @@ describe GroupsController do
it 'does not create the Group' do
original_group_count = Group.count
- post :create, group: { path: 'subgroup' }
+ post :create, params: { group: { path: 'subgroup' } }
expect(Group.count).to eq(original_group_count)
expect(response).to render_template(:new)
@@ -215,36 +215,37 @@ describe GroupsController do
context 'sorting by votes' do
it 'sorts most popular issues' do
- get :issues, id: group.to_param, sort: 'upvotes_desc'
+ get :issues, params: { id: group.to_param, sort: 'upvotes_desc' }
expect(assigns(:issues)).to eq [issue_2, issue_1]
end
it 'sorts least popular issues' do
- get :issues, id: group.to_param, sort: 'downvotes_desc'
+ get :issues, params: { id: group.to_param, sort: 'downvotes_desc' }
expect(assigns(:issues)).to eq [issue_2, issue_1]
end
end
context 'searching' do
- # Remove as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/52271
before do
+ # Remove in https://gitlab.com/gitlab-org/gitlab-ce/issues/54643
stub_feature_flags(use_cte_for_group_issues_search: false)
+ stub_feature_flags(use_subquery_for_group_issues_search: true)
end
it 'works with popularity sort' do
- get :issues, id: group.to_param, search: 'foo', sort: 'popularity'
+ get :issues, params: { id: group.to_param, search: 'foo', sort: 'popularity' }
expect(assigns(:issues)).to eq([issue_1])
end
it 'works with priority sort' do
- get :issues, id: group.to_param, search: 'foo', sort: 'priority'
+ get :issues, params: { id: group.to_param, search: 'foo', sort: 'priority' }
expect(assigns(:issues)).to eq([issue_1])
end
it 'works with label priority sort' do
- get :issues, id: group.to_param, search: 'foo', sort: 'label_priority'
+ get :issues, params: { id: group.to_param, search: 'foo', sort: 'label_priority' }
expect(assigns(:issues)).to eq([issue_1])
end
@@ -265,12 +266,12 @@ describe GroupsController do
context 'sorting by votes' do
it 'sorts most popular merge requests' do
- get :merge_requests, id: group.to_param, sort: 'upvotes_desc'
+ get :merge_requests, params: { id: group.to_param, sort: 'upvotes_desc' }
expect(assigns(:merge_requests)).to eq [merge_request_2, merge_request_1]
end
it 'sorts least popular merge requests' do
- get :merge_requests, id: group.to_param, sort: 'downvotes_desc'
+ get :merge_requests, params: { id: group.to_param, sort: 'downvotes_desc' }
expect(assigns(:merge_requests)).to eq [merge_request_2, merge_request_1]
end
end
@@ -281,7 +282,7 @@ describe GroupsController do
it 'returns 404' do
sign_in(create(:user))
- delete :destroy, id: group.to_param
+ delete :destroy, params: { id: group.to_param }
expect(response.status).to eq(404)
end
@@ -294,12 +295,12 @@ describe GroupsController do
it 'schedules a group destroy' do
Sidekiq::Testing.fake! do
- expect { delete :destroy, id: group.to_param }.to change(GroupDestroyWorker.jobs, :size).by(1)
+ expect { delete :destroy, params: { id: group.to_param } }.to change(GroupDestroyWorker.jobs, :size).by(1)
end
end
it 'redirects to the root path' do
- delete :destroy, id: group.to_param
+ delete :destroy, params: { id: group.to_param }
expect(response).to redirect_to(root_path)
end
@@ -312,7 +313,7 @@ describe GroupsController do
end
it 'updates the path successfully' do
- post :update, id: group.to_param, group: { path: 'new_path' }
+ post :update, params: { id: group.to_param, group: { path: 'new_path' } }
expect(response).to have_gitlab_http_status(302)
expect(controller).to set_flash[:notice]
@@ -320,7 +321,7 @@ describe GroupsController do
it 'does not update the path on error' do
allow_any_instance_of(Group).to receive(:move_dir).and_raise(Gitlab::UpdatePathError)
- post :update, id: group.to_param, group: { path: 'new_path' }
+ post :update, params: { id: group.to_param, group: { path: 'new_path' } }
expect(assigns(:group).errors).not_to be_empty
expect(assigns(:group).path).not_to eq('new_path')
@@ -336,7 +337,7 @@ describe GroupsController do
context 'when requesting groups at the root path' do
before do
allow(request).to receive(:original_fullpath).and_return("/#{group_full_path}")
- get :show, id: group_full_path
+ get :show, params: { id: group_full_path }
end
context 'when requesting the canonical path with different casing' do
@@ -383,7 +384,7 @@ describe GroupsController do
context 'non-show path' do
context 'with exactly matching casing' do
it 'does not redirect' do
- get :issues, id: group.to_param
+ get :issues, params: { id: group.to_param }
expect(response).not_to have_gitlab_http_status(301)
end
@@ -391,7 +392,7 @@ describe GroupsController do
context 'with different casing' do
it 'redirects to the correct casing' do
- get :issues, id: group.to_param.upcase
+ get :issues, params: { id: group.to_param.upcase }
expect(response).to redirect_to(issues_group_path(group.to_param))
expect(controller).not_to set_flash[:notice]
@@ -402,7 +403,7 @@ describe GroupsController do
context 'show path' do
context 'with exactly matching casing' do
it 'does not redirect' do
- get :show, id: group.to_param
+ get :show, params: { id: group.to_param }
expect(response).not_to have_gitlab_http_status(301)
end
@@ -410,7 +411,7 @@ describe GroupsController do
context 'with different casing' do
it 'redirects to the correct casing at the root path' do
- get :show, id: group.to_param.upcase
+ get :show, params: { id: group.to_param.upcase }
expect(response).to redirect_to(group)
expect(controller).not_to set_flash[:notice]
@@ -423,7 +424,7 @@ describe GroupsController do
let(:redirect_route) { group.redirect_routes.create(path: 'old-path') }
it 'redirects to the canonical path' do
- get :issues, id: redirect_route.path
+ get :issues, params: { id: redirect_route.path }
expect(response).to redirect_to(issues_group_path(group.to_param))
expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
@@ -433,7 +434,7 @@ describe GroupsController do
let(:redirect_route) { group.redirect_routes.create(path: 'http') }
it 'does not modify the requested host' do
- get :issues, id: redirect_route.path
+ get :issues, params: { id: redirect_route.path }
expect(response).to redirect_to(issues_group_path(group.to_param))
expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
@@ -445,7 +446,7 @@ describe GroupsController do
let(:redirect_route) { group.redirect_routes.create(path: 'oups') }
it 'does not modify the /groups part of the path' do
- get :issues, id: redirect_route.path
+ get :issues, params: { id: redirect_route.path }
expect(response).to redirect_to(issues_group_path(group.to_param))
expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
@@ -457,7 +458,7 @@ describe GroupsController do
let(:redirect_route) { group.redirect_routes.create(path: 'oups/oup') }
it 'does not modify the /groups part of the path' do
- get :issues, id: redirect_route.path
+ get :issues, params: { id: redirect_route.path }
expect(response).to redirect_to(issues_group_path(group.to_param))
expect(controller).to set_flash[:notice].to(group_moved_message(redirect_route, group))
@@ -469,13 +470,13 @@ describe GroupsController do
context 'for a POST request' do
context 'when requesting the canonical path with different casing' do
it 'does not 404' do
- post :update, id: group.to_param.upcase, group: { path: 'new_path' }
+ post :update, params: { id: group.to_param.upcase, group: { path: 'new_path' } }
expect(response).not_to have_gitlab_http_status(404)
end
it 'does not redirect to the correct casing' do
- post :update, id: group.to_param.upcase, group: { path: 'new_path' }
+ post :update, params: { id: group.to_param.upcase, group: { path: 'new_path' } }
expect(response).not_to have_gitlab_http_status(301)
end
@@ -485,7 +486,7 @@ describe GroupsController do
let(:redirect_route) { group.redirect_routes.create(path: 'old-path') }
it 'returns not found' do
- post :update, id: redirect_route.path, group: { path: 'new_path' }
+ post :update, params: { id: redirect_route.path, group: { path: 'new_path' } }
expect(response).to have_gitlab_http_status(404)
end
@@ -495,13 +496,13 @@ describe GroupsController do
context 'for a DELETE request' do
context 'when requesting the canonical path with different casing' do
it 'does not 404' do
- delete :destroy, id: group.to_param.upcase
+ delete :destroy, params: { id: group.to_param.upcase }
expect(response).not_to have_gitlab_http_status(404)
end
it 'does not redirect to the correct casing' do
- delete :destroy, id: group.to_param.upcase
+ delete :destroy, params: { id: group.to_param.upcase }
expect(response).not_to have_gitlab_http_status(301)
end
@@ -511,7 +512,7 @@ describe GroupsController do
let(:redirect_route) { group.redirect_routes.create(path: 'old-path') }
it 'returns not found' do
- delete :destroy, id: redirect_route.path
+ delete :destroy, params: { id: redirect_route.path }
expect(response).to have_gitlab_http_status(404)
end
@@ -536,8 +537,10 @@ describe GroupsController do
before do
put :transfer,
- id: group.to_param,
- new_parent_group_id: new_parent_group.id
+ params: {
+ id: group.to_param,
+ new_parent_group_id: new_parent_group.id
+ }
end
it 'should return a notice' do
@@ -555,8 +558,10 @@ describe GroupsController do
before do
put :transfer,
- id: group.to_param,
- new_parent_group_id: ''
+ params: {
+ id: group.to_param,
+ new_parent_group_id: ''
+ }
end
it 'should return a notice' do
@@ -577,8 +582,10 @@ describe GroupsController do
allow_any_instance_of(::Groups::TransferService).to receive(:proceed_to_transfer).and_raise(Gitlab::UpdatePathError, 'namespace directory cannot be moved')
put :transfer,
- id: group.to_param,
- new_parent_group_id: new_parent_group.id
+ params: {
+ id: group.to_param,
+ new_parent_group_id: new_parent_group.id
+ }
end
it 'should return an alert' do
@@ -597,8 +604,10 @@ describe GroupsController do
before do
put :transfer,
- id: group.to_param,
- new_parent_group_id: new_parent_group.id
+ params: {
+ id: group.to_param,
+ new_parent_group_id: new_parent_group.id
+ }
end
it 'should be denied' do
diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb
index 387ca46ef6f..29e159ad5d7 100644
--- a/spec/controllers/health_check_controller_spec.rb
+++ b/spec/controllers/health_check_controller_spec.rb
@@ -37,7 +37,7 @@ describe HealthCheckController do
end
it 'supports passing the token in query params' do
- get :index, token: token
+ get :index, params: { token: token }
expect(response).to be_success
expect(response.content_type).to eq 'text/plain'
@@ -74,7 +74,7 @@ describe HealthCheckController do
end
it 'supports successful responses for specific checks' do
- get :index, checks: 'email', format: :json
+ get :index, params: { checks: 'email' }, format: :json
expect(response).to be_success
expect(response.content_type).to eq 'application/json'
@@ -124,7 +124,7 @@ describe HealthCheckController do
end
it 'supports failure responses for specific checks' do
- get :index, checks: 'email', format: :json
+ get :index, params: { checks: 'email' }, format: :json
expect(response).to have_gitlab_http_status(500)
expect(response.content_type).to eq 'application/json'
diff --git a/spec/controllers/health_controller_spec.rb b/spec/controllers/health_controller_spec.rb
index ec73c89cb11..f685f2b41c0 100644
--- a/spec/controllers/health_controller_spec.rb
+++ b/spec/controllers/health_controller_spec.rb
@@ -18,7 +18,7 @@ describe HealthController do
shared_context 'endpoint responding with readiness data' do
let(:request_params) { {} }
- subject { get :readiness, request_params }
+ subject { get :readiness, params: request_params }
it 'responds with readiness checks data' do
subject
@@ -112,7 +112,7 @@ describe HealthController do
context 'token passed as URL param' do
it_behaves_like 'endpoint responding with liveness data' do
- subject { get :liveness, token: token }
+ subject { get :liveness, params: { token: token } }
end
end
end
diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index 21d59c62613..5cb284e7e2d 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -43,7 +43,7 @@ describe HelpController do
context 'for Markdown formats' do
context 'when requested file exists' do
before do
- get :show, path: 'ssh/README', format: :md
+ get :show, params: { path: 'ssh/README' }, format: :md
end
it 'assigns to @markdown' do
@@ -58,7 +58,7 @@ describe HelpController do
context 'when requested file is missing' do
it 'renders not found' do
- get :show, path: 'foo/bar', format: :md
+ get :show, params: { path: 'foo/bar' }, format: :md
expect(response).to be_not_found
end
end
@@ -68,7 +68,9 @@ describe HelpController do
context 'when requested file exists' do
it 'renders the raw file' do
get :show,
- path: 'user/project/img/labels_default',
+ params: {
+ path: 'user/project/img/labels_default'
+ },
format: :png
expect(response).to be_success
expect(response.content_type).to eq 'image/png'
@@ -79,7 +81,9 @@ describe HelpController do
context 'when requested file is missing' do
it 'renders not found' do
get :show,
- path: 'foo/bar',
+ params: {
+ path: 'foo/bar'
+ },
format: :png
expect(response).to be_not_found
end
@@ -89,7 +93,9 @@ describe HelpController do
context 'for other formats' do
it 'always renders not found' do
get :show,
- path: 'ssh/README',
+ params: {
+ path: 'ssh/README'
+ },
format: :foo
expect(response).to be_not_found
end
diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb
index be49b92d23f..51793f2c048 100644
--- a/spec/controllers/import/bitbucket_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_controller_spec.rb
@@ -237,7 +237,7 @@ describe Import::BitbucketController do
.to receive(:new).with(bitbucket_repo, test_name, nested_namespace, user, access_params)
.and_return(double(execute: project))
- post :create, { target_namespace: nested_namespace.full_path, new_name: test_name, format: :json }
+ post :create, params: { target_namespace: nested_namespace.full_path, new_name: test_name }, format: :json
end
end
@@ -249,7 +249,7 @@ describe Import::BitbucketController do
.to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params)
.and_return(double(execute: project))
- post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json }
+ post :create, params: { target_namespace: 'foo/bar', new_name: test_name }, format: :json
end
it 'creates the namespaces' do
@@ -257,7 +257,7 @@ describe Import::BitbucketController do
.to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params)
.and_return(double(execute: project))
- expect { post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json } }
+ expect { post :create, params: { target_namespace: 'foo/bar', new_name: test_name }, format: :json }
.to change { Namespace.count }.by(2)
end
@@ -266,7 +266,7 @@ describe Import::BitbucketController do
.to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params)
.and_return(double(execute: project))
- post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json }
+ post :create, params: { target_namespace: 'foo/bar', new_name: test_name }, format: :json
expect(Namespace.find_by_path_or_name('bar').parent.path).to eq('foo')
end
@@ -285,7 +285,7 @@ describe Import::BitbucketController do
.to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params)
.and_return(double(execute: project))
- post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :json }
+ post :create, params: { target_namespace: 'foo/foobar/bar', new_name: test_name }, format: :json
end
it 'creates the namespaces' do
@@ -293,7 +293,7 @@ describe Import::BitbucketController do
.to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params)
.and_return(double(execute: project))
- expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :json } }
+ expect { post :create, params: { target_namespace: 'foo/foobar/bar', new_name: test_name }, format: :json }
.to change { Namespace.count }.by(2)
end
end
@@ -302,7 +302,7 @@ describe Import::BitbucketController do
it 'returns 422 response' do
other_namespace = create(:group, name: 'other_namespace')
- post :create, { target_namespace: other_namespace.name, format: :json }
+ post :create, params: { target_namespace: other_namespace.name }, format: :json
expect(response).to have_gitlab_http_status(422)
end
diff --git a/spec/controllers/import/bitbucket_server_controller_spec.rb b/spec/controllers/import/bitbucket_server_controller_spec.rb
index db912641894..73195463a50 100644
--- a/spec/controllers/import/bitbucket_server_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_server_controller_spec.rb
@@ -42,19 +42,19 @@ describe Import::BitbucketServerController do
.to receive(:new).with(project_key, repo_slug, anything, 'my-project', user.namespace, user, anything)
.and_return(double(execute: project))
- post :create, project: project_key, repository: repo_slug, format: :json
+ post :create, params: { project: project_key, repository: repo_slug }, format: :json
expect(response).to have_gitlab_http_status(200)
end
it 'returns an error when an invalid project key is used' do
- post :create, project: 'some&project'
+ post :create, params: { project: 'some&project' }
expect(response).to have_gitlab_http_status(422)
end
it 'returns an error when an invalid repository slug is used' do
- post :create, project: 'some-project', repository: 'try*this'
+ post :create, params: { project: 'some-project', repository: 'try*this' }
expect(response).to have_gitlab_http_status(422)
end
@@ -62,7 +62,7 @@ describe Import::BitbucketServerController do
it 'returns an error when the project cannot be found' do
allow(client).to receive(:repo).with(project_key, repo_slug).and_return(nil)
- post :create, project: project_key, repository: repo_slug, format: :json
+ post :create, params: { project: project_key, repository: repo_slug }, format: :json
expect(response).to have_gitlab_http_status(422)
end
@@ -72,7 +72,7 @@ describe Import::BitbucketServerController do
.to receive(:new).with(project_key, repo_slug, anything, 'my-project', user.namespace, user, anything)
.and_return(double(execute: build(:project)))
- post :create, project: project_key, repository: repo_slug, format: :json
+ post :create, params: { project: project_key, repository: repo_slug }, format: :json
expect(response).to have_gitlab_http_status(422)
end
@@ -80,7 +80,7 @@ describe Import::BitbucketServerController do
it "returns an error when the server can't be contacted" do
expect(client).to receive(:repo).with(project_key, repo_slug).and_raise(BitbucketServer::Client::ServerError)
- post :create, project: project_key, repository: repo_slug, format: :json
+ post :create, params: { project: project_key, repository: repo_slug }, format: :json
expect(response).to have_gitlab_http_status(422)
end
@@ -103,7 +103,7 @@ describe Import::BitbucketServerController do
end
it 'sets the session variables' do
- post :configure, personal_access_token: token, bitbucket_username: username, bitbucket_server_url: url
+ post :configure, params: { personal_access_token: token, bitbucket_username: username, bitbucket_server_url: url }
expect(session[:bitbucket_server_url]).to eq(url)
expect(session[:bitbucket_server_username]).to eq(username)
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
index 9bbd97ec305..780e49f7b93 100644
--- a/spec/controllers/import/github_controller_spec.rb
+++ b/spec/controllers/import/github_controller_spec.rb
@@ -16,6 +16,15 @@ describe Import::GithubController do
get :new
end
+
+ it "prompts for an access token if GitHub not configured" do
+ allow(controller).to receive(:github_import_configured?).and_return(false)
+ expect(controller).not_to receive(:go_to_provider_for_permissions)
+
+ get :new
+
+ expect(response).to have_http_status(200)
+ end
end
describe "GET callback" do
diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb
index 742f4787126..a874a7d36f6 100644
--- a/spec/controllers/import/gitlab_controller_spec.rb
+++ b/spec/controllers/import/gitlab_controller_spec.rb
@@ -209,7 +209,7 @@ describe Import::GitlabController do
.to receive(:new).with(gitlab_repo, nested_namespace, user, access_params)
.and_return(double(execute: project))
- post :create, { target_namespace: nested_namespace.full_path, format: :json }
+ post :create, params: { target_namespace: nested_namespace.full_path }, format: :json
end
end
@@ -221,7 +221,7 @@ describe Import::GitlabController do
.to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params)
.and_return(double(execute: project))
- post :create, { target_namespace: 'foo/bar', format: :json }
+ post :create, params: { target_namespace: 'foo/bar' }, format: :json
end
it 'creates the namespaces' do
@@ -229,7 +229,7 @@ describe Import::GitlabController do
.to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params)
.and_return(double(execute: project))
- expect { post :create, { target_namespace: 'foo/bar', format: :json } }
+ expect { post :create, params: { target_namespace: 'foo/bar' }, format: :json }
.to change { Namespace.count }.by(2)
end
@@ -238,7 +238,7 @@ describe Import::GitlabController do
.to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params)
.and_return(double(execute: project))
- post :create, { target_namespace: 'foo/bar', format: :json }
+ post :create, params: { target_namespace: 'foo/bar' }, format: :json
expect(Namespace.find_by_path_or_name('bar').parent.path).to eq('foo')
end
@@ -257,7 +257,7 @@ describe Import::GitlabController do
.to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params)
.and_return(double(execute: project))
- post :create, { target_namespace: 'foo/foobar/bar', format: :json }
+ post :create, params: { target_namespace: 'foo/foobar/bar' }, format: :json
end
it 'creates the namespaces' do
@@ -265,7 +265,7 @@ describe Import::GitlabController do
.to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params)
.and_return(double(execute: project))
- expect { post :create, { target_namespace: 'foo/foobar/bar', format: :json } }
+ expect { post :create, params: { target_namespace: 'foo/foobar/bar' }, format: :json }
.to change { Namespace.count }.by(2)
end
end
@@ -274,7 +274,7 @@ describe Import::GitlabController do
it 'returns 422 response' do
other_namespace = create(:group, name: 'other_namespace')
- post :create, { target_namespace: other_namespace.name, format: :json }
+ post :create, params: { target_namespace: other_namespace.name }, format: :json
expect(response).to have_gitlab_http_status(422)
end
diff --git a/spec/controllers/import/gitlab_projects_controller_spec.rb b/spec/controllers/import/gitlab_projects_controller_spec.rb
index cbd1a112602..55bd8ae7182 100644
--- a/spec/controllers/import/gitlab_projects_controller_spec.rb
+++ b/spec/controllers/import/gitlab_projects_controller_spec.rb
@@ -12,14 +12,14 @@ describe Import::GitlabProjectsController do
describe 'POST create' do
context 'with an invalid path' do
it 'redirects with an error' do
- post :create, namespace_id: namespace.id, path: '/test', file: file
+ post :create, params: { namespace_id: namespace.id, path: '/test', file: file }
expect(flash[:alert]).to start_with('Project could not be imported')
expect(response).to have_gitlab_http_status(302)
end
it 'redirects with an error when a relative path is used' do
- post :create, namespace_id: namespace.id, path: '../test', file: file
+ post :create, params: { namespace_id: namespace.id, path: '../test', file: file }
expect(flash[:alert]).to start_with('Project could not be imported')
expect(response).to have_gitlab_http_status(302)
@@ -28,7 +28,7 @@ describe Import::GitlabProjectsController do
context 'with a valid path' do
it 'redirects to the new project path' do
- post :create, namespace_id: namespace.id, path: 'test', file: file
+ post :create, params: { namespace_id: namespace.id, path: 'test', file: file }
expect(flash[:notice]).to include('is being imported')
expect(response).to have_gitlab_http_status(302)
diff --git a/spec/controllers/import/google_code_controller_spec.rb b/spec/controllers/import/google_code_controller_spec.rb
index 0763492d88a..3e5ed2afd93 100644
--- a/spec/controllers/import/google_code_controller_spec.rb
+++ b/spec/controllers/import/google_code_controller_spec.rb
@@ -12,7 +12,7 @@ describe Import::GoogleCodeController do
describe "POST callback" do
it "stores Google Takeout dump list in session" do
- post :callback, dump_file: dump_file
+ post :callback, params: { dump_file: dump_file }
expect(session[:google_code_dump]).to be_a(Hash)
expect(session[:google_code_dump]["kind"]).to eq("projecthosting#user")
diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb
index 6c09ca7dc66..7bbaf36e4df 100644
--- a/spec/controllers/invites_controller_spec.rb
+++ b/spec/controllers/invites_controller_spec.rb
@@ -12,7 +12,7 @@ describe InvitesController do
describe 'GET #accept' do
it 'accepts user' do
- get :accept, id: token
+ get :accept, params: { id: token }
member.reload
expect(response).to have_gitlab_http_status(302)
@@ -23,7 +23,7 @@ describe InvitesController do
describe 'GET #decline' do
it 'declines user' do
- get :decline, id: token
+ get :decline, params: { id: token }
expect {member.reload}.to raise_error ActiveRecord::RecordNotFound
expect(response).to have_gitlab_http_status(302)
diff --git a/spec/controllers/ldap/omniauth_callbacks_controller_spec.rb b/spec/controllers/ldap/omniauth_callbacks_controller_spec.rb
index 87c10a86cdd..c9d36a16008 100644
--- a/spec/controllers/ldap/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/ldap/omniauth_callbacks_controller_spec.rb
@@ -11,7 +11,7 @@ describe Ldap::OmniauthCallbacksController do
it 'respects remember me checkbox' do
expect do
- post provider, remember_me: '1'
+ post provider, params: { remember_me: '1' }
end.to change { user.reload.remember_created_at }.from(nil)
end
@@ -19,7 +19,7 @@ describe Ldap::OmniauthCallbacksController do
let(:user) { create(:omniauth_user, :two_factor_via_otp, extern_uid: uid, provider: provider) }
it 'passes remember_me to the Devise view' do
- post provider, remember_me: '1'
+ post provider, params: { remember_me: '1' }
expect(assigns[:user].remember_me).to eq '1'
end
diff --git a/spec/controllers/notification_settings_controller_spec.rb b/spec/controllers/notification_settings_controller_spec.rb
index a3356a86d4b..cf52ce834b6 100644
--- a/spec/controllers/notification_settings_controller_spec.rb
+++ b/spec/controllers/notification_settings_controller_spec.rb
@@ -13,8 +13,10 @@ describe NotificationSettingsController do
context 'when not authorized' do
it 'redirects to sign in page' do
post :create,
- project_id: project.id,
- notification_setting: { level: :participating }
+ params: {
+ project_id: project.id,
+ notification_setting: { level: :participating }
+ }
expect(response).to redirect_to(new_user_session_path)
end
@@ -41,8 +43,10 @@ describe NotificationSettingsController do
it 'creates notification setting' do
post :create,
- project_id: project.id,
- notification_setting: { level: :participating }
+ params: {
+ project_id: project.id,
+ notification_setting: { level: :participating }
+ }
expect(response.status).to eq 200
expect(notification_setting.level).to eq("participating")
@@ -54,8 +58,10 @@ describe NotificationSettingsController do
context 'with custom settings' do
it 'creates notification setting' do
post :create,
- project_id: project.id,
- notification_setting: { level: :custom }.merge(custom_events)
+ params: {
+ project_id: project.id,
+ notification_setting: { level: :custom }.merge(custom_events)
+ }
expect(response.status).to eq 200
expect(notification_setting.level).to eq("custom")
@@ -72,8 +78,10 @@ describe NotificationSettingsController do
it 'creates notification setting' do
post :create,
- namespace_id: group.id,
- notification_setting: { level: :watch }
+ params: {
+ namespace_id: group.id,
+ notification_setting: { level: :watch }
+ }
expect(response.status).to eq 200
expect(notification_setting.level).to eq("watch")
@@ -85,8 +93,10 @@ describe NotificationSettingsController do
context 'with custom settings' do
it 'creates notification setting' do
post :create,
- namespace_id: group.id,
- notification_setting: { level: :custom }.merge(custom_events)
+ params: {
+ namespace_id: group.id,
+ notification_setting: { level: :custom }.merge(custom_events)
+ }
expect(response.status).to eq 200
expect(notification_setting.level).to eq("custom")
@@ -108,8 +118,10 @@ describe NotificationSettingsController do
it 'returns 404' do
post :create,
- project_id: private_project.id,
- notification_setting: { level: :participating }
+ params: {
+ project_id: private_project.id,
+ notification_setting: { level: :participating }
+ }
expect(response).to have_gitlab_http_status(404)
end
@@ -122,8 +134,10 @@ describe NotificationSettingsController do
context 'when not authorized' do
it 'redirects to sign in page' do
put :update,
- id: notification_setting,
- notification_setting: { level: :participating }
+ params: {
+ id: notification_setting,
+ notification_setting: { level: :participating }
+ }
expect(response).to redirect_to(new_user_session_path)
end
@@ -136,8 +150,10 @@ describe NotificationSettingsController do
it 'returns success' do
put :update,
- id: notification_setting,
- notification_setting: { level: :participating }
+ params: {
+ id: notification_setting,
+ notification_setting: { level: :participating }
+ }
expect(response.status).to eq 200
end
@@ -153,8 +169,10 @@ describe NotificationSettingsController do
it 'returns success' do
put :update,
- id: notification_setting,
- notification_setting: { level: :participating, events: custom_events }
+ params: {
+ id: notification_setting,
+ notification_setting: { level: :participating, events: custom_events }
+ }
expect(response.status).to eq 200
end
@@ -170,8 +188,10 @@ describe NotificationSettingsController do
it 'returns 404' do
put :update,
- id: notification_setting,
- notification_setting: { level: :participating }
+ params: {
+ id: notification_setting,
+ notification_setting: { level: :participating }
+ }
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/controllers/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb
index b4219856fc0..caf2b87428b 100644
--- a/spec/controllers/oauth/applications_controller_spec.rb
+++ b/spec/controllers/oauth/applications_controller_spec.rb
@@ -26,7 +26,7 @@ describe Oauth::ApplicationsController do
describe 'POST #create' do
it 'creates an application' do
- post :create, oauth_params
+ post :create, params: oauth_params
expect(response).to have_gitlab_http_status(302)
expect(response).to redirect_to(oauth_application_path(Doorkeeper::Application.last))
@@ -35,7 +35,7 @@ describe Oauth::ApplicationsController do
it 'redirects back to profile page if OAuth applications are disabled' do
disable_user_oauth
- post :create, oauth_params
+ post :create, params: oauth_params
expect(response).to have_gitlab_http_status(302)
expect(response).to redirect_to(profile_path)
@@ -52,7 +52,7 @@ describe Oauth::ApplicationsController do
}
}
- post :create, invalid_uri_params
+ post :create, params: invalid_uri_params
expect(response.body).to include 'Redirect URI is forbidden by the server'
end
diff --git a/spec/controllers/oauth/authorizations_controller_spec.rb b/spec/controllers/oauth/authorizations_controller_spec.rb
index 8c10ea53a7a..cc8fa2c01b4 100644
--- a/spec/controllers/oauth/authorizations_controller_spec.rb
+++ b/spec/controllers/oauth/authorizations_controller_spec.rb
@@ -30,7 +30,7 @@ describe Oauth::AuthorizationsController do
render_views
it 'returns 200 code and renders view' do
- get :new, params
+ get :new, params: params
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template('doorkeeper/authorizations/new')
@@ -40,7 +40,7 @@ describe Oauth::AuthorizationsController do
application.update(trusted: true)
request.session['user_return_to'] = 'http://example.com'
- get :new, params
+ get :new, params: params
expect(request.session['user_return_to']).to be_nil
expect(response).to have_gitlab_http_status(302)
@@ -57,7 +57,7 @@ describe Oauth::AuthorizationsController do
end
it 'authorizes the request and redirects' do
- get :new, params
+ get :new, params: params
expect(request.session['user_return_to']).to be_nil
expect(response).to have_gitlab_http_status(302)
diff --git a/spec/controllers/passwords_controller_spec.rb b/spec/controllers/passwords_controller_spec.rb
index 4d31cfedbd2..0af55cf3408 100644
--- a/spec/controllers/passwords_controller_spec.rb
+++ b/spec/controllers/passwords_controller_spec.rb
@@ -22,7 +22,7 @@ describe PasswordsController do
let(:user) { create(:omniauth_user, provider: 'ldapmain', email: 'ldapuser@gitlab.com') }
it 'prevents a password reset' do
- post :create, user: { email: user.email }
+ post :create, params: { user: { email: user.email } }
expect(flash[:alert]).to eq 'Password authentication is unavailable.'
end
diff --git a/spec/controllers/profiles/accounts_controller_spec.rb b/spec/controllers/profiles/accounts_controller_spec.rb
index f8d9d7e39ee..bb2ab27e2dd 100644
--- a/spec/controllers/profiles/accounts_controller_spec.rb
+++ b/spec/controllers/profiles/accounts_controller_spec.rb
@@ -9,7 +9,7 @@ describe Profiles::AccountsController do
end
it 'renders 404 if someone tries to unlink a non existent provider' do
- delete :unlink, provider: 'github'
+ delete :unlink, params: { provider: 'github' }
expect(response).to have_gitlab_http_status(404)
end
@@ -21,7 +21,7 @@ describe Profiles::AccountsController do
it "does not allow to unlink connected account" do
identity = user.identities.last
- delete :unlink, provider: provider.to_s
+ delete :unlink, params: { provider: provider.to_s }
expect(response).to have_gitlab_http_status(302)
expect(user.reload.identities).to include(identity)
@@ -36,7 +36,7 @@ describe Profiles::AccountsController do
it 'allows to unlink connected account' do
identity = user.identities.last
- delete :unlink, provider: provider.to_s
+ delete :unlink, params: { provider: provider.to_s }
expect(response).to have_gitlab_http_status(302)
expect(user.reload.identities).not_to include(identity)
diff --git a/spec/controllers/profiles/emails_controller_spec.rb b/spec/controllers/profiles/emails_controller_spec.rb
index ecf14aad54f..a8a1f96befe 100644
--- a/spec/controllers/profiles/emails_controller_spec.rb
+++ b/spec/controllers/profiles/emails_controller_spec.rb
@@ -11,7 +11,7 @@ describe Profiles::EmailsController do
let(:email_params) { { email: "add_email@example.com" } }
it 'sends an email confirmation' do
- expect { post(:create, { email: email_params }) }.to change { ActionMailer::Base.deliveries.size }
+ expect { post(:create, params: { email: email_params }) }.to change { ActionMailer::Base.deliveries.size }
expect(ActionMailer::Base.deliveries.last.to).to eq [email_params[:email]]
expect(ActionMailer::Base.deliveries.last.subject).to match "Confirmation instructions"
end
@@ -23,13 +23,13 @@ describe Profiles::EmailsController do
it 'resends an email confirmation' do
email = user.emails.create(email: 'add_email@example.com')
- expect { put(:resend_confirmation_instructions, { id: email }) }.to change { ActionMailer::Base.deliveries.size }
+ expect { put(:resend_confirmation_instructions, params: { id: email }) }.to change { ActionMailer::Base.deliveries.size }
expect(ActionMailer::Base.deliveries.last.to).to eq [email_params[:email]]
expect(ActionMailer::Base.deliveries.last.subject).to match "Confirmation instructions"
end
it 'unable to resend an email confirmation' do
- expect { put(:resend_confirmation_instructions, { id: 1 }) }.not_to change { ActionMailer::Base.deliveries.size }
+ expect { put(:resend_confirmation_instructions, params: { id: 1 }) }.not_to change { ActionMailer::Base.deliveries.size }
end
end
end
diff --git a/spec/controllers/profiles/keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb
index 685db8488f0..5e2cc82bd8c 100644
--- a/spec/controllers/profiles/keys_controller_spec.rb
+++ b/spec/controllers/profiles/keys_controller_spec.rb
@@ -6,7 +6,7 @@ describe Profiles::KeysController do
describe "#get_keys" do
describe "non existent user" do
it "does not generally work" do
- get :get_keys, username: 'not-existent'
+ get :get_keys, params: { username: 'not-existent' }
expect(response).not_to be_success
end
@@ -14,19 +14,19 @@ describe Profiles::KeysController do
describe "user with no keys" do
it "does generally work" do
- get :get_keys, username: user.username
+ get :get_keys, params: { username: user.username }
expect(response).to be_success
end
it "renders all keys separated with a new line" do
- get :get_keys, username: user.username
+ get :get_keys, params: { username: user.username }
expect(response.body).to eq("")
end
it "responds with text/plain content type" do
- get :get_keys, username: user.username
+ get :get_keys, params: { username: user.username }
expect(response.content_type).to eq("text/plain")
end
end
@@ -37,13 +37,13 @@ describe Profiles::KeysController do
let!(:deploy_key) { create(:deploy_key, user: user) }
it "does generally work" do
- get :get_keys, username: user.username
+ get :get_keys, params: { username: user.username }
expect(response).to be_success
end
it "renders all non deploy keys separated with a new line" do
- get :get_keys, username: user.username
+ get :get_keys, params: { username: user.username }
expect(response.body).not_to eq('')
expect(response.body).to eq(user.all_ssh_keys.join("\n"))
@@ -55,22 +55,16 @@ describe Profiles::KeysController do
end
it "does not render the comment of the key" do
- get :get_keys, username: user.username
+ get :get_keys, params: { username: user.username }
expect(response.body).not_to match(/dummy@gitlab.com/)
end
it "responds with text/plain content type" do
- get :get_keys, username: user.username
+ get :get_keys, params: { username: user.username }
expect(response.content_type).to eq("text/plain")
end
-
- it "responds with attachment content disposition" do
- get :get_keys, username: user.username
-
- expect(response.headers['Content-Disposition']).to eq('attachment')
- end
end
end
end
diff --git a/spec/controllers/profiles/notifications_controller_spec.rb b/spec/controllers/profiles/notifications_controller_spec.rb
index b97cdd4d489..1b76446a0cf 100644
--- a/spec/controllers/profiles/notifications_controller_spec.rb
+++ b/spec/controllers/profiles/notifications_controller_spec.rb
@@ -24,7 +24,7 @@ describe Profiles::NotificationsController do
it 'updates only permitted attributes' do
sign_in(user)
- put :update, user: { notification_email: 'new@example.com', notified_of_own_activity: true, admin: true }
+ put :update, params: { user: { notification_email: 'new@example.com', notified_of_own_activity: true, admin: true } }
user.reload
expect(user.notification_email).to eq('new@example.com')
@@ -36,7 +36,7 @@ describe Profiles::NotificationsController do
it 'shows an error message if the params are invalid' do
sign_in(user)
- put :update, user: { notification_email: '' }
+ put :update, params: { user: { notification_email: '' } }
expect(user.reload.notification_email).to eq('original@example.com')
expect(controller).to set_flash[:alert].to('Failed to save new settings')
diff --git a/spec/controllers/profiles/personal_access_tokens_controller_spec.rb b/spec/controllers/profiles/personal_access_tokens_controller_spec.rb
index f5860d4296b..021bf2429e3 100644
--- a/spec/controllers/profiles/personal_access_tokens_controller_spec.rb
+++ b/spec/controllers/profiles/personal_access_tokens_controller_spec.rb
@@ -17,7 +17,7 @@ describe Profiles::PersonalAccessTokensController do
name = 'My PAT'
scopes = %w[api read_user]
- post :create, personal_access_token: token_attributes.merge(scopes: scopes, name: name)
+ post :create, params: { personal_access_token: token_attributes.merge(scopes: scopes, name: name) }
expect(created_token).not_to be_nil
expect(created_token.name).to eq(name)
@@ -28,7 +28,7 @@ describe Profiles::PersonalAccessTokensController do
it "allows creation of a token with an expiry date" do
expires_at = 5.days.from_now.to_date
- post :create, personal_access_token: token_attributes.merge(expires_at: expires_at)
+ post :create, params: { personal_access_token: token_attributes.merge(expires_at: expires_at) }
expect(created_token).not_to be_nil
expect(created_token.expires_at).to eq(expires_at)
diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb
index a66b4ab0902..012f016b091 100644
--- a/spec/controllers/profiles/preferences_controller_spec.rb
+++ b/spec/controllers/profiles/preferences_controller_spec.rb
@@ -29,7 +29,7 @@ describe Profiles::PreferencesController do
theme_id: '1'
)
- patch :update, user: params, format: format
+ patch :update, params: { user: params }, format: format
end
context 'on successful update' do
@@ -45,7 +45,7 @@ describe Profiles::PreferencesController do
theme_id: '2'
}.with_indifferent_access
- expect(user).to receive(:assign_attributes).with(prefs)
+ expect(user).to receive(:assign_attributes).with(ActionController::Parameters.new(prefs).permit!)
expect(user).to receive(:save)
go params: prefs
diff --git a/spec/controllers/profiles/two_factor_auths_controller_spec.rb b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
index d08d0018b35..0151a434998 100644
--- a/spec/controllers/profiles/two_factor_auths_controller_spec.rb
+++ b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
@@ -32,7 +32,7 @@ describe Profiles::TwoFactorAuthsController do
let(:pin) { 'pin-code' }
def go
- post :create, pin_code: pin
+ post :create, params: { pin_code: pin }
end
context 'with valid pin' do
diff --git a/spec/controllers/profiles_controller_spec.rb b/spec/controllers/profiles_controller_spec.rb
index 360c536c667..11cb59aa12a 100644
--- a/spec/controllers/profiles_controller_spec.rb
+++ b/spec/controllers/profiles_controller_spec.rb
@@ -9,7 +9,7 @@ describe ProfilesController, :request_store do
expect do
post :update,
- user: { password: 'hello12345', password_confirmation: 'hello12345' }
+ params: { user: { password: 'hello12345', password_confirmation: 'hello12345' } }
end.not_to change { user.reload.encrypted_password }
expect(response.status).to eq(302)
@@ -21,7 +21,7 @@ describe ProfilesController, :request_store do
sign_in(user)
put :update,
- user: { email: "john@gmail.com", name: "John" }
+ params: { user: { email: "john@gmail.com", name: "John" } }
user.reload
@@ -35,7 +35,7 @@ describe ProfilesController, :request_store do
sign_in(user)
put :update,
- user: { email: "john@gmail.com", name: "John" }
+ params: { user: { email: "john@gmail.com", name: "John" } }
user.reload
@@ -52,7 +52,7 @@ describe ProfilesController, :request_store do
sign_in(ldap_user)
put :update,
- user: { email: "john@gmail.com", name: "John" }
+ params: { user: { email: "john@gmail.com", name: "John" } }
ldap_user.reload
@@ -69,7 +69,7 @@ describe ProfilesController, :request_store do
sign_in(ldap_user)
put :update,
- user: { email: "john@gmail.com", name: "John", location: "City, Country" }
+ params: { user: { email: "john@gmail.com", name: "John", location: "City, Country" } }
ldap_user.reload
@@ -82,7 +82,7 @@ describe ProfilesController, :request_store do
it 'allows setting a user status' do
sign_in(user)
- put :update, user: { status: { message: 'Working hard!' } }
+ put :update, params: { user: { status: { message: 'Working hard!' } } }
expect(user.reload.status.message).to eq('Working hard!')
expect(response).to have_gitlab_http_status(302)
@@ -98,7 +98,7 @@ describe ProfilesController, :request_store do
sign_in(user)
put :update_username,
- user: { username: new_username }
+ params: { user: { username: new_username } }
user.reload
@@ -110,7 +110,9 @@ describe ProfilesController, :request_store do
sign_in(user)
put :update_username,
- user: { username: new_username },
+ params: {
+ user: { username: new_username }
+ },
format: :json
expect(response.status).to eq(200)
@@ -121,7 +123,9 @@ describe ProfilesController, :request_store do
sign_in(user)
put :update_username,
- user: { username: 'invalid username.git' },
+ params: {
+ user: { username: 'invalid username.git' }
+ },
format: :json
expect(response.status).to eq(422)
@@ -131,7 +135,7 @@ describe ProfilesController, :request_store do
it 'raises a correct error when the username is missing' do
sign_in(user)
- expect { put :update_username, user: { gandalf: 'you shall not pass' } }
+ expect { put :update_username, params: { user: { gandalf: 'you shall not pass' } } }
.to raise_error(ActionController::ParameterMissing)
end
@@ -142,7 +146,7 @@ describe ProfilesController, :request_store do
sign_in(user)
put :update_username,
- user: { username: new_username }
+ params: { user: { username: new_username } }
user.reload
@@ -160,7 +164,7 @@ describe ProfilesController, :request_store do
sign_in(user)
put :update_username,
- user: { username: new_username }
+ params: { user: { username: new_username } }
user.reload
diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb
index b3c8d6a954e..bd10de45b67 100644
--- a/spec/controllers/projects/artifacts_controller_spec.rb
+++ b/spec/controllers/projects/artifacts_controller_spec.rb
@@ -22,7 +22,7 @@ describe Projects::ArtifactsController do
def download_artifact(extra_params = {})
params = { namespace_id: project.namespace, project_id: project, job_id: job }.merge(extra_params)
- get :download, params
+ get :download, params: params
end
context 'when no file type is supplied' do
@@ -86,7 +86,7 @@ describe Projects::ArtifactsController do
describe 'GET browse' do
context 'when the directory exists' do
it 'renders the browse view' do
- get :browse, namespace_id: project.namespace, project_id: project, job_id: job, path: 'other_artifacts_0.1.2'
+ get :browse, params: { namespace_id: project.namespace, project_id: project, job_id: job, path: 'other_artifacts_0.1.2' }
expect(response).to render_template('projects/artifacts/browse')
end
@@ -94,7 +94,7 @@ describe Projects::ArtifactsController do
context 'when the directory does not exist' do
it 'responds Not Found' do
- get :browse, namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown'
+ get :browse, params: { namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown' }
expect(response).to be_not_found
end
@@ -113,7 +113,7 @@ describe Projects::ArtifactsController do
context 'when the file exists' do
it 'renders the file view' do
- get :file, namespace_id: project.namespace, project_id: project, job_id: job, path: 'ci_artifacts.txt'
+ get :file, params: { namespace_id: project.namespace, project_id: project, job_id: job, path: 'ci_artifacts.txt' }
expect(response).to have_gitlab_http_status(302)
end
@@ -121,7 +121,7 @@ describe Projects::ArtifactsController do
context 'when the file does not exist' do
it 'responds Not Found' do
- get :file, namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown'
+ get :file, params: { namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown' }
expect(response).to be_not_found
end
@@ -131,7 +131,7 @@ describe Projects::ArtifactsController do
context 'when the file is served through Rails' do
context 'when the file exists' do
it 'renders the file view' do
- get :file, namespace_id: project.namespace, project_id: project, job_id: job, path: 'ci_artifacts.txt'
+ get :file, params: { namespace_id: project.namespace, project_id: project, job_id: job, path: 'ci_artifacts.txt' }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template('projects/artifacts/file')
@@ -140,7 +140,7 @@ describe Projects::ArtifactsController do
context 'when the file does not exist' do
it 'responds Not Found' do
- get :file, namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown'
+ get :file, params: { namespace_id: project.namespace, project_id: project, job_id: job, path: 'unknown' }
expect(response).to be_not_found
end
@@ -159,7 +159,7 @@ describe Projects::ArtifactsController do
end
it 'does not redirect the request' do
- get :file, namespace_id: private_project.namespace, project_id: private_project, job_id: job, path: 'ci_artifacts.txt'
+ get :file, params: { namespace_id: private_project.namespace, project_id: private_project, job_id: job, path: 'ci_artifacts.txt' }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template('projects/artifacts/file')
@@ -168,7 +168,7 @@ describe Projects::ArtifactsController do
end
describe 'GET raw' do
- subject { get(:raw, namespace_id: project.namespace, project_id: project, job_id: job, path: path) }
+ subject { get(:raw, params: { namespace_id: project.namespace, project_id: project, job_id: job, path: path }) }
context 'when the file exists' do
let(:path) { 'ci_artifacts.txt' }
@@ -239,7 +239,7 @@ describe Projects::ArtifactsController do
context 'has no such ref' do
before do
- get :latest_succeeded, params_from_ref('TAIL', job.name)
+ get :latest_succeeded, params: params_from_ref('TAIL', job.name)
end
it_behaves_like 'not found'
@@ -247,7 +247,7 @@ describe Projects::ArtifactsController do
context 'has no such job' do
before do
- get :latest_succeeded, params_from_ref(pipeline.ref, 'NOBUILD')
+ get :latest_succeeded, params: params_from_ref(pipeline.ref, 'NOBUILD')
end
it_behaves_like 'not found'
@@ -255,7 +255,7 @@ describe Projects::ArtifactsController do
context 'has no path' do
before do
- get :latest_succeeded, params_from_ref(pipeline.sha, job.name, '')
+ get :latest_succeeded, params: params_from_ref(pipeline.sha, job.name, '')
end
it_behaves_like 'not found'
@@ -276,7 +276,7 @@ describe Projects::ArtifactsController do
pipeline.update(ref: 'master',
sha: project.commit('master').sha)
- get :latest_succeeded, params_from_ref('master')
+ get :latest_succeeded, params: params_from_ref('master')
end
it_behaves_like 'redirect to the job'
@@ -287,7 +287,7 @@ describe Projects::ArtifactsController do
pipeline.update(ref: 'improve/awesome',
sha: project.commit('improve/awesome').sha)
- get :latest_succeeded, params_from_ref('improve/awesome')
+ get :latest_succeeded, params: params_from_ref('improve/awesome')
end
it_behaves_like 'redirect to the job'
@@ -298,7 +298,7 @@ describe Projects::ArtifactsController do
pipeline.update(ref: 'improve/awesome',
sha: project.commit('improve/awesome').sha)
- get :latest_succeeded, params_from_ref('improve/awesome', job.name, 'file/README.md')
+ get :latest_succeeded, params: params_from_ref('improve/awesome', job.name, 'file/README.md')
end
it 'redirects' do
diff --git a/spec/controllers/projects/avatars_controller_spec.rb b/spec/controllers/projects/avatars_controller_spec.rb
index 14059cff74c..95b7ae5885a 100644
--- a/spec/controllers/projects/avatars_controller_spec.rb
+++ b/spec/controllers/projects/avatars_controller_spec.rb
@@ -8,7 +8,7 @@ describe Projects::AvatarsController do
end
describe 'GET #show' do
- subject { get :show, namespace_id: project.namespace, project_id: project }
+ subject { get :show, params: { namespace_id: project.namespace, project_id: project } }
context 'when repository has no avatar' do
it 'shows 404' do
@@ -30,8 +30,9 @@ describe Projects::AvatarsController do
subject
expect(response).to have_gitlab_http_status(200)
- expect(response.header['Content-Type']).to eq('image/png')
+ expect(response.header['Content-Disposition']).to eq('inline')
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
+ expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
end
end
@@ -46,7 +47,7 @@ describe Projects::AvatarsController do
describe 'DELETE #destroy' do
it 'removes avatar from DB by calling destroy' do
- delete :destroy, namespace_id: project.namespace.id, project_id: project.id
+ delete :destroy, params: { namespace_id: project.namespace.id, project_id: project.id }
expect(project.avatar.present?).to be_falsey
expect(project).to be_valid
diff --git a/spec/controllers/projects/badges_controller_spec.rb b/spec/controllers/projects/badges_controller_spec.rb
index dfe34171b55..2556bc3ae50 100644
--- a/spec/controllers/projects/badges_controller_spec.rb
+++ b/spec/controllers/projects/badges_controller_spec.rb
@@ -23,6 +23,6 @@ describe Projects::BadgesController do
end
def get_badge(badge)
- get badge, namespace_id: project.namespace.to_param, project_id: project, ref: pipeline.ref, format: :svg
+ get badge, params: { namespace_id: project.namespace.to_param, project_id: project, ref: pipeline.ref }, format: :svg
end
end
diff --git a/spec/controllers/projects/blame_controller_spec.rb b/spec/controllers/projects/blame_controller_spec.rb
index fe4c4863717..eb110ea0002 100644
--- a/spec/controllers/projects/blame_controller_spec.rb
+++ b/spec/controllers/projects/blame_controller_spec.rb
@@ -16,9 +16,11 @@ describe Projects::BlameController do
before do
get(:show,
- namespace_id: project.namespace,
- project_id: project,
- id: id)
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: id
+ })
end
context "valid file" do
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 9fc6af6a045..38957e96798 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -11,9 +11,11 @@ describe Projects::BlobController do
context 'with file path' do
before do
get(:show,
- namespace_id: project.namespace,
- project_id: project,
- id: id)
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: id
+ })
end
context "valid branch, valid file" do
@@ -48,9 +50,11 @@ describe Projects::BlobController do
before do
get(:show,
- namespace_id: project.namespace,
- project_id: project,
- id: id,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: id
+ },
format: :json)
end
@@ -66,11 +70,13 @@ describe Projects::BlobController do
before do
get(:show,
- namespace_id: project.namespace,
- project_id: project,
- id: id,
- format: :json,
- viewer: 'none')
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: id,
+ viewer: 'none'
+ },
+ format: :json)
end
it do
@@ -84,9 +90,11 @@ describe Projects::BlobController do
context 'with tree path' do
before do
get(:show,
- namespace_id: project.namespace,
- project_id: project,
- id: id)
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: id
+ })
controller.instance_variable_set(:@blob, nil)
end
@@ -109,7 +117,7 @@ describe Projects::BlobController do
params = { namespace_id: project.namespace,
project_id: project,
id: 'master/CHANGELOG' }
- get :diff, params.merge(opts)
+ get :diff, params: params.merge(opts)
end
before do
@@ -200,7 +208,7 @@ describe Projects::BlobController do
context 'anonymous' do
before do
- get :edit, default_params
+ get :edit, params: default_params
end
it 'redirects to sign in and returns' do
@@ -213,7 +221,7 @@ describe Projects::BlobController do
before do
sign_in(guest)
- get :edit, default_params
+ get :edit, params: default_params
end
it 'redirects to blob show' do
@@ -227,7 +235,7 @@ describe Projects::BlobController do
before do
project.add_developer(developer)
sign_in(developer)
- get :edit, default_params
+ get :edit, params: default_params
end
it 'redirects to blob show' do
@@ -241,7 +249,7 @@ describe Projects::BlobController do
before do
project.add_maintainer(maintainer)
sign_in(maintainer)
- get :edit, default_params
+ get :edit, params: default_params
end
it 'redirects to blob show' do
@@ -274,7 +282,7 @@ describe Projects::BlobController do
end
it 'redirects to blob' do
- put :update, default_params
+ put :update, params: default_params
expect(response).to redirect_to(blob_after_edit_path)
end
@@ -284,7 +292,7 @@ describe Projects::BlobController do
let(:mr_params) { default_params.merge(from_merge_request_iid: merge_request.iid) }
it 'redirects to MR diff' do
- put :update, mr_params
+ put :update, params: mr_params
after_edit_path = diffs_project_merge_request_path(project, merge_request)
file_anchor = "##{Digest::SHA1.hexdigest('CHANGELOG')}"
@@ -298,7 +306,7 @@ describe Projects::BlobController do
end
it "it redirect to blob" do
- put :update, mr_params
+ put :update, params: mr_params
expect(response).to redirect_to(blob_after_edit_path)
end
@@ -320,7 +328,7 @@ describe Projects::BlobController do
end
it 'redirects to blob' do
- put :update, default_params
+ put :update, params: default_params
expect(response).to redirect_to(project_blob_path(forked_project, 'master/CHANGELOG'))
end
@@ -331,7 +339,7 @@ describe Projects::BlobController do
default_params[:branch_name] = "fork-test-1"
default_params[:create_merge_request] = 1
- put :update, default_params
+ put :update, params: default_params
expect(response).to redirect_to(
project_new_merge_request_path(
@@ -374,7 +382,7 @@ describe Projects::BlobController do
let(:after_delete_path) { project_tree_path(project, 'master/files') }
it 'redirects to the sub directory' do
- delete :destroy, default_params
+ delete :destroy, params: default_params
expect(response).to redirect_to(after_delete_path)
end
@@ -393,7 +401,7 @@ describe Projects::BlobController do
end
it 'redirects to the project root' do
- delete :destroy, default_params
+ delete :destroy, params: default_params
expect(response).to redirect_to(project_root_path)
end
@@ -413,7 +421,7 @@ describe Projects::BlobController do
let(:after_delete_path) { project_tree_path(project, 'binary-encoding') }
it 'redirects to the project root of the branch' do
- delete :destroy, default_params
+ delete :destroy, params: default_params
expect(response).to redirect_to(after_delete_path)
end
diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb
index 8d503f6ad32..09199067024 100644
--- a/spec/controllers/projects/boards_controller_spec.rb
+++ b/spec/controllers/projects/boards_controller_spec.rb
@@ -121,8 +121,10 @@ describe Projects::BoardsController do
end
def list_boards(format: :html)
- get :index, namespace_id: project.namespace,
- project_id: project,
+ get :index, params: {
+ namespace_id: project.namespace,
+ project_id: project
+ },
format: format
end
end
@@ -207,9 +209,11 @@ describe Projects::BoardsController do
end
def read_board(board:, format: :html)
- get :show, namespace_id: project.namespace,
- project_id: project,
- id: board.to_param,
+ get :show, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: board.to_param
+ },
format: format
end
end
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index 31471cde420..02b3d5269a6 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -22,10 +22,12 @@ describe Projects::BranchesController do
sign_in(user)
post :create,
- namespace_id: project.namespace,
- project_id: project,
- branch_name: branch,
- ref: ref
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ branch_name: branch,
+ ref: ref
+ }
end
context "valid branch name, valid source" do
@@ -76,10 +78,12 @@ describe Projects::BranchesController do
it 'redirects' do
post :create,
- namespace_id: project.namespace,
- project_id: project,
- branch_name: branch,
- issue_iid: issue.iid
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ branch_name: branch,
+ issue_iid: issue.iid
+ }
expect(subject)
.to redirect_to("/#{project.full_path}/tree/1-feature-branch")
@@ -89,10 +93,12 @@ describe Projects::BranchesController do
expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, "1-feature-branch")
post :create,
- namespace_id: project.namespace,
- project_id: project,
- branch_name: branch,
- issue_iid: issue.iid
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ branch_name: branch,
+ issue_iid: issue.iid
+ }
end
context 'repository-less project' do
@@ -105,10 +111,12 @@ describe Projects::BranchesController do
expect(SystemNoteService).to receive(:new_issue_branch).and_return(true)
post :create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- branch_name: branch,
- issue_iid: issue.iid
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ branch_name: branch,
+ issue_iid: issue.iid
+ }
expect(response).to redirect_to project_tree_path(project, branch)
end
@@ -121,10 +129,12 @@ describe Projects::BranchesController do
expect(SystemNoteService).to receive(:new_issue_branch).and_return(true)
post :create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- branch_name: branch,
- issue_iid: issue.iid
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ branch_name: branch,
+ issue_iid: issue.iid
+ }
expect(response.location).to include(project_new_blob_path(project, branch))
expect(response).to have_gitlab_http_status(302)
@@ -156,10 +166,12 @@ describe Projects::BranchesController do
expect(SystemNoteService).to receive(:new_issue_branch).and_return(true)
post :create,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- branch_name: branch,
- issue_iid: issue.iid
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ branch_name: branch,
+ issue_iid: issue.iid
+ }
expect(response.location).to include(project_new_blob_path(project, branch))
expect(response).to have_gitlab_http_status(302)
@@ -173,10 +185,12 @@ describe Projects::BranchesController do
expect(SystemNoteService).not_to receive(:new_issue_branch)
post :create,
- namespace_id: project.namespace,
- project_id: project,
- branch_name: branch,
- issue_iid: issue.iid
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ branch_name: branch,
+ issue_iid: issue.iid
+ }
end
end
@@ -191,10 +205,12 @@ describe Projects::BranchesController do
expect(SystemNoteService).not_to receive(:new_issue_branch)
post :create,
- namespace_id: project.namespace,
- project_id: project,
- branch_name: branch,
- issue_iid: issue.iid
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ branch_name: branch,
+ issue_iid: issue.iid
+ }
end
end
end
@@ -228,11 +244,14 @@ describe Projects::BranchesController do
end
def create_branch(name:, ref:)
- post :create, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- branch_name: name,
- ref: ref,
- format: :json
+ post :create,
+ format: :json,
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ branch_name: name,
+ ref: ref
+ }
end
end
@@ -246,9 +265,11 @@ describe Projects::BranchesController do
it 'returns 303' do
post :destroy,
format: :html,
- id: 'foo/bar/baz',
- namespace_id: project.namespace,
- project_id: project
+ params: {
+ id: 'foo/bar/baz',
+ namespace_id: project.namespace,
+ project_id: project
+ }
expect(response).to have_gitlab_http_status(303)
end
@@ -261,10 +282,12 @@ describe Projects::BranchesController do
sign_in(user)
post :destroy,
- format: format,
- id: branch,
- namespace_id: project.namespace,
- project_id: project
+ format: format,
+ params: {
+ id: branch,
+ namespace_id: project.namespace,
+ project_id: project
+ }
end
context 'as JS' do
@@ -359,8 +382,10 @@ describe Projects::BranchesController do
describe "DELETE destroy_all_merged" do
def destroy_all_merged
delete :destroy_all_merged,
- namespace_id: project.namespace,
- project_id: project
+ params: {
+ namespace_id: project.namespace,
+ project_id: project
+ }
end
context 'when user is allowed to push' do
@@ -404,10 +429,12 @@ describe Projects::BranchesController do
context 'when rendering a JSON format' do
it 'filters branches by name' do
get :index,
- namespace_id: project.namespace,
- project_id: project,
format: :json,
- search: 'master'
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ search: 'master'
+ }
parsed_response = JSON.parse(response.body)
@@ -423,10 +450,12 @@ describe Projects::BranchesController do
context 'when cache is enabled yet cold', :request_store do
it 'return with a status 200' do
get :index,
- namespace_id: project.namespace,
- project_id: project,
- state: 'all',
- format: :html
+ format: :html,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ state: 'all'
+ }
expect(response).to have_gitlab_http_status(200)
end
@@ -439,10 +468,12 @@ describe Projects::BranchesController do
it 'return with a status 200' do
get :index,
- namespace_id: project.namespace,
- project_id: project,
- state: 'all',
- format: :html
+ format: :html,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ state: 'all'
+ }
expect(response).to have_gitlab_http_status(200)
end
@@ -451,30 +482,36 @@ describe Projects::BranchesController do
context 'when deprecated sort/search/page parameters are specified' do
it 'returns with a status 301 when sort specified' do
get :index,
- namespace_id: project.namespace,
- project_id: project,
- sort: 'updated_asc',
- format: :html
+ format: :html,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ sort: 'updated_asc'
+ }
expect(response).to redirect_to project_branches_filtered_path(project, state: 'all')
end
it 'returns with a status 301 when search specified' do
get :index,
- namespace_id: project.namespace,
- project_id: project,
- search: 'feature',
- format: :html
+ format: :html,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ search: 'feature'
+ }
expect(response).to redirect_to project_branches_filtered_path(project, state: 'all')
end
it 'returns with a status 301 when page specified' do
get :index,
- namespace_id: project.namespace,
- project_id: project,
- page: 2,
- format: :html
+ format: :html,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ page: 2
+ }
expect(response).to redirect_to project_branches_filtered_path(project, state: 'all')
end
diff --git a/spec/controllers/projects/ci/lints_controller_spec.rb b/spec/controllers/projects/ci/lints_controller_spec.rb
index 1249a5528a9..82c1374aa4f 100644
--- a/spec/controllers/projects/ci/lints_controller_spec.rb
+++ b/spec/controllers/projects/ci/lints_controller_spec.rb
@@ -13,7 +13,7 @@ describe Projects::Ci::LintsController do
before do
project.add_developer(user)
- get :show, namespace_id: project.namespace, project_id: project
+ get :show, params: { namespace_id: project.namespace, project_id: project }
end
it 'should be success' do
@@ -33,7 +33,7 @@ describe Projects::Ci::LintsController do
before do
project.add_guest(user)
- get :show, namespace_id: project.namespace, project_id: project
+ get :show, params: { namespace_id: project.namespace, project_id: project }
end
it 'should respond with 404' do
@@ -72,7 +72,7 @@ describe Projects::Ci::LintsController do
WebMock.stub_request(:get, remote_file_path).to_return(body: remote_file_content)
project.add_developer(user)
- post :create, namespace_id: project.namespace, project_id: project, content: content
+ post :create, params: { namespace_id: project.namespace, project_id: project, content: content }
end
it 'should be success' do
@@ -100,7 +100,7 @@ describe Projects::Ci::LintsController do
before do
project.add_developer(user)
- post :create, namespace_id: project.namespace, project_id: project, content: content
+ post :create, params: { namespace_id: project.namespace, project_id: project, content: content }
end
it 'should assign errors' do
@@ -112,7 +112,7 @@ describe Projects::Ci::LintsController do
before do
project.add_guest(user)
- post :create, namespace_id: project.namespace, project_id: project, content: content
+ post :create, params: { namespace_id: project.namespace, project_id: project, content: content }
end
it 'should respond with 404' do
diff --git a/spec/controllers/projects/clusters/applications_controller_spec.rb b/spec/controllers/projects/clusters/applications_controller_spec.rb
index 8106453a775..cb558259225 100644
--- a/spec/controllers/projects/clusters/applications_controller_spec.rb
+++ b/spec/controllers/projects/clusters/applications_controller_spec.rb
@@ -81,7 +81,7 @@ describe Projects::Clusters::ApplicationsController do
end
def go
- post :create, params.merge(namespace_id: project.namespace, project_id: project)
+ post :create, params: params.merge(namespace_id: project.namespace, project_id: project)
end
end
end
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb
index 483222363bb..d94c18ddc02 100644
--- a/spec/controllers/projects/clusters_controller_spec.rb
+++ b/spec/controllers/projects/clusters_controller_spec.rb
@@ -18,7 +18,7 @@ describe Projects::ClustersController do
describe 'GET index' do
def go(params = {})
- get :index, params.reverse_merge(namespace_id: project.namespace.to_param, project_id: project)
+ get :index, params: params.reverse_merge(namespace_id: project.namespace.to_param, project_id: project)
end
describe 'functionality' do
@@ -80,7 +80,7 @@ describe Projects::ClustersController do
describe 'GET new' do
def go
- get :new, namespace_id: project.namespace, project_id: project
+ get :new, params: { namespace_id: project.namespace, project_id: project }
end
describe 'functionality for new cluster' do
@@ -174,7 +174,7 @@ describe Projects::ClustersController do
end
def go
- post :create_gcp, params.merge(namespace_id: project.namespace, project_id: project)
+ post :create_gcp, params: params.merge(namespace_id: project.namespace, project_id: project)
end
describe 'functionality' do
@@ -261,7 +261,7 @@ describe Projects::ClustersController do
end
def go
- post :create_user, params.merge(namespace_id: project.namespace, project_id: project)
+ post :create_user, params: params.merge(namespace_id: project.namespace, project_id: project)
end
describe 'functionality' do
@@ -311,7 +311,7 @@ describe Projects::ClustersController do
describe 'security' do
before do
- allow(ClusterPlatformConfigureWorker).to receive(:perform_async)
+ allow(ClusterConfigureWorker).to receive(:perform_async)
stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace')
end
@@ -331,9 +331,11 @@ describe Projects::ClustersController do
def go
get :cluster_status,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: cluster,
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: cluster
+ },
format: :json
end
@@ -369,9 +371,11 @@ describe Projects::ClustersController do
def go
get :show,
- namespace_id: project.namespace,
- project_id: project,
- id: cluster
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: cluster
+ }
end
describe 'functionality' do
@@ -397,15 +401,15 @@ describe Projects::ClustersController do
describe 'PUT update' do
def go(format: :html)
- put :update, params.merge(namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: cluster,
- format: format
- )
+ put :update, params: params.merge(namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: cluster,
+ format: format
+ )
end
before do
- allow(ClusterPlatformConfigureWorker).to receive(:perform_async)
+ allow(ClusterConfigureWorker).to receive(:perform_async)
stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace')
end
@@ -500,9 +504,11 @@ describe Projects::ClustersController do
def go
delete :destroy,
- namespace_id: project.namespace,
- project_id: project,
- id: cluster
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: cluster
+ }
end
describe 'functionality' do
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index e34fdee62d6..26eec90da06 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -21,7 +21,7 @@ describe Projects::CommitController do
project_id: project
}
- get :show, params.merge(extra_params)
+ get :show, params: params.merge(extra_params)
end
context 'with valid id' do
@@ -102,9 +102,11 @@ describe Projects::CommitController do
it 'renders it' do
get(:show,
- namespace_id: fork_project.namespace,
- project_id: fork_project,
- id: commit.id)
+ params: {
+ namespace_id: fork_project.namespace,
+ project_id: fork_project,
+ id: commit.id
+ })
expect(response).to be_success
end
@@ -132,9 +134,11 @@ describe Projects::CommitController do
commit = project.commit('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
get(:branches,
- namespace_id: project.namespace,
- project_id: project,
- id: commit.id)
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: commit.id
+ })
expect(assigns(:branches)).to include('master', 'feature_conflict')
expect(assigns(:branches_limit_exceeded)).to be_falsey
@@ -148,9 +152,11 @@ describe Projects::CommitController do
allow_any_instance_of(Repository).to receive(:tag_count).and_return(1001)
get(:branches,
- namespace_id: project.namespace,
- project_id: project,
- id: commit.id)
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: commit.id
+ })
expect(assigns(:branches)).to eq([])
expect(assigns(:branches_limit_exceeded)).to be_truthy
@@ -163,9 +169,11 @@ describe Projects::CommitController do
context 'when target branch is not provided' do
it 'renders the 404 page' do
post(:revert,
- namespace_id: project.namespace,
- project_id: project,
- id: commit.id)
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: commit.id
+ })
expect(response).not_to be_success
expect(response).to have_gitlab_http_status(404)
@@ -175,10 +183,12 @@ describe Projects::CommitController do
context 'when the revert was successful' do
it 'redirects to the commits page' do
post(:revert,
- namespace_id: project.namespace,
- project_id: project,
- start_branch: 'master',
- id: commit.id)
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ start_branch: 'master',
+ id: commit.id
+ })
expect(response).to redirect_to project_commits_path(project, 'master')
expect(flash[:notice]).to eq('The commit has been successfully reverted.')
@@ -188,19 +198,23 @@ describe Projects::CommitController do
context 'when the revert failed' do
before do
post(:revert,
- namespace_id: project.namespace,
- project_id: project,
- start_branch: 'master',
- id: commit.id)
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ start_branch: 'master',
+ id: commit.id
+ })
end
it 'redirects to the commit page' do
# Reverting a commit that has been already reverted.
post(:revert,
- namespace_id: project.namespace,
- project_id: project,
- start_branch: 'master',
- id: commit.id)
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ start_branch: 'master',
+ id: commit.id
+ })
expect(response).to redirect_to project_commit_path(project, commit.id)
expect(flash[:alert]).to match('Sorry, we cannot revert this commit automatically.')
@@ -212,9 +226,11 @@ describe Projects::CommitController do
context 'when target branch is not provided' do
it 'renders the 404 page' do
post(:cherry_pick,
- namespace_id: project.namespace,
- project_id: project,
- id: master_pickable_commit.id)
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: master_pickable_commit.id
+ })
expect(response).not_to be_success
expect(response).to have_gitlab_http_status(404)
@@ -224,10 +240,12 @@ describe Projects::CommitController do
context 'when the cherry-pick was successful' do
it 'redirects to the commits page' do
post(:cherry_pick,
- namespace_id: project.namespace,
- project_id: project,
- start_branch: 'master',
- id: master_pickable_commit.id)
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ start_branch: 'master',
+ id: master_pickable_commit.id
+ })
expect(response).to redirect_to project_commits_path(project, 'master')
expect(flash[:notice]).to eq('The commit has been successfully cherry-picked into master.')
@@ -237,19 +255,23 @@ describe Projects::CommitController do
context 'when the cherry_pick failed' do
before do
post(:cherry_pick,
- namespace_id: project.namespace,
- project_id: project,
- start_branch: 'master',
- id: master_pickable_commit.id)
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ start_branch: 'master',
+ id: master_pickable_commit.id
+ })
end
it 'redirects to the commit page' do
# Cherry-picking a commit that has been already cherry-picked.
post(:cherry_pick,
- namespace_id: project.namespace,
- project_id: project,
- start_branch: 'master',
- id: master_pickable_commit.id)
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ start_branch: 'master',
+ id: master_pickable_commit.id
+ })
expect(response).to redirect_to project_commit_path(project, master_pickable_commit.id)
expect(flash[:alert]).to match('Sorry, we cannot cherry-pick this commit automatically.')
@@ -264,7 +286,7 @@ describe Projects::CommitController do
project_id: project
}
- get :diff_for_path, params.merge(extra_params)
+ get :diff_for_path, params: params.merge(extra_params)
end
let(:existing_path) { '.gitmodules' }
@@ -332,7 +354,7 @@ describe Projects::CommitController do
project_id: project
}
- get :pipelines, params.merge(extra_params)
+ get :pipelines, params: params.merge(extra_params)
end
context 'when the commit exists' do
diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb
index 5c72dab698c..8cb9130b834 100644
--- a/spec/controllers/projects/commits_controller_spec.rb
+++ b/spec/controllers/projects/commits_controller_spec.rb
@@ -17,8 +17,10 @@ describe Projects::CommitsController do
context "no ref is provided" do
it 'should redirect to the default branch of the project' do
get(:commits_root,
- namespace_id: project.namespace,
- project_id: project)
+ params: {
+ namespace_id: project.namespace,
+ project_id: project
+ })
expect(response).to redirect_to project_commits_path(project)
end
@@ -31,9 +33,11 @@ describe Projects::CommitsController do
context 'with file path' do
before do
get(:show,
- namespace_id: project.namespace,
- project_id: project,
- id: id)
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: id
+ })
end
context "valid branch, valid file" do
@@ -53,15 +57,23 @@ describe Projects::CommitsController do
it { is_expected.to respond_with(:not_found) }
end
+
+ context "branch with invalid format, valid file" do
+ let(:id) { 'branch with space/README.md' }
+
+ it { is_expected.to respond_with(:not_found) }
+ end
end
context "when the ref name ends in .atom" do
context "when the ref does not exist with the suffix" do
before do
get(:show,
- namespace_id: project.namespace,
- project_id: project,
- id: "master.atom")
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: "master.atom"
+ })
end
it "renders as atom" do
@@ -82,9 +94,11 @@ describe Projects::CommitsController do
allow_any_instance_of(Repository).to receive(:commit).with('master.atom').and_return(commit)
get(:show,
- namespace_id: project.namespace,
- project_id: project,
- id: "master.atom")
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: "master.atom"
+ })
end
it "renders as HTML" do
@@ -94,6 +108,32 @@ describe Projects::CommitsController do
end
end
end
+
+ describe "GET /commits/:id/signatures" do
+ render_views
+
+ before do
+ get(:signatures,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: id
+ },
+ format: :json)
+ end
+
+ context "valid branch" do
+ let(:id) { 'master' }
+
+ it { is_expected.to respond_with(:success) }
+ end
+
+ context "invalid branch format" do
+ let(:id) { 'some branch' }
+
+ it { is_expected.to respond_with(:not_found) }
+ end
+ end
end
context 'token authentication' do
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index 17883d0fadd..cfd70e93efb 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -13,7 +13,7 @@ describe Projects::CompareController do
render_views
before do
- get :index, namespace_id: project.namespace, project_id: project
+ get :index, params: { namespace_id: project.namespace, project_id: project }
end
it 'returns successfully' do
@@ -24,7 +24,7 @@ describe Projects::CompareController do
describe 'GET show' do
render_views
- subject(:show_request) { get :show, request_params }
+ subject(:show_request) { get :show, params: request_params }
let(:request_params) do
{
@@ -130,7 +130,7 @@ describe Projects::CompareController do
project_id: project
}
- get :diff_for_path, params.merge(extra_params)
+ get :diff_for_path, params: params.merge(extra_params)
end
let(:existing_path) { 'files/ruby/feature.rb' }
@@ -201,7 +201,7 @@ describe Projects::CompareController do
end
describe 'POST create' do
- subject(:create_request) { post :create, request_params }
+ subject(:create_request) { post :create, params: request_params }
let(:request_params) do
{
@@ -260,7 +260,7 @@ describe Projects::CompareController do
end
describe 'GET signatures' do
- subject(:signatures_request) { get :signatures, request_params }
+ subject(:signatures_request) { get :signatures, params: request_params }
let(:request_params) do
{
diff --git a/spec/controllers/projects/cycle_analytics_controller_spec.rb b/spec/controllers/projects/cycle_analytics_controller_spec.rb
index 5c79269e8f1..6a63cbdf8e2 100644
--- a/spec/controllers/projects/cycle_analytics_controller_spec.rb
+++ b/spec/controllers/projects/cycle_analytics_controller_spec.rb
@@ -13,8 +13,10 @@ describe Projects::CycleAnalyticsController do
context 'with no data' do
it 'is true' do
get(:show,
- namespace_id: project.namespace,
- project_id: project)
+ params: {
+ namespace_id: project.namespace,
+ project_id: project
+ })
expect(response).to be_success
expect(assigns(:cycle_analytics_no_data)).to eq(true)
@@ -32,8 +34,10 @@ describe Projects::CycleAnalyticsController do
it 'is false' do
get(:show,
- namespace_id: project.namespace,
- project_id: project)
+ params: {
+ namespace_id: project.namespace,
+ project_id: project
+ })
expect(response).to be_success
expect(assigns(:cycle_analytics_no_data)).to eq(false)
diff --git a/spec/controllers/projects/deploy_keys_controller_spec.rb b/spec/controllers/projects/deploy_keys_controller_spec.rb
index 4567a51b88e..e54cf3e8181 100644
--- a/spec/controllers/projects/deploy_keys_controller_spec.rb
+++ b/spec/controllers/projects/deploy_keys_controller_spec.rb
@@ -16,8 +16,8 @@ describe Projects::DeployKeysController do
end
context 'when html requested' do
- it 'redirects to blob' do
- get :index, params
+ it 'redirects to project settings with the correct anchor' do
+ get :index, params: params
expect(response).to redirect_to(project_settings_repository_path(project, anchor: 'js-deploy-keys-settings'))
end
@@ -48,7 +48,7 @@ describe Projects::DeployKeysController do
end
it 'returns json in a correct format' do
- get :index, params.merge(format: :json)
+ get :index, params: params.merge(format: :json)
json = JSON.parse(response.body)
@@ -60,6 +60,40 @@ describe Projects::DeployKeysController do
end
end
+ describe 'POST create' do
+ def create_params(title = 'my-key')
+ {
+ namespace_id: project.namespace.path,
+ project_id: project.path,
+ deploy_key: {
+ title: title,
+ key: attributes_for(:deploy_key)[:key],
+ deploy_keys_projects_attributes: { '0' => { can_push: '1' } }
+ }
+ }
+ end
+
+ it 'creates a new deploy key for the project' do
+ expect { post :create, params: create_params }.to change(project.deploy_keys, :count).by(1)
+
+ expect(response).to redirect_to(project_settings_repository_path(project, anchor: 'js-deploy-keys-settings'))
+ end
+
+ it 'redirects to project settings with the correct anchor' do
+ post :create, params: create_params
+
+ expect(response).to redirect_to(project_settings_repository_path(project, anchor: 'js-deploy-keys-settings'))
+ end
+
+ context 'when the deploy key is invalid' do
+ it 'shows an alert with the validations errors' do
+ post :create, params: create_params(nil)
+
+ expect(flash[:alert]).to eq("Title can't be blank, Deploy keys projects deploy key title can't be blank")
+ end
+ end
+ end
+
describe '/enable/:id' do
let(:deploy_key) { create(:deploy_key) }
let(:project2) { create(:project) }
@@ -74,7 +108,7 @@ describe Projects::DeployKeysController do
it 'redirects to login' do
expect do
- put :enable, id: deploy_key.id, namespace_id: project.namespace, project_id: project
+ put :enable, params: { id: deploy_key.id, namespace_id: project.namespace, project_id: project }
end.not_to change { DeployKeysProject.count }
expect(response).to have_http_status(302)
@@ -89,7 +123,7 @@ describe Projects::DeployKeysController do
it 'returns 404' do
expect do
- put :enable, id: deploy_key.id, namespace_id: project.namespace, project_id: project
+ put :enable, params: { id: deploy_key.id, namespace_id: project.namespace, project_id: project }
end.not_to change { DeployKeysProject.count }
expect(response).to have_http_status(404)
@@ -103,7 +137,7 @@ describe Projects::DeployKeysController do
it 'returns 302' do
expect do
- put :enable, id: deploy_key.id, namespace_id: project.namespace, project_id: project
+ put :enable, params: { id: deploy_key.id, namespace_id: project.namespace, project_id: project }
end.to change { DeployKeysProject.count }.by(1)
expect(DeployKeysProject.where(project_id: project.id, deploy_key_id: deploy_key.id).count).to eq(1)
@@ -112,7 +146,7 @@ describe Projects::DeployKeysController do
end
it 'returns 404' do
- put :enable, id: 0, namespace_id: project.namespace, project_id: project
+ put :enable, params: { id: 0, namespace_id: project.namespace, project_id: project }
expect(response).to have_http_status(404)
end
@@ -125,7 +159,7 @@ describe Projects::DeployKeysController do
it 'returns 302' do
expect do
- put :enable, id: deploy_key.id, namespace_id: project.namespace, project_id: project
+ put :enable, params: { id: deploy_key.id, namespace_id: project.namespace, project_id: project }
end.to change { DeployKeysProject.count }.by(1)
expect(DeployKeysProject.where(project_id: project.id, deploy_key_id: deploy_key.id).count).to eq(1)
@@ -145,7 +179,7 @@ describe Projects::DeployKeysController do
end
it 'redirects to login' do
- put :disable, id: deploy_key.id, namespace_id: project.namespace, project_id: project
+ put :disable, params: { id: deploy_key.id, namespace_id: project.namespace, project_id: project }
expect(response).to have_http_status(302)
expect(response).to redirect_to(new_user_session_path)
@@ -159,7 +193,7 @@ describe Projects::DeployKeysController do
end
it 'returns 404' do
- put :disable, id: deploy_key.id, namespace_id: project.namespace, project_id: project
+ put :disable, params: { id: deploy_key.id, namespace_id: project.namespace, project_id: project }
expect(response).to have_http_status(404)
expect(DeployKey.find(deploy_key.id)).to eq(deploy_key)
@@ -168,7 +202,7 @@ describe Projects::DeployKeysController do
context 'with user with permission' do
it 'returns 302' do
- put :disable, id: deploy_key.id, namespace_id: project.namespace, project_id: project
+ put :disable, params: { id: deploy_key.id, namespace_id: project.namespace, project_id: project }
expect(response).to have_http_status(302)
expect(response).to redirect_to(namespace_project_settings_repository_path(anchor: 'js-deploy-keys-settings'))
@@ -177,7 +211,7 @@ describe Projects::DeployKeysController do
end
it 'returns 404' do
- put :disable, id: 0, namespace_id: project.namespace, project_id: project
+ put :disable, params: { id: 0, namespace_id: project.namespace, project_id: project }
expect(response).to have_http_status(404)
end
@@ -190,7 +224,7 @@ describe Projects::DeployKeysController do
it 'returns 302' do
expect do
- put :disable, id: deploy_key.id, namespace_id: project.namespace, project_id: project
+ put :disable, params: { id: deploy_key.id, namespace_id: project.namespace, project_id: project }
end.to change { DeployKey.count }.by(-1)
expect(response).to have_http_status(302)
diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb
index 5b7da81b6a1..5c33098fd31 100644
--- a/spec/controllers/projects/deployments_controller_spec.rb
+++ b/spec/controllers/projects/deployments_controller_spec.rb
@@ -19,7 +19,7 @@ describe Projects::DeploymentsController do
create(:deployment, :success, environment: environment, created_at: 7.hours.ago)
create(:deployment, :success, environment: environment)
- get :index, deployment_params(after: 8.hours.ago)
+ get :index, params: deployment_params(after: 8.hours.ago)
expect(response).to be_ok
@@ -29,7 +29,7 @@ describe Projects::DeploymentsController do
it 'returns a list with deployments information' do
create(:deployment, :success, environment: environment)
- get :index, deployment_params
+ get :index, params: deployment_params
expect(response).to be_ok
expect(response).to match_response_schema('deployments')
@@ -49,7 +49,7 @@ describe Projects::DeploymentsController do
end
it 'responds with not found' do
- get :metrics, deployment_params(id: deployment.id)
+ get :metrics, params: deployment_params(id: deployment.id)
expect(response).to be_not_found
end
@@ -66,7 +66,7 @@ describe Projects::DeploymentsController do
end
it 'returns a empty response 204 resposne' do
- get :metrics, deployment_params(id: deployment.id)
+ get :metrics, params: deployment_params(id: deployment.id)
expect(response).to have_gitlab_http_status(204)
expect(response.body).to eq('')
end
@@ -86,7 +86,7 @@ describe Projects::DeploymentsController do
end
it 'returns a metrics JSON document' do
- get :metrics, deployment_params(id: deployment.id)
+ get :metrics, params: deployment_params(id: deployment.id)
expect(response).to be_ok
expect(json_response['success']).to be(true)
@@ -101,7 +101,7 @@ describe Projects::DeploymentsController do
end
it 'responds with not found' do
- get :metrics, deployment_params(id: deployment.id)
+ get :metrics, params: deployment_params(id: deployment.id)
expect(response).to be_not_found
end
@@ -122,7 +122,7 @@ describe Projects::DeploymentsController do
end
it 'responds with not found' do
- get :metrics, deployment_params(id: deployment.id)
+ get :metrics, params: deployment_params(id: deployment.id)
expect(response).to be_not_found
end
@@ -141,7 +141,7 @@ describe Projects::DeploymentsController do
end
it 'returns a empty response 204 response' do
- get :additional_metrics, deployment_params(id: deployment.id, format: :json)
+ get :additional_metrics, params: deployment_params(id: deployment.id, format: :json)
expect(response).to have_gitlab_http_status(204)
expect(response.body).to eq('')
end
@@ -161,7 +161,7 @@ describe Projects::DeploymentsController do
end
it 'returns a metrics JSON document' do
- get :additional_metrics, deployment_params(id: deployment.id, format: :json)
+ get :additional_metrics, params: deployment_params(id: deployment.id, format: :json)
expect(response).to be_ok
expect(json_response['success']).to be(true)
diff --git a/spec/controllers/projects/discussions_controller_spec.rb b/spec/controllers/projects/discussions_controller_spec.rb
index 4aa33dbbb01..0b9f336cf13 100644
--- a/spec/controllers/projects/discussions_controller_spec.rb
+++ b/spec/controllers/projects/discussions_controller_spec.rb
@@ -23,7 +23,7 @@ describe Projects::DiscussionsController do
context 'when user is not authorized to read the MR' do
it 'returns 404' do
- get :show, request_params, format: :json
+ get :show, params: request_params, session: { format: :json }
expect(response).to have_gitlab_http_status(404)
end
@@ -35,7 +35,7 @@ describe Projects::DiscussionsController do
end
it 'returns status 200' do
- get :show, request_params, format: :json
+ get :show, params: request_params, session: { format: :json }
expect(response).to have_gitlab_http_status(200)
end
@@ -43,7 +43,7 @@ describe Projects::DiscussionsController do
it 'returns status 404 if MR does not exists' do
merge_request.destroy!
- get :show, request_params, format: :json
+ get :show, params: request_params, session: { format: :json }
expect(response).to have_gitlab_http_status(404)
end
@@ -56,7 +56,7 @@ describe Projects::DiscussionsController do
end
it 'returns status 200' do
- get :show, request_params, format: :json
+ get :show, params: request_params, session: { format: :json }
expect(response).to have_gitlab_http_status(200)
end
@@ -70,7 +70,7 @@ describe Projects::DiscussionsController do
context "when the user is not authorized to resolve the discussion" do
it "returns status 404" do
- post :resolve, request_params
+ post :resolve, params: request_params
expect(response).to have_gitlab_http_status(404)
end
@@ -87,7 +87,7 @@ describe Projects::DiscussionsController do
end
it "returns status 404" do
- post :resolve, request_params
+ post :resolve, params: request_params
expect(response).to have_gitlab_http_status(404)
end
@@ -95,7 +95,7 @@ describe Projects::DiscussionsController do
context "when the discussion is resolvable" do
it "resolves the discussion" do
- post :resolve, request_params
+ post :resolve, params: request_params
expect(note.reload.discussion.resolved?).to be true
expect(note.reload.discussion.resolved_by).to eq(user)
@@ -104,17 +104,17 @@ describe Projects::DiscussionsController do
it "sends notifications if all discussions are resolved" do
expect_any_instance_of(MergeRequests::ResolvedDiscussionNotificationService).to receive(:execute).with(merge_request)
- post :resolve, request_params
+ post :resolve, params: request_params
end
it "returns the name of the resolving user" do
- post :resolve, request_params
+ post :resolve, params: request_params
expect(JSON.parse(response.body)['resolved_by']['name']).to eq(user.name)
end
it "returns status 200" do
- post :resolve, request_params
+ post :resolve, params: request_params
expect(response).to have_gitlab_http_status(200)
end
@@ -123,7 +123,7 @@ describe Projects::DiscussionsController do
expect_any_instance_of(DiscussionSerializer).to receive(:represent)
.with(instance_of(Discussion), { context: instance_of(described_class), render_truncated_diff_lines: true })
- post :resolve, request_params
+ post :resolve, params: request_params
end
context 'diff discussion' do
@@ -131,7 +131,7 @@ describe Projects::DiscussionsController do
let(:discussion) { note.discussion }
it "returns truncated diff lines" do
- post :resolve, request_params
+ post :resolve, params: request_params
expect(JSON.parse(response.body)['truncated_diff_lines']).to be_present
end
@@ -149,7 +149,7 @@ describe Projects::DiscussionsController do
context "when the user is not authorized to resolve the discussion" do
it "returns status 404" do
- delete :unresolve, request_params
+ delete :unresolve, params: request_params
expect(response).to have_gitlab_http_status(404)
end
@@ -166,7 +166,7 @@ describe Projects::DiscussionsController do
end
it "returns status 404" do
- delete :unresolve, request_params
+ delete :unresolve, params: request_params
expect(response).to have_gitlab_http_status(404)
end
@@ -174,13 +174,13 @@ describe Projects::DiscussionsController do
context "when the discussion is resolvable" do
it "unresolves the discussion" do
- delete :unresolve, request_params
+ delete :unresolve, params: request_params
expect(note.reload.discussion.resolved?).to be false
end
it "returns status 200" do
- delete :unresolve, request_params
+ delete :unresolve, params: request_params
expect(response).to have_gitlab_http_status(200)
end
@@ -194,7 +194,7 @@ describe Projects::DiscussionsController do
expect_any_instance_of(DiscussionSerializer).to receive(:represent)
.with(instance_of(Discussion), { context: instance_of(described_class), render_truncated_diff_lines: true })
- delete :unresolve, request_params
+ delete :unresolve, params: request_params
end
end
end
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index 5fa0488014f..94fb85f217c 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -17,7 +17,7 @@ describe Projects::EnvironmentsController do
describe 'GET index' do
context 'when a request for the HTML is made' do
it 'responds with status code 200' do
- get :index, environment_params
+ get :index, params: environment_params
expect(response).to have_gitlab_http_status(:ok)
end
@@ -26,7 +26,7 @@ describe Projects::EnvironmentsController do
expect_any_instance_of(Gitlab::EtagCaching::Store)
.to receive(:touch).with(project_environments_path(project, format: :json))
- get :index, environment_params
+ get :index, params: environment_params
end
end
@@ -49,7 +49,7 @@ describe Projects::EnvironmentsController do
context 'when requesting available environments scope' do
before do
- get :index, environment_params(format: :json, scope: :available)
+ get :index, params: environment_params(format: :json, scope: :available)
end
it 'responds with a payload describing available environments' do
@@ -73,7 +73,7 @@ describe Projects::EnvironmentsController do
context 'when requesting stopped environments scope' do
before do
- get :index, environment_params(format: :json, scope: :stopped)
+ get :index, params: environment_params(format: :json, scope: :stopped)
end
it 'responds with a payload describing stopped environments' do
@@ -103,9 +103,11 @@ describe Projects::EnvironmentsController do
context 'when using default format' do
it 'responds with HTML' do
- get :folder, namespace_id: project.namespace,
- project_id: project,
- id: 'staging-1.0'
+ get :folder, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: 'staging-1.0'
+ }
expect(response).to be_ok
expect(response).to render_template 'folder'
@@ -114,9 +116,11 @@ describe Projects::EnvironmentsController do
context 'when using JSON format' do
it 'sorts the subfolders lexicographically' do
- get :folder, namespace_id: project.namespace,
- project_id: project,
- id: 'staging-1.0',
+ get :folder, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: 'staging-1.0'
+ },
format: :json
expect(response).to be_ok
@@ -132,7 +136,7 @@ describe Projects::EnvironmentsController do
describe 'GET show' do
context 'with valid id' do
it 'responds with a status code 200' do
- get :show, environment_params
+ get :show, params: environment_params
expect(response).to be_ok
end
@@ -142,7 +146,7 @@ describe Projects::EnvironmentsController do
it 'responds with a status code 404' do
params = environment_params
params[:id] = 12345
- get :show, params
+ get :show, params: params
expect(response).to have_gitlab_http_status(404)
end
@@ -151,7 +155,7 @@ describe Projects::EnvironmentsController do
describe 'GET edit' do
it 'responds with a status code 200' do
- get :edit, environment_params
+ get :edit, params: environment_params
expect(response).to be_ok
end
@@ -160,7 +164,7 @@ describe Projects::EnvironmentsController do
describe 'PATCH #update' do
it 'responds with a 302' do
patch_params = environment_params.merge(environment: { external_url: 'https://git.gitlab.com' })
- patch :update, patch_params
+ patch :update, params: patch_params
expect(response).to have_gitlab_http_status(302)
end
@@ -171,7 +175,7 @@ describe Projects::EnvironmentsController do
it 'returns 404' do
allow_any_instance_of(Environment).to receive(:available?) { false }
- patch :stop, environment_params(format: :json)
+ patch :stop, params: environment_params(format: :json)
expect(response).to have_gitlab_http_status(404)
end
@@ -184,7 +188,7 @@ describe Projects::EnvironmentsController do
allow_any_instance_of(Environment)
.to receive_messages(available?: true, stop_with_action!: action)
- patch :stop, environment_params(format: :json)
+ patch :stop, params: environment_params(format: :json)
expect(response).to have_gitlab_http_status(200)
expect(json_response).to eq(
@@ -198,7 +202,7 @@ describe Projects::EnvironmentsController do
allow_any_instance_of(Environment)
.to receive_messages(available?: true, stop_with_action!: nil)
- patch :stop, environment_params(format: :json)
+ patch :stop, params: environment_params(format: :json)
expect(response).to have_gitlab_http_status(200)
expect(json_response).to eq(
@@ -211,7 +215,7 @@ describe Projects::EnvironmentsController do
describe 'GET #terminal' do
context 'with valid id' do
it 'responds with a status code 200' do
- get :terminal, environment_params
+ get :terminal, params: environment_params
expect(response).to have_gitlab_http_status(200)
end
@@ -222,13 +226,13 @@ describe Projects::EnvironmentsController do
expect_any_instance_of(defined?(EE) ? EE::Environment : Environment)
.to receive(:terminals)
- get :terminal, environment_params
+ get :terminal, params: environment_params
end
end
context 'with invalid id' do
it 'responds with a status code 404' do
- get :terminal, environment_params(id: 666)
+ get :terminal, params: environment_params(id: 666)
expect(response).to have_gitlab_http_status(404)
end
@@ -254,7 +258,7 @@ describe Projects::EnvironmentsController do
.with(:fake_terminal)
.and_return(workhorse: :response)
- get :terminal_websocket_authorize, environment_params
+ get :terminal_websocket_authorize, params: environment_params
expect(response).to have_gitlab_http_status(200)
expect(response.headers["Content-Type"]).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
@@ -264,7 +268,7 @@ describe Projects::EnvironmentsController do
context 'and invalid id' do
it 'returns 404' do
- get :terminal_websocket_authorize, environment_params(id: 666)
+ get :terminal_websocket_authorize, params: environment_params(id: 666)
expect(response).to have_gitlab_http_status(404)
end
@@ -275,7 +279,7 @@ describe Projects::EnvironmentsController do
it 'aborts with an exception' do
allow(Gitlab::Workhorse).to receive(:verify_api_request!).and_raise(JWT::DecodeError)
- expect { get :terminal_websocket_authorize, environment_params }.to raise_error(JWT::DecodeError)
+ expect { get :terminal_websocket_authorize, params: environment_params }.to raise_error(JWT::DecodeError)
# controller tests don't set the response status correctly. It's enough
# to check that the action raised an exception
end
@@ -288,13 +292,13 @@ describe Projects::EnvironmentsController do
it 'redirects to environment if it exists' do
environment = create(:environment, name: 'production', project: project)
- get :metrics_redirect, namespace_id: project.namespace, project_id: project
+ get :metrics_redirect, params: { namespace_id: project.namespace, project_id: project }
expect(response).to redirect_to(environment_metrics_path(environment))
end
it 'redirects to empty page if no environment exists' do
- get :metrics_redirect, namespace_id: project.namespace, project_id: project
+ get :metrics_redirect, params: { namespace_id: project.namespace, project_id: project }
expect(response).to be_ok
expect(response).to render_template 'empty'
@@ -312,14 +316,14 @@ describe Projects::EnvironmentsController do
end
it 'returns a metrics page' do
- get :metrics, environment_params
+ get :metrics, params: environment_params
expect(response).to be_ok
end
context 'when requesting metrics as JSON' do
it 'returns a metrics JSON document' do
- get :metrics, environment_params(format: :json)
+ get :metrics, params: environment_params(format: :json)
expect(response).to have_gitlab_http_status(204)
expect(json_response).to eq({})
@@ -337,7 +341,7 @@ describe Projects::EnvironmentsController do
end
it 'returns a metrics JSON document' do
- get :metrics, environment_params(format: :json)
+ get :metrics, params: environment_params(format: :json)
expect(response).to be_ok
expect(json_response['success']).to be(true)
@@ -359,7 +363,7 @@ describe Projects::EnvironmentsController do
context 'when requesting metrics as JSON' do
it 'returns a metrics JSON document' do
- get :additional_metrics, environment_params(format: :json)
+ get :additional_metrics, params: environment_params(format: :json)
expect(response).to have_gitlab_http_status(204)
expect(json_response).to eq({})
@@ -379,7 +383,7 @@ describe Projects::EnvironmentsController do
end
it 'returns a metrics JSON document' do
- get :additional_metrics, environment_params(format: :json)
+ get :additional_metrics, params: environment_params(format: :json)
expect(response).to be_ok
expect(json_response['success']).to be(true)
diff --git a/spec/controllers/projects/find_file_controller_spec.rb b/spec/controllers/projects/find_file_controller_spec.rb
index 66fe41108e2..9072d67af07 100644
--- a/spec/controllers/projects/find_file_controller_spec.rb
+++ b/spec/controllers/projects/find_file_controller_spec.rb
@@ -17,9 +17,11 @@ describe Projects::FindFileController do
before do
get(:show,
- namespace_id: project.namespace,
- project_id: project,
- id: id)
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: id
+ })
end
context "valid branch" do
@@ -36,9 +38,11 @@ describe Projects::FindFileController do
describe "GET #list" do
def go(format: 'json')
get :list,
- namespace_id: project.namespace,
- project_id: project,
- id: id,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: id
+ },
format: format
end
diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb
index 945b6142abf..0e1663c8585 100644
--- a/spec/controllers/projects/forks_controller_spec.rb
+++ b/spec/controllers/projects/forks_controller_spec.rb
@@ -13,8 +13,10 @@ describe Projects::ForksController do
describe 'GET index' do
def get_forks
get :index,
- namespace_id: project.namespace,
- project_id: project
+ params: {
+ namespace_id: project.namespace,
+ project_id: project
+ }
end
context 'when fork is public' do
@@ -83,8 +85,10 @@ describe Projects::ForksController do
describe 'GET new' do
def get_new
get :new,
- namespace_id: project.namespace,
- project_id: project
+ params: {
+ namespace_id: project.namespace,
+ project_id: project
+ }
end
context 'when user is signed in' do
@@ -111,9 +115,11 @@ describe Projects::ForksController do
describe 'POST create' do
def post_create
post :create,
- namespace_id: project.namespace,
- project_id: project,
- namespace_key: user.namespace.id
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ namespace_key: user.namespace.id
+ }
end
context 'when user is signed in' do
diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb
index da78592a6f6..73fb7307e11 100644
--- a/spec/controllers/projects/graphs_controller_spec.rb
+++ b/spec/controllers/projects/graphs_controller_spec.rb
@@ -11,7 +11,7 @@ describe Projects::GraphsController do
describe 'GET languages' do
it "redirects_to action charts" do
- get(:commits, namespace_id: project.namespace.path, project_id: project.path, id: 'master')
+ get(:commits, params: { namespace_id: project.namespace.path, project_id: project.path, id: 'master' })
expect(response).to redirect_to action: :charts
end
@@ -19,7 +19,7 @@ describe Projects::GraphsController do
describe 'GET commits' do
it "redirects_to action charts" do
- get(:commits, namespace_id: project.namespace.path, project_id: project.path, id: 'master')
+ get(:commits, params: { namespace_id: project.namespace.path, project_id: project.path, id: 'master' })
expect(response).to redirect_to action: :charts
end
diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb
index 879aff26deb..675eeff8d12 100644
--- a/spec/controllers/projects/group_links_controller_spec.rb
+++ b/spec/controllers/projects/group_links_controller_spec.rb
@@ -14,10 +14,12 @@ describe Projects::GroupLinksController do
describe '#create' do
shared_context 'link project to group' do
before do
- post(:create, namespace_id: project.namespace,
- project_id: project,
- link_group_id: group.id,
- link_group_access: ProjectGroupLink.default_access)
+ post(:create, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ link_group_id: group.id,
+ link_group_access: ProjectGroupLink.default_access
+ })
end
end
@@ -65,10 +67,12 @@ describe Projects::GroupLinksController do
context 'when project group id equal link group id' do
before do
- post(:create, namespace_id: project.namespace,
- project_id: project,
- link_group_id: group2.id,
- link_group_access: ProjectGroupLink.default_access)
+ post(:create, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ link_group_id: group2.id,
+ link_group_access: ProjectGroupLink.default_access
+ })
end
it 'does not share project with selected group' do
@@ -84,9 +88,11 @@ describe Projects::GroupLinksController do
context 'when link group id is not present' do
before do
- post(:create, namespace_id: project.namespace,
- project_id: project,
- link_group_access: ProjectGroupLink.default_access)
+ post(:create, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ link_group_access: ProjectGroupLink.default_access
+ })
end
it 'redirects to project group links page' do
diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb
index 7d3a8c3d0d3..3037c922b68 100644
--- a/spec/controllers/projects/hooks_controller_spec.rb
+++ b/spec/controllers/projects/hooks_controller_spec.rb
@@ -11,7 +11,7 @@ describe Projects::HooksController do
describe '#index' do
it 'redirects to settings/integrations page' do
- get(:index, namespace_id: project.namespace, project_id: project)
+ get(:index, params: { namespace_id: project.namespace, project_id: project })
expect(response).to redirect_to(
project_settings_integrations_path(project)
@@ -38,7 +38,7 @@ describe Projects::HooksController do
wiki_page_events: true
}
- post :create, namespace_id: project.namespace, project_id: project, hook: hook_params
+ post :create, params: { namespace_id: project.namespace, project_id: project, hook: hook_params }
expect(response).to have_http_status(302)
expect(ProjectHook.all.size).to eq(1)
diff --git a/spec/controllers/projects/imports_controller_spec.rb b/spec/controllers/projects/imports_controller_spec.rb
index cdc63f5aab3..3ebfe4b0918 100644
--- a/spec/controllers/projects/imports_controller_spec.rb
+++ b/spec/controllers/projects/imports_controller_spec.rb
@@ -12,13 +12,13 @@ describe Projects::ImportsController do
describe 'GET #show' do
context 'when repository does not exists' do
it 'renders template' do
- get :show, namespace_id: project.namespace.to_param, project_id: project
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
expect(response).to render_template :show
end
it 'sets flash.now if params is present' do
- get :show, namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'Started' }
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'Started' } }
expect(flash.now[:notice]).to eq 'Started'
end
@@ -34,13 +34,13 @@ describe Projects::ImportsController do
end
it 'renders template' do
- get :show, namespace_id: project.namespace.to_param, project_id: project
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
expect(response).to render_template :show
end
it 'sets flash.now if params is present' do
- get :show, namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'In progress' }
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: { to: '/', notice_now: 'In progress' } }
expect(flash.now[:notice]).to eq 'In progress'
end
@@ -52,7 +52,7 @@ describe Projects::ImportsController do
end
it 'redirects to new_namespace_project_import_path' do
- get :show, namespace_id: project.namespace.to_param, project_id: project
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
expect(response).to redirect_to new_project_import_path(project)
end
@@ -67,7 +67,7 @@ describe Projects::ImportsController do
it 'redirects to namespace_project_path' do
allow_any_instance_of(Project).to receive(:forked?).and_return(true)
- get :show, namespace_id: project.namespace.to_param, project_id: project
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
expect(flash[:notice]).to eq 'The project was successfully forked.'
expect(response).to redirect_to project_path(project)
@@ -76,7 +76,7 @@ describe Projects::ImportsController do
context 'when project is external' do
it 'redirects to namespace_project_path' do
- get :show, namespace_id: project.namespace.to_param, project_id: project
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
expect(flash[:notice]).to eq 'The project was successfully imported.'
expect(response).to redirect_to project_path(project)
@@ -92,7 +92,7 @@ describe Projects::ImportsController do
end
it 'redirects to internal params[:to]' do
- get :show, namespace_id: project.namespace.to_param, project_id: project, continue: params
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: params }
expect(flash[:notice]).to eq params[:notice]
expect(response).to redirect_to params[:to]
@@ -101,7 +101,7 @@ describe Projects::ImportsController do
it 'does not redirect to external params[:to]' do
params[:to] = "//google.com"
- get :show, namespace_id: project.namespace.to_param, project_id: project, continue: params
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project, continue: params }
expect(response).not_to redirect_to params[:to]
end
end
@@ -113,7 +113,7 @@ describe Projects::ImportsController do
end
it 'redirects to namespace_project_path' do
- get :show, namespace_id: project.namespace.to_param, project_id: project
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }
expect(response).to redirect_to project_path(project)
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 02930edbf72..a239ac16c0d 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -18,7 +18,7 @@ describe Projects::IssuesController do
project.issues_enabled = false
project.save!
- get :index, namespace_id: project.namespace, project_id: project
+ get :index, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(404)
end
@@ -26,7 +26,7 @@ describe Projects::IssuesController do
context 'when GitLab issues enabled' do
it 'renders the "index" template' do
- get :index, namespace_id: project.namespace, project_id: project
+ get :index, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template(:index)
@@ -42,14 +42,16 @@ describe Projects::IssuesController do
it_behaves_like "issuables list meta-data", :issue
+ it_behaves_like 'set sort order from user preference'
+
it "returns index" do
- get :index, namespace_id: project.namespace, project_id: project
+ get :index, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(200)
end
it "returns 301 if request path doesn't match project path" do
- get :index, namespace_id: project.namespace, project_id: project.path.upcase
+ get :index, params: { namespace_id: project.namespace, project_id: project.path.upcase }
expect(response).to redirect_to(project_issues_path(project))
end
@@ -58,7 +60,7 @@ describe Projects::IssuesController do
project.issues_enabled = false
project.save!
- get :index, namespace_id: project.namespace, project_id: project
+ get :index, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(404)
end
end
@@ -75,18 +77,22 @@ describe Projects::IssuesController do
it 'redirects to last_page if page number is larger than number of pages' do
get :index,
- namespace_id: project.namespace.to_param,
- project_id: project,
- page: (last_page + 1).to_param
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ page: (last_page + 1).to_param
+ }
expect(response).to redirect_to(namespace_project_issues_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope]))
end
it 'redirects to specified page' do
get :index,
- namespace_id: project.namespace.to_param,
- project_id: project,
- page: last_page.to_param
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ page: last_page.to_param
+ }
expect(assigns(:issues).current_page).to eq(last_page)
expect(response).to have_gitlab_http_status(200)
@@ -95,10 +101,12 @@ describe Projects::IssuesController do
it 'does not redirect to external sites when provided a host field' do
external_host = "www.example.com"
get :index,
- namespace_id: project.namespace.to_param,
- project_id: project,
- page: (last_page + 1).to_param,
- host: external_host
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ page: (last_page + 1).to_param,
+ host: external_host
+ }
expect(response).to redirect_to(namespace_project_issues_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope]))
end
@@ -107,9 +115,11 @@ describe Projects::IssuesController do
allow(controller).to receive(:pagination_disabled?).and_return(true)
get :index,
- namespace_id: project.namespace.to_param,
- project_id: project,
- page: (last_page + 1).to_param
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ page: (last_page + 1).to_param
+ }
expect(response).to have_gitlab_http_status(200)
expect(assigns(:issues).size).to eq(2)
@@ -119,7 +129,7 @@ describe Projects::IssuesController do
describe 'GET #new' do
it 'redirects to signin if not logged in' do
- get :new, namespace_id: project.namespace, project_id: project
+ get :new, params: { namespace_id: project.namespace, project_id: project }
expect(flash[:notice]).to eq 'Please sign in to create the new issue.'
expect(response).to redirect_to(new_user_session_path)
@@ -132,7 +142,7 @@ describe Projects::IssuesController do
end
it 'builds a new issue' do
- get :new, namespace_id: project.namespace, project_id: project
+ get :new, params: { namespace_id: project.namespace, project_id: project }
expect(assigns(:issue)).to be_a_new(Issue)
end
@@ -142,7 +152,7 @@ describe Projects::IssuesController do
project_with_repository.add_developer(user)
mr = create(:merge_request_with_diff_notes, source_project: project_with_repository)
- get :new, namespace_id: project_with_repository.namespace, project_id: project_with_repository, merge_request_to_resolve_discussions_of: mr.iid
+ get :new, params: { namespace_id: project_with_repository.namespace, project_id: project_with_repository, merge_request_to_resolve_discussions_of: mr.iid }
expect(assigns(:issue).title).not_to be_empty
expect(assigns(:issue).description).not_to be_empty
@@ -151,7 +161,7 @@ describe Projects::IssuesController do
it 'fills in an issue for a discussion' do
note = create(:note_on_merge_request, project: project)
- get :new, namespace_id: project.namespace.path, project_id: project, merge_request_to_resolve_discussions_of: note.noteable.iid, discussion_to_resolve: note.discussion_id
+ get :new, params: { namespace_id: project.namespace.path, project_id: project, merge_request_to_resolve_discussions_of: note.noteable.iid, discussion_to_resolve: note.discussion_id }
expect(assigns(:issue).title).not_to be_empty
expect(assigns(:issue).description).not_to be_empty
@@ -176,7 +186,7 @@ describe Projects::IssuesController do
project.issues_enabled = false
project.save!
- get :new, namespace_id: project.namespace, project_id: project
+ get :new, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(404)
end
@@ -184,7 +194,7 @@ describe Projects::IssuesController do
context 'when GitLab issues enabled' do
it 'renders the "new" template' do
- get :new, namespace_id: project.namespace, project_id: project
+ get :new, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template(:new)
@@ -196,12 +206,12 @@ describe Projects::IssuesController do
describe 'Redirect after sign in' do
context 'with an AJAX request' do
it 'does not store the visited URL' do
- xhr :get,
- :show,
+ get :show, params: {
format: :json,
namespace_id: project.namespace,
project_id: project,
id: issue.iid
+ }, xhr: true
expect(session['user_return_to']).to be_blank
end
@@ -210,9 +220,11 @@ describe Projects::IssuesController do
context 'without an AJAX request' do
it 'stores the visited URL' do
get :show,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: issue.iid
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: issue.iid
+ }
expect(session['user_return_to']).to eq("/#{project.namespace.to_param}/#{project.to_param}/issues/#{issue.iid}")
end
@@ -251,11 +263,13 @@ describe Projects::IssuesController do
def move_issue
post :move,
- format: :json,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: issue.iid,
- move_to_project_id: another_project.id
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: issue.iid,
+ move_to_project_id: another_project.id
+ },
+ format: :json
end
end
end
@@ -263,10 +277,13 @@ describe Projects::IssuesController do
describe 'PUT #update' do
subject do
put :update,
- namespace_id: project.namespace,
- project_id: project,
- id: issue.to_param,
- issue: { title: 'New title' }, format: :json
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: issue.to_param,
+ issue: { title: 'New title' }
+ },
+ format: :json
end
before do
@@ -316,9 +333,11 @@ describe Projects::IssuesController do
describe 'GET #realtime_changes' do
def go(id:)
get :realtime_changes,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: id
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: id
+ }
end
context 'when an issue was edited' do
@@ -431,8 +450,10 @@ describe Projects::IssuesController do
def get_issues
get :index,
- namespace_id: project.namespace.to_param,
- project_id: project
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project
+ }
end
end
@@ -500,7 +521,7 @@ describe Projects::IssuesController do
format: :json
}.merge(additional_params)
- put :update, params
+ put :update, params: params
end
def go(id:)
@@ -633,9 +654,11 @@ describe Projects::IssuesController do
def go(id:)
get :show,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: id
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: id
+ }
end
it 'avoids (most) N+1s loading labels', :request_store do
@@ -656,9 +679,11 @@ describe Projects::IssuesController do
def go(id:)
get :realtime_changes,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: id
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: id
+ }
end
end
@@ -667,9 +692,11 @@ describe Projects::IssuesController do
def go(id:)
get :edit,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: id
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: id
+ }
end
end
@@ -678,10 +705,12 @@ describe Projects::IssuesController do
def go(id:)
put :update,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: id,
- issue: { title: 'New title' }
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: id,
+ issue: { title: 'New title' }
+ }
end
end
end
@@ -692,7 +721,7 @@ describe Projects::IssuesController do
project = create(:project, :public)
project.add_developer(user)
- post :create, {
+ post :create, params: {
namespace_id: project.namespace.to_param,
project_id: project,
issue: { title: 'Title', description: 'Description' }.merge(issue_attrs)
@@ -716,7 +745,7 @@ describe Projects::IssuesController do
end
def post_issue(issue_params, other_params: {})
- post :create, { namespace_id: project.namespace.to_param, project_id: project, issue: issue_params, merge_request_to_resolve_discussions_of: merge_request.iid }.merge(other_params)
+ post :create, params: { namespace_id: project.namespace.to_param, project_id: project, issue: issue_params, merge_request_to_resolve_discussions_of: merge_request.iid }.merge(other_params)
end
it 'creates an issue for the project' do
@@ -883,7 +912,7 @@ describe Projects::IssuesController do
create(:user_agent_detail, subject: issue)
project.add_maintainer(admin)
sign_in(admin)
- post :mark_as_spam, {
+ post :mark_as_spam, params: {
namespace_id: project.namespace,
project_id: project,
id: issue.iid
@@ -904,7 +933,7 @@ describe Projects::IssuesController do
end
it "rejects a developer to destroy an issue" do
- delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
expect(response).to have_gitlab_http_status(404)
end
end
@@ -919,7 +948,7 @@ describe Projects::IssuesController do
end
it "deletes the issue" do
- delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid
+ 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(/The issue was successfully deleted\./)
@@ -928,7 +957,7 @@ describe Projects::IssuesController do
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, namespace_id: project.namespace, project_id: project, id: issue.iid
+ delete :destroy, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
end
end
end
@@ -941,8 +970,12 @@ describe Projects::IssuesController do
it "toggles the award emoji" do
expect do
- post(:toggle_award_emoji, namespace_id: project.namespace,
- project_id: project, id: issue.iid, name: "thumbsup")
+ post(:toggle_award_emoji, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: issue.iid,
+ name: "thumbsup"
+ })
end.to change { issue.award_emoji.count }.by(1)
expect(response).to have_gitlab_http_status(200)
@@ -984,9 +1017,11 @@ describe Projects::IssuesController do
end
def create_merge_request
- post :create_merge_request, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: issue.to_param,
+ post :create_merge_request, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: issue.to_param
+ },
format: :json
end
end
@@ -1000,7 +1035,7 @@ describe Projects::IssuesController do
end
it 'returns discussion json' do
- get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
expect(json_response.first.keys).to match_array(%w[id reply_id expanded notes diff_discussion discussion_path individual_note resolvable resolved resolved_at resolved_by resolved_by_push commit_id for_commit project_id])
end
@@ -1008,7 +1043,7 @@ describe Projects::IssuesController do
it 'renders the author status html if there is a status' do
create(:user_status, user: discussion.author)
- get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
note_json = json_response.first['notes'].first
@@ -1017,14 +1052,14 @@ describe Projects::IssuesController do
it 'does not cause an extra query for the status' do
control = ActiveRecord::QueryRecorder.new do
- get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
end
create(:user_status, user: discussion.author)
second_discussion = create(:discussion_note_on_issue, noteable: issue, project: issue.project, author: create(:user))
create(:user_status, user: second_discussion.author)
- expect { get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid }
+ expect { get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } }
.not_to exceed_query_limit(control)
end
@@ -1044,26 +1079,26 @@ describe Projects::IssuesController do
end
it 'filters notes that the user should not see' do
- get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
expect(JSON.parse(response.body).count).to eq(1)
end
it 'does not result in N+1 queries' do
# Instantiate the controller variables to ensure QueryRecorder has an accurate base count
- get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
RequestStore.clear!
control_count = ActiveRecord::QueryRecorder.new do
- get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
end.count
RequestStore.clear!
create_list(:discussion_note_on_issue, 2, :system, noteable: issue, project: issue.project, note: cross_reference)
- expect { get :discussions, namespace_id: project.namespace, project_id: project, id: issue.iid }.not_to exceed_query_limit(control_count)
+ expect { get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } }.not_to exceed_query_limit(control_count)
end
end
end
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index da3d658d061..7f65fe551e9 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -96,7 +96,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
project_id: project
}
- get :index, params.merge(extra_params)
+ get :index, params: params.merge(extra_params)
end
end
@@ -401,18 +401,56 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
context 'with variables' do
before do
create(:ci_pipeline_variable, pipeline: pipeline, key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1')
+ end
- get_show(id: job.id, format: :json)
+ context 'user is a maintainer' do
+ before do
+ project.add_maintainer(user)
+
+ get_show(id: job.id, format: :json)
+ end
+
+ it 'returns a job_detail' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('job/job_details')
+ end
+
+ it 'exposes trigger information and variables' do
+ expect(json_response['trigger']['short_token']).to eq 'toke'
+ expect(json_response['trigger']['variables'].length).to eq 1
+ end
+
+ it 'exposes correct variable properties' do
+ first_variable = json_response['trigger']['variables'].first
+
+ expect(first_variable['key']).to eq "TRIGGER_KEY_1"
+ expect(first_variable['value']).to eq "TRIGGER_VALUE_1"
+ expect(first_variable['public']).to eq false
+ end
end
- it 'exposes trigger information and variables' do
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('job/job_details')
- expect(json_response['trigger']['short_token']).to eq 'toke'
- expect(json_response['trigger']['variables'].length).to eq 1
- expect(json_response['trigger']['variables'].first['key']).to eq "TRIGGER_KEY_1"
- expect(json_response['trigger']['variables'].first['value']).to eq "TRIGGER_VALUE_1"
- expect(json_response['trigger']['variables'].first['public']).to eq false
+ context 'user is not a mantainer' do
+ before do
+ get_show(id: job.id, format: :json)
+ end
+
+ it 'returns a job_detail' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('job/job_details')
+ end
+
+ it 'exposes trigger information and variables' do
+ expect(json_response['trigger']['short_token']).to eq 'toke'
+ expect(json_response['trigger']['variables'].length).to eq 1
+ end
+
+ it 'exposes correct variable properties' do
+ first_variable = json_response['trigger']['variables'].first
+
+ expect(first_variable['key']).to eq "TRIGGER_KEY_1"
+ expect(first_variable['value']).to be_nil
+ expect(first_variable['public']).to eq false
+ end
end
end
end
@@ -423,7 +461,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
project_id: project
}
- get :show, params.merge(extra_params)
+ get :show, params: params.merge(extra_params)
end
end
@@ -514,9 +552,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
def get_trace
- get :trace, namespace_id: project.namespace,
- project_id: project,
- id: job.id,
+ get :trace, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: job.id
+ },
format: :json
end
end
@@ -526,9 +566,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:status) { job.detailed_status(double('user')) }
before do
- get :status, namespace_id: project.namespace,
- project_id: project,
- id: job.id,
+ get :status, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: job.id
+ },
format: :json
end
@@ -567,9 +609,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
def post_retry
- post :retry, namespace_id: project.namespace,
- project_id: project,
- id: job.id
+ post :retry, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: job.id
+ }
end
end
@@ -607,9 +651,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
def post_play
- post :play, namespace_id: project.namespace,
- project_id: project,
- id: job.id
+ post :play, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: job.id
+ }
end
end
@@ -676,9 +722,9 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
def post_cancel(additional_params = {})
- post :cancel, { namespace_id: project.namespace,
- project_id: project,
- id: job.id }.merge(additional_params)
+ post :cancel, params: { namespace_id: project.namespace,
+ project_id: project,
+ id: job.id }.merge(additional_params)
end
end
@@ -716,9 +762,11 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
def post_unschedule
- post :unschedule, namespace_id: project.namespace,
- project_id: project,
- id: job.id
+ post :unschedule, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: job.id
+ }
end
end
@@ -759,8 +807,10 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
def post_cancel_all
- post :cancel_all, namespace_id: project.namespace,
- project_id: project
+ post :cancel_all, params: {
+ namespace_id: project.namespace,
+ project_id: project
+ }
end
end
@@ -822,39 +872,45 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
def post_erase
- post :erase, namespace_id: project.namespace,
- project_id: project,
- id: job.id
+ post :erase, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: job.id
+ }
end
end
describe 'GET raw' do
subject do
- post :raw, namespace_id: project.namespace,
- project_id: project,
- id: job.id
+ post :raw, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: job.id
+ }
end
context "when job has a trace artifact" do
let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
- it 'returns a trace' do
+ it "sets #{Gitlab::Workhorse::DETECT_HEADER} header" do
response = subject
expect(response).to have_gitlab_http_status(:ok)
expect(response.headers["Content-Type"]).to eq("text/plain; charset=utf-8")
expect(response.body).to eq(job.job_artifacts_trace.open.read)
+ expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
end
end
context "when job has a trace file" do
let(:job) { create(:ci_build, :trace_live, pipeline: pipeline) }
- it "send a trace file" do
+ it 'sends a trace file' do
response = subject
expect(response).to have_gitlab_http_status(:ok)
expect(response.headers["Content-Type"]).to eq("text/plain; charset=utf-8")
+ expect(response.headers["Content-Disposition"]).to match(/^inline/)
expect(response.body).to eq("BUILD TRACE")
end
end
@@ -866,12 +922,27 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
job.update_column(:trace, "Sample trace")
end
- it "send a trace file" do
+ it 'sends a trace file' do
response = subject
expect(response).to have_gitlab_http_status(:ok)
- expect(response.headers["Content-Type"]).to eq("text/plain; charset=utf-8")
- expect(response.body).to eq("Sample trace")
+ expect(response.headers['Content-Type']).to eq('text/plain; charset=utf-8')
+ expect(response.headers['Content-Disposition']).to match(/^inline/)
+ expect(response.body).to eq('Sample trace')
+ end
+
+ context 'when trace format is not text/plain' do
+ before do
+ job.update_column(:trace, '<html></html>')
+ end
+
+ it 'sets content disposition to attachment' do
+ response = subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['Content-Type']).to eq('text/plain; charset=utf-8')
+ expect(response.headers['Content-Disposition']).to match(/^attachment/)
+ end
end
end
@@ -942,7 +1013,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
project_id: project
}
- get :terminal, params.merge(extra_params)
+ get :terminal, params: params.merge(extra_params)
end
end
@@ -996,7 +1067,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
project_id: project
}
- get :terminal_websocket_authorize, params.merge(extra_params)
+ get :terminal_websocket_authorize, params: params.merge(extra_params)
end
end
end
diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb
index e03d23bcdf6..32897a0f1b4 100644
--- a/spec/controllers/projects/labels_controller_spec.rb
+++ b/spec/controllers/projects/labels_controller_spec.rb
@@ -67,7 +67,7 @@ describe Projects::LabelsController do
end
def list_labels
- get :index, namespace_id: project.namespace.to_param, project_id: project
+ get :index, params: { namespace_id: project.namespace.to_param, project_id: project }
end
end
@@ -76,7 +76,7 @@ describe Projects::LabelsController do
let(:personal_project) { create(:project, namespace: user.namespace) }
it 'creates labels' do
- post :generate, namespace_id: personal_project.namespace.to_param, project_id: personal_project
+ post :generate, params: { namespace_id: personal_project.namespace.to_param, project_id: personal_project }
expect(response).to have_gitlab_http_status(302)
end
@@ -84,7 +84,7 @@ describe Projects::LabelsController do
context 'project belonging to a group' do
it 'creates labels' do
- post :generate, namespace_id: project.namespace.to_param, project_id: project
+ post :generate, params: { namespace_id: project.namespace.to_param, project_id: project }
expect(response).to have_gitlab_http_status(302)
end
@@ -109,7 +109,7 @@ describe Projects::LabelsController do
end
def toggle_subscription(label)
- post :toggle_subscription, namespace_id: project.namespace.to_param, project_id: project, id: label.to_param
+ post :toggle_subscription, params: { namespace_id: project.namespace.to_param, project_id: project, id: label.to_param }
end
end
@@ -119,7 +119,7 @@ describe Projects::LabelsController do
context 'not group reporters' do
it 'denies access' do
- post :promote, namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param
+ post :promote, params: { namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param }
expect(response).to have_gitlab_http_status(404)
end
@@ -131,13 +131,13 @@ describe Projects::LabelsController do
end
it 'gives access' do
- post :promote, namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param
+ post :promote, params: { namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param }
expect(response).to redirect_to(namespace_project_labels_path)
end
it 'promotes the label' do
- post :promote, namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param
+ post :promote, params: { namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param }
expect(Label.where(id: label_1.id)).to be_empty
expect(GroupLabel.find_by(title: promoted_label_name)).not_to be_nil
@@ -146,7 +146,7 @@ describe Projects::LabelsController do
it 'renders label name without parsing it as HTML' do
label_1.update!(name: 'CCC&lt;img src=x onerror=alert(document.domain)&gt;')
- post :promote, namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param
+ post :promote, params: { namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param }
expect(flash[:notice]).to eq("CCC&lt;img src=x onerror=alert(document.domain)&gt; promoted to <a href=\"#{group_labels_path(project.group)}\"><u>group label</u></a>.")
end
@@ -159,7 +159,7 @@ describe Projects::LabelsController do
end
it 'returns to label list' do
- post :promote, namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param
+ post :promote, params: { namespace_id: project.namespace.to_param, project_id: project, id: label_1.to_param }
expect(response).to redirect_to(namespace_project_labels_path)
end
end
@@ -176,7 +176,7 @@ describe Projects::LabelsController do
context 'non-show path' do
context 'with exactly matching casing' do
it 'does not redirect' do
- get :index, namespace_id: project.namespace, project_id: project.to_param
+ get :index, params: { namespace_id: project.namespace, project_id: project.to_param }
expect(response).not_to have_gitlab_http_status(301)
end
@@ -184,7 +184,7 @@ describe Projects::LabelsController do
context 'with different casing' do
it 'redirects to the correct casing' do
- get :index, namespace_id: project.namespace, project_id: project.to_param.upcase
+ get :index, params: { namespace_id: project.namespace, project_id: project.to_param.upcase }
expect(response).to redirect_to(project_labels_path(project))
expect(controller).not_to set_flash[:notice]
@@ -197,7 +197,7 @@ describe Projects::LabelsController do
let!(:redirect_route) { project.redirect_routes.create(path: project.full_path + 'old') }
it 'redirects to the canonical path' do
- get :index, namespace_id: project.namespace, project_id: project.to_param + 'old'
+ get :index, params: { namespace_id: project.namespace, project_id: project.to_param + 'old' }
expect(response).to redirect_to(project_labels_path(project))
expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, project))
@@ -209,13 +209,13 @@ describe Projects::LabelsController do
context 'for a non-GET request' do
context 'when requesting the canonical path with different casing' do
it 'does not 404' do
- post :generate, namespace_id: project.namespace, project_id: project
+ post :generate, params: { namespace_id: project.namespace, project_id: project }
expect(response).not_to have_gitlab_http_status(404)
end
it 'does not redirect to the correct casing' do
- post :generate, namespace_id: project.namespace, project_id: project
+ post :generate, params: { namespace_id: project.namespace, project_id: project }
expect(response).not_to have_gitlab_http_status(301)
end
@@ -225,7 +225,7 @@ describe Projects::LabelsController do
let!(:redirect_route) { project.redirect_routes.create(path: project.full_path + 'old') }
it 'returns not found' do
- post :generate, namespace_id: project.namespace, project_id: project.to_param + 'old'
+ post :generate, params: { namespace_id: project.namespace, project_id: project.to_param + 'old' }
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb
index c2a334a849c..6c8c7cd8f2b 100644
--- a/spec/controllers/projects/mattermosts_controller_spec.rb
+++ b/spec/controllers/projects/mattermosts_controller_spec.rb
@@ -17,8 +17,10 @@ describe Projects::MattermostsController do
it 'accepts the request' do
get(:new,
- namespace_id: project.namespace.to_param,
- project_id: project)
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project
+ })
expect(response).to have_gitlab_http_status(200)
end
@@ -29,9 +31,11 @@ describe Projects::MattermostsController do
subject do
post(:create,
- namespace_id: project.namespace.to_param,
- project_id: project,
- mattermost: mattermost_params)
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ mattermost: mattermost_params
+ })
end
context 'no request can be made to mattermost' do
diff --git a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb
index 1e1ea9a7144..039f35875d2 100644
--- a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb
@@ -21,9 +21,11 @@ describe Projects::MergeRequests::ConflictsController do
.and_raise(Gitlab::Git::Conflict::Parser::UnmergeableFile)
get :show,
- namespace_id: merge_request_with_conflicts.project.namespace.to_param,
- project_id: merge_request_with_conflicts.project,
- id: merge_request_with_conflicts.iid,
+ params: {
+ namespace_id: merge_request_with_conflicts.project.namespace.to_param,
+ project_id: merge_request_with_conflicts.project,
+ id: merge_request_with_conflicts.iid
+ },
format: 'json'
end
@@ -39,9 +41,11 @@ describe Projects::MergeRequests::ConflictsController do
context 'with valid conflicts' do
before do
get :show,
- namespace_id: merge_request_with_conflicts.project.namespace.to_param,
- project_id: merge_request_with_conflicts.project,
- id: merge_request_with_conflicts.iid,
+ params: {
+ namespace_id: merge_request_with_conflicts.project.namespace.to_param,
+ project_id: merge_request_with_conflicts.project,
+ id: merge_request_with_conflicts.iid
+ },
format: 'json'
end
@@ -99,11 +103,13 @@ describe Projects::MergeRequests::ConflictsController do
describe 'GET conflict_for_path' do
def conflict_for_path(path)
get :conflict_for_path,
- namespace_id: merge_request_with_conflicts.project.namespace.to_param,
- project_id: merge_request_with_conflicts.project,
- id: merge_request_with_conflicts.iid,
- old_path: path,
- new_path: path,
+ params: {
+ namespace_id: merge_request_with_conflicts.project.namespace.to_param,
+ project_id: merge_request_with_conflicts.project,
+ id: merge_request_with_conflicts.iid,
+ old_path: path,
+ new_path: path
+ },
format: 'json'
end
@@ -160,12 +166,14 @@ describe Projects::MergeRequests::ConflictsController do
def resolve_conflicts(files)
post :resolve_conflicts,
- namespace_id: merge_request_with_conflicts.project.namespace.to_param,
- project_id: merge_request_with_conflicts.project,
- id: merge_request_with_conflicts.iid,
- format: 'json',
- files: files,
- commit_message: 'Commit message'
+ params: {
+ namespace_id: merge_request_with_conflicts.project.namespace.to_param,
+ project_id: merge_request_with_conflicts.project,
+ id: merge_request_with_conflicts.iid,
+ files: files,
+ commit_message: 'Commit message'
+ },
+ format: 'json'
end
context 'with valid params' do
diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
index f8c37c0a676..ac93393ac3a 100644
--- a/spec/controllers/projects/merge_requests/creations_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
@@ -24,7 +24,7 @@ describe Projects::MergeRequests::CreationsController do
describe 'GET new' do
context 'merge request that removes a submodule' do
it 'renders new merge request widget template' do
- get :new, get_diff_params
+ get :new, params: get_diff_params
expect(response).to be_success
end
@@ -52,7 +52,7 @@ describe Projects::MergeRequests::CreationsController do
end
it 'limits total commits' do
- get :new, large_diff_params
+ get :new, params: large_diff_params
expect(response).to be_success
@@ -66,7 +66,7 @@ describe Projects::MergeRequests::CreationsController do
end
it 'shows total commits' do
- get :new, large_diff_params
+ get :new, params: large_diff_params
expect(response).to be_success
@@ -85,7 +85,7 @@ describe Projects::MergeRequests::CreationsController do
it 'does not assign diffs var' do
allow_any_instance_of(MergeRequest).to receive(:can_be_created).and_return(false)
- get :diffs, get_diff_params.merge(format: 'json')
+ get :diffs, params: get_diff_params.merge(format: 'json')
expect(response).to be_success
expect(assigns[:diffs]).to be_nil
@@ -101,7 +101,7 @@ describe Projects::MergeRequests::CreationsController do
end
it 'renders JSON including serialized pipelines' do
- get :pipelines, get_diff_params.merge(format: 'json')
+ get :pipelines, params: get_diff_params.merge(format: 'json')
expect(response).to be_ok
expect(json_response).to have_key 'pipelines'
@@ -117,7 +117,7 @@ describe Projects::MergeRequests::CreationsController do
format: 'json'
}
- get :diff_for_path, params.merge(extra_params)
+ get :diff_for_path, params: params.merge(extra_params)
end
let(:existing_path) { 'files/ruby/feature.rb' }
@@ -184,10 +184,12 @@ describe Projects::MergeRequests::CreationsController do
expect(Ability).to receive(:allowed?).with(user, :read_project, project) { true }
get :branch_to,
- namespace_id: fork_project.namespace,
- project_id: fork_project,
- target_project_id: project.id,
- ref: 'master'
+ params: {
+ namespace_id: fork_project.namespace,
+ project_id: fork_project,
+ target_project_id: project.id,
+ ref: 'master'
+ }
expect(assigns(:commit)).not_to be_nil
expect(response).to have_gitlab_http_status(200)
@@ -197,10 +199,12 @@ describe Projects::MergeRequests::CreationsController do
expect(Ability).to receive(:allowed?).with(user, :read_project, project) { false }
get :branch_to,
- namespace_id: fork_project.namespace,
- project_id: fork_project,
- target_project_id: project.id,
- ref: 'master'
+ params: {
+ namespace_id: fork_project.namespace,
+ project_id: fork_project,
+ target_project_id: project.id,
+ ref: 'master'
+ }
expect(assigns(:commit)).to be_nil
expect(response).to have_gitlab_http_status(200)
diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
index 8fc5d302af6..a6017d8e5e6 100644
--- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
@@ -20,7 +20,7 @@ describe Projects::MergeRequests::DiffsController do
format: 'json'
}
- get :show, params.merge(extra_params)
+ get :show, params: params.merge(extra_params)
end
context 'with default params' do
@@ -89,7 +89,7 @@ describe Projects::MergeRequests::DiffsController do
format: 'json'
}
- get :diff_for_path, params.merge(extra_params)
+ get :diff_for_path, params: params.merge(extra_params)
end
let(:existing_path) { 'files/ruby/popen.rb' }
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index e62523c65c9..d46b9ffb3ce 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -19,26 +19,17 @@ describe Projects::MergeRequestsController do
describe 'GET commit_change_content' do
it 'renders commit_change_content template' do
get :commit_change_content,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: merge_request.iid,
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: merge_request.iid
+ },
format: 'html'
expect(response).to render_template('_commit_change_content')
end
end
- shared_examples "loads labels" do |action|
- it "loads labels into the @labels variable" do
- get action,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: merge_request.iid,
- format: 'html'
- expect(assigns(:labels)).not_to be_nil
- end
- end
-
describe "GET show" do
def go(extra_params = {})
params = {
@@ -47,11 +38,9 @@ describe Projects::MergeRequestsController do
id: merge_request.iid
}
- get :show, params.merge(extra_params)
+ get :show, params: params.merge(extra_params)
end
- it_behaves_like "loads labels", :show
-
describe 'as html' do
context 'when diff files were cleaned' do
render_views
@@ -153,13 +142,18 @@ describe Projects::MergeRequestsController do
def get_merge_requests(page = nil)
get :index,
- namespace_id: project.namespace.to_param,
- project_id: project,
- state: 'opened', page: page.to_param
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ state: 'opened',
+ page: page.to_param
+ }
end
it_behaves_like "issuables list meta-data", :merge_request
+ it_behaves_like 'set sort order from user preference'
+
context 'when page param' do
let(:last_page) { project.merge_requests.page().total_pages }
let!(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
@@ -180,11 +174,13 @@ describe Projects::MergeRequestsController do
it 'does not redirect to external sites when provided a host field' do
external_host = "www.example.com"
get :index,
- namespace_id: project.namespace.to_param,
- project_id: project,
- state: 'opened',
- page: (last_page + 1).to_param,
- host: external_host
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ state: 'opened',
+ page: (last_page + 1).to_param,
+ host: external_host
+ }
expect(response).to redirect_to(namespace_project_merge_requests_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope]))
end
@@ -225,7 +221,7 @@ describe Projects::MergeRequestsController do
merge_request: mr_params
}.merge(additional_params)
- put :update, params
+ put :update, params: params
end
context 'changing the assignee' do
@@ -290,6 +286,20 @@ describe Projects::MergeRequestsController do
it_behaves_like 'update invalid issuable', MergeRequest
end
+
+ context 'two merge requests with the same source branch' do
+ it 'does not allow a closed merge request to be reopened if another one is open' do
+ merge_request.close!
+ create(:merge_request, source_project: merge_request.source_project, source_branch: merge_request.source_branch)
+
+ update_merge_request(state_event: 'reopen')
+
+ errors = assigns[:merge_request].errors
+
+ expect(errors[:validate_branches]).to include(/Another open merge request already exists for this source branch/)
+ expect(merge_request.reload).to be_closed
+ end
+ end
end
describe 'POST merge' do
@@ -308,7 +318,7 @@ describe Projects::MergeRequestsController do
before do
project.add_reporter(user)
- xhr :post, :merge, base_params
+ post :merge, params: base_params, xhr: true
end
it 'returns 404' do
@@ -320,7 +330,7 @@ describe Projects::MergeRequestsController do
before do
merge_request.update(title: "WIP: #{merge_request.title}")
- post :merge, base_params
+ post :merge, params: base_params
end
it 'returns :failed' do
@@ -330,7 +340,7 @@ describe Projects::MergeRequestsController do
context 'when the sha parameter does not match the source SHA' do
before do
- post :merge, base_params.merge(sha: 'foo')
+ post :merge, params: base_params.merge(sha: 'foo')
end
it 'returns :sha_mismatch' do
@@ -341,11 +351,7 @@ describe Projects::MergeRequestsController do
context 'when the sha parameter matches the source SHA' do
def merge_with_sha(params = {})
post_params = base_params.merge(sha: merge_request.diff_head_sha).merge(params)
- if Gitlab.rails5?
- post :merge, params: post_params, as: :json
- else
- post :merge, post_params
- end
+ post :merge, params: post_params, as: :json
end
it 'returns :success' do
@@ -384,7 +390,7 @@ describe Projects::MergeRequestsController do
end
def merge_when_pipeline_succeeds
- post :merge, base_params.merge(sha: merge_request.diff_head_sha, merge_when_pipeline_succeeds: '1')
+ post :merge, params: base_params.merge(sha: merge_request.diff_head_sha, merge_when_pipeline_succeeds: '1')
end
it 'returns :merge_when_pipeline_succeeds' do
@@ -501,7 +507,7 @@ describe Projects::MergeRequestsController do
let(:user) { create(:user) }
it "denies access to users unless they're admin or project owner" do
- delete :destroy, 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 }
expect(response).to have_gitlab_http_status(404)
end
@@ -516,7 +522,7 @@ describe Projects::MergeRequestsController do
end
it "deletes the merge request" do
- delete :destroy, 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 }
expect(response).to have_gitlab_http_status(302)
expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./)
@@ -525,7 +531,7 @@ describe Projects::MergeRequestsController do
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, 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 }
end
end
end
@@ -533,9 +539,11 @@ describe Projects::MergeRequestsController do
describe 'GET commits' do
def go(format: 'html')
get :commits,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: merge_request.iid,
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: merge_request.iid
+ },
format: format
end
@@ -554,9 +562,11 @@ describe Projects::MergeRequestsController do
sha: merge_request.diff_head_sha)
get :pipelines,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: merge_request.iid,
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: merge_request.iid
+ },
format: :json
end
@@ -570,9 +580,11 @@ describe Projects::MergeRequestsController do
describe 'GET test_reports' do
subject do
get :test_reports,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: merge_request.iid,
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: merge_request.iid
+ },
format: :json
end
@@ -654,11 +666,14 @@ describe Projects::MergeRequestsController do
merge_request.title = merge_request.wip_title
merge_request.save
- xhr :post, :remove_wip,
- namespace_id: merge_request.project.namespace.to_param,
- project_id: merge_request.project,
- id: merge_request.iid,
- format: :json
+ post :remove_wip,
+ params: {
+ format: :json,
+ namespace_id: merge_request.project.namespace.to_param,
+ project_id: merge_request.project,
+ id: merge_request.iid
+ },
+ xhr: true
end
it 'removes the wip status' do
@@ -672,11 +687,14 @@ describe Projects::MergeRequestsController do
describe 'POST cancel_merge_when_pipeline_succeeds' do
subject do
- xhr :post, :cancel_merge_when_pipeline_succeeds,
- namespace_id: merge_request.project.namespace.to_param,
- project_id: merge_request.project,
- id: merge_request.iid,
- format: :json
+ post :cancel_merge_when_pipeline_succeeds,
+ params: {
+ format: :json,
+ namespace_id: merge_request.project.namespace.to_param,
+ project_id: merge_request.project,
+ id: merge_request.iid
+ },
+ xhr: true
end
it 'calls MergeRequests::MergeWhenPipelineSucceedsService' do
@@ -711,9 +729,11 @@ describe Projects::MergeRequestsController do
target_branch: 'master')
post :assign_related_issues,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: merge_request.iid
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: merge_request.iid
+ }
end
it 'shows a flash message on success' do
@@ -812,7 +832,7 @@ describe Projects::MergeRequestsController do
format: 'json'
}
- get :ci_environments_status, params.merge(extra_params)
+ get :ci_environments_status, params: params.merge(extra_params)
end
end
end
@@ -853,9 +873,11 @@ describe Projects::MergeRequestsController do
end
def get_pipeline_status
- get :pipeline_status, namespace_id: project.namespace,
- project_id: project,
- id: merge_request.iid,
+ get :pipeline_status, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: merge_request.iid
+ },
format: :json
end
end
@@ -864,7 +886,7 @@ describe Projects::MergeRequestsController do
let(:viewer) { user }
def post_rebase
- post :rebase, namespace_id: project.namespace, project_id: project, id: merge_request
+ post :rebase, params: { namespace_id: project.namespace, project_id: project, id: merge_request }
end
def expect_rebase_worker_for(user)
@@ -920,15 +942,79 @@ describe Projects::MergeRequestsController do
end
end
+ describe 'GET discussions' do
+ context 'when authenticated' do
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ it 'returns 200' do
+ get :discussions, namespace_id: project.namespace, project_id: project, id: merge_request.iid
+
+ expect(response.status).to eq(200)
+ end
+
+ context 'highlight preloading' do
+ context 'with commit diff notes' do
+ let!(:commit_diff_note) do
+ create(:diff_note_on_commit, project: merge_request.project)
+ end
+
+ it 'preloads notes diffs highlights' do
+ expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection|
+ note_diff_file = commit_diff_note.note_diff_file
+
+ expect(collection).to receive(:load_highlight).with([note_diff_file.id]).and_call_original
+ expect(collection).to receive(:find_by_id).with(note_diff_file.id).and_call_original
+ end
+
+ get :discussions, namespace_id: project.namespace, project_id: project, id: merge_request.iid
+ end
+ end
+
+ context 'with diff notes' do
+ let!(:diff_note) do
+ create(:diff_note_on_merge_request, noteable: merge_request, project: merge_request.project)
+ end
+
+ it 'preloads notes diffs highlights' do
+ expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection|
+ note_diff_file = diff_note.note_diff_file
+
+ expect(collection).to receive(:load_highlight).with([note_diff_file.id]).and_call_original
+ expect(collection).to receive(:find_by_id).with(note_diff_file.id).and_call_original
+ end
+
+ get :discussions, namespace_id: project.namespace, project_id: project, id: merge_request.iid
+ end
+
+ it 'does not preload highlights when diff note is resolved' do
+ Notes::ResolveService.new(diff_note.project, user).execute(diff_note)
+
+ expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection|
+ note_diff_file = diff_note.note_diff_file
+
+ expect(collection).to receive(:load_highlight).with([]).and_call_original
+ expect(collection).to receive(:find_by_id).with(note_diff_file.id).and_call_original
+ end
+
+ get :discussions, namespace_id: project.namespace, project_id: project, id: merge_request.iid
+ end
+ end
+ end
+ end
+ end
+
describe 'GET edit' do
it 'responds successfully' do
- get :edit, namespace_id: project.namespace, project_id: project, id: merge_request
+ get :edit, params: { namespace_id: project.namespace, project_id: project, id: merge_request }
expect(response).to have_gitlab_http_status(:success)
end
it 'assigns the noteable to make sure autocompletes work' do
- get :edit, namespace_id: project.namespace, project_id: project, id: merge_request
+ get :edit, params: { namespace_id: project.namespace, project_id: project, id: merge_request }
expect(assigns(:noteable)).not_to be_nil
end
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index 658aa2a6738..5892024e756 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -22,7 +22,7 @@ describe Projects::MilestonesController do
def view_milestone(options = {})
params = { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid }
- get :show, params.merge(options)
+ get :show, params: params.merge(options)
end
it 'shows milestone page' do
@@ -43,9 +43,11 @@ describe Projects::MilestonesController do
describe "#index" do
context "as html" do
def render_index(project:, page:)
- get :index, namespace_id: project.namespace.id,
- project_id: project.id,
- page: page
+ get :index, params: {
+ namespace_id: project.namespace.id,
+ project_id: project.id,
+ page: page
+ }
end
it "queries only projects milestones" do
@@ -90,7 +92,7 @@ describe Projects::MilestonesController do
context 'with a single group ancestor' do
before do
project.update(namespace: group)
- get :index, namespace_id: project.namespace.id, project_id: project.id, format: :json
+ get :index, params: { namespace_id: project.namespace.id, project_id: project.id }, format: :json
end
it "queries projects milestones and groups milestones" do
@@ -107,7 +109,7 @@ describe Projects::MilestonesController do
before do
project.update(namespace: subgroup)
- get :index, namespace_id: project.namespace.id, project_id: project.id, format: :json
+ get :index, params: { namespace_id: project.namespace.id, project_id: project.id }, format: :json
end
it "queries projects milestones and all ancestors milestones" do
@@ -124,7 +126,7 @@ describe Projects::MilestonesController do
it "removes milestone" do
expect(issue.milestone_id).to eq(milestone.id)
- delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid, format: :js
+ delete :destroy, params: { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid }, format: :js
expect(response).to be_success
expect(Event.recent.first.action).to eq(Event::DESTROYED)
@@ -155,7 +157,7 @@ describe Projects::MilestonesController do
end
it 'renders 404' do
- post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid
+ post :promote, params: { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid }
expect(response).to have_gitlab_http_status(404)
end
@@ -167,7 +169,7 @@ describe Projects::MilestonesController do
end
it 'shows group milestone' do
- post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid
+ post :promote, params: { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid }
expect(flash[:notice]).to eq("#{milestone.title} promoted to <a href=\"#{group_milestone_path(project.group, milestone.iid)}\"><u>group milestone</u></a>.")
expect(response).to redirect_to(project_milestones_path(project))
@@ -176,7 +178,7 @@ describe Projects::MilestonesController do
it 'renders milestone name without parsing it as HTML' do
milestone.update!(name: 'CCC&lt;img src=x onerror=alert(document.domain)&gt;')
- post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid
+ post :promote, params: { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid }
expect(flash[:notice]).to eq("CCC promoted to <a href=\"#{group_milestone_path(project.group, milestone.iid)}\"><u>group milestone</u></a>.")
end
@@ -190,7 +192,7 @@ describe Projects::MilestonesController do
it 'renders 404' do
project.update(namespace: user.namespace)
- post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid
+ post :promote, params: { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid }
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/controllers/projects/mirrors_controller_spec.rb b/spec/controllers/projects/mirrors_controller_spec.rb
index 976f480930c..86a12a5e903 100644
--- a/spec/controllers/projects/mirrors_controller_spec.rb
+++ b/spec/controllers/projects/mirrors_controller_spec.rb
@@ -147,7 +147,7 @@ describe Projects::MirrorsController do
end
def do_get(project, url = 'ssh://example.com')
- get :ssh_host_keys, namespace_id: project.namespace, project_id: project, ssh_url: url
+ get :ssh_host_keys, params: { namespace_id: project.namespace, project_id: project, ssh_url: url }
end
end
@@ -155,6 +155,6 @@ describe Projects::MirrorsController do
attrs = extra_attrs.merge(namespace_id: project.namespace.to_param, project_id: project.to_param)
attrs[:project] = options
- put :update, attrs
+ put :update, params: attrs
end
end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index d2a26068362..81892575889 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -44,7 +44,7 @@ describe Projects::NotesController do
.with(anything, anything, hash_including(last_fetched_at: last_fetched_at))
.and_call_original
- get :index, request_params
+ get :index, params: request_params
end
context 'when user notes_filter is present' do
@@ -55,7 +55,7 @@ describe Projects::NotesController do
it 'filters system notes by comments' do
user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issue)
- get :index, request_params
+ get :index, params: request_params
expect(notes_json.count).to eq(1)
expect(notes_json.first[:id].to_i).to eq(comment.id)
@@ -64,7 +64,7 @@ describe Projects::NotesController do
it 'returns all notes' do
user.set_notes_filter(UserPreference::NOTES_FILTERS[:all_notes], issue)
- get :index, request_params
+ get :index, params: request_params
expect(notes_json.map { |note| note[:id].to_i }).to contain_exactly(comment.id, system_note.id)
end
@@ -74,7 +74,7 @@ describe Projects::NotesController do
expect(ResourceEvents::MergeIntoNotesService).not_to receive(:new)
- get :index, request_params
+ get :index, params: request_params
end
end
@@ -85,7 +85,7 @@ describe Projects::NotesController do
let(:params) { request_params.merge(target_type: 'merge_request', target_id: note.noteable_id, html: true) }
it 'responds with the expected attributes' do
- get :index, params
+ get :index, params: params
expect(note_json[:id]).to eq(note.id)
expect(note_json[:discussion_html]).not_to be_nil
@@ -101,7 +101,7 @@ describe Projects::NotesController do
let(:params) { request_params.merge(target_type: 'merge_request', target_id: note.noteable_id, html: true) }
it 'responds with the expected attributes' do
- get :index, params
+ get :index, params: params
expect(note_json[:id]).to eq(note.id)
expect(note_json[:discussion_html]).not_to be_nil
@@ -120,7 +120,7 @@ describe Projects::NotesController do
let(:params) { request_params.merge(target_type: 'merge_request', target_id: merge_request.id, html: true) }
it 'responds with the expected attributes' do
- get :index, params
+ get :index, params: params
expect(note_json[:id]).to eq(note.id)
expect(note_json[:discussion_html]).not_to be_nil
@@ -133,7 +133,7 @@ describe Projects::NotesController do
let(:params) { request_params.merge(target_type: 'commit', target_id: note.commit_id, html: true) }
it 'responds with the expected attributes' do
- get :index, params
+ get :index, params: params
expect(note_json[:id]).to eq(note.id)
expect(note_json[:discussion_html]).to be_nil
@@ -148,7 +148,7 @@ describe Projects::NotesController do
end
it 'renders 404' do
- get :index, params
+ get :index, params: params
expect(response).to have_gitlab_http_status(404)
end
@@ -162,7 +162,7 @@ describe Projects::NotesController do
let(:params) { request_params.merge(target_type: 'merge_request', target_id: note.noteable_id, html: true) }
it 'responds with the expected attributes' do
- get :index, params
+ get :index, params: params
expect(note_json[:id]).to eq(note.id)
expect(note_json[:html]).not_to be_nil
@@ -182,7 +182,7 @@ describe Projects::NotesController do
end
it 'filters notes that the user should not see' do
- get :index, request_params
+ get :index, params: request_params
expect(parsed_response[:notes].count).to eq(1)
expect(note_json[:id]).to eq(note.id.to_s)
@@ -190,19 +190,19 @@ describe Projects::NotesController do
it 'does not result in N+1 queries' do
# Instantiate the controller variables to ensure QueryRecorder has an accurate base count
- get :index, request_params
+ get :index, params: request_params
RequestStore.clear!
control_count = ActiveRecord::QueryRecorder.new do
- get :index, request_params
+ get :index, params: request_params
end.count
RequestStore.clear!
create_list(:discussion_note_on_issue, 2, :system, noteable: issue, project: issue.project, note: cross_reference)
- expect { get :index, request_params }.not_to exceed_query_limit(control_count)
+ expect { get :index, params: request_params }.not_to exceed_query_limit(control_count)
end
end
end
@@ -227,19 +227,19 @@ describe Projects::NotesController do
end
it "returns status 302 for html" do
- post :create, request_params
+ post :create, params: request_params
expect(response).to have_gitlab_http_status(302)
end
it "returns status 200 for json" do
- post :create, request_params.merge(format: :json)
+ post :create, params: request_params.merge(format: :json)
expect(response).to have_gitlab_http_status(200)
end
it 'returns discussion JSON when the return_discussion param is set' do
- post :create, request_params.merge(format: :json, return_discussion: 'true')
+ post :create, params: request_params.merge(format: :json, return_discussion: 'true')
expect(response).to have_gitlab_http_status(200)
expect(json_response).to have_key 'discussion'
@@ -248,19 +248,19 @@ describe Projects::NotesController do
context 'when merge_request_diff_head_sha present' do
before do
- service_params = {
+ service_params = ActionController::Parameters.new({
note: 'some note',
noteable_id: merge_request.id.to_s,
noteable_type: 'MergeRequest',
merge_request_diff_head_sha: 'sha',
in_reply_to_discussion_id: nil
- }
+ }).permit!
expect(Notes::CreateService).to receive(:new).with(project, user, service_params).and_return(double(execute: true))
end
it "returns status 302 for html" do
- post :create, request_params
+ post :create, params: request_params
expect(response).to have_gitlab_http_status(302)
end
@@ -282,7 +282,7 @@ describe Projects::NotesController do
end
def post_create(extra_params = {})
- post :create, {
+ post :create, params: {
note: { note: 'some other note', noteable_id: merge_request.id },
namespace_id: project.namespace,
project_id: project,
@@ -342,7 +342,7 @@ describe Projects::NotesController do
namespace_id: project.namespace
}
- expect { post :create, request_params }.to change { issue.notes.count }.by(1)
+ expect { post :create, params: request_params }.to change { issue.notes.count }.by(1)
.and change { locked_issue.notes.count }.by(0)
expect(response).to have_gitlab_http_status(302)
end
@@ -357,7 +357,7 @@ describe Projects::NotesController do
context 'when a noteable is not found' do
it 'returns 404 status' do
request_params[:target_id] = 9999
- post :create, request_params.merge(format: :json)
+ post :create, params: request_params.merge(format: :json)
expect(response).to have_gitlab_http_status(404)
end
@@ -365,19 +365,19 @@ describe Projects::NotesController do
context 'when a user is a team member' do
it 'returns 302 status for html' do
- post :create, request_params
+ post :create, params: request_params
expect(response).to have_gitlab_http_status(302)
end
it 'returns 200 status for json' do
- post :create, request_params.merge(format: :json)
+ post :create, params: request_params.merge(format: :json)
expect(response).to have_gitlab_http_status(200)
end
it 'creates a new note' do
- expect { post :create, request_params }.to change { Note.count }.by(1)
+ expect { post :create, params: request_params }.to change { Note.count }.by(1)
end
end
@@ -387,13 +387,13 @@ describe Projects::NotesController do
end
it 'returns 404 status' do
- post :create, request_params
+ post :create, params: request_params
expect(response).to have_gitlab_http_status(404)
end
it 'does not create a new note' do
- expect { post :create, request_params }.not_to change { Note.count }
+ expect { post :create, params: request_params }.not_to change { Note.count }
end
end
end
@@ -419,7 +419,7 @@ describe Projects::NotesController do
end
it "updates the note" do
- expect { put :update, request_params }.to change { note.reload.note }
+ expect { put :update, params: request_params }.to change { note.reload.note }
end
end
context "doesnt update the note" do
@@ -441,7 +441,7 @@ describe Projects::NotesController do
note: "New comment"
}
}
- expect { put :update, request_params }.not_to change { note.reload.note }
+ expect { put :update, params: request_params }.not_to change { note.reload.note }
expect(response).to have_gitlab_http_status(404)
end
end
@@ -464,13 +464,13 @@ describe Projects::NotesController do
end
it "returns status 200 for html" do
- delete :destroy, request_params
+ delete :destroy, params: request_params
expect(response).to have_gitlab_http_status(200)
end
it "deletes the note" do
- expect { delete :destroy, request_params }.to change { Note.count }.from(1).to(0)
+ expect { delete :destroy, params: request_params }.to change { Note.count }.from(1).to(0)
end
end
@@ -481,7 +481,7 @@ describe Projects::NotesController do
end
it "returns status 404" do
- delete :destroy, request_params
+ delete :destroy, params: request_params
expect(response).to have_gitlab_http_status(404)
end
@@ -496,17 +496,17 @@ describe Projects::NotesController do
it "toggles the award emoji" do
expect do
- post(:toggle_award_emoji, request_params.merge(name: "thumbsup"))
+ post(:toggle_award_emoji, params: request_params.merge(name: "thumbsup"))
end.to change { note.award_emoji.count }.by(1)
expect(response).to have_gitlab_http_status(200)
end
it "removes the already awarded emoji" do
- post(:toggle_award_emoji, request_params.merge(name: "thumbsup"))
+ post(:toggle_award_emoji, params: request_params.merge(name: "thumbsup"))
expect do
- post(:toggle_award_emoji, request_params.merge(name: "thumbsup"))
+ post(:toggle_award_emoji, params: request_params.merge(name: "thumbsup"))
end.to change { AwardEmoji.count }.by(-1)
expect(response).to have_gitlab_http_status(200)
@@ -525,7 +525,7 @@ describe Projects::NotesController do
context "when the user is not authorized to resolve the note" do
it "returns status 404" do
- post :resolve, request_params
+ post :resolve, params: request_params
expect(response).to have_gitlab_http_status(404)
end
@@ -542,7 +542,7 @@ describe Projects::NotesController do
end
it "returns status 404" do
- post :resolve, request_params
+ post :resolve, params: request_params
expect(response).to have_gitlab_http_status(404)
end
@@ -550,7 +550,7 @@ describe Projects::NotesController do
context "when the note is resolvable" do
it "resolves the note" do
- post :resolve, request_params
+ post :resolve, params: request_params
expect(note.reload.resolved?).to be true
expect(note.reload.resolved_by).to eq(user)
@@ -559,17 +559,17 @@ describe Projects::NotesController do
it "sends notifications if all discussions are resolved" do
expect_any_instance_of(MergeRequests::ResolvedDiscussionNotificationService).to receive(:execute).with(merge_request)
- post :resolve, request_params
+ post :resolve, params: request_params
end
it "returns the name of the resolving user" do
- post :resolve, request_params.merge(html: true)
+ post :resolve, params: request_params.merge(html: true)
expect(JSON.parse(response.body)["resolved_by"]).to eq(user.name)
end
it "returns status 200" do
- post :resolve, request_params
+ post :resolve, params: request_params
expect(response).to have_gitlab_http_status(200)
end
@@ -586,7 +586,7 @@ describe Projects::NotesController do
context "when the user is not authorized to resolve the note" do
it "returns status 404" do
- delete :unresolve, request_params
+ delete :unresolve, params: request_params
expect(response).to have_gitlab_http_status(404)
end
@@ -603,7 +603,7 @@ describe Projects::NotesController do
end
it "returns status 404" do
- delete :unresolve, request_params
+ delete :unresolve, params: request_params
expect(response).to have_gitlab_http_status(404)
end
@@ -611,13 +611,13 @@ describe Projects::NotesController do
context "when the note is resolvable" do
it "unresolves the note" do
- delete :unresolve, request_params
+ delete :unresolve, params: request_params
expect(note.reload.resolved?).to be false
end
it "returns status 200" do
- delete :unresolve, request_params
+ delete :unresolve, params: request_params
expect(response).to have_gitlab_http_status(200)
end
diff --git a/spec/controllers/projects/pages_controller_spec.rb b/spec/controllers/projects/pages_controller_spec.rb
index 927b6e0c473..382c1b5d124 100644
--- a/spec/controllers/projects/pages_controller_spec.rb
+++ b/spec/controllers/projects/pages_controller_spec.rb
@@ -19,7 +19,7 @@ describe Projects::PagesController do
describe 'GET show' do
it 'returns 200 status' do
- get :show, request_params
+ get :show, params: request_params
expect(response).to have_gitlab_http_status(200)
end
@@ -29,7 +29,7 @@ describe Projects::PagesController do
let(:project) { create(:project, namespace: group) }
it 'returns a 404 status code' do
- get :show, request_params
+ get :show, params: request_params
expect(response).to have_gitlab_http_status(404)
end
@@ -38,7 +38,7 @@ describe Projects::PagesController do
describe 'DELETE destroy' do
it 'returns 302 status' do
- delete :destroy, request_params
+ delete :destroy, params: request_params
expect(response).to have_gitlab_http_status(302)
end
@@ -51,7 +51,7 @@ describe Projects::PagesController do
describe 'GET show' do
it 'returns 404 status' do
- get :show, request_params
+ get :show, params: request_params
expect(response).to have_gitlab_http_status(404)
end
@@ -59,7 +59,7 @@ describe Projects::PagesController do
describe 'DELETE destroy' do
it 'returns 404 status' do
- delete :destroy, request_params
+ delete :destroy, params: request_params
expect(response).to have_gitlab_http_status(404)
end
@@ -82,13 +82,13 @@ describe Projects::PagesController do
end
it 'returns 302 status' do
- patch :update, request_params
+ patch :update, params: request_params
expect(response).to have_gitlab_http_status(:found)
end
it 'redirects back to the pages settings' do
- patch :update, request_params
+ patch :update, params: request_params
expect(response).to redirect_to(project_pages_path(project))
end
@@ -99,7 +99,7 @@ describe Projects::PagesController do
.with(project, user, ActionController::Parameters.new(request_params[:project]).permit!)
.and_return(update_service)
- patch :update, request_params
+ patch :update, params: request_params
end
end
end
diff --git a/spec/controllers/projects/pages_domains_controller_spec.rb b/spec/controllers/projects/pages_domains_controller_spec.rb
index 75871eab1ab..8b7f7587701 100644
--- a/spec/controllers/projects/pages_domains_controller_spec.rb
+++ b/spec/controllers/projects/pages_domains_controller_spec.rb
@@ -24,7 +24,7 @@ describe Projects::PagesDomainsController do
describe 'GET show' do
it "displays the 'show' page" do
- get(:show, request_params.merge(id: pages_domain.domain))
+ get(:show, params: request_params.merge(id: pages_domain.domain))
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template('show')
@@ -33,7 +33,7 @@ describe Projects::PagesDomainsController do
describe 'GET new' do
it "displays the 'new' page" do
- get(:new, request_params)
+ get(:new, params: request_params)
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template('new')
@@ -43,7 +43,7 @@ describe Projects::PagesDomainsController do
describe 'POST create' do
it "creates a new pages domain" do
expect do
- post(:create, request_params.merge(pages_domain: pages_domain_params))
+ post(:create, params: request_params.merge(pages_domain: pages_domain_params))
end.to change { PagesDomain.count }.by(1)
created_domain = PagesDomain.reorder(:id).last
@@ -55,7 +55,7 @@ describe Projects::PagesDomainsController do
describe 'GET edit' do
it "displays the 'edit' page" do
- get(:edit, request_params.merge(id: pages_domain.domain))
+ get(:edit, params: request_params.merge(id: pages_domain.domain))
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template('edit')
@@ -78,14 +78,14 @@ describe Projects::PagesDomainsController do
it 'updates the domain' do
expect(pages_domain)
.to receive(:update)
- .with(pages_domain_params)
+ .with(ActionController::Parameters.new(pages_domain_params).permit!)
.and_return(true)
- patch(:update, params)
+ patch(:update, params: params)
end
it 'redirects to the project page' do
- patch(:update, params)
+ patch(:update, params: params)
expect(flash[:notice]).to eq 'Domain was updated'
expect(response).to redirect_to(project_pages_path(project))
@@ -95,7 +95,7 @@ describe Projects::PagesDomainsController do
it 'renders the edit action' do
allow(pages_domain).to receive(:update).and_return(false)
- patch(:update, params)
+ patch(:update, params: params)
expect(response).to render_template('edit')
end
@@ -108,7 +108,7 @@ describe Projects::PagesDomainsController do
.with(hash_not_including(:domain))
.and_return(true)
- patch(:update, params.deep_merge(pages_domain: { domain: 'abc' }))
+ patch(:update, params: params.deep_merge(pages_domain: { domain: 'abc' }))
end
end
end
@@ -127,7 +127,7 @@ describe Projects::PagesDomainsController do
it 'handles verification success' do
expect(stub_service).to receive(:execute).and_return(status: :success)
- post :verify, params
+ post :verify, params: params
expect(response).to redirect_to project_pages_domain_path(project, pages_domain)
expect(flash[:notice]).to eq('Successfully verified domain ownership')
@@ -136,14 +136,14 @@ describe Projects::PagesDomainsController do
it 'handles verification failure' do
expect(stub_service).to receive(:execute).and_return(status: :failed)
- post :verify, params
+ post :verify, params: params
expect(response).to redirect_to project_pages_domain_path(project, pages_domain)
expect(flash[:alert]).to eq('Failed to verify domain ownership')
end
it 'returns a 404 response for an unknown domain' do
- post :verify, request_params.merge(id: 'unknown-domain')
+ post :verify, params: request_params.merge(id: 'unknown-domain')
expect(response).to have_gitlab_http_status(404)
end
@@ -152,7 +152,7 @@ describe Projects::PagesDomainsController do
describe 'DELETE destroy' do
it "deletes the pages domain" do
expect do
- delete(:destroy, request_params.merge(id: pages_domain.domain))
+ delete(:destroy, params: request_params.merge(id: pages_domain.domain))
end.to change { PagesDomain.count }.by(-1)
expect(response).to redirect_to(project_pages_path(project))
@@ -166,7 +166,7 @@ describe Projects::PagesDomainsController do
describe 'GET show' do
it 'returns 404 status' do
- get(:show, request_params.merge(id: pages_domain.domain))
+ get(:show, params: request_params.merge(id: pages_domain.domain))
expect(response).to have_gitlab_http_status(404)
end
@@ -174,7 +174,7 @@ describe Projects::PagesDomainsController do
describe 'GET new' do
it 'returns 404 status' do
- get :new, request_params
+ get :new, params: request_params
expect(response).to have_gitlab_http_status(404)
end
@@ -182,7 +182,7 @@ describe Projects::PagesDomainsController do
describe 'POST create' do
it "returns 404 status" do
- post(:create, request_params.merge(pages_domain: pages_domain_params))
+ post(:create, params: request_params.merge(pages_domain: pages_domain_params))
expect(response).to have_gitlab_http_status(404)
end
@@ -190,7 +190,7 @@ describe Projects::PagesDomainsController do
describe 'DELETE destroy' do
it "deletes the pages domain" do
- delete(:destroy, request_params.merge(id: pages_domain.domain))
+ delete(:destroy, params: request_params.merge(id: pages_domain.domain))
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
index 7179423dde2..80506249ea9 100644
--- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb
+++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
@@ -44,7 +44,7 @@ describe Projects::PipelineSchedulesController do
end
def visit_pipelines_schedules
- get :index, namespace_id: project.namespace.to_param, project_id: project, scope: scope
+ get :index, params: { namespace_id: project.namespace.to_param, project_id: project, scope: scope }
end
end
@@ -57,7 +57,7 @@ describe Projects::PipelineSchedulesController do
end
it 'initializes a pipeline schedule model' do
- get :new, namespace_id: project.namespace.to_param, project_id: project
+ get :new, params: { namespace_id: project.namespace.to_param, project_id: project }
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:schedule)).to be_a_new(Ci::PipelineSchedule)
@@ -131,7 +131,7 @@ describe Projects::PipelineSchedulesController do
end
def go
- post :create, namespace_id: project.namespace.to_param, project_id: project, schedule: schedule
+ post :create, params: { namespace_id: project.namespace.to_param, project_id: project, schedule: schedule }
end
end
@@ -310,19 +310,11 @@ describe Projects::PipelineSchedulesController do
end
def go
- if Gitlab.rails5?
- put :update, params: { namespace_id: project.namespace.to_param,
- project_id: project,
- id: pipeline_schedule,
- schedule: schedule },
- as: :html
-
- else
- put :update, namespace_id: project.namespace.to_param,
- project_id: project,
- id: pipeline_schedule,
- schedule: schedule
- end
+ put :update, params: { namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: pipeline_schedule,
+ schedule: schedule },
+ as: :html
end
end
@@ -336,7 +328,7 @@ describe Projects::PipelineSchedulesController do
end
it 'loads the pipeline schedule' do
- get :edit, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+ get :edit, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:schedule)).to eq(pipeline_schedule)
@@ -356,7 +348,7 @@ describe Projects::PipelineSchedulesController do
end
def go
- get :edit, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+ get :edit, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
end
end
@@ -374,7 +366,7 @@ describe Projects::PipelineSchedulesController do
end
def go
- post :take_ownership, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+ post :take_ownership, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
end
end
@@ -396,7 +388,7 @@ describe Projects::PipelineSchedulesController do
it 'does not allow pipeline to be executed' do
expect(RunPipelineScheduleWorker).not_to receive(:perform_async)
- post :play, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+ post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
expect(response).to have_gitlab_http_status(404)
end
@@ -406,7 +398,7 @@ describe Projects::PipelineSchedulesController do
it 'executes a new pipeline' do
expect(RunPipelineScheduleWorker).to receive(:perform_async).with(pipeline_schedule.id, user.id).and_return('job-123')
- post :play, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+ post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
expect(flash[:notice]).to start_with 'Successfully scheduled a pipeline to run'
expect(response).to have_gitlab_http_status(302)
@@ -414,7 +406,7 @@ describe Projects::PipelineSchedulesController do
it 'prevents users from scheduling the same pipeline repeatedly' do
2.times do
- post :play, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+ post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
end
expect(flash.to_a.size).to eq(2)
@@ -430,7 +422,7 @@ describe Projects::PipelineSchedulesController do
expect(RunPipelineScheduleWorker).not_to receive(:perform_async)
- post :play, namespace_id: project.namespace.to_param, project_id: project, id: protected_schedule.id
+ post :play, params: { namespace_id: project.namespace.to_param, project_id: project, id: protected_schedule.id }
expect(response).to have_gitlab_http_status(404)
end
@@ -445,7 +437,7 @@ describe Projects::PipelineSchedulesController do
project.add_developer(user)
sign_in(user)
- delete :destroy, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+ delete :destroy, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
end
it 'does not delete the pipeline schedule' do
@@ -461,7 +453,7 @@ describe Projects::PipelineSchedulesController do
it 'destroys the pipeline schedule' do
expect do
- delete :destroy, namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id
+ delete :destroy, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
end.to change { project.pipeline_schedules.count }.by(-1)
expect(response).to have_gitlab_http_status(302)
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index 5c7415a318d..0bb3ef76a3b 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -119,8 +119,10 @@ describe Projects::PipelinesController do
end
def get_pipelines_index_json
- get :index, namespace_id: project.namespace,
- project_id: project,
+ get :index, params: {
+ namespace_id: project.namespace,
+ project_id: project
+ },
format: :json
end
@@ -185,7 +187,7 @@ describe Projects::PipelinesController do
end
def get_pipeline_json
- get :show, namespace_id: project.namespace, project_id: project, id: pipeline, format: :json
+ get :show, params: { namespace_id: project.namespace, project_id: project, id: pipeline }, format: :json
end
def create_build(stage, stage_idx, name)
@@ -240,12 +242,14 @@ describe Projects::PipelinesController do
end
def get_stage(name, params = {})
- get :stage, **params.merge(
- namespace_id: project.namespace,
- project_id: project,
- id: pipeline.id,
- stage: name,
- format: :json)
+ get :stage, params: {
+**params.merge(
+ namespace_id: project.namespace,
+ project_id: project,
+ id: pipeline.id,
+ stage: name,
+ format: :json)
+}
end
end
@@ -277,10 +281,12 @@ describe Projects::PipelinesController do
end
def get_stage_ajax(name)
- get :stage_ajax, namespace_id: project.namespace,
- project_id: project,
- id: pipeline.id,
- stage: name,
+ get :stage_ajax, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: pipeline.id,
+ stage: name
+ },
format: :json
end
end
@@ -290,9 +296,11 @@ describe Projects::PipelinesController do
let(:status) { pipeline.detailed_status(double('user')) }
before do
- get :status, namespace_id: project.namespace,
- project_id: project,
- id: pipeline.id,
+ get :status, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: pipeline.id
+ },
format: :json
end
@@ -310,9 +318,11 @@ describe Projects::PipelinesController do
let!(:build) { create(:ci_build, :failed, pipeline: pipeline) }
before do
- post :retry, namespace_id: project.namespace,
- project_id: project,
- id: pipeline.id,
+ post :retry, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: pipeline.id
+ },
format: :json
end
@@ -337,9 +347,11 @@ describe Projects::PipelinesController do
let!(:build) { create(:ci_build, :running, pipeline: pipeline) }
before do
- post :cancel, namespace_id: project.namespace,
- project_id: project,
- id: pipeline.id,
+ post :cancel, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: pipeline.id
+ },
format: :json
end
diff --git a/spec/controllers/projects/pipelines_settings_controller_spec.rb b/spec/controllers/projects/pipelines_settings_controller_spec.rb
index b1ba9f74e38..269f105bed2 100644
--- a/spec/controllers/projects/pipelines_settings_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_settings_controller_spec.rb
@@ -13,7 +13,7 @@ describe Projects::PipelinesSettingsController do
describe 'GET show' do
it 'redirects with 302 status code' do
- get :show, namespace_id: project.namespace, project_id: project
+ get :show, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(302)
end
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index 519af10d78c..3cc3fe69fba 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -6,7 +6,7 @@ describe Projects::ProjectMembersController do
describe 'GET index' do
it 'should have the project_members address with a 200 status code' do
- get :index, namespace_id: project.namespace, project_id: project
+ get :index, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(200)
end
@@ -25,10 +25,12 @@ describe Projects::ProjectMembersController do
end
it 'returns 404' do
- post :create, namespace_id: project.namespace,
- project_id: project,
- user_ids: project_user.id,
- access_level: Gitlab::Access::GUEST
+ post :create, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ user_ids: project_user.id,
+ access_level: Gitlab::Access::GUEST
+ }
expect(response).to have_gitlab_http_status(404)
expect(project.users).not_to include project_user
@@ -43,10 +45,12 @@ describe Projects::ProjectMembersController do
it 'adds user to members' do
expect_any_instance_of(Members::CreateService).to receive(:execute).and_return(status: :success)
- post :create, namespace_id: project.namespace,
- project_id: project,
- user_ids: project_user.id,
- access_level: Gitlab::Access::GUEST
+ post :create, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ user_ids: project_user.id,
+ access_level: Gitlab::Access::GUEST
+ }
expect(response).to set_flash.to 'Users were successfully added.'
expect(response).to redirect_to(project_project_members_path(project))
@@ -55,10 +59,12 @@ describe Projects::ProjectMembersController do
it 'adds no user to members' do
expect_any_instance_of(Members::CreateService).to receive(:execute).and_return(status: :failure, message: 'Message')
- post :create, namespace_id: project.namespace,
- project_id: project,
- user_ids: '',
- access_level: Gitlab::Access::GUEST
+ post :create, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ user_ids: '',
+ access_level: Gitlab::Access::GUEST
+ }
expect(response).to set_flash.to 'Message'
expect(response).to redirect_to(project_project_members_path(project))
@@ -76,10 +82,12 @@ describe Projects::ProjectMembersController do
Gitlab::Access.options.each do |label, value|
it "can change the access level to #{label}" do
- xhr :put, :update, project_member: { access_level: value },
- namespace_id: project.namespace,
- project_id: project,
- id: requester
+ put :update, params: {
+ project_member: { access_level: value },
+ namespace_id: project.namespace,
+ project_id: project,
+ id: requester
+ }, xhr: true
expect(requester.reload.human_access).to eq(label)
end
@@ -95,9 +103,11 @@ describe Projects::ProjectMembersController do
context 'when member is not found' do
it 'returns 404' do
- delete :destroy, namespace_id: project.namespace,
- project_id: project,
- id: 42
+ delete :destroy, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: 42
+ }
expect(response).to have_gitlab_http_status(404)
end
@@ -110,9 +120,11 @@ describe Projects::ProjectMembersController do
end
it 'returns 404' do
- delete :destroy, namespace_id: project.namespace,
- project_id: project,
- id: member
+ delete :destroy, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: member
+ }
expect(response).to have_gitlab_http_status(404)
expect(project.members).to include member
@@ -125,9 +137,11 @@ describe Projects::ProjectMembersController do
end
it '[HTML] removes user from members' do
- delete :destroy, namespace_id: project.namespace,
- project_id: project,
- id: member
+ delete :destroy, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: member
+ }
expect(response).to redirect_to(
project_project_members_path(project)
@@ -136,9 +150,11 @@ describe Projects::ProjectMembersController do
end
it '[JS] removes user from members' do
- xhr :delete, :destroy, namespace_id: project.namespace,
- project_id: project,
- id: member
+ delete :destroy, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: member
+ }, xhr: true
expect(response).to be_success
expect(project.members).not_to include member
@@ -154,8 +170,10 @@ describe Projects::ProjectMembersController do
context 'when member is not found' do
it 'returns 404' do
- delete :leave, namespace_id: project.namespace,
- project_id: project
+ delete :leave, params: {
+ namespace_id: project.namespace,
+ project_id: project
+ }
expect(response).to have_gitlab_http_status(404)
end
@@ -168,8 +186,10 @@ describe Projects::ProjectMembersController do
end
it 'removes user from members' do
- delete :leave, namespace_id: project.namespace,
- project_id: project
+ delete :leave, params: {
+ namespace_id: project.namespace,
+ project_id: project
+ }
expect(response).to set_flash.to "You left the \"#{project.human_name}\" project."
expect(response).to redirect_to(dashboard_projects_path)
@@ -185,8 +205,10 @@ describe Projects::ProjectMembersController do
end
it 'cannot remove themselves from the project' do
- delete :leave, namespace_id: project.namespace,
- project_id: project
+ delete :leave, params: {
+ namespace_id: project.namespace,
+ project_id: project
+ }
expect(response).to have_gitlab_http_status(403)
end
@@ -198,8 +220,10 @@ describe Projects::ProjectMembersController do
end
it 'removes user from members' do
- delete :leave, namespace_id: project.namespace,
- project_id: project
+ delete :leave, params: {
+ namespace_id: project.namespace,
+ project_id: project
+ }
expect(response).to set_flash.to 'Your access request to the project has been withdrawn.'
expect(response).to redirect_to(project_path(project))
@@ -216,8 +240,10 @@ describe Projects::ProjectMembersController do
end
it 'creates a new ProjectMember that is not a team member' do
- post :request_access, namespace_id: project.namespace,
- project_id: project
+ post :request_access, params: {
+ namespace_id: project.namespace,
+ project_id: project
+ }
expect(response).to set_flash.to 'Your request for access has been queued for review.'
expect(response).to redirect_to(
@@ -237,9 +263,11 @@ describe Projects::ProjectMembersController do
context 'when member is not found' do
it 'returns 404' do
- post :approve_access_request, namespace_id: project.namespace,
- project_id: project,
- id: 42
+ post :approve_access_request, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: 42
+ }
expect(response).to have_gitlab_http_status(404)
end
@@ -252,9 +280,11 @@ describe Projects::ProjectMembersController do
end
it 'returns 404' do
- post :approve_access_request, namespace_id: project.namespace,
- project_id: project,
- id: member
+ post :approve_access_request, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: member
+ }
expect(response).to have_gitlab_http_status(404)
expect(project.members).not_to include member
@@ -267,9 +297,11 @@ describe Projects::ProjectMembersController do
end
it 'adds user to members' do
- post :approve_access_request, namespace_id: project.namespace,
- project_id: project,
- id: member
+ post :approve_access_request, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: member
+ }
expect(response).to redirect_to(
project_project_members_path(project)
@@ -292,9 +324,11 @@ describe Projects::ProjectMembersController do
shared_context 'import applied' do
before do
- post(:apply_import, namespace_id: project.namespace,
- project_id: project,
- source_project_id: another_project.id)
+ post(:apply_import, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ source_project_id: another_project.id
+ })
end
end
@@ -338,10 +372,12 @@ describe Projects::ProjectMembersController do
it 'does not create a member' do
expect do
- post :create, user_ids: stranger.id,
- namespace_id: project.namespace,
- access_level: Member::OWNER,
- project_id: project
+ post :create, params: {
+ user_ids: stranger.id,
+ namespace_id: project.namespace,
+ access_level: Member::OWNER,
+ project_id: project
+ }
end.to change { project.members.count }.by(0)
end
end
@@ -354,10 +390,12 @@ describe Projects::ProjectMembersController do
it 'creates a member' do
expect do
- post :create, user_ids: stranger.id,
- namespace_id: project.namespace,
- access_level: Member::MAINTAINER,
- project_id: project
+ post :create, params: {
+ user_ids: stranger.id,
+ namespace_id: project.namespace,
+ access_level: Member::MAINTAINER,
+ project_id: project
+ }
end.to change { project.members.count }.by(1)
end
end
diff --git a/spec/controllers/projects/prometheus/metrics_controller_spec.rb b/spec/controllers/projects/prometheus/metrics_controller_spec.rb
index 5c56a712245..635763ce1d3 100644
--- a/spec/controllers/projects/prometheus/metrics_controller_spec.rb
+++ b/spec/controllers/projects/prometheus/metrics_controller_spec.rb
@@ -24,7 +24,7 @@ describe Projects::Prometheus::MetricsController do
end
it 'returns no content response' do
- get :active_common, project_params(format: :json)
+ get :active_common, params: project_params(format: :json)
expect(response).to have_gitlab_http_status(204)
end
@@ -38,7 +38,7 @@ describe Projects::Prometheus::MetricsController do
end
it 'returns no content response' do
- get :active_common, project_params(format: :json)
+ get :active_common, params: project_params(format: :json)
expect(response).to have_gitlab_http_status(200)
expect(json_response).to eq(sample_response.deep_stringify_keys)
@@ -47,7 +47,7 @@ describe Projects::Prometheus::MetricsController do
context 'when requesting non json response' do
it 'returns not found response' do
- get :active_common, project_params
+ get :active_common, params: project_params
expect(response).to have_gitlab_http_status(404)
end
@@ -62,7 +62,7 @@ describe Projects::Prometheus::MetricsController do
allow(controller).to receive(:prometheus_adapter).and_return(prometheus_adapter)
allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return({})
- get :active_common, project_params(format: :json)
+ get :active_common, params: project_params(format: :json)
expect(response).to have_gitlab_http_status(404)
end
@@ -70,7 +70,7 @@ describe Projects::Prometheus::MetricsController do
context 'when prometheus_adapter is disabled' do
it 'renders 404' do
- get :active_common, project_params(format: :json)
+ get :active_common, params: project_params(format: :json)
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/controllers/projects/protected_branches_controller_spec.rb b/spec/controllers/projects/protected_branches_controller_spec.rb
index ac812707e74..483d3bbc37c 100644
--- a/spec/controllers/projects/protected_branches_controller_spec.rb
+++ b/spec/controllers/projects/protected_branches_controller_spec.rb
@@ -15,7 +15,7 @@ describe Projects::ProtectedBranchesController do
let(:project) { create(:project_empty_repo, :public) }
it "redirects empty repo to projects page" do
- get(:index, namespace_id: project.namespace.to_param, project_id: project)
+ get(:index, params: { namespace_id: project.namespace.to_param, project_id: project })
end
end
@@ -33,7 +33,7 @@ describe Projects::ProtectedBranchesController do
it 'creates the protected branch rule' do
expect do
- post(:create, project_params.merge(protected_branch: create_params))
+ post(:create, params: project_params.merge(protected_branch: create_params))
end.to change(ProtectedBranch, :count).by(1)
end
@@ -44,7 +44,7 @@ describe Projects::ProtectedBranchesController do
end
it "prevents creation of the protected branch rule" do
- post(:create, project_params.merge(protected_branch: create_params))
+ post(:create, params: project_params.merge(protected_branch: create_params))
expect(ProtectedBranch.count).to eq 0
end
@@ -59,7 +59,7 @@ describe Projects::ProtectedBranchesController do
end
it 'updates the protected branch rule' do
- put(:update, base_params.merge(protected_branch: update_params))
+ put(:update, params: base_params.merge(protected_branch: update_params))
expect(protected_branch.reload.name).to eq('new_name')
expect(json_response["name"]).to eq('new_name')
@@ -74,7 +74,7 @@ describe Projects::ProtectedBranchesController do
it "prevents update of the protected branch rule" do
old_name = protected_branch.name
- put(:update, base_params.merge(protected_branch: update_params))
+ put(:update, params: base_params.merge(protected_branch: update_params))
expect(protected_branch.reload.name).to eq(old_name)
end
@@ -87,7 +87,7 @@ describe Projects::ProtectedBranchesController do
end
it "deletes the protected branch rule" do
- delete(:destroy, base_params)
+ delete(:destroy, params: base_params)
expect { ProtectedBranch.find(protected_branch.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
@@ -99,7 +99,7 @@ describe Projects::ProtectedBranchesController do
end
it "prevents deletion of the protected branch rule" do
- delete(:destroy, base_params)
+ delete(:destroy, params: base_params)
expect(response.status).to eq(403)
end
diff --git a/spec/controllers/projects/protected_tags_controller_spec.rb b/spec/controllers/projects/protected_tags_controller_spec.rb
index 20440c5a5d5..1553e081dee 100644
--- a/spec/controllers/projects/protected_tags_controller_spec.rb
+++ b/spec/controllers/projects/protected_tags_controller_spec.rb
@@ -5,7 +5,7 @@ describe Projects::ProtectedTagsController do
let(:project) { create(:project_empty_repo, :public) }
it "redirects empty repo to projects page" do
- get(:index, namespace_id: project.namespace.to_param, project_id: project)
+ get(:index, params: { namespace_id: project.namespace.to_param, project_id: project })
end
end
@@ -20,7 +20,7 @@ describe Projects::ProtectedTagsController do
end
it "deletes the protected tag" do
- delete(:destroy, namespace_id: project.namespace.to_param, project_id: project, id: protected_tag.id)
+ delete(:destroy, params: { namespace_id: project.namespace.to_param, project_id: project, id: protected_tag.id })
expect { ProtectedTag.find(protected_tag.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index 6b658bf5295..cffdf30da6b 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -6,9 +6,11 @@ describe Projects::RawController do
describe 'GET #show' do
subject do
get(:show,
- namespace_id: project.namespace,
- project_id: project,
- id: filepath)
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: filepath
+ })
end
context 'regular filename' do
@@ -19,8 +21,8 @@ describe Projects::RawController do
expect(response).to have_gitlab_http_status(200)
expect(response.header['Content-Type']).to eq('text/plain; charset=utf-8')
- expect(response.header['Content-Disposition'])
- .to eq('inline')
+ expect(response.header['Content-Disposition']).to eq('inline')
+ expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
end
@@ -28,11 +30,12 @@ describe Projects::RawController do
context 'image header' do
let(:filepath) { 'master/files/images/6049019_460s.jpg' }
- it 'sets image content type header' do
+ it 'leaves image content disposition' do
subject
expect(response).to have_gitlab_http_status(200)
- expect(response.header['Content-Type']).to eq('image/jpeg')
+ expect(response.header['Content-Disposition']).to eq('inline')
+ expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
end
end
diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb
index ceaffd92623..62f2af947e4 100644
--- a/spec/controllers/projects/refs_controller_spec.rb
+++ b/spec/controllers/projects/refs_controller_spec.rb
@@ -12,21 +12,23 @@ describe Projects::RefsController do
describe 'GET #logs_tree' do
def default_get(format = :html)
get :logs_tree,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: 'master',
- path: 'foo/bar/baz.html',
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: 'master',
+ path: 'foo/bar/baz.html'
+ },
format: format
end
def xhr_get(format = :html)
- xhr :get,
- :logs_tree,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: 'master',
- path: 'foo/bar/baz.html',
- format: format
+ get :logs_tree, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: 'master',
+ path: 'foo/bar/baz.html',
+ format: format
+ }, xhr: true
end
it 'never throws MissingTemplate' do
diff --git a/spec/controllers/projects/registry/repositories_controller_spec.rb b/spec/controllers/projects/registry/repositories_controller_spec.rb
index d11e42b411b..eca187af33d 100644
--- a/spec/controllers/projects/registry/repositories_controller_spec.rb
+++ b/spec/controllers/projects/registry/repositories_controller_spec.rb
@@ -111,15 +111,19 @@ describe Projects::Registry::RepositoriesController do
end
def go_to_index(format: :html)
- get :index, namespace_id: project.namespace,
- project_id: project,
+ get :index, params: {
+ namespace_id: project.namespace,
+ project_id: project
+ },
format: format
end
def delete_repository(repository)
- delete :destroy, namespace_id: project.namespace,
- project_id: project,
- id: repository,
+ delete :destroy, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: repository
+ },
format: :json
end
end
diff --git a/spec/controllers/projects/registry/tags_controller_spec.rb b/spec/controllers/projects/registry/tags_controller_spec.rb
index 7fee8fd44ff..ed0197afcfc 100644
--- a/spec/controllers/projects/registry/tags_controller_spec.rb
+++ b/spec/controllers/projects/registry/tags_controller_spec.rb
@@ -65,9 +65,11 @@ describe Projects::Registry::TagsController do
private
def get_tags
- get :index, namespace_id: project.namespace,
- project_id: project,
- repository_id: repository,
+ get :index, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ repository_id: repository
+ },
format: :json
end
end
@@ -100,10 +102,12 @@ describe Projects::Registry::TagsController do
private
def destroy_tag(name)
- post :destroy, namespace_id: project.namespace,
- project_id: project,
- repository_id: repository,
- id: name,
+ post :destroy, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ repository_id: repository,
+ id: name
+ },
format: :json
end
end
diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb
index 20a6beb3df8..f170a2ab613 100644
--- a/spec/controllers/projects/releases_controller_spec.rb
+++ b/spec/controllers/projects/releases_controller_spec.rb
@@ -1,55 +1,65 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::ReleasesController do
- let!(:project) { create(:project, :repository) }
+ let!(:project) { create(:project, :repository, :public) }
let!(:user) { create(:user) }
- let!(:release) { create(:release, project: project) }
- let!(:tag) { release.tag }
before do
- project.add_developer(user)
- sign_in(user)
+ stub_feature_flags(releases_page: true)
end
- describe 'GET #edit' do
- it 'initializes a new release' do
- tag_id = release.tag
- project.releases.destroy_all # rubocop: disable DestroyAll
-
- get :edit, namespace_id: project.namespace, project_id: project, tag_id: tag_id
+ describe 'GET #index' do
+ it 'renders a 200' do
+ get_index
- release = assigns(:release)
- expect(release).not_to be_nil
- expect(release).not_to be_persisted
+ expect(response.status).to eq(200)
end
- it 'retrieves an existing release' do
- get :edit, namespace_id: project.namespace, project_id: project, tag_id: release.tag
+ context 'when the project is private' do
+ let!(:project) { create(:project, :repository, :private) }
- release = assigns(:release)
- expect(release).not_to be_nil
- expect(release).to be_persisted
- end
- end
+ it 'renders a 302' do
+ get_index
+
+ expect(response.status).to eq(302)
+ end
+
+ it 'renders a 200 for a logged in developer' do
+ project.add_developer(user)
+ sign_in(user)
- describe 'PUT #update' do
- it 'updates release note description' do
- update_release('description updated')
+ get_index
- release = project.releases.find_by_tag(tag)
- expect(release.description).to eq("description updated")
+ expect(response.status).to eq(200)
+ end
+
+ it 'renders a 404 when logged in but not in the project' do
+ sign_in(user)
+
+ get_index
+
+ expect(response.status).to eq(404)
+ end
end
- it 'deletes release note when description is null' do
- expect { update_release('') }.to change(project.releases, :count).by(-1)
+ context 'when releases_page feature flag is disabled' do
+ before do
+ stub_feature_flags(releases_page: false)
+ end
+
+ it 'renders a 404' do
+ get_index
+
+ expect(response.status).to eq(404)
+ end
end
end
- def update_release(description)
- put :update,
- namespace_id: project.namespace.to_param,
- project_id: project,
- tag_id: release.tag,
- release: { description: description }
+ private
+
+ def get_index
+ get :index, params: { namespace_id: project.namespace, project_id: project }
end
end
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index a102a3a3c8c..5f4f6f8558f 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -6,7 +6,7 @@ describe Projects::RepositoriesController do
describe "GET archive" do
context 'as a guest' do
it 'responds with redirect in correct format' do
- get :archive, namespace_id: project.namespace, project_id: project, id: "master", format: "zip"
+ get :archive, params: { namespace_id: project.namespace, project_id: project, id: "master" }, format: "zip"
expect(response.header["Content-Type"]).to start_with('text/html')
expect(response).to be_redirect
@@ -22,26 +22,26 @@ describe Projects::RepositoriesController do
end
it "uses Gitlab::Workhorse" do
- get :archive, namespace_id: project.namespace, project_id: project, id: "master", format: "zip"
+ get :archive, params: { namespace_id: project.namespace, project_id: project, id: "master" }, format: "zip"
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
end
it 'responds with redirect to the short name archive if fully qualified' do
- get :archive, namespace_id: project.namespace, project_id: project, id: "master/#{project.path}-master", format: "zip"
+ get :archive, params: { namespace_id: project.namespace, project_id: project, id: "master/#{project.path}-master" }, format: "zip"
expect(assigns(:ref)).to eq("master")
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
end
it 'handles legacy queries with no ref' do
- get :archive, namespace_id: project.namespace, project_id: project, format: "zip"
+ get :archive, params: { namespace_id: project.namespace, project_id: project }, format: "zip"
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
end
it 'handles legacy queries with the ref specified as ref in params' do
- get :archive, namespace_id: project.namespace, project_id: project, ref: 'feature', format: 'zip'
+ get :archive, params: { namespace_id: project.namespace, project_id: project, ref: 'feature' }, format: 'zip'
expect(response).to have_gitlab_http_status(200)
expect(assigns(:ref)).to eq('feature')
@@ -49,7 +49,7 @@ describe Projects::RepositoriesController do
end
it 'handles legacy queries with the ref specified as id in params' do
- get :archive, namespace_id: project.namespace, project_id: project, id: 'feature', format: 'zip'
+ get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'feature' }, format: 'zip'
expect(response).to have_gitlab_http_status(200)
expect(assigns(:ref)).to eq('feature')
@@ -57,7 +57,7 @@ describe Projects::RepositoriesController do
end
it 'prioritizes the id param over the ref param when both are specified' do
- get :archive, namespace_id: project.namespace, project_id: project, id: 'feature', ref: 'feature_conflict', format: 'zip'
+ get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'feature', ref: 'feature_conflict' }, format: 'zip'
expect(response).to have_gitlab_http_status(200)
expect(assigns(:ref)).to eq('feature')
@@ -70,7 +70,7 @@ describe Projects::RepositoriesController do
end
it "renders Not Found" do
- get :archive, namespace_id: project.namespace, project_id: project, id: "master", format: "zip"
+ get :archive, params: { namespace_id: project.namespace, project_id: project, id: "master" }, format: "zip"
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/controllers/projects/runners_controller_spec.rb b/spec/controllers/projects/runners_controller_spec.rb
index b1e0b496ede..0baaa4e7192 100644
--- a/spec/controllers/projects/runners_controller_spec.rb
+++ b/spec/controllers/projects/runners_controller_spec.rb
@@ -23,7 +23,7 @@ describe Projects::RunnersController do
new_desc = runner.description.swapcase
expect do
- post :update, params.merge(runner: { description: new_desc } )
+ post :update, params: params.merge(runner: { description: new_desc } )
end.to change { runner.ensure_runner_queue_value }
runner.reload
@@ -35,7 +35,7 @@ describe Projects::RunnersController do
describe '#destroy' do
it 'destroys the runner' do
- delete :destroy, params
+ delete :destroy, params: params
expect(response).to have_gitlab_http_status(302)
expect(Ci::Runner.find_by(id: runner.id)).to be_nil
@@ -47,7 +47,7 @@ describe Projects::RunnersController do
runner.update(active: false)
expect do
- post :resume, params
+ post :resume, params: params
end.to change { runner.ensure_runner_queue_value }
runner.reload
@@ -62,7 +62,7 @@ describe Projects::RunnersController do
runner.update(active: true)
expect do
- post :pause, params
+ post :pause, params: params
end.to change { runner.ensure_runner_queue_value }
runner.reload
diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb
new file mode 100644
index 00000000000..a9759c4fbd8
--- /dev/null
+++ b/spec/controllers/projects/serverless/functions_controller_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::Serverless::FunctionsController do
+ include KubernetesHelpers
+ include ReactiveCachingHelpers
+
+ let(:user) { create(:user) }
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) }
+ let(:service) { cluster.platform_kubernetes }
+ let(:project) { cluster.project}
+
+ let(:namespace) do
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster,
+ cluster_project: cluster.cluster_project,
+ project: cluster.cluster_project.project)
+ end
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ def params(opts = {})
+ opts.reverse_merge(namespace_id: project.namespace.to_param,
+ project_id: project.to_param)
+ end
+
+ describe 'GET #index' do
+ context 'empty cache' do
+ it 'has no data' do
+ get :index, params: params({ format: :json })
+
+ expect(response).to have_gitlab_http_status(204)
+ end
+
+ it 'renders an html page' do
+ get :index, params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+ end
+
+ describe 'GET #index with data', :use_clean_rails_memory_store_caching do
+ before do
+ stub_reactive_cache(knative, services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"])
+ end
+
+ it 'has data' do
+ get :index, params: params({ format: :json })
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response).to contain_exactly(
+ a_hash_including(
+ "name" => project.name,
+ "url" => "http://#{project.name}.#{namespace.namespace}.example.com"
+ )
+ )
+ end
+
+ it 'has data in html' do
+ get :index, params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+end
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 45cea8c1351..4a5d2bdecb7 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -3,9 +3,8 @@ require 'spec_helper'
describe Projects::ServicesController do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
- let(:service) { create(:hipchat_service, project: project) }
- let(:hipchat_client) { { '#room' => double(send: true) } }
- let(:service_params) { { token: 'hipchat_token_p', room: '#room' } }
+ let(:service) { create(:jira_service, project: project) }
+ let(:service_params) { { username: 'username', password: 'password', url: 'http://example.com' } }
before do
sign_in(user)
@@ -17,20 +16,20 @@ describe Projects::ServicesController do
it 'renders 404' do
allow_any_instance_of(Service).to receive(:can_test?).and_return(false)
- put :test, namespace_id: project.namespace, project_id: project, id: service.to_param
+ put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param }
expect(response).to have_gitlab_http_status(404)
end
end
context 'when validations fail' do
- let(:service_params) { { active: 'true', token: '' } }
+ let(:service_params) { { active: 'true', url: '' } }
it 'returns error messages in JSON response' do
- put :test, namespace_id: project.namespace, project_id: project, id: :hipchat, service: service_params
+ put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params }
expect(json_response['message']).to eq "Validations failed."
- expect(json_response['service_response']).to eq "Token can't be blank"
+ expect(json_response['service_response']).to include "Url can't be blank"
expect(response).to have_gitlab_http_status(200)
end
end
@@ -45,25 +44,27 @@ describe Projects::ServicesController do
it 'returns success' do
allow_any_instance_of(MicrosoftTeams::Notifier).to receive(:ping).and_return(true)
- put :test, namespace_id: project.namespace, project_id: project, id: service.to_param
+ put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param }
expect(response.status).to eq(200)
end
end
it 'returns success' do
- expect(HipChat::Client).to receive(:new).with('hipchat_token_p', anything).and_return(hipchat_client)
+ stub_request(:get, 'http://example.com/rest/api/2/serverInfo')
+ .to_return(status: 200, body: '{}')
- put :test, namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params
+ put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params }
expect(response.status).to eq(200)
end
end
it 'returns success' do
- expect(HipChat::Client).to receive(:new).with('hipchat_token_p', anything).and_return(hipchat_client)
+ stub_request(:get, 'http://example.com/rest/api/2/serverInfo')
+ .to_return(status: 200, body: '{}')
- put :test, namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params
+ put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params }
expect(response.status).to eq(200)
end
@@ -76,33 +77,44 @@ describe Projects::ServicesController do
it 'persist the object' do
do_put
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_empty
expect(BuildkiteService.first).to be_present
end
it 'creates the ServiceHook object' do
do_put
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_empty
expect(BuildkiteService.first.service_hook).to be_present
end
def do_put
- put :test, namespace_id: project.namespace,
- project_id: project,
- id: 'buildkite',
- service: { 'active' => '1', 'push_events' => '1', token: 'token', 'project_url' => 'http://test.com' }
+ put :test, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: 'buildkite',
+ service: { 'active' => '1', 'push_events' => '1', token: 'token', 'project_url' => 'http://test.com' }
+ }
end
end
end
context 'failure' do
it 'returns success status code and the error message' do
- expect(HipChat::Client).to receive(:new).with('hipchat_token_p', anything).and_raise('Bad test')
+ stub_request(:get, 'http://example.com/rest/api/2/serverInfo')
+ .to_return(status: 404)
- put :test, namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params
+ put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params }
- expect(response.status).to eq(200)
- expect(JSON.parse(response.body))
- .to eq('error' => true, 'message' => 'Test failed.', 'service_response' => 'Bad test', 'test_failed' => true)
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to eq(
+ 'error' => true,
+ 'message' => 'Test failed.',
+ 'service_response' => '',
+ 'test_failed' => true
+ )
end
end
end
@@ -111,19 +123,19 @@ describe Projects::ServicesController do
context 'when param `active` is set to true' do
it 'activates the service and redirects to integrations paths' do
put :update,
- namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: true }
+ params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: true } }
expect(response).to redirect_to(project_settings_integrations_path(project))
- expect(flash[:notice]).to eq 'HipChat activated.'
+ expect(flash[:notice]).to eq 'JIRA activated.'
end
end
context 'when param `active` is set to false' do
it 'does not activate the service but saves the settings' do
put :update,
- namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: false }
+ params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: false } }
- expect(flash[:notice]).to eq 'HipChat settings saved, but not activated.'
+ expect(flash[:notice]).to eq 'JIRA settings saved, but not activated.'
end
end
@@ -132,7 +144,7 @@ describe Projects::ServicesController do
before do
put :update,
- namespace_id: project.namespace, project_id: project, id: service.to_param, service: { namespace: 'updated_namespace' }
+ params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { namespace: 'updated_namespace' } }
end
it 'should not update the service' do
@@ -144,7 +156,7 @@ describe Projects::ServicesController do
describe "GET #edit" do
before do
- get :edit, namespace_id: project.namespace, project_id: project, id: service_id
+ get :edit, params: { namespace_id: project.namespace, project_id: project, id: service_id }
end
context 'with approved services' do
diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
index 4629929f9af..41cc0607cee 100644
--- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
@@ -12,7 +12,7 @@ describe Projects::Settings::CiCdController do
describe 'GET show' do
it 'renders show with 200 status code' do
- get :show, namespace_id: project.namespace, project_id: project
+ get :show, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template(:show)
@@ -29,7 +29,7 @@ describe Projects::Settings::CiCdController do
it 'sets assignable project runners only' do
group.add_maintainer(user)
- get :show, namespace_id: project.namespace, project_id: project
+ get :show, params: { namespace_id: project.namespace, project_id: project }
expect(assigns(:assignable_runners)).to contain_exactly(project_runner)
end
@@ -45,7 +45,7 @@ describe Projects::Settings::CiCdController do
allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(true)
end
- subject { post :reset_cache, namespace_id: project.namespace, project_id: project, format: :json }
+ subject { post :reset_cache, params: { namespace_id: project.namespace, project_id: project }, format: :json }
it 'calls reset project cache service' do
expect(ResetProjectCacheService).to receive_message_chain(:new, :execute)
@@ -75,7 +75,7 @@ describe Projects::Settings::CiCdController do
end
describe 'PUT #reset_registration_token' do
- subject { put :reset_registration_token, namespace_id: project.namespace, project_id: project }
+ subject { put :reset_registration_token, params: { namespace_id: project.namespace, project_id: project } }
it 'resets runner registration token' do
expect { subject }.to change { project.reload.runners_token }
end
@@ -92,9 +92,11 @@ describe Projects::Settings::CiCdController do
subject do
patch :update,
- namespace_id: project.namespace.to_param,
- project_id: project,
- project: params
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ project: params
+ }
end
it 'redirects to the settings page' do
diff --git a/spec/controllers/projects/settings/integrations_controller_spec.rb b/spec/controllers/projects/settings/integrations_controller_spec.rb
index a2484c04c7a..8624eb4d1a0 100644
--- a/spec/controllers/projects/settings/integrations_controller_spec.rb
+++ b/spec/controllers/projects/settings/integrations_controller_spec.rb
@@ -11,7 +11,7 @@ describe Projects::Settings::IntegrationsController do
describe 'GET show' do
it 'renders show with 200 status code' do
- get :show, namespace_id: project.namespace, project_id: project
+ get :show, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template(:show)
diff --git a/spec/controllers/projects/settings/repository_controller_spec.rb b/spec/controllers/projects/settings/repository_controller_spec.rb
index 9cee40b7553..638cce60a25 100644
--- a/spec/controllers/projects/settings/repository_controller_spec.rb
+++ b/spec/controllers/projects/settings/repository_controller_spec.rb
@@ -11,10 +11,23 @@ describe Projects::Settings::RepositoryController do
describe 'GET show' do
it 'renders show with 200 status code' do
- get :show, namespace_id: project.namespace, project_id: project
+ get :show, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template(:show)
end
end
+
+ describe 'PUT cleanup' do
+ let(:object_map) { fixture_file_upload('spec/fixtures/bfg_object_map.txt') }
+
+ it 'enqueues a RepositoryCleanupWorker' do
+ allow(RepositoryCleanupWorker).to receive(:perform_async)
+
+ put :cleanup, params: { namespace_id: project.namespace, project_id: project, project: { object_map: object_map } }
+
+ expect(response).to redirect_to project_settings_repository_path(project)
+ expect(RepositoryCleanupWorker).to have_received(:perform_async).once
+ end
+ end
end
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index 9c383bd7628..1a3fb4da15f 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -17,16 +17,22 @@ describe Projects::SnippetsController do
it 'redirects to last_page if page number is larger than number of pages' do
get :index,
- namespace_id: project.namespace,
- project_id: project, page: (last_page + 1).to_param
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ page: (last_page + 1).to_param
+ }
expect(response).to redirect_to(namespace_project_snippets_path(page: last_page))
end
it 'redirects to specified page' do
get :index,
- namespace_id: project.namespace,
- project_id: project, page: last_page.to_param
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ page: last_page.to_param
+ }
expect(assigns(:snippets).current_page).to eq(last_page)
expect(response).to have_gitlab_http_status(200)
@@ -38,7 +44,7 @@ describe Projects::SnippetsController do
context 'when anonymous' do
it 'does not include the private snippet' do
- get :index, namespace_id: project.namespace, project_id: project
+ get :index, params: { namespace_id: project.namespace, project_id: project }
expect(assigns(:snippets)).not_to include(project_snippet)
expect(response).to have_gitlab_http_status(200)
@@ -51,7 +57,7 @@ describe Projects::SnippetsController do
end
it 'renders the snippet' do
- get :index, namespace_id: project.namespace, project_id: project
+ get :index, params: { namespace_id: project.namespace, project_id: project }
expect(assigns(:snippets)).to include(project_snippet)
expect(response).to have_gitlab_http_status(200)
@@ -64,7 +70,7 @@ describe Projects::SnippetsController do
end
it 'renders the snippet' do
- get :index, namespace_id: project.namespace, project_id: project
+ get :index, params: { namespace_id: project.namespace, project_id: project }
expect(assigns(:snippets)).to include(project_snippet)
expect(response).to have_gitlab_http_status(200)
@@ -79,7 +85,7 @@ describe Projects::SnippetsController do
project.add_developer(user)
- post :create, {
+ post :create, params: {
namespace_id: project.namespace.to_param,
project_id: project,
project_snippet: { title: 'Title', content: 'Content', description: 'Description' }.merge(snippet_params)
@@ -164,7 +170,7 @@ describe Projects::SnippetsController do
project.add_developer(user)
- put :update, {
+ put :update, params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: snippet.id,
@@ -295,9 +301,11 @@ describe Projects::SnippetsController do
sign_in(admin)
post :mark_as_spam,
- namespace_id: project.namespace,
- project_id: project,
- id: snippet.id
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: snippet.id
+ }
end
it 'updates the snippet' do
@@ -314,7 +322,7 @@ describe Projects::SnippetsController do
context 'when anonymous' do
it 'responds with status 404' do
- get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param
+ get action, params: { namespace_id: project.namespace, project_id: project, id: project_snippet.to_param }
expect(response).to have_gitlab_http_status(404)
end
@@ -326,7 +334,7 @@ describe Projects::SnippetsController do
end
it 'renders the snippet' do
- get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param
+ get action, params: { namespace_id: project.namespace, project_id: project, id: project_snippet.to_param }
expect(assigns(:snippet)).to eq(project_snippet)
expect(response).to have_gitlab_http_status(200)
@@ -339,7 +347,7 @@ describe Projects::SnippetsController do
end
it 'renders the snippet' do
- get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param
+ get action, params: { namespace_id: project.namespace, project_id: project, id: project_snippet.to_param }
expect(assigns(:snippet)).to eq(project_snippet)
expect(response).to have_gitlab_http_status(200)
@@ -350,7 +358,7 @@ describe Projects::SnippetsController do
context 'when the project snippet does not exist' do
context 'when anonymous' do
it 'responds with status 404' do
- get action, namespace_id: project.namespace, project_id: project, id: 42
+ get action, params: { namespace_id: project.namespace, project_id: project, id: 42 }
expect(response).to have_gitlab_http_status(404)
end
@@ -362,7 +370,7 @@ describe Projects::SnippetsController do
end
it 'responds with status 404' do
- get action, namespace_id: project.namespace, project_id: project, id: 42
+ get action, params: { namespace_id: project.namespace, project_id: project, id: 42 }
expect(response).to have_gitlab_http_status(404)
end
@@ -391,13 +399,13 @@ describe Projects::SnippetsController do
end
it 'returns LF line endings by default' do
- get :raw, params
+ get :raw, params: params
expect(response.body).to eq("first line\nsecond line\nthird line")
end
it 'does not convert line endings when parameter present' do
- get :raw, params.merge(line_ending: :raw)
+ get :raw, params: params.merge(line_ending: :raw)
expect(response.body).to eq("first line\r\nsecond line\r\nthird line")
end
diff --git a/spec/controllers/projects/tags/releases_controller_spec.rb b/spec/controllers/projects/tags/releases_controller_spec.rb
new file mode 100644
index 00000000000..29f206c574b
--- /dev/null
+++ b/spec/controllers/projects/tags/releases_controller_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::Tags::ReleasesController do
+ let!(:project) { create(:project, :repository) }
+ let!(:user) { create(:user) }
+ let!(:release) { create(:release, project: project) }
+ let!(:tag) { release.tag }
+
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ describe 'GET #edit' do
+ it 'initializes a new release' do
+ tag_id = release.tag
+ project.releases.destroy_all # rubocop: disable DestroyAll
+
+ get :edit, params: { namespace_id: project.namespace, project_id: project, tag_id: tag_id }
+
+ release = assigns(:release)
+ expect(release).not_to be_nil
+ expect(release).not_to be_persisted
+ end
+
+ it 'retrieves an existing release' do
+ get :edit, params: { namespace_id: project.namespace, project_id: project, tag_id: release.tag }
+
+ release = assigns(:release)
+ expect(release).not_to be_nil
+ expect(release).to be_persisted
+ end
+ end
+
+ describe 'PUT #update' do
+ it 'updates release note description' do
+ update_release('description updated')
+
+ release = project.releases.find_by_tag(tag)
+ expect(release.description).to eq("description updated")
+ end
+
+ it 'deletes release note when description is null' do
+ expect { update_release('') }.to change(project.releases, :count).by(-1)
+ end
+ end
+
+ def update_release(description)
+ put :update, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ tag_id: release.tag,
+ release: { description: description }
+ }
+ end
+end
diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb
index 6fbf75d0259..379430bff3b 100644
--- a/spec/controllers/projects/tags_controller_spec.rb
+++ b/spec/controllers/projects/tags_controller_spec.rb
@@ -7,7 +7,7 @@ describe Projects::TagsController do
describe 'GET index' do
before do
- get :index, namespace_id: project.namespace.to_param, project_id: project
+ get :index, params: { namespace_id: project.namespace.to_param, project_id: project }
end
it 'returns the tags for the page' do
@@ -22,7 +22,7 @@ describe Projects::TagsController do
describe 'GET show' do
before do
- get :show, namespace_id: project.namespace.to_param, project_id: project, id: id
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project, id: id }
end
context "valid tag" do
diff --git a/spec/controllers/projects/templates_controller_spec.rb b/spec/controllers/projects/templates_controller_spec.rb
index d7f07aa2b01..01e53669627 100644
--- a/spec/controllers/projects/templates_controller_spec.rb
+++ b/spec/controllers/projects/templates_controller_spec.rb
@@ -20,7 +20,7 @@ describe Projects::TemplatesController do
describe '#show' do
it 'renders template name and content as json' do
- get(:show, namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project, format: :json)
+ get(:show, params: { namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project }, format: :json)
expect(response.status).to eq(200)
expect(body["name"]).to eq("bug")
@@ -29,21 +29,21 @@ describe Projects::TemplatesController do
it 'renders 404 when unauthorized' do
sign_in(user2)
- get(:show, namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project, format: :json)
+ get(:show, params: { namespace_id: project.namespace.to_param, template_type: "issue", key: "bug", project_id: project }, format: :json)
expect(response.status).to eq(404)
end
it 'renders 404 when template type is not found' do
sign_in(user)
- get(:show, namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project, format: :json)
+ get(:show, params: { namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project }, format: :json)
expect(response.status).to eq(404)
end
it 'renders 404 without errors' do
sign_in(user)
- expect { get(:show, namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project, format: :json) }.not_to raise_error
+ expect { get(:show, params: { namespace_id: project.namespace.to_param, template_type: "dont_exist", key: "bug", project_id: project }, format: :json) }.not_to raise_error
end
end
end
diff --git a/spec/controllers/projects/todos_controller_spec.rb b/spec/controllers/projects/todos_controller_spec.rb
index 58f2817c7cc..987772f38aa 100644
--- a/spec/controllers/projects/todos_controller_spec.rb
+++ b/spec/controllers/projects/todos_controller_spec.rb
@@ -29,10 +29,12 @@ describe Projects::TodosController do
describe 'POST create' do
def post_create
post :create,
- namespace_id: project.namespace,
- project_id: project,
- issuable_id: issue.id,
- issuable_type: 'issue',
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ issuable_id: issue.id,
+ issuable_type: 'issue'
+ },
format: 'html'
end
@@ -44,10 +46,12 @@ describe Projects::TodosController do
describe 'POST create' do
def post_create
post :create,
- namespace_id: project.namespace,
- project_id: project,
- issuable_id: merge_request.id,
- issuable_type: 'merge_request',
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ issuable_id: merge_request.id,
+ issuable_type: 'merge_request'
+ },
format: 'html'
end
diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb
index 9982b49eebb..b15a2bc84a5 100644
--- a/spec/controllers/projects/tree_controller_spec.rb
+++ b/spec/controllers/projects/tree_controller_spec.rb
@@ -17,9 +17,11 @@ describe Projects::TreeController do
before do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: id)
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: id
+ })
end
context "valid branch, no path" do
@@ -73,9 +75,11 @@ describe Projects::TreeController do
before do
get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: id)
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: id
+ })
end
context 'redirect to blob' do
@@ -93,12 +97,14 @@ describe Projects::TreeController do
before do
post(:create_dir,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: 'master',
- dir_name: path,
- branch_name: branch_name,
- commit_message: 'Test commit message')
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: 'master',
+ dir_name: path,
+ branch_name: branch_name,
+ commit_message: 'Test commit message'
+ })
end
context 'successful creation' do
diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb
index 9802e4d5b1e..cfa67683dd3 100644
--- a/spec/controllers/projects/uploads_controller_spec.rb
+++ b/spec/controllers/projects/uploads_controller_spec.rb
@@ -12,7 +12,7 @@ describe Projects::UploadsController do
context 'when the URL the old style, without /-/system' do
it 'responds with a redirect to the login page' do
- get :show, namespace_id: 'project', project_id: 'avatar', filename: 'foo.png', secret: 'bar'
+ get :show, params: { namespace_id: 'project', project_id: 'avatar', filename: 'foo.png', secret: 'bar' }
expect(response).to redirect_to(new_user_session_path)
end
@@ -35,6 +35,6 @@ describe Projects::UploadsController do
def post_authorize(verified: true)
request.headers.merge!(workhorse_internal_api_request_header) if verified
- post :authorize, namespace_id: model.namespace, project_id: model.path, format: :json
+ post :authorize, params: { namespace_id: model.namespace, project_id: model.path }, format: :json
end
end
diff --git a/spec/controllers/projects/variables_controller_spec.rb b/spec/controllers/projects/variables_controller_spec.rb
index 9afd1f751c6..8cceda72c28 100644
--- a/spec/controllers/projects/variables_controller_spec.rb
+++ b/spec/controllers/projects/variables_controller_spec.rb
@@ -13,7 +13,7 @@ describe Projects::VariablesController do
let!(:variable) { create(:ci_variable, project: project) }
subject do
- get :show, namespace_id: project.namespace.to_param, project_id: project, format: :json
+ get :show, params: { namespace_id: project.namespace.to_param, project_id: project }, format: :json
end
include_examples 'GET #show lists all variables'
@@ -25,9 +25,11 @@ describe Projects::VariablesController do
subject do
patch :update,
- namespace_id: project.namespace.to_param,
- project_id: project,
- variables_attributes: variables_attributes,
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ variables_attributes: variables_attributes
+ },
format: :json
end
diff --git a/spec/controllers/projects/wikis_controller_spec.rb b/spec/controllers/projects/wikis_controller_spec.rb
index 6d75152857b..b2f40231796 100644
--- a/spec/controllers/projects/wikis_controller_spec.rb
+++ b/spec/controllers/projects/wikis_controller_spec.rb
@@ -20,7 +20,7 @@ describe Projects::WikisController do
describe 'GET #show' do
render_views
- subject { get :show, namespace_id: project.namespace, project_id: project, id: wiki_title }
+ subject { get :show, params: { namespace_id: project.namespace, project_id: project, id: wiki_title } }
it 'limits the retrieved pages for the sidebar' do
expect(controller).to receive(:load_wiki).and_return(project_wiki)
@@ -53,23 +53,23 @@ describe Projects::WikisController do
let(:path) { upload_file_to_wiki(project, user, file_name) }
before do
- subject
+ get :show, params: { namespace_id: project.namespace, project_id: project, id: path }
end
- subject { get :show, namespace_id: project.namespace, project_id: project, id: path }
-
context 'when file is an image' do
let(:file_name) { 'dk.png' }
- it 'renders the content inline' do
+ it 'delivers the image' do
expect(response.headers['Content-Disposition']).to match(/^inline/)
+ expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
end
context 'when file is a svg' do
let(:file_name) { 'unsanitized.svg' }
- it 'renders the content as an attachment' do
- expect(response.headers['Content-Disposition']).to match(/^attachment/)
+ it 'delivers the image' do
+ expect(response.headers['Content-Disposition']).to match(/^inline/)
+ expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
end
end
end
@@ -77,8 +77,9 @@ describe Projects::WikisController do
context 'when file is a pdf' do
let(:file_name) { 'git-cheat-sheet.pdf' }
- it 'sets the content type to application/octet-stream' do
- expect(response.headers['Content-Type']).to eq 'application/octet-stream'
+ it 'sets the content type to sets the content response headers' do
+ expect(response.headers['Content-Disposition']).to match(/^inline/)
+ expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
end
end
end
@@ -86,14 +87,14 @@ describe Projects::WikisController do
describe 'POST #preview_markdown' do
it 'renders json in a correct format' do
- post :preview_markdown, namespace_id: project.namespace, project_id: project, id: 'page/path', text: '*Markdown* text'
+ post :preview_markdown, params: { namespace_id: project.namespace, project_id: project, id: 'page/path', text: '*Markdown* text' }
expect(JSON.parse(response.body).keys).to match_array(%w(body references))
end
end
describe 'GET #edit' do
- subject { get(:edit, namespace_id: project.namespace, project_id: project, id: wiki_title) }
+ subject { get(:edit, params: { namespace_id: project.namespace, project_id: project, id: wiki_title }) }
context 'when page content encoding is invalid' do
it 'redirects to show' do
@@ -122,10 +123,12 @@ describe Projects::WikisController do
let(:new_content) { 'New content' }
subject do
patch(:update,
- namespace_id: project.namespace,
- project_id: project,
- id: wiki_title,
- wiki: { title: new_title, content: new_content })
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: wiki_title,
+ wiki: { title: new_title, content: new_content }
+ })
end
context 'when page content encoding is invalid' do
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 7849bec4762..ea067a01295 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -22,7 +22,7 @@ describe ProjectsController do
it 'renders the template' do
group.add_owner(user)
- get :new, namespace_id: group.id
+ get :new, params: { namespace_id: group.id }
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template('new')
@@ -31,7 +31,7 @@ describe ProjectsController do
context 'when user does not have access to the namespace' do
it 'responds with status 404' do
- get :new, namespace_id: group.id
+ get :new, params: { namespace_id: group.id }
expect(response).to have_gitlab_http_status(404)
expect(response).not_to render_template('new')
@@ -71,7 +71,7 @@ describe ProjectsController do
let(:private_project) { create(:project, :private) }
it "does not initialize notification setting" do
- get :show, namespace_id: private_project.namespace, id: private_project
+ get :show, params: { namespace_id: private_project.namespace, id: private_project }
expect(assigns(:notification_setting)).to be_nil
end
end
@@ -79,7 +79,7 @@ describe ProjectsController do
context "user has access to project" do
context "and does not have notification setting" do
it "initializes notification as disabled" do
- get :show, namespace_id: public_project.namespace, id: public_project
+ get :show, params: { namespace_id: public_project.namespace, id: public_project }
expect(assigns(:notification_setting).level).to eq("global")
end
end
@@ -92,7 +92,7 @@ describe ProjectsController do
end
it "shows current notification setting" do
- get :show, namespace_id: public_project.namespace, id: public_project
+ get :show, params: { namespace_id: public_project.namespace, id: public_project }
expect(assigns(:notification_setting).level).to eq("watch")
end
end
@@ -107,7 +107,7 @@ describe ProjectsController do
end
it 'shows wiki homepage' do
- get :show, namespace_id: project.namespace, id: project
+ get :show, params: { namespace_id: project.namespace, id: project }
expect(response).to render_template('projects/_wiki')
end
@@ -116,7 +116,7 @@ describe ProjectsController do
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
create(:issue, project: project)
- get :show, namespace_id: project.namespace, id: project
+ get :show, params: { namespace_id: project.namespace, id: project }
expect(response).to render_template('projects/issues/_issues')
expect(assigns(:issuable_meta_data)).not_to be_nil
@@ -126,7 +126,7 @@ describe ProjectsController do
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
- get :show, namespace_id: project.namespace, id: project
+ get :show, params: { namespace_id: project.namespace, id: project }
expect(response).to render_template("projects/_customize_workflow")
end
@@ -134,7 +134,7 @@ describe ProjectsController do
it 'shows activity if enabled by user' do
user.update_attribute(:project_view, 'activity')
- get :show, namespace_id: project.namespace, id: project
+ get :show, params: { namespace_id: project.namespace, id: project }
expect(response).to render_template("projects/_activity")
end
@@ -150,7 +150,7 @@ describe ProjectsController do
end
it 'renders a 503' do
- get :show, namespace_id: project.namespace, id: project
+ get :show, params: { namespace_id: project.namespace, id: project }
expect(response).to have_gitlab_http_status(503)
end
@@ -168,7 +168,7 @@ describe ProjectsController do
before do
user.update(project_view: project_view)
- get :show, namespace_id: empty_project.namespace, id: empty_project
+ get :show, params: { namespace_id: empty_project.namespace, id: empty_project }
end
it "renders the empty project view" do
@@ -190,7 +190,7 @@ describe ProjectsController do
before do
user.update(project_view: project_view)
- get :show, namespace_id: empty_project.namespace, id: empty_project
+ get :show, params: { namespace_id: empty_project.namespace, id: empty_project }
end
it "renders the empty project view" do
@@ -211,7 +211,7 @@ describe ProjectsController do
allow(controller).to receive(:current_user).and_return(user)
allow(user).to receive(:project_view).and_return('activity')
- get :show, namespace_id: public_project.namespace, id: public_project
+ get :show, params: { namespace_id: public_project.namespace, id: public_project }
expect(response).to render_template('_activity')
end
@@ -219,7 +219,7 @@ describe ProjectsController do
allow(controller).to receive(:current_user).and_return(user)
allow(user).to receive(:project_view).and_return('files')
- get :show, namespace_id: public_project.namespace, id: public_project
+ get :show, params: { namespace_id: public_project.namespace, id: public_project }
expect(response).to render_template('_files')
end
@@ -227,7 +227,7 @@ describe ProjectsController do
allow(controller).to receive(:current_user).and_return(user)
allow(user).to receive(:project_view).and_return('readme')
- get :show, namespace_id: public_project.namespace, id: public_project
+ get :show, params: { namespace_id: public_project.namespace, id: public_project }
expect(response).to render_template('_readme')
end
end
@@ -245,7 +245,7 @@ describe ProjectsController do
project = create(:project, pending_delete: true)
sign_in(user)
- get :show, namespace_id: project.namespace, id: project
+ get :show, params: { namespace_id: project.namespace, id: project }
expect(response.status).to eq 404
end
@@ -255,7 +255,7 @@ describe ProjectsController do
it 'redirects to project page (format.html)' do
project = create(:project, :public)
- get :show, namespace_id: project.namespace, id: project, format: :git
+ get :show, params: { namespace_id: project.namespace, id: project }, format: :git
expect(response).to have_gitlab_http_status(302)
expect(response).to redirect_to(namespace_project_path)
@@ -278,8 +278,8 @@ describe ProjectsController do
it 'does not increase the number of queries when the project is forked' do
expected_query = /#{public_project.fork_network.find_forks_in(other_user.namespace).to_sql}/
- expect { get(:show, namespace_id: public_project.namespace, id: public_project) }
- .not_to exceed_query_limit(1).for_query(expected_query)
+ expect { get(:show, params: { namespace_id: public_project.namespace, id: public_project }) }
+ .not_to exceed_query_limit(2).for_query(expected_query)
end
end
end
@@ -290,8 +290,10 @@ describe ProjectsController do
project.add_maintainer(user)
get :edit,
- namespace_id: project.namespace.path,
- id: project.path
+ params: {
+ namespace_id: project.namespace.path,
+ id: project.path
+ }
expect(assigns(:badge_api_endpoint)).not_to be_nil
end
@@ -355,9 +357,11 @@ describe ProjectsController do
}
put :update,
- namespace_id: project.namespace,
- id: project.id,
- project: params
+ params: {
+ namespace_id: project.namespace,
+ id: project.id,
+ project: params
+ }
expect(response).to have_gitlab_http_status(302)
params.each do |param, value|
@@ -367,9 +371,11 @@ describe ProjectsController do
def update_project(**parameters)
put :update,
- namespace_id: project.namespace.path,
- id: project.path,
- project: parameters
+ params: {
+ namespace_id: project.namespace.path,
+ id: project.path,
+ project: parameters
+ }
end
end
@@ -397,9 +403,11 @@ describe ProjectsController do
sign_in(admin)
put :transfer,
- namespace_id: project.namespace.path,
- new_namespace_id: new_namespace.id,
- id: project.path,
+ params: {
+ namespace_id: project.namespace.path,
+ new_namespace_id: new_namespace.id,
+ id: project.path
+ },
format: :js
project.reload
@@ -416,9 +424,11 @@ describe ProjectsController do
old_namespace = project.namespace
put :transfer,
- namespace_id: old_namespace.path,
- new_namespace_id: nil,
- id: project.path,
+ params: {
+ namespace_id: old_namespace.path,
+ new_namespace_id: nil,
+ id: project.path
+ },
format: :js
project.reload
@@ -438,7 +448,7 @@ describe ProjectsController do
sign_in(admin)
orig_id = project.id
- delete :destroy, namespace_id: project.namespace, id: project
+ delete :destroy, params: { namespace_id: project.namespace, id: project }
expect { Project.find(orig_id) }.to raise_error(ActiveRecord::RecordNotFound)
expect(response).to have_gitlab_http_status(302)
@@ -458,7 +468,7 @@ describe ProjectsController do
project.merge_requests << merge_request
sign_in(admin)
- delete :destroy, namespace_id: forked_project.namespace, id: forked_project
+ delete :destroy, params: { namespace_id: forked_project.namespace, id: forked_project }
expect(merge_request.reload.state).to eq('closed')
end
@@ -468,9 +478,11 @@ describe ProjectsController do
describe 'PUT #new_issuable_address for issue' do
subject do
put :new_issuable_address,
- namespace_id: project.namespace,
- id: project,
- issuable_type: 'issue'
+ params: {
+ namespace_id: project.namespace,
+ id: project,
+ issuable_type: 'issue'
+ }
user.reload
end
@@ -496,9 +508,11 @@ describe ProjectsController do
describe 'PUT #new_issuable_address for merge request' do
subject do
put :new_issuable_address,
- namespace_id: project.namespace,
- id: project,
- issuable_type: 'merge_request'
+ params: {
+ namespace_id: project.namespace,
+ id: project,
+ issuable_type: 'merge_request'
+ }
user.reload
end
@@ -526,23 +540,31 @@ describe ProjectsController do
sign_in(user)
expect(user.starred?(public_project)).to be_falsey
post(:toggle_star,
- namespace_id: public_project.namespace,
- id: public_project)
+ params: {
+ namespace_id: public_project.namespace,
+ id: public_project
+ })
expect(user.starred?(public_project)).to be_truthy
post(:toggle_star,
- namespace_id: public_project.namespace,
- id: public_project)
+ params: {
+ namespace_id: public_project.namespace,
+ id: public_project
+ })
expect(user.starred?(public_project)).to be_falsey
end
it "does nothing if user is not signed in" do
post(:toggle_star,
- namespace_id: project.namespace,
- id: public_project)
+ params: {
+ namespace_id: project.namespace,
+ id: public_project
+ })
expect(user.starred?(public_project)).to be_falsey
post(:toggle_star,
- namespace_id: project.namespace,
- id: public_project)
+ params: {
+ namespace_id: project.namespace,
+ id: public_project
+ })
expect(user.starred?(public_project)).to be_falsey
end
end
@@ -558,8 +580,11 @@ describe ProjectsController do
it 'removes fork from project' do
delete(:remove_fork,
- namespace_id: forked_project.namespace.to_param,
- id: forked_project.to_param, format: :js)
+ params: {
+ namespace_id: forked_project.namespace.to_param,
+ id: forked_project.to_param
+ },
+ format: :js)
expect(forked_project.reload.forked?).to be_falsey
expect(flash[:notice]).to eq('The fork relationship has been removed.')
@@ -572,8 +597,11 @@ describe ProjectsController do
it 'does nothing if project was not forked' do
delete(:remove_fork,
- namespace_id: unforked_project.namespace,
- id: unforked_project, format: :js)
+ params: {
+ namespace_id: unforked_project.namespace,
+ id: unforked_project
+ },
+ format: :js)
expect(flash[:notice]).to be_nil
expect(response).to render_template(:remove_fork)
@@ -583,8 +611,11 @@ describe ProjectsController do
it "does nothing if user is not signed in" do
delete(:remove_fork,
- namespace_id: project.namespace,
- id: project, format: :js)
+ params: {
+ namespace_id: project.namespace,
+ id: project
+ },
+ format: :js)
expect(response).to have_gitlab_http_status(401)
end
end
@@ -593,7 +624,7 @@ describe ProjectsController do
let(:public_project) { create(:project, :public, :repository) }
it 'gets a list of branches and tags' do
- get :refs, namespace_id: public_project.namespace, id: public_project, sort: 'updated_desc'
+ get :refs, params: { namespace_id: public_project.namespace, id: public_project, sort: 'updated_desc' }
parsed_body = JSON.parse(response.body)
expect(parsed_body['Branches']).to include('master')
@@ -603,7 +634,7 @@ describe ProjectsController do
end
it "gets a list of branches, tags and commits" do
- get :refs, namespace_id: public_project.namespace, id: public_project, ref: "123456"
+ get :refs, params: { namespace_id: public_project.namespace, id: public_project, ref: "123456" }
parsed_body = JSON.parse(response.body)
expect(parsed_body["Branches"]).to include("master")
@@ -618,7 +649,7 @@ describe ProjectsController do
end
it "gets a list of branches, tags and commits" do
- get :refs, namespace_id: public_project.namespace, id: public_project, ref: "123456"
+ get :refs, params: { namespace_id: public_project.namespace, id: public_project, ref: "123456" }
parsed_body = JSON.parse(response.body)
expect(parsed_body["Branches"]).to include("master")
@@ -634,7 +665,7 @@ describe ProjectsController do
end
it 'renders json in a correct format' do
- post :preview_markdown, namespace_id: public_project.namespace, id: public_project, text: '*Markdown* text'
+ post :preview_markdown, params: { namespace_id: public_project.namespace, id: public_project, text: '*Markdown* text' }
expect(JSON.parse(response.body).keys).to match_array(%w(body references))
end
@@ -644,9 +675,11 @@ describe ProjectsController do
let(:merge_request) { create(:merge_request, :closed, target_project: public_project) }
it 'renders JSON body with state filter for issues' do
- post :preview_markdown, namespace_id: public_project.namespace,
- id: public_project,
- text: issue.to_reference
+ post :preview_markdown, params: {
+ namespace_id: public_project.namespace,
+ id: public_project,
+ text: issue.to_reference
+ }
json_response = JSON.parse(response.body)
@@ -654,9 +687,11 @@ describe ProjectsController do
end
it 'renders JSON body with state filter for MRs' do
- post :preview_markdown, namespace_id: public_project.namespace,
- id: public_project,
- text: merge_request.to_reference
+ post :preview_markdown, params: {
+ namespace_id: public_project.namespace,
+ id: public_project,
+ text: merge_request.to_reference
+ }
json_response = JSON.parse(response.body)
@@ -674,7 +709,7 @@ describe ProjectsController do
context 'when requesting the canonical path' do
context "with exactly matching casing" do
it "loads the project" do
- get :show, namespace_id: public_project.namespace, id: public_project
+ get :show, params: { namespace_id: public_project.namespace, id: public_project }
expect(assigns(:project)).to eq(public_project)
expect(response).to have_gitlab_http_status(200)
@@ -683,7 +718,7 @@ describe ProjectsController do
context "with different casing" do
it "redirects to the normalized path" do
- get :show, namespace_id: public_project.namespace, id: public_project.path.upcase
+ get :show, params: { namespace_id: public_project.namespace, id: public_project.path.upcase }
expect(assigns(:project)).to eq(public_project)
expect(response).to redirect_to("/#{public_project.full_path}")
@@ -696,14 +731,14 @@ describe ProjectsController do
let!(:redirect_route) { public_project.redirect_routes.create!(path: "foo/bar") }
it 'redirects to the canonical path' do
- get :show, namespace_id: 'foo', id: 'bar'
+ get :show, params: { namespace_id: 'foo', id: 'bar' }
expect(response).to redirect_to(public_project)
expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, public_project))
end
it 'redirects to the canonical path (testing non-show action)' do
- get :refs, namespace_id: 'foo', id: 'bar'
+ get :refs, params: { namespace_id: 'foo', id: 'bar' }
expect(response).to redirect_to(refs_project_path(public_project))
expect(controller).to set_flash[:notice].to(project_moved_message(redirect_route, public_project))
@@ -714,13 +749,13 @@ describe ProjectsController do
context 'for a POST request' do
context 'when requesting the canonical path with different casing' do
it 'does not 404' do
- post :toggle_star, namespace_id: public_project.namespace, id: public_project.path.upcase
+ post :toggle_star, params: { namespace_id: public_project.namespace, id: public_project.path.upcase }
expect(response).not_to have_gitlab_http_status(404)
end
it 'does not redirect to the correct casing' do
- post :toggle_star, namespace_id: public_project.namespace, id: public_project.path.upcase
+ post :toggle_star, params: { namespace_id: public_project.namespace, id: public_project.path.upcase }
expect(response).not_to have_gitlab_http_status(301)
end
@@ -730,7 +765,7 @@ describe ProjectsController do
let!(:redirect_route) { public_project.redirect_routes.create!(path: "foo/bar") }
it 'returns not found' do
- post :toggle_star, namespace_id: 'foo', id: 'bar'
+ post :toggle_star, params: { namespace_id: 'foo', id: 'bar' }
expect(response).to have_gitlab_http_status(404)
end
@@ -744,13 +779,13 @@ describe ProjectsController do
context 'when requesting the canonical path with different casing' do
it 'does not 404' do
- delete :destroy, namespace_id: project.namespace, id: project.path.upcase
+ delete :destroy, params: { namespace_id: project.namespace, id: project.path.upcase }
expect(response).not_to have_gitlab_http_status(404)
end
it 'does not redirect to the correct casing' do
- delete :destroy, namespace_id: project.namespace, id: project.path.upcase
+ delete :destroy, params: { namespace_id: project.namespace, id: project.path.upcase }
expect(response).not_to have_gitlab_http_status(301)
end
@@ -760,7 +795,7 @@ describe ProjectsController do
let!(:redirect_route) { project.redirect_routes.create!(path: "foo/bar") }
it 'returns not found' do
- delete :destroy, namespace_id: 'foo', id: 'bar'
+ delete :destroy, params: { namespace_id: 'foo', id: 'bar' }
expect(response).to have_gitlab_http_status(404)
end
@@ -777,7 +812,7 @@ describe ProjectsController do
context 'when project export is enabled' do
it 'returns 302' do
- get :export, namespace_id: project.namespace, id: project
+ get :export, params: { namespace_id: project.namespace, id: project }
expect(response).to have_gitlab_http_status(302)
end
@@ -789,7 +824,7 @@ describe ProjectsController do
end
it 'returns 404' do
- get :export, namespace_id: project.namespace, id: project
+ get :export, params: { namespace_id: project.namespace, id: project }
expect(response).to have_gitlab_http_status(404)
end
@@ -806,7 +841,7 @@ describe ProjectsController do
context 'object storage enabled' do
context 'when project export is enabled' do
it 'returns 302' do
- get :download_export, namespace_id: project.namespace, id: project
+ get :download_export, params: { namespace_id: project.namespace, id: project }
expect(response).to have_gitlab_http_status(302)
end
@@ -818,7 +853,7 @@ describe ProjectsController do
end
it 'returns 404' do
- get :download_export, namespace_id: project.namespace, id: project
+ get :download_export, params: { namespace_id: project.namespace, id: project }
expect(response).to have_gitlab_http_status(404)
end
@@ -835,7 +870,7 @@ describe ProjectsController do
context 'when project export is enabled' do
it 'returns 302' do
- post :remove_export, namespace_id: project.namespace, id: project
+ post :remove_export, params: { namespace_id: project.namespace, id: project }
expect(response).to have_gitlab_http_status(302)
end
@@ -847,7 +882,7 @@ describe ProjectsController do
end
it 'returns 404' do
- post :remove_export, namespace_id: project.namespace, id: project
+ post :remove_export, params: { namespace_id: project.namespace, id: project }
expect(response).to have_gitlab_http_status(404)
end
@@ -863,7 +898,7 @@ describe ProjectsController do
context 'when project export is enabled' do
it 'returns 302' do
- post :generate_new_export, namespace_id: project.namespace, id: project
+ post :generate_new_export, params: { namespace_id: project.namespace, id: project }
expect(response).to have_gitlab_http_status(302)
end
@@ -875,7 +910,7 @@ describe ProjectsController do
end
it 'returns 404' do
- post :generate_new_export, namespace_id: project.namespace, id: project
+ post :generate_new_export, params: { namespace_id: project.namespace, id: project }
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index d334a2ff566..fd151e8a298 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -17,7 +17,7 @@ describe RegistrationsController do
it 'signs the user in' do
allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(false)
- expect { post(:create, user_params) }.not_to change { ActionMailer::Base.deliveries.size }
+ expect { post(:create, params: user_params) }.not_to change { ActionMailer::Base.deliveries.size }
expect(subject.current_user).not_to be_nil
end
end
@@ -26,7 +26,7 @@ describe RegistrationsController do
it 'does not authenticate user and sends confirmation email' do
allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true)
- post(:create, user_params)
+ post(:create, params: user_params)
expect(ActionMailer::Base.deliveries.last.to.first).to eq(user_params[:user][:email])
expect(subject.current_user).to be_nil
@@ -37,7 +37,7 @@ describe RegistrationsController do
it 'redirects to sign_in' do
allow_any_instance_of(ApplicationSetting).to receive(:signup_enabled?).and_return(false)
- expect { post(:create, user_params) }.not_to change(User, :count)
+ expect { post(:create, params: user_params) }.not_to change(User, :count)
expect(response).to redirect_to(new_user_session_path)
end
end
@@ -52,7 +52,7 @@ describe RegistrationsController do
# Without this, `verify_recaptcha` arbitrarily returns true in test env
Recaptcha.configuration.skip_verify_env.delete('test')
- post(:create, user_params)
+ post(:create, params: 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.'
@@ -64,7 +64,7 @@ describe RegistrationsController do
Recaptcha.configuration.skip_verify_env << 'test'
end
- post(:create, user_params)
+ post(:create, params: user_params)
expect(flash[:notice]).to include 'Welcome! You have signed up successfully.'
end
@@ -76,13 +76,13 @@ describe RegistrationsController do
end
it 'redirects back with a notice when the checkbox was not checked' do
- post :create, user_params
+ post :create, params: user_params
expect(flash[:alert]).to match /you must accept our terms/i
end
it 'creates the user with agreement when terms are accepted' do
- post :create, user_params.merge(terms_opt_in: '1')
+ post :create, params: user_params.merge(terms_opt_in: '1')
expect(subject.current_user).to be_present
expect(subject.current_user.terms_accepted?).to be(true)
@@ -125,13 +125,13 @@ describe RegistrationsController do
end
it 'fails if password confirmation is wrong' do
- post :destroy, password: 'wrong password'
+ post :destroy, params: { password: 'wrong password' }
expect_password_failure
end
it 'succeeds if password is confirmed' do
- post :destroy, password: '12345678'
+ post :destroy, params: { password: '12345678' }
expect_success
end
@@ -150,13 +150,13 @@ describe RegistrationsController do
end
it 'fails if username confirmation is wrong' do
- post :destroy, username: 'wrong username'
+ post :destroy, params: { username: 'wrong username' }
expect_username_failure
end
it 'succeeds if username is confirmed' do
- post :destroy, username: user.username
+ post :destroy, params: { username: user.username }
expect_success
end
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
index 416a09e1684..c9b53336fd1 100644
--- a/spec/controllers/search_controller_spec.rb
+++ b/spec/controllers/search_controller_spec.rb
@@ -11,7 +11,7 @@ describe SearchController do
project = create(:project, :public)
note = create(:note_on_issue, project: project)
- get :show, project_id: project.id, scope: 'notes', search: note.note
+ get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
expect(assigns[:search_objects].first).to eq note
end
@@ -30,13 +30,13 @@ describe SearchController do
end
it 'still blocks searches without a project_id' do
- get :show, search: 'hello'
+ get :show, params: { search: 'hello' }
expect(response).to have_gitlab_http_status(403)
end
it 'allows searches with a project_id' do
- get :show, search: 'hello', project_id: create(:project, :public).id
+ get :show, params: { search: 'hello', project_id: create(:project, :public).id }
expect(response).to have_gitlab_http_status(200)
end
@@ -52,7 +52,7 @@ describe SearchController do
project = create(:project, :public, :issues_private)
note = create(:note_on_issue, project: project)
- get :show, project_id: project.id, scope: 'notes', search: note.note
+ get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
expect(assigns[:search_objects].count).to eq(0)
end
@@ -62,7 +62,7 @@ describe SearchController do
project = create(:project, :public, :merge_requests_private)
note = create(:note_on_merge_request, project: project)
- get :show, project_id: project.id, scope: 'notes', search: note.note
+ get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
expect(assigns[:search_objects].count).to eq(0)
end
@@ -71,7 +71,7 @@ describe SearchController do
project = create(:project, :public, :snippets_private)
note = create(:note_on_project_snippet, project: project)
- get :show, project_id: project.id, scope: 'notes', search: note.note
+ get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
expect(assigns[:search_objects].count).to eq(0)
end
diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb
index 54a9af92f07..75c91dd8607 100644
--- a/spec/controllers/sent_notifications_controller_spec.rb
+++ b/spec/controllers/sent_notifications_controller_spec.rb
@@ -15,7 +15,7 @@ describe SentNotificationsController do
context 'when the user is not logged in' do
context 'when the force param is passed' do
before do
- get(:unsubscribe, id: sent_notification.reply_key, force: true)
+ get(:unsubscribe, params: { id: sent_notification.reply_key, force: true })
end
it 'unsubscribes the user' do
@@ -33,7 +33,7 @@ describe SentNotificationsController do
context 'when the force param is not passed' do
before do
- get(:unsubscribe, id: sent_notification.reply_key)
+ get(:unsubscribe, params: { id: sent_notification.reply_key })
end
it 'does not unsubscribe the user' do
@@ -57,7 +57,7 @@ describe SentNotificationsController do
context 'when the ID passed does not exist' do
before do
- get(:unsubscribe, id: sent_notification.reply_key.reverse)
+ get(:unsubscribe, params: { id: sent_notification.reply_key.reverse })
end
it 'does not unsubscribe the user' do
@@ -75,7 +75,7 @@ describe SentNotificationsController do
context 'when the force param is passed' do
before do
- get(:unsubscribe, id: sent_notification.reply_key, force: true)
+ get(:unsubscribe, params: { id: sent_notification.reply_key, force: true })
end
it 'unsubscribes the user' do
@@ -101,7 +101,7 @@ describe SentNotificationsController do
let(:sent_notification) { create(:sent_notification, project: project, noteable: merge_request, recipient: user) }
before do
- get(:unsubscribe, id: sent_notification.reply_key)
+ get(:unsubscribe, params: { id: sent_notification.reply_key })
end
it 'unsubscribes the user' do
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index c691b3f478b..ea7242c1aa8 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -26,7 +26,7 @@ describe SessionsController do
context 'and auto_sign_in=false param is passed' do
it 'responds with 200' do
- get(:new, auto_sign_in: 'false')
+ get(:new, params: { auto_sign_in: 'false' })
expect(response).to have_gitlab_http_status(200)
end
@@ -42,7 +42,7 @@ describe SessionsController do
context 'when using standard authentications' do
context 'invalid password' do
it 'does not authenticate user' do
- post(:create, user: { login: 'invalid', password: 'invalid' })
+ post(:create, params: { user: { login: 'invalid', password: 'invalid' } })
expect(response)
.to set_flash.now[:alert].to /Invalid Login or password/
@@ -54,19 +54,19 @@ describe SessionsController do
let(:user_params) { { login: user.username, password: user.password } }
it 'authenticates user correctly' do
- post(:create, user: user_params)
+ post(:create, params: { user: user_params })
expect(subject.current_user). to eq user
end
it 'creates an audit log record' do
- expect { post(:create, user: user_params) }.to change { SecurityEvent.count }.by(1)
+ expect { post(:create, params: { user: user_params }) }.to change { SecurityEvent.count }.by(1)
expect(SecurityEvent.last.details[:with]).to eq('standard')
end
include_examples 'user login request with unique ip limit', 302 do
def request
- post(:create, user: user_params)
+ post(:create, params: { user: user_params })
expect(subject.current_user).to eq user
subject.sign_out user
end
@@ -74,7 +74,7 @@ describe SessionsController do
it 'updates the user activity' do
expect do
- post(:create, user: user_params)
+ post(:create, params: { user: user_params })
end.to change { user.reload.last_activity_on }.to(Date.today)
end
end
@@ -98,7 +98,7 @@ describe SessionsController do
.with(:failed_login_captcha_total, anything)
.and_return(counter)
- post(:create, user: user_params)
+ 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.'
@@ -116,7 +116,7 @@ describe SessionsController do
.and_return(counter)
expect(Gitlab::Metrics).to receive(:counter).and_call_original
- post(:create, user: user_params)
+ post(:create, params: { user: user_params })
expect(subject.current_user).to eq user
end
@@ -127,7 +127,7 @@ describe SessionsController do
let(:user) { create(:user, :two_factor) }
def authenticate_2fa(user_params)
- post(:create, { user: user_params }, { otp_user_id: user.id })
+ post(:create, params: { user: user_params }, session: { otp_user_id: user.id })
end
context 'remember_me field' do
@@ -233,7 +233,7 @@ describe SessionsController do
end
it 'keeps the user locked on future login attempts' do
- post(:create, user: { login: user.username, password: user.password })
+ post(:create, params: { user: { login: user.username, password: user.password } })
expect(response)
.to set_flash.now[:alert].to /Invalid Login or password/
@@ -265,7 +265,7 @@ describe SessionsController do
let(:user) { create(:user, :two_factor) }
def authenticate_2fa_u2f(user_params)
- post(:create, { user: user_params }, { otp_user_id: user.id })
+ post(:create, params: { user: user_params }, session: { otp_user_id: user.id })
end
context 'remember_me field' do
@@ -309,7 +309,7 @@ describe SessionsController do
search_path = "/search?search=seed_project"
request.headers[:HTTP_REFERER] = "http://#{host}#{search_path}"
- get(:new, redirect_to_referer: :yes)
+ get(:new, params: { redirect_to_referer: :yes })
expect(controller.stored_location_for(:redirect)).to eq(search_path)
end
diff --git a/spec/controllers/snippets/notes_controller_spec.rb b/spec/controllers/snippets/notes_controller_spec.rb
index e6148ea1734..6efbd1f6c71 100644
--- a/spec/controllers/snippets/notes_controller_spec.rb
+++ b/spec/controllers/snippets/notes_controller_spec.rb
@@ -16,7 +16,7 @@ describe Snippets::NotesController do
before do
note_on_public
- get :index, { snippet_id: public_snippet }
+ get :index, params: { snippet_id: public_snippet }
end
it "returns status 200" do
@@ -35,7 +35,7 @@ describe Snippets::NotesController do
context 'when user not logged in' do
it "returns status 404" do
- get :index, { snippet_id: internal_snippet }
+ get :index, params: { snippet_id: internal_snippet }
expect(response).to have_gitlab_http_status(404)
end
@@ -47,7 +47,7 @@ describe Snippets::NotesController do
end
it "returns status 200" do
- get :index, { snippet_id: internal_snippet }
+ get :index, params: { snippet_id: internal_snippet }
expect(response).to have_gitlab_http_status(200)
end
@@ -61,7 +61,7 @@ describe Snippets::NotesController do
context 'when user not logged in' do
it "returns status 404" do
- get :index, { snippet_id: private_snippet }
+ get :index, params: { snippet_id: private_snippet }
expect(response).to have_gitlab_http_status(404)
end
@@ -73,7 +73,7 @@ describe Snippets::NotesController do
end
it "returns status 404" do
- get :index, { snippet_id: private_snippet }
+ get :index, params: { snippet_id: private_snippet }
expect(response).to have_gitlab_http_status(404)
end
@@ -87,13 +87,13 @@ describe Snippets::NotesController do
end
it "returns status 200" do
- get :index, { snippet_id: private_snippet }
+ get :index, params: { snippet_id: private_snippet }
expect(response).to have_gitlab_http_status(200)
end
it "returns 1 note" do
- get :index, { snippet_id: private_snippet }
+ get :index, params: { snippet_id: private_snippet }
expect(JSON.parse(response.body)['notes'].count).to eq(1)
end
@@ -110,7 +110,7 @@ describe Snippets::NotesController do
end
it "does not return any note" do
- get :index, { snippet_id: public_snippet }
+ get :index, params: { snippet_id: public_snippet }
expect(JSON.parse(response.body)['notes'].count).to eq(0)
end
@@ -132,13 +132,13 @@ describe Snippets::NotesController do
end
it "returns status 200" do
- delete :destroy, request_params
+ delete :destroy, params: request_params
expect(response).to have_gitlab_http_status(200)
end
it "deletes the note" do
- expect { delete :destroy, request_params }.to change { Note.count }.from(1).to(0)
+ expect { delete :destroy, params: request_params }.to change { Note.count }.from(1).to(0)
end
context 'system note' do
@@ -147,7 +147,7 @@ describe Snippets::NotesController do
end
it "does not delete the note" do
- expect { delete :destroy, request_params }.not_to change { Note.count }
+ expect { delete :destroy, params: request_params }.not_to change { Note.count }
end
end
end
@@ -160,13 +160,13 @@ describe Snippets::NotesController do
end
it "returns status 404" do
- delete :destroy, request_params
+ delete :destroy, params: request_params
expect(response).to have_gitlab_http_status(404)
end
it "does not update the note" do
- expect { delete :destroy, request_params }.not_to change { Note.count }
+ expect { delete :destroy, params: request_params }.not_to change { Note.count }
end
end
end
@@ -177,7 +177,7 @@ describe Snippets::NotesController do
sign_in(user)
end
- subject { post(:toggle_award_emoji, snippet_id: public_snippet, id: note.id, name: "thumbsup") }
+ subject { post(:toggle_award_emoji, params: { snippet_id: public_snippet, id: note.id, name: "thumbsup" }) }
it "toggles the award emoji" do
expect { subject }.to change { note.award_emoji.count }.by(1)
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 9effe47ab05..d2a56518f65 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -8,7 +8,7 @@ describe SnippetsController do
context 'when username parameter is present' do
it 'renders snippets of a user when username is present' do
- get :index, username: user.username
+ get :index, params: { username: user.username }
expect(response).to render_template(:index)
end
@@ -67,7 +67,7 @@ describe SnippetsController do
let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) }
it 'responds with status 404' do
- get :show, id: other_personal_snippet.to_param
+ get :show, params: { id: other_personal_snippet.to_param }
expect(response).to have_gitlab_http_status(404)
end
@@ -75,7 +75,7 @@ describe SnippetsController do
context 'when signed in user is the author' do
it 'renders the snippet' do
- get :show, id: personal_snippet.to_param
+ get :show, params: { id: personal_snippet.to_param }
expect(assigns(:snippet)).to eq(personal_snippet)
expect(response).to have_gitlab_http_status(200)
@@ -85,7 +85,7 @@ describe SnippetsController do
context 'when not signed in' do
it 'redirects to the sign in page' do
- get :show, id: personal_snippet.to_param
+ get :show, params: { id: personal_snippet.to_param }
expect(response).to redirect_to(new_user_session_path)
end
@@ -101,7 +101,7 @@ describe SnippetsController do
end
it 'renders the snippet' do
- get :show, id: personal_snippet.to_param
+ get :show, params: { id: personal_snippet.to_param }
expect(assigns(:snippet)).to eq(personal_snippet)
expect(response).to have_gitlab_http_status(200)
@@ -110,7 +110,7 @@ describe SnippetsController do
context 'when not signed in' do
it 'redirects to the sign in page' do
- get :show, id: personal_snippet.to_param
+ get :show, params: { id: personal_snippet.to_param }
expect(response).to redirect_to(new_user_session_path)
end
@@ -126,7 +126,7 @@ describe SnippetsController do
end
it 'renders the snippet' do
- get :show, id: personal_snippet.to_param
+ get :show, params: { id: personal_snippet.to_param }
expect(assigns(:snippet)).to eq(personal_snippet)
expect(response).to have_gitlab_http_status(200)
@@ -135,7 +135,7 @@ describe SnippetsController do
context 'when not signed in' do
it 'renders the snippet' do
- get :show, id: personal_snippet.to_param
+ get :show, params: { id: personal_snippet.to_param }
expect(assigns(:snippet)).to eq(personal_snippet)
expect(response).to have_gitlab_http_status(200)
@@ -150,7 +150,7 @@ describe SnippetsController do
end
it 'responds with status 404' do
- get :show, id: 'doesntexist'
+ get :show, params: { id: 'doesntexist' }
expect(response).to have_gitlab_http_status(404)
end
@@ -158,7 +158,7 @@ describe SnippetsController do
context 'when not signed in' do
it 'responds with status 404' do
- get :show, id: 'doesntexist'
+ get :show, params: { id: 'doesntexist' }
expect(response).to redirect_to(new_user_session_path)
end
@@ -170,7 +170,7 @@ describe SnippetsController do
def create_snippet(snippet_params = {}, additional_params = {})
sign_in(user)
- post :create, {
+ post :create, params: {
personal_snippet: { title: 'Title', content: 'Content', description: 'Description' }.merge(snippet_params)
}.merge(additional_params)
@@ -279,7 +279,7 @@ describe SnippetsController do
def update_snippet(snippet_params = {}, additional_params = {})
sign_in(user)
- put :update, {
+ put :update, params: {
id: snippet.id,
personal_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
}.merge(additional_params)
@@ -406,7 +406,7 @@ describe SnippetsController do
create(:user_agent_detail, subject: snippet)
sign_in(admin)
- post :mark_as_spam, id: snippet.id
+ post :mark_as_spam, params: { id: snippet.id }
end
it 'updates the snippet' do
@@ -430,7 +430,7 @@ describe SnippetsController do
let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) }
it 'responds with status 404' do
- get :raw, id: other_personal_snippet.to_param
+ get :raw, params: { id: other_personal_snippet.to_param }
expect(response).to have_gitlab_http_status(404)
end
@@ -438,7 +438,7 @@ describe SnippetsController do
context 'when signed in user is the author' do
before do
- get :raw, id: personal_snippet.to_param
+ get :raw, params: { id: personal_snippet.to_param }
end
it 'responds with status 200' do
@@ -451,12 +451,17 @@ describe SnippetsController do
expect(response.header['Content-Disposition']).to match(/inline/)
end
+
+ it "sets #{Gitlab::Workhorse::DETECT_HEADER} header" do
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
+ end
end
end
context 'when not signed in' do
it 'redirects to the sign in page' do
- get :raw, id: personal_snippet.to_param
+ get :raw, params: { id: personal_snippet.to_param }
expect(response).to redirect_to(new_user_session_path)
end
@@ -472,7 +477,7 @@ describe SnippetsController do
end
it 'responds with status 200' do
- get :raw, id: personal_snippet.to_param
+ get :raw, params: { id: personal_snippet.to_param }
expect(assigns(:snippet)).to eq(personal_snippet)
expect(response).to have_gitlab_http_status(200)
@@ -481,7 +486,7 @@ describe SnippetsController do
context 'when not signed in' do
it 'redirects to the sign in page' do
- get :raw, id: personal_snippet.to_param
+ get :raw, params: { id: personal_snippet.to_param }
expect(response).to redirect_to(new_user_session_path)
end
@@ -497,7 +502,7 @@ describe SnippetsController do
end
it 'responds with status 200' do
- get :raw, id: personal_snippet.to_param
+ get :raw, params: { id: personal_snippet.to_param }
expect(assigns(:snippet)).to eq(personal_snippet)
expect(response).to have_gitlab_http_status(200)
@@ -509,13 +514,13 @@ describe SnippetsController do
end
it 'returns LF line endings by default' do
- get :raw, id: personal_snippet.to_param
+ get :raw, params: { id: personal_snippet.to_param }
expect(response.body).to eq("first line\nsecond line\nthird line")
end
it 'does not convert line endings when parameter present' do
- get :raw, id: personal_snippet.to_param, line_ending: :raw
+ get :raw, params: { id: personal_snippet.to_param, line_ending: :raw }
expect(response.body).to eq("first line\r\nsecond line\r\nthird line")
end
@@ -524,7 +529,7 @@ describe SnippetsController do
context 'when not signed in' do
it 'responds with status 200' do
- get :raw, id: personal_snippet.to_param
+ get :raw, params: { id: personal_snippet.to_param }
expect(assigns(:snippet)).to eq(personal_snippet)
expect(response).to have_gitlab_http_status(200)
@@ -539,7 +544,7 @@ describe SnippetsController do
end
it 'responds with status 404' do
- get :raw, id: 'doesntexist'
+ get :raw, params: { id: 'doesntexist' }
expect(response).to have_gitlab_http_status(404)
end
@@ -547,7 +552,7 @@ describe SnippetsController do
context 'when not signed in' do
it 'redirects to the sign in path' do
- get :raw, id: 'doesntexist'
+ get :raw, params: { id: 'doesntexist' }
expect(response).to redirect_to(new_user_session_path)
end
@@ -566,17 +571,17 @@ describe SnippetsController do
describe 'POST #toggle_award_emoji' do
it "toggles the award emoji" do
expect do
- post(:toggle_award_emoji, id: personal_snippet.to_param, name: "thumbsup")
+ post(:toggle_award_emoji, params: { id: personal_snippet.to_param, name: "thumbsup" })
end.to change { personal_snippet.award_emoji.count }.from(0).to(1)
expect(response.status).to eq(200)
end
it "removes the already awarded emoji" do
- post(:toggle_award_emoji, id: personal_snippet.to_param, name: "thumbsup")
+ post(:toggle_award_emoji, params: { id: personal_snippet.to_param, name: "thumbsup" })
expect do
- post(:toggle_award_emoji, id: personal_snippet.to_param, name: "thumbsup")
+ post(:toggle_award_emoji, params: { id: personal_snippet.to_param, name: "thumbsup" })
end.to change { personal_snippet.award_emoji.count }.from(1).to(0)
expect(response.status).to eq(200)
@@ -590,7 +595,7 @@ describe SnippetsController do
it 'renders json in a correct format' do
sign_in(user)
- post :preview_markdown, id: snippet, text: '*Markdown* text'
+ post :preview_markdown, params: { id: snippet, text: '*Markdown* text' }
expect(JSON.parse(response.body).keys).to match_array(%w(body references))
end
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index 6420b70a54f..19142aa1272 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -8,11 +8,7 @@ end
shared_examples 'content not cached without revalidation and no-store' do
it 'ensures content will not be cached without revalidation' do
# Fixed in newer versions of ActivePack, it will only output a single `private`.
- if Gitlab.rails5?
- expect(subject['Cache-Control']).to eq('max-age=0, private, must-revalidate, no-store')
- else
- expect(subject['Cache-Control']).to eq('max-age=0, private, must-revalidate, private, no-store')
- end
+ expect(subject['Cache-Control']).to eq('max-age=0, private, must-revalidate, no-store')
end
end
@@ -27,7 +23,7 @@ describe UploadsController do
context 'when a user does not have permissions to upload a file' do
it "returns 401 when the user is not logged in" do
- post :create, model: model, id: snippet.id, format: :json
+ post :create, params: { model: model, id: snippet.id }, format: :json
expect(response).to have_gitlab_http_status(401)
end
@@ -36,7 +32,7 @@ describe UploadsController do
private_snippet = create(:personal_snippet, :private)
sign_in(user)
- post :create, model: model, id: private_snippet.id, format: :json
+ post :create, params: { model: model, id: private_snippet.id }, format: :json
expect(response).to have_gitlab_http_status(404)
end
@@ -48,25 +44,25 @@ describe UploadsController do
end
it "returns an error without file" do
- post :create, model: model, id: snippet.id, format: :json
+ post :create, params: { model: model, id: snippet.id }, format: :json
expect(response).to have_gitlab_http_status(422)
end
it "returns an error with invalid model" do
- expect { post :create, model: 'invalid', id: snippet.id, format: :json }
+ expect { post :create, params: { model: 'invalid', id: snippet.id }, format: :json }
.to raise_error(ActionController::UrlGenerationError)
end
it "returns 404 status when object not found" do
- post :create, model: model, id: 9999, format: :json
+ post :create, params: { model: model, id: 9999 }, format: :json
expect(response).to have_gitlab_http_status(404)
end
context 'with valid image' do
before do
- post :create, model: 'personal_snippet', id: snippet.id, file: jpg, format: :json
+ post :create, params: { model: 'personal_snippet', id: snippet.id, file: jpg }, format: :json
end
it 'returns a content with original filename, new link, and correct type.' do
@@ -86,7 +82,7 @@ describe UploadsController do
context 'with valid non-image file' do
before do
- post :create, model: 'personal_snippet', id: snippet.id, file: txt, format: :json
+ post :create, params: { model: 'personal_snippet', id: snippet.id, file: txt }, format: :json
end
it 'returns a content with original filename, new link, and correct type.' do
@@ -106,7 +102,7 @@ describe UploadsController do
context 'temporal with valid image' do
subject do
- post :create, model: 'personal_snippet', file: jpg, format: :json
+ post :create, params: { model: 'personal_snippet', file: jpg }, format: :json
end
it 'returns a content with original filename, new link, and correct type.' do
@@ -123,7 +119,7 @@ describe UploadsController do
context 'temporal with valid non-image file' do
subject do
- post :create, model: 'personal_snippet', file: txt, format: :json
+ post :create, params: { model: 'personal_snippet', file: txt }, format: :json
end
it 'returns a content with original filename, new link, and correct type.' do
@@ -147,7 +143,7 @@ describe UploadsController do
context 'for PNG files' do
it 'returns Content-Disposition: inline' do
note = create(:note, :with_attachment, project: project)
- get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png'
+ get :show, params: { model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' }
expect(response['Content-Disposition']).to start_with('inline;')
end
@@ -156,7 +152,7 @@ describe UploadsController do
context 'for SVG files' do
it 'returns Content-Disposition: attachment' do
note = create(:note, :with_svg_attachment, project: project)
- get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'unsanitized.svg'
+ get :show, params: { model: 'note', mounted_as: 'attachment', id: note.id, filename: 'unsanitized.svg' }
expect(response['Content-Disposition']).to start_with('attachment;')
end
@@ -175,7 +171,7 @@ describe UploadsController do
end
it "redirects to the sign in page" do
- get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png"
+ get :show, params: { model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png" }
expect(response).to redirect_to(new_user_session_path)
end
@@ -183,14 +179,14 @@ describe UploadsController do
context "when the user isn't blocked" do
it "responds with status 200" do
- get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png"
+ get :show, params: { model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png" }
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation and no-store' do
subject do
- get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png'
+ get :show, params: { model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png' }
response
end
@@ -200,14 +196,14 @@ describe UploadsController do
context "when not signed in" do
it "responds with status 200" do
- get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png"
+ get :show, params: { model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png" }
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation' do
subject do
- get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png'
+ get :show, params: { model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png' }
response
end
@@ -225,14 +221,14 @@ describe UploadsController do
context "when not signed in" do
it "responds with status 200" do
- get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png"
+ get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" }
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation' do
subject do
- get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png'
+ get :show, params: { model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png' }
response
end
@@ -245,14 +241,14 @@ describe UploadsController do
end
it "responds with status 200" do
- get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png"
+ get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" }
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation and no-store' do
subject do
- get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png'
+ get :show, params: { model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png' }
response
end
@@ -267,7 +263,7 @@ describe UploadsController do
context "when not signed in" do
it "redirects to the sign in page" do
- get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png"
+ get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" }
expect(response).to redirect_to(new_user_session_path)
end
@@ -290,7 +286,7 @@ describe UploadsController do
end
it "redirects to the sign in page" do
- get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png"
+ get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" }
expect(response).to redirect_to(new_user_session_path)
end
@@ -298,14 +294,14 @@ describe UploadsController do
context "when the user isn't blocked" do
it "responds with status 200" do
- get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png"
+ get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" }
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation and no-store' do
subject do
- get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png'
+ get :show, params: { model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png' }
response
end
@@ -315,7 +311,7 @@ describe UploadsController do
context "when the user doesn't have access to the project" do
it "responds with status 404" do
- get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png"
+ get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" }
expect(response).to have_gitlab_http_status(404)
end
@@ -330,14 +326,14 @@ describe UploadsController do
context "when the group is public" do
context "when not signed in" do
it "responds with status 200" do
- get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png"
+ get :show, params: { model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" }
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation' do
subject do
- get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png'
+ get :show, params: { model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png' }
response
end
@@ -350,14 +346,14 @@ describe UploadsController do
end
it "responds with status 200" do
- get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png"
+ get :show, params: { model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" }
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation and no-store' do
subject do
- get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png'
+ get :show, params: { model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png' }
response
end
@@ -386,7 +382,7 @@ describe UploadsController do
end
it "redirects to the sign in page" do
- get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png"
+ get :show, params: { model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" }
expect(response).to redirect_to(new_user_session_path)
end
@@ -394,14 +390,14 @@ describe UploadsController do
context "when the user isn't blocked" do
it "responds with status 200" do
- get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png"
+ get :show, params: { model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" }
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation and no-store' do
subject do
- get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png'
+ get :show, params: { model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png' }
response
end
@@ -411,7 +407,7 @@ describe UploadsController do
context "when the user doesn't have access to the project" do
it "responds with status 404" do
- get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png"
+ get :show, params: { model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" }
expect(response).to have_gitlab_http_status(404)
end
@@ -431,14 +427,14 @@ describe UploadsController do
context "when not signed in" do
it "responds with status 200" do
- get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png"
+ get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" }
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation' do
subject do
- get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png'
+ get :show, params: { model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' }
response
end
@@ -451,14 +447,14 @@ describe UploadsController do
end
it "responds with status 200" do
- get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png"
+ get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" }
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation and no-store' do
subject do
- get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png'
+ get :show, params: { model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' }
response
end
@@ -473,7 +469,7 @@ describe UploadsController do
context "when not signed in" do
it "redirects to the sign in page" do
- get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png"
+ get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" }
expect(response).to redirect_to(new_user_session_path)
end
@@ -496,7 +492,7 @@ describe UploadsController do
end
it "redirects to the sign in page" do
- get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png"
+ get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" }
expect(response).to redirect_to(new_user_session_path)
end
@@ -504,14 +500,14 @@ describe UploadsController do
context "when the user isn't blocked" do
it "responds with status 200" do
- get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png"
+ get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" }
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation and no-store' do
subject do
- get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png'
+ get :show, params: { model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' }
response
end
@@ -521,7 +517,7 @@ describe UploadsController do
context "when the user doesn't have access to the project" do
it "responds with status 404" do
- get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png"
+ get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" }
expect(response).to have_gitlab_http_status(404)
end
@@ -536,14 +532,14 @@ describe UploadsController do
context 'when not signed in' do
it 'responds with status 200' do
- get :show, model: 'appearance', mounted_as: 'header_logo', id: appearance.id, filename: 'dk.png'
+ get :show, params: { model: 'appearance', mounted_as: 'header_logo', id: appearance.id, filename: 'dk.png' }
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation' do
subject do
- get :show, model: 'appearance', mounted_as: 'header_logo', id: appearance.id, filename: 'dk.png'
+ get :show, params: { model: 'appearance', mounted_as: 'header_logo', id: appearance.id, filename: 'dk.png' }
response
end
@@ -556,14 +552,14 @@ describe UploadsController do
context 'when not signed in' do
it 'responds with status 200' do
- get :show, model: 'appearance', mounted_as: 'logo', id: appearance.id, filename: 'dk.png'
+ get :show, params: { model: 'appearance', mounted_as: 'logo', id: appearance.id, filename: 'dk.png' }
expect(response).to have_gitlab_http_status(200)
end
it_behaves_like 'content not cached without revalidation' do
subject do
- get :show, model: 'appearance', mounted_as: 'logo', id: appearance.id, filename: 'dk.png'
+ get :show, params: { model: 'appearance', mounted_as: 'logo', id: appearance.id, filename: 'dk.png' }
response
end
@@ -577,7 +573,7 @@ describe UploadsController do
context 'has a valid filename on the original file' do
it 'successfully returns the file' do
- get :show, model: 'appearance', mounted_as: 'favicon', id: appearance.id, filename: 'dk.png'
+ get :show, params: { model: 'appearance', mounted_as: 'favicon', id: appearance.id, filename: 'dk.png' }
expect(response).to have_gitlab_http_status(200)
expect(response.header['Content-Disposition']).to end_with 'filename="dk.png"'
@@ -586,7 +582,7 @@ describe UploadsController do
context 'has an invalid filename on the original file' do
it 'returns a 404' do
- get :show, model: 'appearance', mounted_as: 'favicon', id: appearance.id, filename: 'bogus.png'
+ get :show, params: { model: 'appearance', mounted_as: 'favicon', id: appearance.id, filename: 'bogus.png' }
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/controllers/user_callouts_controller_spec.rb b/spec/controllers/user_callouts_controller_spec.rb
index 48e2ff75cac..c71d75a3e7f 100644
--- a/spec/controllers/user_callouts_controller_spec.rb
+++ b/spec/controllers/user_callouts_controller_spec.rb
@@ -8,7 +8,7 @@ describe UserCalloutsController do
end
describe "POST #create" do
- subject { post :create, feature_name: feature_name, format: :json }
+ subject { post :create, params: { feature_name: feature_name }, format: :json }
context 'with valid feature name' do
let(:feature_name) { UserCallout.feature_names.keys.first }
diff --git a/spec/controllers/users/terms_controller_spec.rb b/spec/controllers/users/terms_controller_spec.rb
index 0d77e91a67d..cbfd2b17864 100644
--- a/spec/controllers/users/terms_controller_spec.rb
+++ b/spec/controllers/users/terms_controller_spec.rb
@@ -40,7 +40,7 @@ describe Users::TermsController do
describe 'POST #accept' do
it 'saves that the user accepted the terms' do
- post :accept, id: term.id
+ post :accept, params: { id: term.id }
agreement = user.term_agreements.find_by(term: term)
@@ -48,7 +48,7 @@ describe Users::TermsController do
end
it 'redirects to a path when specified' do
- post :accept, id: term.id, redirect: groups_path
+ post :accept, params: { id: term.id, redirect: groups_path }
expect(response).to redirect_to(groups_path)
end
@@ -56,14 +56,14 @@ describe Users::TermsController do
it 'redirects to the referer when no redirect specified' do
request.env["HTTP_REFERER"] = groups_url
- post :accept, id: term.id
+ post :accept, params: { id: term.id }
expect(response).to redirect_to(groups_path)
end
context 'redirecting to another domain' do
it 'is prevented when passing a redirect param' do
- post :accept, id: term.id, redirect: '//example.com/random/path'
+ post :accept, params: { id: term.id, redirect: '//example.com/random/path' }
expect(response).to redirect_to(root_path)
end
@@ -71,7 +71,7 @@ describe Users::TermsController do
it 'is prevented when redirecting to the referer' do
request.env["HTTP_REFERER"] = 'http://example.com/and/a/path'
- post :accept, id: term.id
+ post :accept, params: { id: term.id }
expect(response).to redirect_to(root_path)
end
@@ -80,7 +80,7 @@ describe Users::TermsController do
describe 'POST #decline' do
it 'stores that the user declined the terms' do
- post :decline, id: term.id
+ post :decline, params: { id: term.id }
agreement = user.term_agreements.find_by(term: term)
@@ -88,7 +88,7 @@ describe Users::TermsController do
end
it 'signs out the user' do
- post :decline, id: term.id
+ post :decline, params: { id: term.id }
expect(response).to redirect_to(root_path)
expect(assigns(:current_user)).to be_nil
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index fe438e71e9e..27edf226ca3 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -15,7 +15,7 @@ describe UsersController do
end
it 'renders the show template' do
- get :show, username: user.username
+ get :show, params: { username: user.username }
expect(response).to be_success
expect(response).to render_template('show')
@@ -24,7 +24,7 @@ describe UsersController do
describe 'when logged out' do
it 'renders the show template' do
- get :show, username: user.username
+ get :show, params: { username: user.username }
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template('show')
@@ -39,7 +39,7 @@ describe UsersController do
context 'when logged out' do
it 'redirects to login page' do
- get :show, username: user.username
+ get :show, params: { username: user.username }
expect(response).to redirect_to new_user_session_path
end
end
@@ -50,7 +50,7 @@ describe UsersController do
end
it 'renders show' do
- get :show, username: user.username
+ get :show, params: { username: user.username }
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template('show')
end
@@ -60,7 +60,7 @@ describe UsersController do
context 'when a user by that username does not exist' do
context 'when logged out' do
it 'redirects to login page' do
- get :show, username: 'nonexistent'
+ get :show, params: { username: 'nonexistent' }
expect(response).to redirect_to new_user_session_path
end
end
@@ -71,7 +71,7 @@ describe UsersController do
end
it 'renders 404' do
- get :show, username: 'nonexistent'
+ get :show, params: { username: 'nonexistent' }
expect(response).to have_gitlab_http_status(404)
end
end
@@ -87,7 +87,7 @@ describe UsersController do
end
it 'loads events' do
- get :show, username: user, format: :json
+ get :show, params: { username: user }, format: :json
expect(assigns(:events)).not_to be_empty
end
@@ -96,7 +96,7 @@ describe UsersController do
allow(Ability).to receive(:allowed?).and_call_original
expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
- get :show, username: user, format: :json
+ get :show, params: { username: user }, format: :json
expect(assigns(:events)).to be_empty
end
@@ -104,7 +104,7 @@ describe UsersController do
it 'hides events if the user has a private profile' do
Gitlab::DataBuilder::Push.build_sample(project, private_user)
- get :show, username: private_user.username, format: :json
+ get :show, params: { username: private_user.username }, format: :json
expect(assigns(:events)).to be_empty
end
@@ -125,7 +125,7 @@ describe UsersController do
push_data = Gitlab::DataBuilder::Push.build_sample(project, public_user)
EventCreateService.new.push(project, public_user, push_data)
- get :calendar, username: public_user.username, format: :json
+ get :calendar, params: { username: public_user.username }, format: :json
expect(response).to have_gitlab_http_status(200)
end
@@ -136,7 +136,7 @@ describe UsersController do
push_data = Gitlab::DataBuilder::Push.build_sample(project, private_user)
EventCreateService.new.push(project, private_user, push_data)
- get :calendar, username: private_user.username, format: :json
+ get :calendar, params: { username: private_user.username }, format: :json
expect(response).to have_gitlab_http_status(:not_found)
end
@@ -161,7 +161,7 @@ describe UsersController do
end
it 'includes forked projects' do
- get :calendar, username: user.username
+ get :calendar, params: { username: user.username }
expect(assigns(:contributions_calendar).projects.count).to eq(2)
end
end
@@ -179,7 +179,7 @@ describe UsersController do
end
it 'assigns @calendar_date' do
- get :calendar_activities, username: user.username, date: '2014-07-31'
+ get :calendar_activities, params: { username: user.username, date: '2014-07-31' }
expect(assigns(:calendar_date)).to eq(Date.parse('2014-07-31'))
end
@@ -189,7 +189,7 @@ describe UsersController do
push_data = Gitlab::DataBuilder::Push.build_sample(project, public_user)
EventCreateService.new.push(project, public_user, push_data)
- get :calendar_activities, username: public_user.username
+ get :calendar_activities, params: { username: public_user.username }
expect(assigns[:events]).not_to be_empty
end
end
@@ -199,7 +199,7 @@ describe UsersController do
push_data = Gitlab::DataBuilder::Push.build_sample(project, private_user)
EventCreateService.new.push(project, private_user, push_data)
- get :calendar_activities, username: private_user.username
+ get :calendar_activities, params: { username: private_user.username }
expect(response).to have_gitlab_http_status(:not_found)
end
end
@@ -213,7 +213,7 @@ describe UsersController do
context 'format html' do
it 'renders snippets page' do
- get :snippets, username: user.username
+ get :snippets, params: { username: user.username }
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template('show')
end
@@ -221,7 +221,7 @@ describe UsersController do
context 'format json' do
it 'response with snippets json data' do
- get :snippets, username: user.username, format: :json
+ get :snippets, params: { username: user.username }, format: :json
expect(response).to have_gitlab_http_status(200)
expect(JSON.parse(response.body)).to have_key('html')
end
@@ -235,7 +235,7 @@ describe UsersController do
context 'when user exists' do
it 'returns JSON indicating the user exists' do
- get :exists, username: user.username
+ get :exists, params: { username: user.username }
expected_json = { exists: true }.to_json
expect(response.body).to eq(expected_json)
@@ -245,7 +245,7 @@ describe UsersController do
let(:user) { create(:user, username: 'CamelCaseUser') }
it 'returns JSON indicating the user exists' do
- get :exists, username: user.username.downcase
+ get :exists, params: { username: user.username.downcase }
expected_json = { exists: true }.to_json
expect(response.body).to eq(expected_json)
@@ -255,7 +255,7 @@ describe UsersController do
context 'when the user does not exist' do
it 'returns JSON indicating the user does not exist' do
- get :exists, username: 'foo'
+ get :exists, params: { username: 'foo' }
expected_json = { exists: false }.to_json
expect(response.body).to eq(expected_json)
@@ -265,7 +265,7 @@ describe UsersController do
let(:redirect_route) { user.namespace.redirect_routes.create(path: 'old-username') }
it 'returns JSON indicating a user by that username does not exist' do
- get :exists, username: 'old-username'
+ get :exists, params: { username: 'old-username' }
expected_json = { exists: false }.to_json
expect(response.body).to eq(expected_json)
@@ -286,7 +286,7 @@ describe UsersController do
context 'with exactly matching casing' do
it 'responds with success' do
- get :show, username: user.username
+ get :show, params: { username: user.username }
expect(response).to be_success
end
@@ -294,7 +294,7 @@ describe UsersController do
context 'with different casing' do
it 'redirects to the correct casing' do
- get :show, username: user.username.downcase
+ get :show, params: { username: user.username.downcase }
expect(response).to redirect_to(user)
expect(controller).not_to set_flash[:notice]
@@ -306,7 +306,7 @@ describe UsersController do
let(:redirect_route) { user.namespace.redirect_routes.create(path: 'old-path') }
it 'redirects to the canonical path' do
- get :show, username: redirect_route.path
+ get :show, params: { username: redirect_route.path }
expect(response).to redirect_to(user)
expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user))
@@ -316,7 +316,7 @@ describe UsersController do
let(:redirect_route) { user.namespace.redirect_routes.create(path: 'http') }
it 'does not modify the requested host' do
- get :show, username: redirect_route.path
+ get :show, params: { username: redirect_route.path }
expect(response).to redirect_to(user)
expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user))
@@ -327,7 +327,7 @@ describe UsersController do
let(:redirect_route) { user.namespace.redirect_routes.create(path: 'ser') }
it 'redirects to the canonical path' do
- get :show, username: redirect_route.path
+ get :show, params: { username: redirect_route.path }
expect(response).to redirect_to(user)
expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user))
@@ -342,7 +342,7 @@ describe UsersController do
context 'with exactly matching casing' do
it 'responds with success' do
- get :projects, username: user.username
+ get :projects, params: { username: user.username }
expect(response).to be_success
end
@@ -350,7 +350,7 @@ describe UsersController do
context 'with different casing' do
it 'redirects to the correct casing' do
- get :projects, username: user.username.downcase
+ get :projects, params: { username: user.username.downcase }
expect(response).to redirect_to(user_projects_path(user))
expect(controller).not_to set_flash[:notice]
@@ -362,7 +362,7 @@ describe UsersController do
let(:redirect_route) { user.namespace.redirect_routes.create(path: 'old-path') }
it 'redirects to the canonical path' do
- get :projects, username: redirect_route.path
+ get :projects, params: { username: redirect_route.path }
expect(response).to redirect_to(user_projects_path(user))
expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user))
@@ -372,7 +372,7 @@ describe UsersController do
let(:redirect_route) { user.namespace.redirect_routes.create(path: 'http') }
it 'does not modify the requested host' do
- get :projects, username: redirect_route.path
+ get :projects, params: { username: redirect_route.path }
expect(response).to redirect_to(user_projects_path(user))
expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user))
@@ -384,7 +384,7 @@ describe UsersController do
# I.e. /users/ser should not become /ufoos/ser
it 'does not modify the /users part of the path' do
- get :projects, username: redirect_route.path
+ get :projects, params: { username: redirect_route.path }
expect(response).to redirect_to(user_projects_path(user))
expect(controller).to set_flash[:notice].to(user_moved_message(redirect_route, user))
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index e8584846b56..7c505ee0d43 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -54,7 +54,8 @@ describe 'Database schema' do
user_agent_details: %w[subject_id],
users: %w[color_scheme_id created_by_id theme_id],
users_star_projects: %w[user_id],
- web_hooks: %w[service_id]
+ web_hooks: %w[service_id],
+ suggestions: %w[commit_id]
}.with_indifferent_access.freeze
context 'for table' do
diff --git a/spec/factories/appearances.rb b/spec/factories/appearances.rb
index 18c7453bd1b..dd5129229d4 100644
--- a/spec/factories/appearances.rb
+++ b/spec/factories/appearances.rb
@@ -15,6 +15,10 @@ FactoryBot.define do
header_logo { fixture_file_upload('spec/fixtures/dk.png') }
end
+ trait :with_favicon do
+ favicon { fixture_file_upload('spec/fixtures/dk.png') }
+ end
+
trait :with_logos do
with_logo
with_header_logo
diff --git a/spec/factories/ci/bridge.rb b/spec/factories/ci/bridge.rb
new file mode 100644
index 00000000000..5f83b80ad7b
--- /dev/null
+++ b/spec/factories/ci/bridge.rb
@@ -0,0 +1,17 @@
+FactoryBot.define do
+ factory :ci_bridge, class: Ci::Bridge do
+ name ' bridge'
+ stage 'test'
+ stage_idx 0
+ ref 'master'
+ tag false
+ created_at 'Di 29. Okt 09:50:00 CET 2013'
+ status :success
+
+ pipeline factory: :ci_pipeline
+
+ after(:build) do |bridge, evaluator|
+ bridge.project ||= bridge.pipeline.project
+ end
+ end
+end
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 2d1f48bf249..c51f2f958f9 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -64,6 +64,21 @@ FactoryBot.define do
resolved_at { Time.now }
resolved_by { create(:user) }
end
+
+ factory :image_diff_note_on_merge_request do
+ position do
+ Gitlab::Diff::Position.new(
+ old_path: "files/images/any_image.png",
+ new_path: "files/images/any_image.png",
+ width: 10,
+ height: 10,
+ x: 1,
+ y: 1,
+ diff_refs: diff_refs,
+ position_type: "image"
+ )
+ end
+ end
end
factory :diff_note_on_commit, traits: [:on_commit], class: DiffNote do
diff --git a/spec/factories/pool_repositories.rb b/spec/factories/pool_repositories.rb
index 2ed0844ed47..36e54cf44b4 100644
--- a/spec/factories/pool_repositories.rb
+++ b/spec/factories/pool_repositories.rb
@@ -1,5 +1,30 @@
FactoryBot.define do
factory :pool_repository do
- shard
+ shard { Shard.by_name("default") }
+ state :none
+
+ before(:create) do |pool|
+ pool.source_project = create(:project, :repository)
+ end
+
+ trait :scheduled do
+ state :scheduled
+ end
+
+ trait :failed do
+ state :failed
+ end
+
+ trait :obsolete do
+ state :obsolete
+ end
+
+ trait :ready do
+ state :ready
+
+ after(:create) do |pool|
+ pool.create_object_pool
+ end
+ end
end
end
diff --git a/spec/factories/project_repositories.rb b/spec/factories/project_repositories.rb
new file mode 100644
index 00000000000..39e8ea2e11e
--- /dev/null
+++ b/spec/factories/project_repositories.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :project_repository do
+ project
+
+ after(:build) do |project_repository, _|
+ project_repository.shard_name = project_repository.project.repository_storage
+ project_repository.disk_path = project_repository.project.disk_path
+ end
+ end
+end
diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb
index d80c65cf8bb..18047c74a5d 100644
--- a/spec/factories/releases.rb
+++ b/spec/factories/releases.rb
@@ -1,6 +1,7 @@
FactoryBot.define do
factory :release do
tag "v1.1.0"
+ name { tag }
description "Awesome release"
project
end
diff --git a/spec/factories/suggestions.rb b/spec/factories/suggestions.rb
new file mode 100644
index 00000000000..307523cc061
--- /dev/null
+++ b/spec/factories/suggestions.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :suggestion do
+ relative_order 0
+ association :note, factory: :diff_note_on_merge_request
+ from_content " vars = {\n"
+ to_content " vars = [\n"
+
+ trait :unappliable do
+ from_content "foo"
+ to_content "foo"
+ end
+
+ trait :applied do
+ applied true
+ commit_id { RepoHelpers.sample_commit.id }
+ end
+ end
+end
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 91c22e7ad82..f066b088800 100644
--- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb
+++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
@@ -1,7 +1,8 @@
require 'rails_helper'
-describe 'Admin disables Git access protocol' do
+describe 'Admin disables Git access protocol', :js do
include StubENV
+ include MobileHelpers
let(:project) { create(:project, :empty_repo) }
let(:admin) { create(:admin) }
@@ -20,7 +21,24 @@ describe 'Admin disables Git access protocol' do
visit_project
expect(page).to have_content("git clone #{project.ssh_url_to_repo}")
- expect(page).not_to have_selector('#clone-dropdown')
+
+ find('.clone-dropdown-btn').click
+
+ within('.git-clone-holder') do
+ expect(page).to have_content('Clone with SSH')
+ expect(page).not_to have_content('Clone with HTTP')
+ end
+ end
+
+ context 'mobile component' do
+ it 'shows only the SSH clone information' do
+ resize_screen_xs
+ visit_project
+ find('.dropdown-toggle').click
+
+ expect(page).to have_content('Copy SSH clone URL')
+ expect(page).not_to have_content('Copy HTTP clone URL')
+ end
end
end
@@ -31,9 +49,25 @@ describe 'Admin disables Git access protocol' do
it 'shows only HTTP url' do
visit_project
+ find('.clone-dropdown-btn').click
expect(page).to have_content("git clone #{project.http_url_to_repo}")
- expect(page).not_to have_selector('#clone-dropdown')
+
+ within('.git-clone-holder') do
+ expect(page).to have_content('Clone with HTTP')
+ expect(page).not_to have_content('Clone with SSH')
+ end
+ end
+
+ context 'mobile component' do
+ it 'shows only the HTTP clone information' do
+ resize_screen_xs
+ visit_project
+ find('.dropdown-toggle').click
+
+ expect(page).to have_content('Copy HTTP clone URL')
+ expect(page).not_to have_content('Copy SSH clone URL')
+ end
end
end
@@ -46,7 +80,24 @@ describe 'Admin disables Git access protocol' do
visit_project
expect(page).to have_content("git clone #{project.ssh_url_to_repo}")
- expect(page).to have_selector('#clone-dropdown')
+
+ find('.clone-dropdown-btn').click
+
+ within('.git-clone-holder') do
+ expect(page).to have_content('Clone with SSH')
+ expect(page).to have_content('Clone with HTTP')
+ end
+ end
+
+ context 'mobile component' do
+ it 'shows both SSH and HTTP clone information' do
+ resize_screen_xs
+ visit_project
+ find('.dropdown-toggle').click
+
+ expect(page).to have_content('Copy HTTP clone URL')
+ expect(page).to have_content('Copy SSH clone URL')
+ end
end
end
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index 9ffa75aee47..282bf542e77 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -6,7 +6,6 @@ describe 'Dashboard Merge Requests' do
include ProjectForksHelper
let(:current_user) { create :user }
- let(:user) { current_user }
let(:project) { create(:project) }
let(:public_project) { create(:project, :public, :repository) }
diff --git a/spec/features/groups/clusters/user_spec.rb b/spec/features/groups/clusters/user_spec.rb
new file mode 100644
index 00000000000..2410cd92e3f
--- /dev/null
+++ b/spec/features/groups/clusters/user_spec.rb
@@ -0,0 +1,126 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'User Cluster', :js do
+ include GoogleApi::CloudPlatformHelpers
+
+ let(:group) { create(:group) }
+ let(:user) { create(:user) }
+
+ before do
+ group.add_maintainer(user)
+ gitlab_sign_in(user)
+
+ allow(Groups::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 }
+ allow_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
+ end
+
+ context 'when user does not have a cluster and visits cluster index page' do
+ before do
+ visit group_clusters_path(group)
+
+ click_link 'Add Kubernetes cluster'
+ click_link 'Add existing cluster'
+ end
+
+ context 'when user filled form with valid parameters' do
+ shared_examples 'valid cluster user form' do
+ it 'user sees a cluster details page' do
+ subject
+
+ expect(page).to have_content('Kubernetes cluster integration')
+ expect(page.find_field('cluster[name]').value).to eq('dev-cluster')
+ expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value)
+ .to have_content('http://example.com')
+ expect(page.find_field('cluster[platform_kubernetes_attributes][token]').value)
+ .to have_content('my-token')
+ end
+ end
+
+ before do
+ fill_in 'cluster_name', with: 'dev-cluster'
+ fill_in 'cluster_platform_kubernetes_attributes_api_url', with: 'http://example.com'
+ fill_in 'cluster_platform_kubernetes_attributes_token', with: 'my-token'
+ end
+
+ subject { click_button 'Add Kubernetes cluster' }
+
+ it_behaves_like 'valid cluster user form'
+
+ context 'RBAC is enabled for the cluster' do
+ before do
+ check 'cluster_platform_kubernetes_attributes_authorization_type'
+ end
+
+ it_behaves_like 'valid cluster user form'
+
+ it 'user sees a cluster details page with RBAC enabled' do
+ subject
+
+ expect(page.find_field('cluster[platform_kubernetes_attributes][authorization_type]', disabled: true)).to be_checked
+ end
+ end
+ end
+
+ context 'when user filled form with invalid parameters' do
+ before do
+ click_button 'Add Kubernetes cluster'
+ end
+
+ it 'user sees a validation error' do
+ expect(page).to have_css('#error_explanation')
+ end
+ end
+ end
+
+ context 'when user does have a cluster and visits cluster page' do
+ let(:cluster) { create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [group]) }
+
+ before do
+ visit group_cluster_path(group, cluster)
+ end
+
+ it 'user sees a cluster details page' do
+ expect(page).to have_button('Save changes')
+ end
+
+ context 'when user disables the cluster' do
+ before do
+ page.find(:css, '.js-cluster-enable-toggle-area .js-project-feature-toggle').click
+ page.within('#cluster-integration') { click_button 'Save changes' }
+ end
+
+ it 'user sees the successful message' do
+ expect(page).to have_content('Kubernetes cluster was successfully updated.')
+ end
+ end
+
+ context 'when user changes cluster parameters' do
+ before do
+ fill_in 'cluster_name', with: 'my-dev-cluster'
+ fill_in 'cluster_platform_kubernetes_attributes_token', with: 'new-token'
+ page.within('#js-cluster-details') { click_button 'Save changes' }
+ end
+
+ it 'user sees the successful message' do
+ expect(page).to have_content('Kubernetes cluster was successfully updated.')
+ expect(cluster.reload.name).to eq('my-dev-cluster')
+ expect(cluster.reload.platform_kubernetes.token).to eq('new-token')
+ end
+ end
+
+ context 'when user destroy the cluster' do
+ before do
+ page.accept_confirm do
+ click_link 'Remove integration'
+ end
+ end
+
+ it 'user sees creation form with the successful message' do
+ expect(page).to have_content('Kubernetes cluster integration was successfully removed.')
+ expect(page).to have_link('Add Kubernetes cluster')
+ end
+ end
+ end
+end
diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb
index 4e6f73ef58a..9671a4d8c49 100644
--- a/spec/features/groups/show_spec.rb
+++ b/spec/features/groups/show_spec.rb
@@ -65,7 +65,7 @@ describe 'Group show page' do
context 'when subgroups are supported', :js, :nested_groups do
before do
- allow(Group).to receive(:supports_nested_groups?) { true }
+ allow(Group).to receive(:supports_nested_objects?) { true }
visit path
end
@@ -76,7 +76,7 @@ describe 'Group show page' do
context 'when subgroups are not supported' do
before do
- allow(Group).to receive(:supports_nested_groups?) { false }
+ allow(Group).to receive(:supports_nested_objects?) { false }
visit path
end
diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb
index c29dfb01381..e24b1f4349d 100644
--- a/spec/features/help_pages_spec.rb
+++ b/spec/features/help_pages_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Help Pages' do
@@ -52,23 +54,21 @@ describe 'Help Pages' do
end
end
- context 'in a production environment with version check enabled', :js do
+ context 'in a production environment with version check enabled' do
before do
- allow(Rails.env).to receive(:production?) { true }
stub_application_setting(version_check_enabled: true)
- allow_any_instance_of(VersionCheck).to receive(:url) { '/version-check-url' }
+
+ allow(Rails.env).to receive(:production?).and_return(true)
+ allow(VersionCheck).to receive(:url).and_return('/version-check-url')
sign_in(create(:user))
visit help_path
end
it 'has a version check image' do
- expect(find('.js-version-status-badge', visible: false)['src']).to end_with('/version-check-url')
- end
-
- it 'hides the version check image if the image request fails' do
- # We use '--load-images=yes' with poltergeist so the image fails to load
- expect(page).to have_selector('.js-version-status-badge', visible: false)
+ # Check `data-src` due to lazy image loading
+ expect(find('.js-version-status-badge', visible: false)['data-src'])
+ .to end_with('/version-check-url')
end
end
diff --git a/spec/features/ics/project_issues_spec.rb b/spec/features/ics/project_issues_spec.rb
index 2ca3d52a5be..54143595e6b 100644
--- a/spec/features/ics/project_issues_spec.rb
+++ b/spec/features/ics/project_issues_spec.rb
@@ -72,5 +72,14 @@ describe 'Project Issues Calendar Feed' do
expect(body).to have_text('TRANSP:TRANSPARENT')
end
end
+
+ context 'sorted by priority' do
+ it 'renders calendar feed' do
+ visit project_issues_path(project, :ics, sort: 'priority', feed_token: user.feed_token)
+
+ expect(response_headers['Content-Type']).to have_content('text/calendar')
+ expect(body).to have_text('BEGIN:VCALENDAR')
+ end
+ end
end
end
diff --git a/spec/features/issuables/default_sort_order_spec.rb b/spec/features/issuables/default_sort_order_spec.rb
deleted file mode 100644
index caee7a67aec..00000000000
--- a/spec/features/issuables/default_sort_order_spec.rb
+++ /dev/null
@@ -1,179 +0,0 @@
-require 'spec_helper'
-
-describe 'Projects > Issuables > Default sort order' do
- let(:project) { create(:project, :public) }
-
- let(:first_created_issuable) { issuables.order_created_asc.first }
- let(:last_created_issuable) { issuables.order_created_desc.first }
-
- let(:first_updated_issuable) { issuables.order_updated_asc.first }
- let(:last_updated_issuable) { issuables.order_updated_desc.first }
-
- context 'for merge requests' do
- include MergeRequestHelpers
-
- let!(:issuables) do
- timestamps = [{ created_at: 3.minutes.ago, updated_at: 20.seconds.ago },
- { created_at: 2.minutes.ago, updated_at: 30.seconds.ago },
- { created_at: 4.minutes.ago, updated_at: 10.seconds.ago }]
-
- timestamps.each_with_index do |ts, i|
- create issuable_type, { title: "#{issuable_type}_#{i}",
- source_branch: "#{issuable_type}_#{i}",
- source_project: project }.merge(ts)
- end
-
- MergeRequest.all
- end
-
- context 'in the "merge requests" tab', :js do
- let(:issuable_type) { :merge_request }
-
- it 'is "last created"' do
- visit_merge_requests project
-
- expect(first_merge_request).to include(last_created_issuable.title)
- expect(last_merge_request).to include(first_created_issuable.title)
- end
- end
-
- context 'in the "merge requests / open" tab', :js do
- let(:issuable_type) { :merge_request }
-
- it 'is "created date"' do
- visit_merge_requests_with_state(project, 'open')
-
- expect(selected_sort_order).to eq('created date')
- expect(first_merge_request).to include(last_created_issuable.title)
- expect(last_merge_request).to include(first_created_issuable.title)
- end
- end
-
- context 'in the "merge requests / merged" tab', :js do
- let(:issuable_type) { :merged_merge_request }
-
- it 'is "last updated"' do
- visit_merge_requests_with_state(project, 'merged')
-
- expect(find('.issues-other-filters')).to have_content('Last updated')
- expect(first_merge_request).to include(last_updated_issuable.title)
- expect(last_merge_request).to include(first_updated_issuable.title)
- end
- end
-
- context 'in the "merge requests / closed" tab', :js do
- let(:issuable_type) { :closed_merge_request }
-
- it 'is "last updated"' do
- visit_merge_requests_with_state(project, 'closed')
-
- expect(find('.issues-other-filters')).to have_content('Last updated')
- expect(first_merge_request).to include(last_updated_issuable.title)
- expect(last_merge_request).to include(first_updated_issuable.title)
- end
- end
-
- context 'in the "merge requests / all" tab', :js do
- let(:issuable_type) { :merge_request }
-
- it 'is "created date"' do
- visit_merge_requests_with_state(project, 'all')
-
- expect(find('.issues-other-filters')).to have_content('Created date')
- expect(first_merge_request).to include(last_created_issuable.title)
- expect(last_merge_request).to include(first_created_issuable.title)
- end
- end
- end
-
- context 'for issues' do
- include IssueHelpers
-
- let!(:issuables) do
- timestamps = [{ created_at: 3.minutes.ago, updated_at: 20.seconds.ago },
- { created_at: 2.minutes.ago, updated_at: 30.seconds.ago },
- { created_at: 4.minutes.ago, updated_at: 10.seconds.ago }]
-
- timestamps.each_with_index do |ts, i|
- create issuable_type, { title: "#{issuable_type}_#{i}",
- project: project }.merge(ts)
- end
-
- Issue.all
- end
-
- context 'in the "issues" tab', :js do
- let(:issuable_type) { :issue }
-
- it 'is "created date"' do
- visit_issues project
-
- expect(find('.issues-other-filters')).to have_content('Created date')
- expect(first_issue).to include(last_created_issuable.title)
- expect(last_issue).to include(first_created_issuable.title)
- end
- end
-
- context 'in the "issues / open" tab', :js do
- let(:issuable_type) { :issue }
-
- it 'is "created date"' do
- visit_issues_with_state(project, 'open')
-
- expect(find('.issues-other-filters')).to have_content('Created date')
- expect(first_issue).to include(last_created_issuable.title)
- expect(last_issue).to include(first_created_issuable.title)
- end
- end
-
- context 'in the "issues / closed" tab', :js do
- let(:issuable_type) { :closed_issue }
-
- it 'is "last updated"' do
- visit_issues_with_state(project, 'closed')
-
- expect(find('.issues-other-filters')).to have_content('Last updated')
- expect(first_issue).to include(last_updated_issuable.title)
- expect(last_issue).to include(first_updated_issuable.title)
- end
- end
-
- context 'in the "issues / all" tab', :js do
- let(:issuable_type) { :issue }
-
- it 'is "created date"' do
- visit_issues_with_state(project, 'all')
-
- expect(find('.issues-other-filters')).to have_content('Created date')
- expect(first_issue).to include(last_created_issuable.title)
- expect(last_issue).to include(first_created_issuable.title)
- end
- end
-
- context 'when the sort in the URL is id_desc' do
- let(:issuable_type) { :issue }
-
- before do
- visit_issues(project, sort: 'id_desc')
- end
-
- it 'shows the sort order as created date' do
- expect(find('.issues-other-filters')).to have_content('Created date')
- expect(first_issue).to include(last_created_issuable.title)
- expect(last_issue).to include(first_created_issuable.title)
- end
- end
- end
-
- def selected_sort_order
- find('.filter-dropdown-container .dropdown button').text.downcase
- end
-
- def visit_merge_requests_with_state(project, state)
- visit_merge_requests project, state: state
- end
-
- def visit_issues_with_state(project, state)
- visit_issues project, state: state
- end
-end
diff --git a/spec/features/issuables/sorting_list_spec.rb b/spec/features/issuables/sorting_list_spec.rb
new file mode 100644
index 00000000000..0601dd47c03
--- /dev/null
+++ b/spec/features/issuables/sorting_list_spec.rb
@@ -0,0 +1,226 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe 'Sort Issuable List' do
+ let(:project) { create(:project, :public) }
+
+ let(:first_created_issuable) { issuables.order_created_asc.first }
+ let(:last_created_issuable) { issuables.order_created_desc.first }
+
+ let(:first_updated_issuable) { issuables.order_updated_asc.first }
+ let(:last_updated_issuable) { issuables.order_updated_desc.first }
+
+ context 'for merge requests' do
+ include MergeRequestHelpers
+
+ let!(:issuables) do
+ timestamps = [{ created_at: 3.minutes.ago, updated_at: 20.seconds.ago },
+ { created_at: 2.minutes.ago, updated_at: 30.seconds.ago },
+ { created_at: 4.minutes.ago, updated_at: 10.seconds.ago }]
+
+ timestamps.each_with_index do |ts, i|
+ create issuable_type, { title: "#{issuable_type}_#{i}",
+ source_branch: "#{issuable_type}_#{i}",
+ source_project: project }.merge(ts)
+ end
+
+ MergeRequest.all
+ end
+
+ context 'default sort order' do
+ context 'in the "merge requests" tab', :js do
+ let(:issuable_type) { :merge_request }
+
+ it 'is "last created"' do
+ visit_merge_requests project
+
+ expect(first_merge_request).to include(last_created_issuable.title)
+ expect(last_merge_request).to include(first_created_issuable.title)
+ end
+ end
+
+ context 'in the "merge requests / open" tab', :js do
+ let(:issuable_type) { :merge_request }
+
+ it 'is "created date"' do
+ visit_merge_requests_with_state(project, 'open')
+
+ expect(selected_sort_order).to eq('created date')
+ expect(first_merge_request).to include(last_created_issuable.title)
+ expect(last_merge_request).to include(first_created_issuable.title)
+ end
+ end
+
+ context 'in the "merge requests / merged" tab', :js do
+ let(:issuable_type) { :merged_merge_request }
+
+ it 'is "last updated"' do
+ visit_merge_requests_with_state(project, 'merged')
+
+ expect(find('.issues-other-filters')).to have_content('Last updated')
+ expect(first_merge_request).to include(last_updated_issuable.title)
+ expect(last_merge_request).to include(first_updated_issuable.title)
+ end
+ end
+
+ context 'in the "merge requests / closed" tab', :js do
+ let(:issuable_type) { :closed_merge_request }
+
+ it 'is "last updated"' do
+ visit_merge_requests_with_state(project, 'closed')
+
+ expect(find('.issues-other-filters')).to have_content('Last updated')
+ expect(first_merge_request).to include(last_updated_issuable.title)
+ expect(last_merge_request).to include(first_updated_issuable.title)
+ end
+ end
+
+ context 'in the "merge requests / all" tab', :js do
+ let(:issuable_type) { :merge_request }
+
+ it 'is "created date"' do
+ visit_merge_requests_with_state(project, 'all')
+
+ expect(find('.issues-other-filters')).to have_content('Created date')
+ expect(first_merge_request).to include(last_created_issuable.title)
+ expect(last_merge_request).to include(first_created_issuable.title)
+ end
+ end
+
+ context 'custom sorting' do
+ let(:issuable_type) { :merge_request }
+
+ it 'supports sorting in asc and desc order' do
+ visit_merge_requests_with_state(project, 'open')
+
+ page.within('.issues-other-filters') do
+ click_button('Created date')
+ click_link('Last updated')
+ end
+
+ expect(first_merge_request).to include(last_updated_issuable.title)
+ expect(last_merge_request).to include(first_updated_issuable.title)
+
+ find('.issues-other-filters .filter-dropdown-container .qa-reverse-sort').click
+
+ expect(first_merge_request).to include(first_updated_issuable.title)
+ expect(last_merge_request).to include(last_updated_issuable.title)
+ end
+ end
+ end
+ end
+
+ context 'for issues' do
+ include IssueHelpers
+
+ let!(:issuables) do
+ timestamps = [{ created_at: 3.minutes.ago, updated_at: 20.seconds.ago },
+ { created_at: 2.minutes.ago, updated_at: 30.seconds.ago },
+ { created_at: 4.minutes.ago, updated_at: 10.seconds.ago }]
+
+ timestamps.each_with_index do |ts, i|
+ create issuable_type, { title: "#{issuable_type}_#{i}",
+ project: project }.merge(ts)
+ end
+
+ Issue.all
+ end
+
+ context 'default sort order' do
+ context 'in the "issues" tab', :js do
+ let(:issuable_type) { :issue }
+
+ it 'is "created date"' do
+ visit_issues project
+
+ expect(find('.issues-other-filters')).to have_content('Created date')
+ expect(first_issue).to include(last_created_issuable.title)
+ expect(last_issue).to include(first_created_issuable.title)
+ end
+ end
+
+ context 'in the "issues / open" tab', :js do
+ let(:issuable_type) { :issue }
+
+ it 'is "created date"' do
+ visit_issues_with_state(project, 'open')
+
+ expect(find('.issues-other-filters')).to have_content('Created date')
+ expect(first_issue).to include(last_created_issuable.title)
+ expect(last_issue).to include(first_created_issuable.title)
+ end
+ end
+
+ context 'in the "issues / closed" tab', :js do
+ let(:issuable_type) { :closed_issue }
+
+ it 'is "last updated"' do
+ visit_issues_with_state(project, 'closed')
+
+ expect(find('.issues-other-filters')).to have_content('Last updated')
+ expect(first_issue).to include(last_updated_issuable.title)
+ expect(last_issue).to include(first_updated_issuable.title)
+ end
+ end
+
+ context 'in the "issues / all" tab', :js do
+ let(:issuable_type) { :issue }
+
+ it 'is "created date"' do
+ visit_issues_with_state(project, 'all')
+
+ expect(find('.issues-other-filters')).to have_content('Created date')
+ expect(first_issue).to include(last_created_issuable.title)
+ expect(last_issue).to include(first_created_issuable.title)
+ end
+ end
+
+ context 'when the sort in the URL is id_desc' do
+ let(:issuable_type) { :issue }
+
+ before do
+ visit_issues(project, sort: 'id_desc')
+ end
+
+ it 'shows the sort order as created date' do
+ expect(find('.issues-other-filters')).to have_content('Created date')
+ expect(first_issue).to include(last_created_issuable.title)
+ expect(last_issue).to include(first_created_issuable.title)
+ end
+ end
+ end
+
+ context 'custom sorting' do
+ let(:issuable_type) { :issue }
+
+ it 'supports sorting in asc and desc order' do
+ visit_issues_with_state(project, 'open')
+
+ page.within('.issues-other-filters') do
+ click_button('Created date')
+ click_link('Last updated')
+ end
+
+ expect(first_issue).to include(last_updated_issuable.title)
+ expect(last_issue).to include(first_updated_issuable.title)
+
+ find('.issues-other-filters .filter-dropdown-container .qa-reverse-sort').click
+
+ expect(first_issue).to include(first_updated_issuable.title)
+ expect(last_issue).to include(last_updated_issuable.title)
+ end
+ end
+ end
+
+ def selected_sort_order
+ find('.filter-dropdown-container .dropdown button').text.downcase
+ end
+
+ def visit_merge_requests_with_state(project, state)
+ visit_merge_requests project, state: state
+ end
+
+ def visit_issues_with_state(project, state)
+ visit_issues project, state: state
+ end
+end
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 4d9b8262f21..a29380a180e 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -430,7 +430,7 @@ describe 'Filter issues', :js do
expect_issues_list_count(2)
- sort_toggle = find('.filter-dropdown-container .dropdown-menu-toggle')
+ sort_toggle = find('.filter-dropdown-container .dropdown')
sort_toggle.click
find('.filter-dropdown-container .dropdown-menu li a', text: 'Created date').click
diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb
index 687a6f1eafc..e60486f6dcb 100644
--- a/spec/features/issues/user_creates_issue_spec.rb
+++ b/spec/features/issues/user_creates_issue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User creates issue" do
@@ -12,7 +14,7 @@ describe "User creates issue" do
visit(new_project_issue_path(project))
end
- it "creates issue" do
+ it "creates issue", :js do
page.within(".issue-form") do
expect(page).to have_no_content("Assign to")
.and have_no_content("Labels")
@@ -25,11 +27,15 @@ describe "User creates issue" do
issue_title = "500 error on profile"
fill_in("Title", with: issue_title)
+ first('.js-md').click
+ first('.qa-issuable-form-description').native.send_keys('Description')
+
click_button("Submit issue")
expect(page).to have_content(issue_title)
.and have_content(user.name)
.and have_content(project.name)
+ expect(page).to have_selector('strong', text: 'Description')
end
end
@@ -64,10 +70,10 @@ describe "User creates issue" do
end
context "with labels" do
- LABEL_TITLES = %w(bug feature enhancement).freeze
+ let(:label_titles) { %w(bug feature enhancement) }
before do
- LABEL_TITLES.each do |title|
+ label_titles.each do |title|
create(:label, project: project, title: title)
end
end
@@ -77,13 +83,13 @@ describe "User creates issue" do
fill_in("Title", with: issue_title)
click_button("Label")
- click_link(LABEL_TITLES.first)
+ click_link(label_titles.first)
click_button("Submit issue")
expect(page).to have_content(issue_title)
.and have_content(user.name)
.and have_content(project.name)
- .and have_content(LABEL_TITLES.first)
+ .and have_content(label_titles.first)
end
end
end
diff --git a/spec/features/issues/user_sorts_issues_spec.rb b/spec/features/issues/user_sorts_issues_spec.rb
index 3bc93933183..eebd2d57cca 100644
--- a/spec/features/issues/user_sorts_issues_spec.rb
+++ b/spec/features/issues/user_sorts_issues_spec.rb
@@ -20,9 +20,9 @@ describe "User sorts issues" do
end
it 'keeps the sort option' do
- find('.filter-dropdown-container button.dropdown-menu-toggle').click
+ find('.filter-dropdown-container .dropdown').click
- page.within('.content ul.dropdown-menu.dropdown-menu-right li') do
+ page.within('ul.dropdown-menu.dropdown-menu-right li') do
click_link('Milestone')
end
@@ -40,9 +40,9 @@ describe "User sorts issues" do
end
it "sorts by popularity" do
- find(".filter-dropdown-container button.dropdown-menu-toggle").click
+ find('.filter-dropdown-container .dropdown').click
- page.within(".content ul.dropdown-menu.dropdown-menu-right li") do
+ page.within('ul.dropdown-menu.dropdown-menu-right li') do
click_link("Popularity")
end
diff --git a/spec/features/merge_request/user_awards_emoji_spec.rb b/spec/features/merge_request/user_awards_emoji_spec.rb
index 859a4c65562..93376bc8ce0 100644
--- a/spec/features/merge_request/user_awards_emoji_spec.rb
+++ b/spec/features/merge_request/user_awards_emoji_spec.rb
@@ -4,11 +4,14 @@ describe 'Merge request > User awards emoji', :js do
let(:project) { create(:project, :public, :repository) }
let(:user) { project.creator }
let(:merge_request) { create(:merge_request, source_project: project, author: create(:user)) }
+ let!(:note) { create(:note, noteable: merge_request, project: merge_request.project) }
describe 'logged in' do
before do
sign_in(user)
visit project_merge_request_path(project, merge_request)
+
+ wait_for_requests
end
it 'adds award to merge request' do
@@ -36,6 +39,15 @@ describe 'Merge request > User awards emoji', :js do
expect(page).to have_selector('.emoji-menu', count: 1)
end
+ it 'adds awards to note' do
+ first('.js-note-emoji').click
+ first('.emoji-menu .js-emoji-btn').click
+
+ wait_for_requests
+
+ expect(page).to have_selector('.js-awards-block')
+ end
+
describe 'the project is archived' do
let(:project) { create(:project, :public, :repository, :archived) }
diff --git a/spec/features/merge_request/user_expands_diff_spec.rb b/spec/features/merge_request/user_expands_diff_spec.rb
index 02fe6352a0f..3560b8d90bb 100644
--- a/spec/features/merge_request/user_expands_diff_spec.rb
+++ b/spec/features/merge_request/user_expands_diff_spec.rb
@@ -2,16 +2,19 @@ require 'spec_helper'
describe 'User expands diff', :js do
let(:project) { create(:project, :public, :repository) }
- let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+ let(:merge_request) { create(:merge_request, source_branch: 'expand-collapse-files', source_project: project, target_project: project) }
before do
+ allow(Gitlab::Git::Diff).to receive(:size_limit).and_return(100.kilobytes)
+ allow(Gitlab::Git::Diff).to receive(:collapse_limit).and_return(10.kilobytes)
+
visit(diffs_project_merge_request_path(project, merge_request))
wait_for_requests
end
it 'allows user to expand diff' do
- page.within find('[id="19763941ab80e8c09871c0a425f0560d9053bcb3"]') do
+ page.within find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd"]') do
click_link 'Click to expand it.'
wait_for_requests
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 53ed5d78598..29b3d2b629b 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
@@ -88,6 +88,8 @@ describe 'Merge request > User merges when pipeline succeeds', :js do
describe 'enabling Merge when pipeline succeeds via dropdown' do
it 'activates the Merge when pipeline succeeds feature' do
+ wait_for_requests
+
find('.js-merge-moment').click
click_link 'Merge when pipeline succeeds'
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 328f96e6ed7..ba4806821f9 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
@@ -361,8 +361,14 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
end
end
- it 'shows jump to next discussion button' do
- expect(page.all('.discussion-reply-holder', count: 2)).to all(have_selector('.discussion-next-btn'))
+ it 'shows jump to next discussion button except on last discussion' do
+ wait_for_requests
+
+ all_discussion_replies = page.all('.discussion-reply-holder')
+
+ expect(all_discussion_replies.count).to eq(2)
+ expect(all_discussion_replies.first.all('.discussion-next-btn').count).to eq(1)
+ expect(all_discussion_replies.last.all('.discussion-next-btn').count).to eq(0)
end
it 'displays next discussion even if hidden' do
@@ -380,7 +386,13 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
page.find('.discussion-next-btn').click
end
- expect(find('.discussion-with-resolve-btn')).to have_selector('.btn', text: 'Resolve discussion')
+ page.all('.note-discussion').first do
+ expect(page.find('.discussion-with-resolve-btn')).to have_selector('.btn', text: 'Resolve discussion')
+ end
+
+ page.all('.note-discussion').last do
+ expect(page.find('.discussion-with-resolve-btn')).not.to have_selector('.btn', text: 'Resolve discussion')
+ end
end
end
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 ae1313cf117..7b473faa884 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
@@ -110,6 +110,22 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
+ it 'sees merge request tag for merge request pipelines' do
+ page.within('.ci-table') do
+ expect(all('.pipeline-tags')[0])
+ .to have_content("merge request")
+
+ expect(all('.pipeline-tags')[1])
+ .to have_content("merge request")
+
+ expect(all('.pipeline-tags')[2])
+ .not_to have_content("merge request")
+
+ expect(all('.pipeline-tags')[3])
+ .not_to have_content("merge request")
+ end
+ end
+
it 'sees the latest merge request pipeline as the head pipeline' do
page.within('.ci-widget-content') do
expect(page).to have_content("##{merge_request_pipeline_2.id}")
@@ -276,6 +292,22 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
+ it 'sees merge request tag for merge request pipelines' do
+ page.within('.ci-table') do
+ expect(all('.pipeline-tags')[0])
+ .to have_content("merge request")
+
+ expect(all('.pipeline-tags')[1])
+ .to have_content("merge request")
+
+ expect(all('.pipeline-tags')[2])
+ .not_to have_content("merge request")
+
+ expect(all('.pipeline-tags')[3])
+ .not_to have_content("merge request")
+ end
+ end
+
it 'sees the latest merge request pipeline as the head pipeline' do
page.within('.ci-widget-content') do
expect(page).to have_content("##{merge_request_pipeline_2.id}")
diff --git a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
new file mode 100644
index 00000000000..c19e299097e
--- /dev/null
+++ b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'User comments on a diff', :js do
+ include MergeRequestDiffHelpers
+ include RepoHelpers
+
+ let(:project) { create(:project, :repository) }
+ let(:merge_request) do
+ create(:merge_request_with_diffs, source_project: project, target_project: project, source_branch: 'merge-test')
+ end
+ let(:user) { create(:user) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+
+ visit(diffs_project_merge_request_path(project, merge_request))
+ end
+
+ context 'single suggestion note' do
+ it 'suggestion is presented' do
+ click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']"))
+
+ page.within('.js-discussion-note-form') do
+ fill_in('note_note', with: "```suggestion\n# change to a comment\n```")
+ click_button('Comment')
+ end
+
+ wait_for_requests
+
+ page.within('.diff-discussions') do
+ expect(page).to have_button('Apply suggestion')
+ expect(page).to have_content('Suggested change')
+ expect(page).to have_content(' url = https://github.com/gitlabhq/gitlab-shell.git')
+ expect(page).to have_content('# change to a comment')
+ end
+ end
+
+ it 'suggestion is appliable' do
+ click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']"))
+
+ page.within('.js-discussion-note-form') do
+ fill_in('note_note', with: "```suggestion\n# change to a comment\n```")
+ click_button('Comment')
+ end
+
+ wait_for_requests
+
+ page.within('.diff-discussions') do
+ expect(page).not_to have_content('Applied')
+
+ click_button('Apply suggestion')
+ wait_for_requests
+
+ expect(page).to have_content('Applied')
+ end
+ end
+ end
+
+ context 'multiple suggestions in a single note' do
+ it 'suggestions are presented' do
+ click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']"))
+
+ page.within('.js-discussion-note-form') do
+ fill_in('note_note', with: "```suggestion\n# change to a comment\n```\n```suggestion\n# or that\n```")
+ click_button('Comment')
+ end
+
+ wait_for_requests
+
+ page.within('.diff-discussions') do
+ suggestion_1 = page.all(:css, '.md-suggestion-diff')[0]
+ suggestion_2 = page.all(:css, '.md-suggestion-diff')[1]
+
+ expect(suggestion_1).to have_content(' url = https://github.com/gitlabhq/gitlab-shell.git')
+ expect(suggestion_1).to have_content('# change to a comment')
+
+ expect(suggestion_2).to have_content(' url = https://github.com/gitlabhq/gitlab-shell.git')
+ expect(suggestion_2).to have_content('# or that')
+ end
+ end
+ end
+end
diff --git a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb
index 61e8f1c4662..fa887110c13 100644
--- a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb
@@ -19,9 +19,9 @@ describe 'User sorts merge requests' do
end
it 'keeps the sort option' do
- find('.filter-dropdown-container button.dropdown-menu-toggle').click
+ find('.filter-dropdown-container .dropdown').click
- page.within('.content ul.dropdown-menu.dropdown-menu-right li') do
+ page.within('ul.dropdown-menu.dropdown-menu-right li') do
click_link('Milestone')
end
@@ -49,9 +49,9 @@ describe 'User sorts merge requests' do
it 'separates remember sorting with issues' do
create(:issue, project: project)
- find('.filter-dropdown-container button.dropdown-menu-toggle').click
+ find('.filter-dropdown-container .dropdown').click
- page.within('.content ul.dropdown-menu.dropdown-menu-right li') do
+ page.within('ul.dropdown-menu.dropdown-menu-right li') do
click_link('Milestone')
end
@@ -70,9 +70,9 @@ describe 'User sorts merge requests' do
end
it 'sorts by popularity' do
- find('.filter-dropdown-container button.dropdown-menu-toggle').click
+ find('.filter-dropdown-container .dropdown').click
- page.within('.content ul.dropdown-menu.dropdown-menu-right li') do
+ page.within('ul.dropdown-menu.dropdown-menu-right li') do
click_link('Popularity')
end
diff --git a/spec/features/projects/clusters/applications_spec.rb b/spec/features/projects/clusters/applications_spec.rb
index 71d715237f5..8918a7b7b9c 100644
--- a/spec/features/projects/clusters/applications_spec.rb
+++ b/spec/features/projects/clusters/applications_spec.rb
@@ -70,6 +70,44 @@ describe 'Clusters Applications', :js do
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: 'Install')
+
+ 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-install-button', exact_text: 'Installed')
+ 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
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index 3d17eb3a73a..06e30571336 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -130,7 +130,7 @@ describe 'Gcp Cluster', :js do
context 'when user changes cluster parameters' do
before do
- allow(ClusterPlatformConfigureWorker).to receive(:perform_async)
+ allow(ClusterConfigureWorker).to receive(:perform_async)
fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace'
page.within('#js-cluster-details') { click_button 'Save changes' }
end
diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb
index f3cf3a282e5..66268355345 100644
--- a/spec/features/projects/files/user_browses_files_spec.rb
+++ b/spec/features/projects/files/user_browses_files_spec.rb
@@ -11,6 +11,7 @@ describe "User browses files" do
let(:user) { project.owner }
before do
+ stub_feature_flags(csslab: false)
sign_in(user)
end
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index d7c4abffddd..60f37f4b74a 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -346,44 +346,85 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
describe 'Variables' do
let(:trigger_request) { create(:ci_trigger_request) }
+ let(:job) { create(:ci_build, pipeline: pipeline, trigger_request: trigger_request) }
- let(:job) do
- create :ci_build, pipeline: pipeline, trigger_request: trigger_request
- end
+ context 'when user is a maintainer' do
+ shared_examples 'no reveal button variables behavior' do
+ it 'renders a hidden value with no reveal values button', :js do
+ expect(page).to have_content('Trigger token')
+ expect(page).to have_content('Trigger variables')
+
+ expect(page).not_to have_css('.js-reveal-variables')
+
+ expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1')
+ expect(page).to have_selector('.js-build-value', text: '••••••')
+ end
+ end
+
+ context 'when variables are stored in trigger_request' do
+ before do
+ trigger_request.update_attribute(:variables, { 'TRIGGER_KEY_1' => 'TRIGGER_VALUE_1' } )
+
+ visit project_job_path(project, job)
+ end
+
+ it_behaves_like 'no reveal button variables behavior'
+ end
- shared_examples 'expected variables behavior' do
- it 'shows variable key and value after click', :js do
- expect(page).to have_content('Token')
- expect(page).to have_css('.js-reveal-variables')
- expect(page).not_to have_css('.js-build-variable')
- expect(page).not_to have_css('.js-build-value')
+ context 'when variables are stored in pipeline_variables' do
+ before do
+ create(:ci_pipeline_variable, pipeline: pipeline, key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1')
- click_button 'Reveal Variables'
+ visit project_job_path(project, job)
+ end
- expect(page).not_to have_css('.js-reveal-variables')
- expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1')
- expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1')
+ it_behaves_like 'no reveal button variables behavior'
end
end
- context 'when variables are stored in trigger_request' do
+ context 'when user is a maintainer' do
before do
- trigger_request.update_attribute(:variables, { 'TRIGGER_KEY_1' => 'TRIGGER_VALUE_1' } )
+ project.add_maintainer(user)
+ end
- visit project_job_path(project, job)
+ shared_examples 'reveal button variables behavior' do
+ it 'renders a hidden value with a reveal values button', :js do
+ expect(page).to have_content('Trigger token')
+ expect(page).to have_content('Trigger variables')
+
+ expect(page).to have_css('.js-reveal-variables')
+
+ expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1')
+ expect(page).to have_selector('.js-build-value', text: '••••••')
+ end
+
+ it 'reveals values on button click', :js do
+ click_button 'Reveal values'
+
+ expect(page).to have_selector('.js-build-variable', text: 'TRIGGER_KEY_1')
+ expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1')
+ end
end
- it_behaves_like 'expected variables behavior'
- end
+ context 'when variables are stored in trigger_request' do
+ before do
+ trigger_request.update_attribute(:variables, { 'TRIGGER_KEY_1' => 'TRIGGER_VALUE_1' } )
- context 'when variables are stored in pipeline_variables' do
- before do
- create(:ci_pipeline_variable, pipeline: pipeline, key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1')
+ visit project_job_path(project, job)
+ end
- visit project_job_path(project, job)
+ it_behaves_like 'reveal button variables behavior'
end
- it_behaves_like 'expected variables behavior'
+ context 'when variables are stored in pipeline_variables' do
+ before do
+ create(:ci_pipeline_variable, pipeline: pipeline, key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1')
+
+ visit project_job_path(project, job)
+ end
+
+ it_behaves_like 'reveal button variables behavior'
+ end
end
end
diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
index b778c72bc76..25417cf4955 100644
--- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
+++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
@@ -32,7 +32,7 @@ describe 'Issue prioritization' do
visit project_issues_path(project, sort: 'label_priority')
# Ensure we are indicating that issues are sorted by priority
- expect(page).to have_selector('.dropdown-menu-toggle', text: 'Label priority')
+ expect(page).to have_selector('.dropdown', text: 'Label priority')
page.within('.issues-holder') do
issue_titles = all('.issues-list .issue-title-text').map(&:text)
@@ -70,7 +70,7 @@ describe 'Issue prioritization' do
sign_in user
visit project_issues_path(project, sort: 'label_priority')
- expect(page).to have_selector('.dropdown-menu-toggle', text: 'Label priority')
+ expect(page).to have_selector('.dropdown', text: 'Label priority')
page.within('.issues-holder') do
issue_titles = all('.issues-list .issue-title-text').map(&:text)
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index 996040fde02..055a0c83a11 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -115,6 +115,21 @@ describe 'Prioritize labels' do
end
end
+ it 'user can see a primary button when there are only prioritized labels', :js do
+ visit project_labels_path(project)
+
+ page.within('.other-labels') do
+ all('.js-toggle-priority').each do |el|
+ el.click
+ end
+ wait_for_requests
+ end
+
+ page.within('.breadcrumbs-container') do
+ expect(page).to have_link('New label')
+ end
+ end
+
it 'shows a help message about prioritized labels' do
visit project_labels_path(project)
diff --git a/spec/features/projects/labels/user_views_labels_spec.rb b/spec/features/projects/labels/user_views_labels_spec.rb
index 0cbeca4e392..2c8267764bd 100644
--- a/spec/features/projects/labels/user_views_labels_spec.rb
+++ b/spec/features/projects/labels/user_views_labels_spec.rb
@@ -1,13 +1,15 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User views labels" do
set(:project) { create(:project_empty_repo, :public) }
set(:user) { create(:user) }
- LABEL_TITLES = %w[bug enhancement feature].freeze
+ let(:label_titles) { %w[bug enhancement feature] }
before do
- LABEL_TITLES.each { |title| create(:label, project: project, title: title) }
+ label_titles.each { |title| create(:label, project: project, title: title) }
project.add_guest(user)
sign_in(user)
@@ -17,7 +19,7 @@ describe "User views labels" do
it "shows all labels" do
page.within('.other-labels .manage-labels-list') do
- LABEL_TITLES.each { |title| expect(page).to have_content(title) }
+ label_titles.each { |title| expect(page).to have_content(title) }
end
end
end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 049bbca958f..a37ad9c3f43 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -499,4 +499,154 @@ describe 'Pipeline', :js do
end
end
end
+
+ context 'when user sees pipeline flags in a pipeline detail page' do
+ let(:project) { create(:project, :repository) }
+
+ context 'when pipeline is latest' do
+ include_context 'pipeline builds'
+
+ let(:pipeline) do
+ create(:ci_pipeline,
+ project: project,
+ ref: 'master',
+ sha: project.commit.id,
+ user: user)
+ end
+
+ before do
+ visit project_pipeline_path(project, pipeline)
+ end
+
+ it 'contains badge that indicates it is the latest build' do
+ page.within(all('.well-segment')[1]) do
+ expect(page).to have_content 'latest'
+ end
+ end
+ end
+
+ context 'when pipeline has configuration errors' do
+ include_context 'pipeline builds'
+
+ let(:pipeline) do
+ create(:ci_pipeline,
+ :invalid,
+ project: project,
+ ref: 'master',
+ sha: project.commit.id,
+ user: user)
+ end
+
+ before do
+ visit project_pipeline_path(project, pipeline)
+ end
+
+ it 'contains badge that indicates errors' do
+ page.within(all('.well-segment')[1]) do
+ expect(page).to have_content 'yaml invalid'
+ end
+ end
+
+ it 'contains badge with tooltip which contains error' do
+ expect(pipeline).to have_yaml_errors
+
+ page.within(all('.well-segment')[1]) do
+ expect(page).to have_selector(
+ %Q{span[title="#{pipeline.yaml_errors}"]})
+ end
+ end
+
+ it 'contains badge that indicates failure reason' do
+ expect(page).to have_content 'error'
+ end
+
+ it 'contains badge with tooltip which contains failure reason' do
+ expect(pipeline.failure_reason?).to eq true
+
+ page.within(all('.well-segment')[1]) do
+ expect(page).to have_selector(
+ %Q{span[title="#{pipeline.present.failure_reason}"]})
+ end
+ end
+ end
+
+ context 'when pipeline is stuck' do
+ include_context 'pipeline builds'
+
+ let(:pipeline) do
+ create(:ci_pipeline,
+ project: project,
+ ref: 'master',
+ sha: project.commit.id,
+ user: user)
+ end
+
+ before do
+ create(:ci_build, :pending, pipeline: pipeline)
+ visit project_pipeline_path(project, pipeline)
+ end
+
+ it 'contains badge that indicates being stuck' do
+ page.within(all('.well-segment')[1]) do
+ expect(page).to have_content 'stuck'
+ end
+ end
+ end
+
+ context 'when pipeline uses auto devops' do
+ include_context 'pipeline builds'
+
+ let(:project) { create(:project, :repository, auto_devops_attributes: { enabled: true }) }
+ let(:pipeline) do
+ create(:ci_pipeline,
+ :auto_devops_source,
+ project: project,
+ ref: 'master',
+ sha: project.commit.id,
+ user: user)
+ end
+
+ before do
+ visit project_pipeline_path(project, pipeline)
+ end
+
+ it 'contains badge that indicates using auto devops' do
+ page.within(all('.well-segment')[1]) do
+ expect(page).to have_content 'Auto DevOps'
+ end
+ end
+ end
+
+ context 'when pipeline runs in a merge request context' do
+ include_context 'pipeline builds'
+
+ let(:pipeline) do
+ create(:ci_pipeline,
+ source: :merge_request,
+ project: merge_request.source_project,
+ ref: 'feature',
+ sha: merge_request.diff_head_sha,
+ user: user,
+ merge_request: merge_request)
+ end
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: 'feature',
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ before do
+ visit project_pipeline_path(project, pipeline)
+ end
+
+ it 'contains badge that indicates merge request pipeline' do
+ page.within(all('.well-segment')[1]) do
+ expect(page).to have_content 'merge request'
+ end
+ end
+ end
+ end
end
diff --git a/spec/features/projects/serverless/functions_spec.rb b/spec/features/projects/serverless/functions_spec.rb
new file mode 100644
index 00000000000..766c63725b3
--- /dev/null
+++ b/spec/features/projects/serverless/functions_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe 'Functions', :js do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_maintainer(user)
+ gitlab_sign_in(user)
+ end
+
+ context 'when user does not have a cluster and visits the serverless page' do
+ before do
+ visit project_serverless_functions_path(project)
+ end
+
+ it 'sees an empty state' do
+ expect(page).to have_link('Install Knative')
+ expect(page).to have_selector('.empty-state')
+ end
+ end
+
+ context 'when the user does have a cluster and visits the serverless page' do
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+
+ before do
+ visit project_serverless_functions_path(project)
+ end
+
+ it 'sees an empty state' do
+ expect(page).to have_link('Install Knative')
+ expect(page).to have_selector('.empty-state')
+ end
+ end
+
+ context 'when the user has a cluster and knative installed and visits the serverless page' do
+ let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) }
+ let(:project) { knative.cluster.project }
+
+ before do
+ visit project_serverless_functions_path(project)
+ end
+
+ it 'sees an empty listing of serverless functions' do
+ expect(page).to have_selector('.gl-responsive-table-row')
+ end
+ end
+end
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index b7a22316d26..1982136b89d 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -196,5 +196,26 @@ describe 'Projects > Settings > Repository settings' do
end
end
end
+
+ context 'repository cleanup settings' do
+ let(:object_map_file) { Rails.root.join('spec', 'fixtures', 'bfg_object_map.txt') }
+
+ it 'uploads an object map file', :js do
+ visit project_settings_repository_path(project)
+
+ expect(page).to have_content('Repository cleanup')
+
+ page.within('#cleanup') do
+ attach_file('project[bfg_object_map]', object_map_file, visible: false)
+
+ Sidekiq::Testing.fake! do
+ click_button 'Start cleanup'
+ end
+ end
+
+ expect(page).to have_content('Repository cleanup has started')
+ expect(RepositoryCleanupWorker.jobs.count).to eq(1)
+ end
+ end
end
end
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 227bdf524fe..8ba91fe7fd7 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
@@ -10,54 +10,9 @@ describe 'Projects > Show > Developer views empty project instructions' do
sign_in(developer)
end
- context 'without an SSH key' do
- it 'defaults to HTTP' do
- visit_project
-
- expect_instructions_for('http')
- end
-
- it 'switches to SSH', :js do
- visit_project
-
- select_protocol('SSH')
-
- expect_instructions_for('ssh')
- end
- end
-
- context 'with an SSH key' do
- before do
- create(:personal_key, user: developer)
- end
-
- it 'defaults to SSH' do
- visit_project
-
- expect_instructions_for('ssh')
- end
-
- it 'switches to HTTP', :js do
- visit_project
-
- select_protocol('HTTP')
-
- expect_instructions_for('http')
- end
- end
-
- def visit_project
+ it 'displays "git clone" instructions' do
visit project_path(project)
- end
-
- def select_protocol(protocol)
- find('#clone-dropdown').click
- find(".#{protocol.downcase}-selector").click
- end
-
- def expect_instructions_for(protocol)
- msg = :"#{protocol.downcase}_url_to_repo"
- expect(page).to have_content("git clone #{project.send(msg)}")
+ expect(page).to have_content("git clone")
end
end
diff --git a/spec/features/projects/show/user_manages_notifications_spec.rb b/spec/features/projects/show/user_manages_notifications_spec.rb
index 546619e88ec..88f3397608f 100644
--- a/spec/features/projects/show/user_manages_notifications_spec.rb
+++ b/spec/features/projects/show/user_manages_notifications_spec.rb
@@ -8,13 +8,18 @@ describe 'Projects > Show > User manages notifications', :js do
visit project_path(project)
end
- it 'changes the notification setting' do
+ def click_notifications_button
first('.notifications-btn').click
+ end
+
+ it 'changes the notification setting' do
+ click_notifications_button
click_link 'On mention'
- page.within '#notifications-button' do
- expect(page).to have_content 'On mention'
- end
+ wait_for_requests
+
+ click_notifications_button
+ expect(find('.update-notification.is-active')).to have_content('On mention')
end
context 'custom notification settings' do
@@ -38,7 +43,7 @@ describe 'Projects > Show > User manages notifications', :js do
end
it 'shows notification settings checkbox' do
- first('.notifications-btn').click
+ click_notifications_button
page.find('a[data-notification-level="custom"]').click
page.within('.custom-notifications-form') do
diff --git a/spec/features/projects/show/user_sees_collaboration_links_spec.rb b/spec/features/projects/show/user_sees_collaboration_links_spec.rb
index 7b3711531c6..24777788248 100644
--- a/spec/features/projects/show/user_sees_collaboration_links_spec.rb
+++ b/spec/features/projects/show/user_sees_collaboration_links_spec.rb
@@ -21,18 +21,6 @@ describe 'Projects > Show > Collaboration links' do
end
end
- # The project header
- page.within('.project-home-panel') do
- aggregate_failures 'dropdown links in the project home panel' do
- expect(page).to have_link('New issue')
- expect(page).to have_link('New merge request')
- expect(page).to have_link('New snippet')
- expect(page).to have_link('New file')
- expect(page).to have_link('New branch')
- expect(page).to have_link('New tag')
- end
- end
-
# The dropdown above the tree
page.within('.repo-breadcrumb') do
aggregate_failures 'dropdown links above the repo tree' do
@@ -61,17 +49,6 @@ describe 'Projects > Show > Collaboration links' do
end
end
- page.within('.project-home-panel') do
- aggregate_failures 'dropdown links' do
- expect(page).not_to have_link('New issue')
- expect(page).not_to have_link('New merge request')
- expect(page).not_to have_link('New snippet')
- expect(page).not_to have_link('New file')
- expect(page).not_to have_link('New branch')
- expect(page).not_to have_link('New tag')
- end
- end
-
page.within('.repo-breadcrumb') do
aggregate_failures 'dropdown links' do
expect(page).not_to have_link('New file')
diff --git a/spec/features/projects/show/user_sees_git_instructions_spec.rb b/spec/features/projects/show/user_sees_git_instructions_spec.rb
index 9a82fee1b5d..ffa80235083 100644
--- a/spec/features/projects/show/user_sees_git_instructions_spec.rb
+++ b/spec/features/projects/show/user_sees_git_instructions_spec.rb
@@ -29,7 +29,7 @@ describe 'Projects > Show > User sees Git instructions' do
expect(element.text).to include(project.http_url_to_repo)
end
- expect(page).to have_field('project_clone', with: project.http_url_to_repo) unless user_has_ssh_key
+ expect(page).to have_field('http_project_clone', with: project.http_url_to_repo) unless user_has_ssh_key
end
end
@@ -41,7 +41,7 @@ describe 'Projects > Show > User sees Git instructions' do
expect(page).to have_content(project.title)
end
- expect(page).to have_field('project_clone', with: project.http_url_to_repo) unless user_has_ssh_key
+ expect(page).to have_field('http_project_clone', with: project.http_url_to_repo) unless user_has_ssh_key
end
end
diff --git a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
index df2b492ae6b..dcca1d388c7 100644
--- a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
+++ b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
@@ -21,7 +21,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
end
it 'no Auto DevOps button if can not manage pipelines' do
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).not_to have_link('Enable Auto DevOps')
expect(page).not_to have_link('Auto DevOps enabled')
end
@@ -30,7 +30,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
it '"Auto DevOps enabled" button not linked' do
visit project_path(project)
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).to have_text('Auto DevOps enabled')
end
end
@@ -45,19 +45,19 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
end
it '"New file" button linked to new file page' do
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).to have_link('New file', href: project_new_blob_path(project, project.default_branch || 'master'))
end
end
- it '"Add Readme" button linked to new file populated for a readme' do
- page.within('.project-stats') do
- expect(page).to have_link('Add Readme', href: presenter.add_readme_path)
+ it '"Add README" button linked to new file populated for a README' do
+ page.within('.project-buttons') do
+ expect(page).to have_link('Add README', href: presenter.add_readme_path)
end
end
it '"Add license" button linked to new file populated for a license' do
- page.within('.project-metadata') do
+ page.within('.project-stats') do
expect(page).to have_link('Add license', href: presenter.add_license_path)
end
end
@@ -67,7 +67,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
it '"Auto DevOps enabled" anchor linked to settings page' do
visit project_path(project)
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).to have_link('Auto DevOps enabled', href: project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
end
end
@@ -77,7 +77,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
let(:project) { create(:project, :public, :empty_repo, auto_devops_attributes: { enabled: false }) }
it '"Enable Auto DevOps" button linked to settings page' do
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).to have_link('Enable Auto DevOps', href: project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
end
end
@@ -86,7 +86,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
describe 'Kubernetes cluster button' do
it '"Add Kubernetes cluster" button linked to clusters page' do
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).to have_link('Add Kubernetes cluster', href: new_project_cluster_path(project))
end
end
@@ -96,7 +96,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
visit project_path(project)
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).to have_link('Kubernetes configured', href: project_cluster_path(project, cluster))
end
end
@@ -119,7 +119,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
it '"Auto DevOps enabled" button not linked' do
visit project_path(project)
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).to have_text('Auto DevOps enabled')
end
end
@@ -129,14 +129,14 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
let(:project) { create(:project, :public, :repository, auto_devops_attributes: { enabled: false }) }
it 'no Auto DevOps button if can not manage pipelines' do
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).not_to have_link('Enable Auto DevOps')
expect(page).not_to have_link('Auto DevOps enabled')
end
end
it 'no Kubernetes cluster button if can not manage clusters' do
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).not_to have_link('Add Kubernetes cluster')
expect(page).not_to have_link('Kubernetes configured')
end
@@ -151,59 +151,59 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
sign_in(user)
end
- context 'Readme button' do
+ context 'README button' do
before do
allow(Project).to receive(:find_by_full_path)
.with(project.full_path, follow_redirects: true)
.and_return(project)
end
- context 'when the project has a populated Readme' do
- it 'show the "Readme" anchor' do
+ context 'when the project has a populated README' do
+ it 'show the "README" anchor' do
visit project_path(project)
expect(project.repository.readme).not_to be_nil
- page.within('.project-stats') do
- expect(page).not_to have_link('Add Readme', href: presenter.add_readme_path)
- expect(page).to have_link('Readme', href: presenter.readme_path)
+ page.within('.project-buttons') do
+ expect(page).not_to have_link('Add README', href: presenter.add_readme_path)
+ expect(page).to have_link('README', href: presenter.readme_path)
end
end
- context 'when the project has an empty Readme' do
- it 'show the "Readme" anchor' do
+ context 'when the project has an empty README' do
+ it 'show the "README" anchor' do
allow(project.repository).to receive(:readme).and_return(fake_blob(path: 'README.md', data: '', size: 0))
visit project_path(project)
- page.within('.project-stats') do
- expect(page).not_to have_link('Add Readme', href: presenter.add_readme_path)
- expect(page).to have_link('Readme', href: presenter.readme_path)
+ page.within('.project-buttons') do
+ expect(page).not_to have_link('Add README', href: presenter.add_readme_path)
+ expect(page).to have_link('README', href: presenter.readme_path)
end
end
end
end
- context 'when the project does not have a Readme' do
- it 'shows the "Add Readme" button' do
+ context 'when the project does not have a README' do
+ it 'shows the "Add README" button' do
allow(project.repository).to receive(:readme).and_return(nil)
visit project_path(project)
- page.within('.project-stats') do
- expect(page).to have_link('Add Readme', href: presenter.add_readme_path)
+ page.within('.project-buttons') do
+ expect(page).to have_link('Add README', href: presenter.add_readme_path)
end
end
end
end
- it 'no "Add Changelog" button if the project already has a changelog' do
+ it 'no "Add CHANGELOG" button if the project already has a changelog' do
visit project_path(project)
expect(project.repository.changelog).not_to be_nil
- page.within('.project-stats') do
- expect(page).not_to have_link('Add Changelog')
+ page.within('.project-buttons') do
+ expect(page).not_to have_link('Add CHANGELOG')
end
end
@@ -212,18 +212,18 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
expect(project.repository.license_blob).not_to be_nil
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).not_to have_link('Add license')
end
end
- it 'no "Add Contribution guide" button if the project already has a contribution guide' do
+ it 'no "Add CONTRIBUTING" button if the project already has a contribution guide' do
visit project_path(project)
expect(project.repository.contribution_guide).not_to be_nil
- page.within('.project-stats') do
- expect(page).not_to have_link('Add Contribution guide')
+ page.within('.project-buttons') do
+ expect(page).not_to have_link('Add CONTRIBUTING')
end
end
@@ -232,7 +232,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
it 'no "Set up CI/CD" button if the project has Auto DevOps enabled' do
visit project_path(project)
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).not_to have_link('Set up CI/CD')
end
end
@@ -246,7 +246,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
expect(project.repository.gitlab_ci_yml).to be_nil
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).to have_link('Set up CI/CD', href: presenter.add_ci_yml_path)
end
end
@@ -266,7 +266,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
visit project_path(project)
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).not_to have_link('Set up CI/CD')
end
end
@@ -278,7 +278,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
it '"Auto DevOps enabled" anchor linked to settings page' do
visit project_path(project)
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).to have_link('Auto DevOps enabled', href: project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
end
end
@@ -290,7 +290,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
it '"Enable Auto DevOps" button linked to settings page' do
visit project_path(project)
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).to have_link('Enable Auto DevOps', href: project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
end
end
@@ -302,7 +302,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
expect(page).to have_selector('.js-autodevops-banner')
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).not_to have_link('Enable Auto DevOps')
expect(page).not_to have_link('Auto DevOps enabled')
end
@@ -323,7 +323,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
visit project_path(project)
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).not_to have_link('Enable Auto DevOps')
expect(page).not_to have_link('Auto DevOps enabled')
end
@@ -335,7 +335,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
it '"Add Kubernetes cluster" button linked to clusters page' do
visit project_path(project)
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).to have_link('Add Kubernetes cluster', href: new_project_cluster_path(project))
end
end
@@ -345,7 +345,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
visit project_path(project)
- page.within('.project-stats') do
+ page.within('.project-buttons') do
expect(page).to have_link('Kubernetes configured', href: project_cluster_path(project, cluster))
end
end
diff --git a/spec/features/tags/master_views_tags_spec.rb b/spec/features/tags/master_views_tags_spec.rb
index 3f4fe549f3e..36cfeb5ed84 100644
--- a/spec/features/tags/master_views_tags_spec.rb
+++ b/spec/features/tags/master_views_tags_spec.rb
@@ -13,7 +13,7 @@ describe 'Maintainer views tags' do
before do
visit project_path(project)
- click_on 'Add Readme'
+ click_on 'Add README'
fill_in :commit_message, with: 'Add a README file', visible: true
click_button 'Commit changes'
visit project_tags_path(project)
diff --git a/spec/features/users/overview_spec.rb b/spec/features/users/overview_spec.rb
index 34ed771340f..873de85708a 100644
--- a/spec/features/users/overview_spec.rb
+++ b/spec/features/users/overview_spec.rb
@@ -119,6 +119,12 @@ describe 'Overview tab on a user profile', :js do
it 'shows a link to the project list' do
expect(find('#js-overview .projects-block')).to have_selector('.js-view-all', visible: true)
end
+
+ it 'shows projects in "compact mode"' do
+ page.within('#js-overview .projects-block') do
+ expect(find('.js-projects-list-holder')).to have_selector('.compact')
+ end
+ end
end
describe 'user has more than ten personal projects' do
diff --git a/spec/finders/cluster_ancestors_finder_spec.rb b/spec/finders/cluster_ancestors_finder_spec.rb
new file mode 100644
index 00000000000..332086c42e2
--- /dev/null
+++ b/spec/finders/cluster_ancestors_finder_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ClusterAncestorsFinder, '#execute' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+ let(:user) { create(:user) }
+
+ let!(:project_cluster) do
+ create(:cluster, :provided_by_user, cluster_type: :project_type, projects: [project])
+ end
+
+ let!(:group_cluster) do
+ create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [group])
+ end
+
+ subject { described_class.new(clusterable, user).execute }
+
+ context 'for a project' do
+ let(:clusterable) { project }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ it 'returns the project clusters followed by group clusters' do
+ is_expected.to eq([project_cluster, group_cluster])
+ end
+
+ context 'nested groups', :nested_groups do
+ let(:group) { create(:group, parent: parent_group) }
+ let(:parent_group) { create(:group) }
+
+ let!(:parent_group_cluster) do
+ create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [parent_group])
+ end
+
+ it 'returns the project clusters followed by group clusters ordered ascending the hierarchy' do
+ is_expected.to eq([project_cluster, group_cluster, parent_group_cluster])
+ end
+ end
+ end
+
+ context 'user cannot read clusters for clusterable' do
+ let(:clusterable) { project }
+
+ it 'returns nothing' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'for a group' do
+ let(:clusterable) { group }
+
+ before do
+ group.add_maintainer(user)
+ end
+
+ it 'returns the list of group clusters' do
+ is_expected.to eq([group_cluster])
+ end
+
+ context 'nested groups', :nested_groups do
+ let(:group) { create(:group, parent: parent_group) }
+ let(:parent_group) { create(:group) }
+
+ let!(:parent_group_cluster) do
+ create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [parent_group])
+ end
+
+ it 'returns the list of group clusters ordered ascending the hierarchy' do
+ is_expected.to eq([group_cluster, parent_group_cluster])
+ end
+ end
+ end
+end
diff --git a/spec/finders/concerns/finder_with_cross_project_access_spec.rb b/spec/finders/concerns/finder_with_cross_project_access_spec.rb
index 1ff65a8101b..f29acb521a8 100644
--- a/spec/finders/concerns/finder_with_cross_project_access_spec.rb
+++ b/spec/finders/concerns/finder_with_cross_project_access_spec.rb
@@ -115,4 +115,20 @@ describe FinderWithCrossProjectAccess do
expect(finder.execute).to include(result)
end
end
+
+ context 'when specifying a model' do
+ let(:finder_class) do
+ Class.new do
+ prepend FinderWithCrossProjectAccess
+
+ requires_cross_project_access model: Project
+ end
+ end
+
+ context '.finder_model' do
+ it 'is set correctly' do
+ expect(finder_class.finder_model).to eq(Project)
+ end
+ end
+ end
end
diff --git a/spec/finders/events_finder_spec.rb b/spec/finders/events_finder_spec.rb
index 62968e83292..3bce46cc4d1 100644
--- a/spec/finders/events_finder_spec.rb
+++ b/spec/finders/events_finder_spec.rb
@@ -14,6 +14,10 @@ describe EventsFinder do
let!(:closed_issue_event2) { create(:event, project: project1, author: user, target: closed_issue, action: Event::CLOSED, created_at: Date.new(2016, 2, 2)) }
let!(:opened_merge_request_event2) { create(:event, project: project2, author: user, target: opened_merge_request, action: Event::CREATED, created_at: Date.new(2017, 2, 2)) }
+ let(:public_project) { create(:project, :public, creator_id: user.id, namespace: user.namespace) }
+ let(:confidential_issue) { create(:closed_issue, confidential: true, project: public_project, author: user) }
+ let!(:confidential_event) { create(:event, project: public_project, author: user, target: confidential_issue, action: Event::CLOSED) }
+
context 'when targeting a user' do
it 'returns events between specified dates filtered on action and type' do
events = described_class.new(source: user, current_user: user, action: 'created', target_type: 'merge_request', after: Date.new(2017, 1, 1), before: Date.new(2017, 2, 1)).execute
@@ -27,6 +31,19 @@ describe EventsFinder do
expect(events).not_to include(opened_merge_request_event)
end
+ it 'does not include events on confidential issues the user does not have access to' do
+ events = described_class.new(source: user, current_user: other_user).execute
+
+ expect(events).not_to include(confidential_event)
+ end
+
+ it 'includes confidential events user has access to' do
+ public_project.add_developer(other_user)
+ events = described_class.new(source: user, current_user: other_user).execute
+
+ expect(events).to include(confidential_event)
+ end
+
it 'returns nothing when the current user cannot read cross project' do
expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
diff --git a/spec/finders/group_members_finder_spec.rb b/spec/finders/group_members_finder_spec.rb
index f545da3aee4..8975ea0f063 100644
--- a/spec/finders/group_members_finder_spec.rb
+++ b/spec/finders/group_members_finder_spec.rb
@@ -19,7 +19,7 @@ describe GroupMembersFinder, '#execute' do
end
it 'returns members for nested group', :nested_groups do
- group.add_maintainer(user2)
+ group.add_developer(user2)
nested_group.request_access(user4)
member1 = group.add_maintainer(user1)
member3 = nested_group.add_maintainer(user2)
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 515f6f70b99..80f7232f282 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -640,4 +640,131 @@ describe IssuesFinder do
end
end
end
+
+ describe '#use_subquery_for_search?' do
+ let(:finder) { described_class.new(nil, params) }
+
+ before do
+ allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
+ stub_feature_flags(use_subquery_for_group_issues_search: true)
+ end
+
+ context 'when there is no search param' do
+ let(:params) { { attempt_group_search_optimizations: true } }
+
+ it 'returns false' do
+ expect(finder.use_subquery_for_search?).to be_falsey
+ end
+ end
+
+ context 'when the database is not Postgres' do
+ let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
+
+ before do
+ allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
+ end
+
+ it 'returns false' do
+ expect(finder.use_subquery_for_search?).to be_falsey
+ end
+ end
+
+ context 'when the attempt_group_search_optimizations param is falsey' do
+ let(:params) { { search: 'foo' } }
+
+ it 'returns false' do
+ expect(finder.use_subquery_for_search?).to be_falsey
+ end
+ end
+
+ context 'when the use_subquery_for_group_issues_search flag is disabled' do
+ let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
+
+ before do
+ stub_feature_flags(use_subquery_for_group_issues_search: false)
+ end
+
+ it 'returns false' do
+ expect(finder.use_subquery_for_search?).to be_falsey
+ end
+ end
+
+ context 'when all conditions are met' do
+ let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
+
+ it 'returns true' do
+ expect(finder.use_subquery_for_search?).to be_truthy
+ end
+ end
+ end
+
+ describe '#use_cte_for_search?' do
+ let(:finder) { described_class.new(nil, params) }
+
+ before do
+ allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
+ stub_feature_flags(use_cte_for_group_issues_search: true)
+ stub_feature_flags(use_subquery_for_group_issues_search: false)
+ end
+
+ context 'when there is no search param' do
+ let(:params) { { attempt_group_search_optimizations: true } }
+
+ it 'returns false' do
+ expect(finder.use_cte_for_search?).to be_falsey
+ end
+ end
+
+ context 'when the database is not Postgres' do
+ let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
+
+ before do
+ allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
+ end
+
+ it 'returns false' do
+ expect(finder.use_cte_for_search?).to be_falsey
+ end
+ end
+
+ context 'when the attempt_group_search_optimizations param is falsey' do
+ let(:params) { { search: 'foo' } }
+
+ it 'returns false' do
+ expect(finder.use_cte_for_search?).to be_falsey
+ end
+ end
+
+ context 'when the use_cte_for_group_issues_search flag is disabled' do
+ let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
+
+ before do
+ stub_feature_flags(use_cte_for_group_issues_search: false)
+ end
+
+ it 'returns false' do
+ expect(finder.use_cte_for_search?).to be_falsey
+ end
+ end
+
+ context 'when use_subquery_for_search? is true' do
+ let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
+
+ before do
+ stub_feature_flags(use_subquery_for_group_issues_search: true)
+ end
+
+ it 'returns false' do
+ expect(finder.use_cte_for_search?).to be_falsey
+ end
+ end
+
+ context 'when all conditions are met' do
+ let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
+
+ it 'returns true' do
+ expect(finder.use_cte_for_search?).to be_truthy
+ end
+ end
+ end
end
diff --git a/spec/finders/projects/serverless/functions_finder_spec.rb b/spec/finders/projects/serverless/functions_finder_spec.rb
new file mode 100644
index 00000000000..60d02b12054
--- /dev/null
+++ b/spec/finders/projects/serverless/functions_finder_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::Serverless::FunctionsFinder do
+ include KubernetesHelpers
+ include ReactiveCachingHelpers
+
+ let(:user) { create(:user) }
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:service) { cluster.platform_kubernetes }
+ let(:project) { cluster.project}
+
+ let(:namespace) do
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster,
+ cluster_project: cluster.cluster_project,
+ project: cluster.cluster_project.project)
+ end
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ describe 'retrieve data from knative' do
+ it 'does not have knative installed' do
+ expect(described_class.new(project.clusters).execute).to be_empty
+ end
+
+ context 'has knative installed' do
+ let!(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) }
+
+ it 'there are no functions' do
+ expect(described_class.new(project.clusters).execute).to be_empty
+ end
+
+ it 'there are functions', :use_clean_rails_memory_store_caching do
+ stub_reactive_cache(knative, services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"])
+
+ expect(described_class.new(project.clusters).execute).not_to be_empty
+ end
+ end
+ end
+
+ describe 'verify if knative is installed' do
+ context 'knative is not installed' do
+ it 'does not have knative installed' do
+ expect(described_class.new(project.clusters).installed?).to be false
+ end
+ end
+
+ context 'knative is installed' do
+ let!(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) }
+
+ it 'does have knative installed' do
+ expect(described_class.new(project.clusters).installed?).to be true
+ end
+ end
+ end
+end
diff --git a/spec/finders/user_recent_events_finder_spec.rb b/spec/finders/user_recent_events_finder_spec.rb
index c5fcd68eb4c..5ebceeb7586 100644
--- a/spec/finders/user_recent_events_finder_spec.rb
+++ b/spec/finders/user_recent_events_finder_spec.rb
@@ -29,8 +29,9 @@ describe UserRecentEventsFinder do
end
it 'does not include the events if the user cannot read cross project' do
- expect(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).and_call_original
expect(Ability).to receive(:allowed?).with(current_user, :read_cross_project) { false }
+
expect(finder.execute).to be_empty
end
end
diff --git a/spec/fixtures/api/schemas/cluster_status.json b/spec/fixtures/api/schemas/cluster_status.json
index ccef17a6615..3d9e0628f63 100644
--- a/spec/fixtures/api/schemas/cluster_status.json
+++ b/spec/fixtures/api/schemas/cluster_status.json
@@ -32,7 +32,8 @@
},
"status_reason": { "type": ["string", "null"] },
"external_ip": { "type": ["string", "null"] },
- "hostname": { "type": ["string", "null"] }
+ "hostname": { "type": ["string", "null"] },
+ "email": { "type": ["string", "null"] }
},
"required" : [ "name", "status" ]
}
diff --git a/spec/fixtures/api/schemas/entities/diff_line.json b/spec/fixtures/api/schemas/entities/diff_line.json
index 66e8b443e1b..9657004cd2d 100644
--- a/spec/fixtures/api/schemas/entities/diff_line.json
+++ b/spec/fixtures/api/schemas/entities/diff_line.json
@@ -8,7 +8,8 @@
"new_line": { "type": ["integer", "null"] },
"text": { "type": ["string"] },
"rich_text": { "type": ["string"] },
- "meta_data": { "type": ["object", "null"] }
+ "meta_data": { "type": ["object", "null"] },
+ "can_receive_suggestion": { "type": "boolean" }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/api/schemas/entities/issuable_sidebar_todo.json b/spec/fixtures/api/schemas/entities/issuable_sidebar_todo.json
new file mode 100644
index 00000000000..b77e60ece12
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/issuable_sidebar_todo.json
@@ -0,0 +1,8 @@
+{
+ "type": ["object", "null"],
+ "properties" : {
+ "id": { "type": "integer" },
+ "delete_path": { "type": "string" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/entities/issue_sidebar.json b/spec/fixtures/api/schemas/entities/issue_sidebar.json
index 682e345d5f5..93adb493d1b 100644
--- a/spec/fixtures/api/schemas/entities/issue_sidebar.json
+++ b/spec/fixtures/api/schemas/entities/issue_sidebar.json
@@ -2,20 +2,46 @@
"type": "object",
"properties" : {
"id": { "type": "integer" },
- "iid": { "type": "integer" },
- "subscribed": { "type": "boolean" },
- "time_estimate": { "type": "integer" },
- "total_time_spent": { "type": "integer" },
- "human_time_estimate": { "type": ["integer", "null"] },
- "human_total_time_spent": { "type": ["integer", "null"] },
- "participants": {
- "type": "array",
- "items": { "$ref": "../public_api/v4/user/basic.json" }
+ "type": { "type": "string" },
+ "author_id": { "type": "integer" },
+ "project_id": { "type": "integer" },
+ "discussion_locked": { "type": ["boolean", "null"] },
+ "due_date": { "type": "date" },
+ "confidential": { "type": "boolean" },
+ "reference": { "type": "string" },
+ "current_user": {
+ "allOf": [
+ { "$ref": "../public_api/v4/user/basic.json" },
+ { "type": "object",
+ "properties" : {
+ "todo": { "$ref": "issuable_sidebar_todo.json" },
+ "can_edit": { "type": "boolean" },
+ "can_move": { "type": "boolean" },
+ "can_admin_label": { "type": "boolean" }
+ }
+ }
+ ]
+ },
+ "milestone": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "../public_api/v4/milestone.json" }
+ ]
},
- "assignees": {
+ "labels": {
"type": "array",
- "items": { "$ref": "../public_api/v4/user/basic.json" }
- }
- },
- "additionalProperties": false
+ "items": { "$ref": "label.json" }
+ },
+ "issuable_json_path": { "type": "string" },
+ "namespace_path": { "type": "string" },
+ "project_path": { "type": "string" },
+ "project_full_path": { "type": "string" },
+ "project_issuables_path": { "type": "string" },
+ "create_todo_path": { "type": "string" },
+ "project_milestones_path": { "type": "string" },
+ "project_labels_path": { "type": "string" },
+ "toggle_subscription_path": { "type": "string" },
+ "move_issue_path": { "type": "string" },
+ "projects_autocomplete_path": { "type": "string" }
+ }
}
diff --git a/spec/fixtures/api/schemas/entities/issue_sidebar_extras.json b/spec/fixtures/api/schemas/entities/issue_sidebar_extras.json
new file mode 100644
index 00000000000..11be903b083
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/issue_sidebar_extras.json
@@ -0,0 +1,18 @@
+{
+ "type": "object",
+ "properties" : {
+ "subscribed": { "type": "boolean" },
+ "time_estimate": { "type": "integer" },
+ "total_time_spent": { "type": "integer" },
+ "human_time_estimate": { "type": ["integer", "null"] },
+ "human_total_time_spent": { "type": ["integer", "null"] },
+ "participants": {
+ "type": "array",
+ "items": { "$ref": "../public_api/v4/user/basic.json" }
+ },
+ "assignees": {
+ "type": "array",
+ "items": { "$ref": "../public_api/v4/user/basic.json" }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/entities/merge_request_basic.json b/spec/fixtures/api/schemas/entities/merge_request_basic.json
index cf257ac00de..4c04c838cb8 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_basic.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_basic.json
@@ -4,15 +4,9 @@
"state": { "type": "string" },
"merge_status": { "type": "string" },
"source_branch_exists": { "type": "boolean" },
- "time_estimate": { "type": "integer" },
- "total_time_spent": { "type": "integer" },
- "human_time_estimate": { "type": ["string", "null"] },
- "human_total_time_spent": { "type": ["string", "null"] },
"merge_error": { "type": ["string", "null"] },
"rebase_in_progress": { "type": "boolean" },
"assignee_id": { "type": ["integer", "null"] },
- "subscribed": { "type": ["boolean", "null"] },
- "participants": { "type": "array" },
"allow_collaboration": { "type": "boolean"},
"allow_maintainer_to_push": { "type": "boolean"},
"assignee": {
diff --git a/spec/fixtures/api/schemas/entities/merge_request_sidebar.json b/spec/fixtures/api/schemas/entities/merge_request_sidebar.json
new file mode 100644
index 00000000000..7e9e048a9fd
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/merge_request_sidebar.json
@@ -0,0 +1,56 @@
+{
+ "type": "object",
+ "properties" : {
+ "id": { "type": "integer" },
+ "type": { "type": "string" },
+ "author_id": { "type": "integer" },
+ "project_id": { "type": "integer" },
+ "discussion_locked": { "type": ["boolean", "null"] },
+ "reference": { "type": "string" },
+ "current_user": {
+ "allOf": [
+ { "$ref": "../public_api/v4/user/basic.json" },
+ { "type": "object",
+ "properties" : {
+ "todo": { "$ref": "issuable_sidebar_todo.json" },
+ "can_edit": { "type": "boolean" },
+ "can_move": { "type": "boolean" },
+ "can_admin_label": { "type": "boolean" }
+ }
+ }
+ ]
+ },
+ "milestone": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "../public_api/v4/milestones.json" }
+ ]
+ },
+ "labels": {
+ "type": "array",
+ "items": { "$ref": "label.json" }
+ },
+ "assignee": {
+ "allOf": [
+ { "$ref": "../public_api/v4/user/basic.json" },
+ { "type": "object",
+ "properties" : {
+ "can_merge": { "type": "boolean" }
+ }
+ }
+ ]
+ },
+ "issuable_json_path": { "type": "string" },
+ "namespace_path": { "type": "string" },
+ "project_path": { "type": "string" },
+ "project_full_path": { "type": "string" },
+ "project_issuables_path": { "type": "string" },
+ "create_todo_path": { "type": "string" },
+ "project_milestones_path": { "type": "string" },
+ "project_labels_path": { "type": "string" },
+ "toggle_subscription_path": { "type": "string" },
+ "move_issue_path": { "type": "string" },
+ "projects_autocomplete_path": { "type": "string" }
+ },
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/entities/merge_request_sidebar_extras.json b/spec/fixtures/api/schemas/entities/merge_request_sidebar_extras.json
new file mode 100644
index 00000000000..682e345d5f5
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/merge_request_sidebar_extras.json
@@ -0,0 +1,21 @@
+{
+ "type": "object",
+ "properties" : {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "subscribed": { "type": "boolean" },
+ "time_estimate": { "type": "integer" },
+ "total_time_spent": { "type": "integer" },
+ "human_time_estimate": { "type": ["integer", "null"] },
+ "human_total_time_spent": { "type": ["integer", "null"] },
+ "participants": {
+ "type": "array",
+ "items": { "$ref": "../public_api/v4/user/basic.json" }
+ },
+ "assignees": {
+ "type": "array",
+ "items": { "$ref": "../public_api/v4/user/basic.json" }
+ }
+ },
+ "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 35971d564d5..193ab6821a5 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json
@@ -119,7 +119,8 @@
"can_push_to_source_branch": { "type": "boolean" },
"rebase_path": { "type": ["string", "null"] },
"squash": { "type": "boolean" },
- "test_reports_path": { "type": ["string", "null"] }
+ "test_reports_path": { "type": ["string", "null"] },
+ "can_receive_suggestion": { "type": "boolean" }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/api/schemas/job/trigger.json b/spec/fixtures/api/schemas/job/trigger.json
index 1c7e9cc7693..807178c662c 100644
--- a/spec/fixtures/api/schemas/job/trigger.json
+++ b/spec/fixtures/api/schemas/job/trigger.json
@@ -12,12 +12,11 @@
"type": "object",
"required": [
"key",
- "value",
"public"
],
"properties": {
"key": { "type": "string" },
- "value": { "type": "string" },
+ "value": { "type": "string", "optional": true },
"public": { "type": "boolean" }
},
"additionalProperties": false
diff --git a/spec/fixtures/api/schemas/public_api/v4/milestone.json b/spec/fixtures/api/schemas/public_api/v4/milestone.json
new file mode 100644
index 00000000000..6ca2e88ae91
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/milestone.json
@@ -0,0 +1,22 @@
+{
+ "type": "object",
+ "properties" : {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "project_id": { "type": ["integer", "null"] },
+ "group_id": { "type": ["integer", "null"] },
+ "title": { "type": "string" },
+ "description": { "type": ["string", "null"] },
+ "state": { "type": "string" },
+ "created_at": { "type": "date" },
+ "updated_at": { "type": "date" },
+ "start_date": { "type": "date" },
+ "due_date": { "type": "date" },
+ "web_url": { "type": "string" }
+ },
+ "required": [
+ "id", "iid", "title", "description", "state",
+ "state", "created_at", "updated_at", "start_date", "due_date"
+ ],
+ "additionalProperties": false
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/milestones.json b/spec/fixtures/api/schemas/public_api/v4/milestones.json
index 448e97d6c85..dcbc1910bfe 100644
--- a/spec/fixtures/api/schemas/public_api/v4/milestones.json
+++ b/spec/fixtures/api/schemas/public_api/v4/milestones.json
@@ -1,25 +1,6 @@
{
"type": "array",
"items": {
- "type": "object",
- "properties" : {
- "id": { "type": "integer" },
- "iid": { "type": "integer" },
- "project_id": { "type": ["integer", "null"] },
- "group_id": { "type": ["integer", "null"] },
- "title": { "type": "string" },
- "description": { "type": ["string", "null"] },
- "state": { "type": "string" },
- "created_at": { "type": "date" },
- "updated_at": { "type": "date" },
- "start_date": { "type": "date" },
- "due_date": { "type": "date" },
- "web_url": { "type": "string" }
- },
- "required": [
- "id", "iid", "title", "description", "state",
- "state", "created_at", "updated_at", "start_date", "due_date"
- ],
- "additionalProperties": false
+ "$ref": "./milestone.json"
}
}
diff --git a/spec/fixtures/bfg_object_map.txt b/spec/fixtures/bfg_object_map.txt
new file mode 100644
index 00000000000..c60171d8770
--- /dev/null
+++ b/spec/fixtures/bfg_object_map.txt
@@ -0,0 +1 @@
+f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 e242ed3bffccdf271b7fbaf34ed72d089537b42f
diff --git a/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json
index 314f04107eb..ce66f562175 100644
--- a/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json
+++ b/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json
@@ -11,7 +11,13 @@
"name": "Gemnasium"
},
"location": {
- "file": "app/pom.xml"
+ "file": "app/pom.xml",
+ "dependency": {
+ "package": {
+ "name": "io.netty/netty"
+ },
+ "version": "3.9.1.Final"
+ }
},
"identifiers": [
{
@@ -55,7 +61,13 @@
"name": "Gemnasium"
},
"location": {
- "file": "app/requirements.txt"
+ "file": "app/requirements.txt",
+ "dependency": {
+ "package": {
+ "name": "Django"
+ },
+ "version": "1.11.3"
+ }
},
"identifiers": [
{
@@ -93,7 +105,13 @@
"name": "Gemnasium"
},
"location": {
- "file": "rails/Gemfile.lock"
+ "file": "rails/Gemfile.lock",
+ "dependency": {
+ "package": {
+ "name": "nokogiri"
+ },
+ "version": "1.8.0"
+ }
},
"identifiers": [
{
@@ -131,7 +149,13 @@
"name": "bundler-audit"
},
"location": {
- "file": "sast-sample-rails/Gemfile.lock"
+ "file": "sast-sample-rails/Gemfile.lock",
+ "dependency": {
+ "package": {
+ "name": "ffi"
+ },
+ "version": "1.9.18"
+ }
},
"identifiers": [
{
diff --git a/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json
index 314f04107eb..ce66f562175 100644
--- a/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json
+++ b/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json
@@ -11,7 +11,13 @@
"name": "Gemnasium"
},
"location": {
- "file": "app/pom.xml"
+ "file": "app/pom.xml",
+ "dependency": {
+ "package": {
+ "name": "io.netty/netty"
+ },
+ "version": "3.9.1.Final"
+ }
},
"identifiers": [
{
@@ -55,7 +61,13 @@
"name": "Gemnasium"
},
"location": {
- "file": "app/requirements.txt"
+ "file": "app/requirements.txt",
+ "dependency": {
+ "package": {
+ "name": "Django"
+ },
+ "version": "1.11.3"
+ }
},
"identifiers": [
{
@@ -93,7 +105,13 @@
"name": "Gemnasium"
},
"location": {
- "file": "rails/Gemfile.lock"
+ "file": "rails/Gemfile.lock",
+ "dependency": {
+ "package": {
+ "name": "nokogiri"
+ },
+ "version": "1.8.0"
+ }
},
"identifiers": [
{
@@ -131,7 +149,13 @@
"name": "bundler-audit"
},
"location": {
- "file": "sast-sample-rails/Gemfile.lock"
+ "file": "sast-sample-rails/Gemfile.lock",
+ "dependency": {
+ "package": {
+ "name": "ffi"
+ },
+ "version": "1.9.18"
+ }
},
"identifiers": [
{
diff --git a/spec/fixtures/symlink_export.tar.gz b/spec/fixtures/symlink_export.tar.gz
new file mode 100644
index 00000000000..f295f69c56c
--- /dev/null
+++ b/spec/fixtures/symlink_export.tar.gz
Binary files differ
diff --git a/spec/frontend/.eslintrc.yml b/spec/frontend/.eslintrc.yml
index 6d73977a891..046215e4c93 100644
--- a/spec/frontend/.eslintrc.yml
+++ b/spec/frontend/.eslintrc.yml
@@ -6,4 +6,4 @@ plugins:
settings:
import/resolver:
jest:
- jestConfigFile: "config/jest.config.js"
+ jestConfigFile: "jest.config.js"
diff --git a/spec/frontend/dummy_spec.js b/spec/frontend/dummy_spec.js
deleted file mode 100644
index 2bfef25e9c6..00000000000
--- a/spec/frontend/dummy_spec.js
+++ /dev/null
@@ -1 +0,0 @@
-it('does nothing', () => {});
diff --git a/spec/frontend/helpers/test_constants.js b/spec/frontend/helpers/test_constants.js
new file mode 100644
index 00000000000..8dc4aef87e1
--- /dev/null
+++ b/spec/frontend/helpers/test_constants.js
@@ -0,0 +1,2 @@
+// eslint-disable-next-line import/prefer-default-export
+export const TEST_HOST = 'http://test.host';
diff --git a/spec/javascripts/pages/profiles/show/emoji_menu_spec.js b/spec/frontend/pages/profiles/show/emoji_menu_spec.js
index 864bda65736..efc338b36eb 100644
--- a/spec/javascripts/pages/profiles/show/emoji_menu_spec.js
+++ b/spec/frontend/pages/profiles/show/emoji_menu_spec.js
@@ -1,7 +1,7 @@
import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import EmojiMenu from '~/pages/profiles/show/emoji_menu';
-import { TEST_HOST } from 'spec/test_constants';
+import { TEST_HOST } from 'helpers/test_constants';
describe('EmojiMenu', () => {
const dummyEmojiTag = '<dummy></tag>';
@@ -56,7 +56,7 @@ describe('EmojiMenu', () => {
});
it('does not make an axios requst', done => {
- spyOn(axios, 'request').and.stub();
+ jest.spyOn(axios, 'request').mockReturnValue();
emojiMenu.addAward(dummyVotesBlock(), dummyAwardUrl, dummyEmoji, false, () => {
expect(axios.request).not.toHaveBeenCalled();
@@ -67,7 +67,7 @@ describe('EmojiMenu', () => {
describe('bindEvents', () => {
beforeEach(() => {
- spyOn(emojiMenu, 'registerEventListener').and.stub();
+ jest.spyOn(emojiMenu, 'registerEventListener').mockReturnValue();
});
it('binds event listeners to custom toggle button', () => {
diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js
new file mode 100644
index 00000000000..7ad2e97e7e6
--- /dev/null
+++ b/spec/frontend/test_setup.js
@@ -0,0 +1,16 @@
+const testTimeoutInMs = 300;
+jest.setTimeout(testTimeoutInMs);
+
+let testStartTime;
+
+// https://github.com/facebook/jest/issues/6947
+beforeEach(() => {
+ testStartTime = Date.now();
+});
+
+afterEach(() => {
+ const elapsedTimeInMs = Date.now() - testStartTime;
+ if (elapsedTimeInMs > testTimeoutInMs) {
+ throw new Error(`Test took too long (${elapsedTimeInMs}ms > ${testTimeoutInMs}ms)!`);
+ }
+});
diff --git a/spec/javascripts/vue_shared/components/notes/timeline_entry_item_spec.js b/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js
index c15635f2105..c15635f2105 100644
--- a/spec/javascripts/vue_shared/components/notes/timeline_entry_item_spec.js
+++ b/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js
diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb
index 139387e0b24..3820cf5cb9d 100644
--- a/spec/helpers/emails_helper_spec.rb
+++ b/spec/helpers/emails_helper_spec.rb
@@ -73,4 +73,59 @@ describe EmailsHelper do
end
end
end
+
+ describe '#create_list_id_string' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:full_path, :list_id_path) do
+ "01234" | "01234"
+ "5/0123" | "012.."
+ "45/012" | "012.."
+ "012" | "012"
+ "23/01" | "01.23"
+ "2/01" | "01.2"
+ "234/01" | "01.."
+ "4/2/0" | "0.2.4"
+ "45/2/0" | "0.2.."
+ "5/23/0" | "0.."
+ "0-2/5" | "5.0-2"
+ "0_2/5" | "5.0-2"
+ "0.2/5" | "5.0-2"
+ end
+
+ with_them do
+ it 'ellipcizes different variants' do
+ project = double("project")
+ allow(project).to receive(:full_path).and_return(full_path)
+ allow(project).to receive(:id).and_return(12345)
+ # Set a max length that gives only 5 chars for the project full path
+ max_length = "12345..#{Gitlab.config.gitlab.host}".length + 5
+ list_id = create_list_id_string(project, max_length)
+
+ expect(list_id).to eq("12345.#{list_id_path}.#{Gitlab.config.gitlab.host}")
+ expect(list_id).to satisfy { |s| s.length <= max_length }
+ end
+ end
+ end
+
+ describe 'Create realistic List-Id identifier' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:full_path, :list_id_path) do
+ "gitlab-org/gitlab-ce" | "gitlab-ce.gitlab-org"
+ "project-name/subproject_name/my.project" | "my-project.subproject-name.project-name"
+ end
+
+ with_them do
+ it 'Produces the right List-Id' do
+ project = double("project")
+ allow(project).to receive(:full_path).and_return(full_path)
+ allow(project).to receive(:id).and_return(12345)
+ list_id = create_list_id_string(project)
+
+ expect(list_id).to eq("12345.#{list_id_path}.#{Gitlab.config.gitlab.host}")
+ expect(list_id).to satisfy { |s| s.length <= 255 }
+ end
+ end
+ end
end
diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
index 8d0679e5699..3d15306d4d2 100644
--- a/spec/helpers/events_helper_spec.rb
+++ b/spec/helpers/events_helper_spec.rb
@@ -84,4 +84,36 @@ describe EventsHelper do
expect(helper.event_feed_url(event)).to eq(push_event_feed_url(event))
end
end
+
+ describe '#event_note_target_url' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:event) { create(:event, project: project) }
+ let(:project_base_url) { namespace_project_url(namespace_id: project.namespace, id: project) }
+
+ subject { helper.event_note_target_url(event) }
+
+ it 'returns a commit note url' do
+ event.target = create(:note_on_commit, note: '+1 from me')
+
+ expect(subject).to eq("#{project_base_url}/commit/#{event.target.commit_id}#note_#{event.target.id}")
+ end
+
+ it 'returns a project snippet note url' do
+ event.target = create(:note, :on_snippet, note: 'keep going')
+
+ expect(subject).to eq("#{project_base_url}/snippets/#{event.note_target.id}#note_#{event.target.id}")
+ end
+
+ it 'returns a project issue url' do
+ event.target = create(:note_on_issue, note: 'nice work')
+
+ expect(subject).to eq("#{project_base_url}/issues/#{event.note_target.iid}#note_#{event.target.id}")
+ end
+
+ it 'returns a merge request url' do
+ event.target = create(:note_on_merge_request, note: 'LGTM!')
+
+ expect(subject).to eq("#{project_base_url}/merge_requests/#{event.note_target.iid}#note_#{event.target.id}")
+ end
+ end
end
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 4af98bc3678..81231cca085 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -43,16 +43,19 @@ describe IssuablesHelper do
end
describe '#issuable_labels_tooltip' do
+ let(:label_entity) { LabelEntity.represent(label).as_json }
+ let(:label2_entity) { LabelEntity.represent(label2).as_json }
+
it 'returns label text with no labels' do
expect(issuable_labels_tooltip([])).to eq("Labels")
end
it 'returns label text with labels within max limit' do
- expect(issuable_labels_tooltip([label])).to eq(label.title)
+ expect(issuable_labels_tooltip([label_entity])).to eq(label[:title])
end
it 'returns label text with labels exceeding max limit' do
- expect(issuable_labels_tooltip([label, label2], limit: 1)).to eq("#{label.title}, and 1 more")
+ expect(issuable_labels_tooltip([label_entity, label2_entity], limit: 1)).to eq("#{label[:title]}, and 1 more")
end
end
@@ -197,33 +200,4 @@ describe IssuablesHelper do
expect(helper.issuable_initial_data(issue)).to eq(expected_data)
end
end
-
- describe '#selected_labels' do
- context 'if label_name param is a string' do
- it 'returns a new label with title' do
- allow(helper).to receive(:params)
- .and_return(ActionController::Parameters.new(label_name: 'test label'))
-
- labels = helper.selected_labels
-
- expect(labels).to be_an(Array)
- expect(labels.size).to eq(1)
- expect(labels.first.title).to eq('test label')
- end
- end
-
- context 'if label_name param is an array' do
- it 'returns a new label with title for each element' do
- allow(helper).to receive(:params)
- .and_return(ActionController::Parameters.new(label_name: ['test label 1', 'test label 2']))
-
- labels = helper.selected_labels
-
- expect(labels).to be_an(Array)
- expect(labels.size).to eq(2)
- expect(labels.first.title).to eq('test label 1')
- expect(labels.second.title).to eq('test label 2')
- end
- end
- end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 976b6c312b4..edd680ee1d1 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -229,6 +229,18 @@ describe ProjectsHelper do
end
end
+ describe '#link_to_project' do
+ let(:group) { create(:group, name: 'group name with space') }
+ let(:project) { create(:project, group: group, name: 'project name with space') }
+ subject { link_to_project(project) }
+
+ it 'returns an HTML link to the project' do
+ expect(subject).to match(%r{/#{group.full_path}/#{project.path}})
+ expect(subject).to include('group name with space /')
+ expect(subject).to include('project name with space')
+ end
+ end
+
describe '#link_to_member_avatar' do
let(:user) { build_stubbed(:user) }
let(:expected) { double }
@@ -471,6 +483,31 @@ describe ProjectsHelper do
end
end
+ describe 'link_to_bfg' do
+ subject { helper.link_to_bfg }
+
+ it 'generates a hardcoded link to the BFG Repo-Cleaner' do
+ result = helper.link_to_bfg
+ doc = Nokogiri::HTML.fragment(result)
+
+ expect(doc.children.size).to eq(1)
+
+ link = doc.children.first
+
+ aggregate_failures do
+ expect(result).to be_html_safe
+
+ expect(link.name).to eq('a')
+ expect(link[:target]).to eq('_blank')
+ expect(link[:rel]).to eq('noopener noreferrer')
+ expect(link[:href]).to eq('https://rtyley.github.io/bfg-repo-cleaner/')
+ expect(link.inner_html).to eq('BFG')
+
+ expect(result).to be_html_safe
+ end
+ end
+ end
+
describe '#legacy_render_context' do
it 'returns the redcarpet engine' do
params = { legacy_render: '1' }
@@ -482,4 +519,114 @@ describe ProjectsHelper do
expect(helper.legacy_render_context({})).to be_empty
end
end
+
+ describe '#explore_projects_tab?' do
+ subject { helper.explore_projects_tab? }
+
+ it 'returns true when on the "All" tab under "Explore projects"' do
+ allow(@request).to receive(:path) { explore_projects_path }
+
+ expect(subject).to be_truthy
+ end
+
+ it 'returns true when on the "Trending" tab under "Explore projects"' do
+ allow(@request).to receive(:path) { trending_explore_projects_path }
+
+ expect(subject).to be_truthy
+ end
+
+ it 'returns true when on the "Starred" tab under "Explore projects"' do
+ allow(@request).to receive(:path) { starred_explore_projects_path }
+
+ expect(subject).to be_truthy
+ end
+
+ it 'returns false when on the "Your projects" tab' do
+ allow(@request).to receive(:path) { dashboard_projects_path }
+
+ expect(subject).to be_falsey
+ end
+ end
+
+ describe '#show_merge_request_count' do
+ context 'when the feature flag is enabled' do
+ before do
+ stub_feature_flags(project_list_show_mr_count: true)
+ end
+
+ it 'returns true if compact mode is disabled' do
+ expect(helper.show_merge_request_count?).to be_truthy
+ end
+
+ it 'returns false if compact mode is enabled' do
+ expect(helper.show_merge_request_count?(compact_mode: true)).to be_falsey
+ end
+ end
+
+ context 'when the feature flag is disabled' do
+ before do
+ stub_feature_flags(project_list_show_mr_count: false)
+ end
+
+ it 'always returns false' do
+ expect(helper.show_merge_request_count?(disabled: false)).to be_falsy
+ expect(helper.show_merge_request_count?(disabled: true)).to be_falsy
+ end
+ end
+
+ context 'disabled flag' do
+ before do
+ stub_feature_flags(project_list_show_mr_count: true)
+ end
+
+ it 'returns false if disabled flag is true' do
+ expect(helper.show_merge_request_count?(disabled: true)).to be_falsey
+ end
+
+ it 'returns true if disabled flag is false' do
+ expect(helper.show_merge_request_count?).to be_truthy
+ end
+ end
+ end
+
+ describe '#show_issue_count?' do
+ context 'when the feature flag is enabled' do
+ before do
+ stub_feature_flags(project_list_show_issue_count: true)
+ end
+
+ it 'returns true if compact mode is disabled' do
+ expect(helper.show_issue_count?).to be_truthy
+ end
+
+ it 'returns false if compact mode is enabled' do
+ expect(helper.show_issue_count?(compact_mode: true)).to be_falsey
+ end
+ end
+
+ context 'when the feature flag is disabled' do
+ before do
+ stub_feature_flags(project_list_show_issue_count: false)
+ end
+
+ it 'always returns false' do
+ expect(helper.show_issue_count?(disabled: false)).to be_falsy
+ expect(helper.show_issue_count?(disabled: true)).to be_falsy
+ end
+ end
+
+ context 'disabled flag' do
+ before do
+ stub_feature_flags(project_list_show_issue_count: true)
+ end
+
+ it 'returns false if disabled flag is true' do
+ expect(helper.show_issue_count?(disabled: true)).to be_falsey
+ end
+
+ it 'returns true if disabled flag is false' do
+ expect(helper.show_issue_count?).to be_truthy
+ end
+ end
+ end
end
diff --git a/spec/helpers/sorting_helper_spec.rb b/spec/helpers/sorting_helper_spec.rb
new file mode 100644
index 00000000000..f405268d198
--- /dev/null
+++ b/spec/helpers/sorting_helper_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe SortingHelper do
+ include ApplicationHelper
+ include IconsHelper
+
+ describe '#issuable_sort_option_title' do
+ it 'returns correct title for issuable_sort_option_overrides key' do
+ expect(issuable_sort_option_title('created_asc')).to eq('Created date')
+ end
+
+ it 'returns correct title for a valid sort value' do
+ expect(issuable_sort_option_title('priority')).to eq('Priority')
+ end
+
+ it 'returns nil for invalid sort value' do
+ expect(issuable_sort_option_title('invalid_key')).to eq(nil)
+ end
+ end
+
+ describe '#issuable_sort_direction_button' do
+ before do
+ allow(self).to receive(:request).and_return(double(path: 'http://test.com', query_parameters: { label_name: 'test_label' }))
+ end
+
+ it 'keeps label filter param' do
+ expect(issuable_sort_direction_button('created_date')).to include('label_name=test_label')
+ end
+
+ it 'returns icon with sort-highest when sort is created_date' do
+ expect(issuable_sort_direction_button('created_date')).to include('sort-highest')
+ end
+
+ it 'returns icon with sort-lowest when sort is asc' do
+ expect(issuable_sort_direction_button('created_asc')).to include('sort-lowest')
+ end
+
+ it 'returns icon with sort-lowest when sorting by milestone' do
+ expect(issuable_sort_direction_button('milestone')).to include('sort-lowest')
+ end
+
+ it 'returns icon with sort-lowest when sorting by due_date' do
+ expect(issuable_sort_direction_button('due_date')).to include('sort-lowest')
+ end
+ end
+end
diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb
index c580b78c908..03df9deafa1 100644
--- a/spec/helpers/storage_helper_spec.rb
+++ b/spec/helpers/storage_helper_spec.rb
@@ -15,11 +15,7 @@ describe StorageHelper do
end
it "uses commas as thousands separator" do
- if Gitlab.rails5?
- expect(helper.storage_counter(100_000_000_000_000_000_000_000)).to eq("86,736.2 EB")
- else
- expect(helper.storage_counter(100_000_000_000_000_000)).to eq("90,949.5 TB")
- end
+ expect(helper.storage_counter(100_000_000_000_000_000_000_000)).to eq("86,736.2 EB")
end
end
end
diff --git a/spec/helpers/version_check_helper_spec.rb b/spec/helpers/version_check_helper_spec.rb
index 9d4e34abef5..bfec7ad4bba 100644
--- a/spec/helpers/version_check_helper_spec.rb
+++ b/spec/helpers/version_check_helper_spec.rb
@@ -13,21 +13,21 @@ describe VersionCheckHelper do
before do
allow(Rails.env).to receive(:production?) { true }
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:version_check_enabled) { true }
- allow_any_instance_of(VersionCheck).to receive(:url) { 'https://version.host.com/check.svg?gitlab_info=xxx' }
-
- @image_tag = helper.version_status_badge
+ allow(VersionCheck).to receive(:url) { 'https://version.host.com/check.svg?gitlab_info=xxx' }
end
it 'should return an image tag' do
- expect(@image_tag).to match(/^<img/)
+ expect(helper.version_status_badge).to start_with('<img')
end
it 'should have a js prefixed css class' do
- expect(@image_tag).to match(/class="js-version-status-badge lazy"/)
+ expect(helper.version_status_badge)
+ .to match(/class="js-version-status-badge lazy"/)
end
it 'should have a VersionCheck url as the src' do
- expect(@image_tag).to match(%r{src="https://version\.host\.com/check\.svg\?gitlab_info=xxx"})
+ expect(helper.version_status_badge)
+ .to include(%{src="https://version.host.com/check.svg?gitlab_info=xxx"})
end
end
end
diff --git a/spec/initializers/lograge_spec.rb b/spec/initializers/lograge_spec.rb
new file mode 100644
index 00000000000..24d366731a2
--- /dev/null
+++ b/spec/initializers/lograge_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'lograge', type: :request do
+ let(:headers) { { 'X-Request-ID' => 'new-correlation-id' } }
+
+ context 'for API requests' do
+ subject { get("/api/v4/endpoint", params: {}, headers: headers) }
+
+ it 'logs to api_json log' do
+ # we assert receiving parameters by grape logger
+ expect_any_instance_of(Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp).to receive(:call)
+ .with(anything, anything, anything, a_hash_including("correlation_id" => "new-correlation-id"))
+ .and_call_original
+
+ subject
+ end
+ end
+
+ context 'for Controller requests' do
+ subject { get("/", params: {}, headers: headers) }
+
+ it 'logs to production_json log' do
+ # formatter receives a hash with correlation id
+ expect(Lograge.formatter).to receive(:call)
+ .with(a_hash_including("correlation_id" => "new-correlation-id"))
+ .and_call_original
+
+ # a log file receives a line with correlation id
+ expect(Lograge.logger).to receive(:send)
+ .with(anything, include('"correlation_id":"new-correlation-id"'))
+ .and_call_original
+
+ subject
+ end
+ end
+end
diff --git a/spec/initializers/8_metrics_spec.rb b/spec/initializers/zz_metrics_spec.rb
index 80c77057065..3eaccfe8d8b 100644
--- a/spec/initializers/8_metrics_spec.rb
+++ b/spec/initializers/zz_metrics_spec.rb
@@ -16,7 +16,7 @@ describe 'instrument_classes' do
end
it 'can autoload and instrument all files' do
- require_relative '../../config/initializers/8_metrics'
+ require_relative '../../config/initializers/zz_metrics'
expect { instrument_classes(config) }.not_to raise_error
end
end
diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js
index 7de38913bae..9d55c615450 100644
--- a/spec/javascripts/api_spec.js
+++ b/spec/javascripts/api_spec.js
@@ -180,6 +180,23 @@ describe('Api', () => {
});
});
+ describe('projectRunners', () => {
+ it('fetches the runners of a project', done => {
+ const projectPath = 7;
+ const params = { scope: 'active' };
+ const mockData = [{ id: 4 }];
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/runners`;
+ mock.onGet(expectedUrl, { params }).reply(200, mockData);
+
+ Api.projectRunners(projectPath, { params })
+ .then(({ data }) => {
+ expect(data).toEqual(mockData);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
describe('newLabel', () => {
it('creates a new label', done => {
const namespace = 'some namespace';
@@ -316,6 +333,40 @@ describe('Api', () => {
});
});
+ describe('user', () => {
+ it('fetches single user', done => {
+ const userId = '123456';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users/${userId}`;
+ mock.onGet(expectedUrl).reply(200, {
+ name: 'testuser',
+ });
+
+ Api.user(userId)
+ .then(({ data }) => {
+ expect(data.name).toBe('testuser');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('user status', () => {
+ it('fetches single user status', done => {
+ const userId = '123456';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/users/${userId}/status`;
+ mock.onGet(expectedUrl).reply(200, {
+ message: 'testmessage',
+ });
+
+ Api.userStatus(userId)
+ .then(({ data }) => {
+ expect(data.message).toBe('testmessage');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
describe('commitPipelines', () => {
it('fetches pipelines for a given commit', done => {
const projectId = 'example/foobar';
diff --git a/spec/javascripts/blob_edit/blob_bundle_spec.js b/spec/javascripts/blob_edit/blob_bundle_spec.js
index 759d170af77..57f60a4a3dd 100644
--- a/spec/javascripts/blob_edit/blob_bundle_spec.js
+++ b/spec/javascripts/blob_edit/blob_bundle_spec.js
@@ -14,6 +14,7 @@ describe('EditBlob', () => {
setFixtures(`
<div class="js-edit-blob-form">
<button class="js-commit-button"></button>
+ <a class="btn btn-cancel" href="#"></a>
</div>`);
blobBundle();
});
@@ -27,4 +28,10 @@ describe('EditBlob', () => {
expect(window.onbeforeunload).toBeNull();
});
+
+ it('removes beforeunload listener when cancel link is clicked', () => {
+ $('.btn.btn-cancel').click();
+
+ expect(window.onbeforeunload).toBeNull();
+ });
});
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index 54f1edfb1f9..22f192bc7f3 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -65,6 +65,13 @@ describe('Store', () => {
expect(list).toBeDefined();
});
+ it('finds list by label ID', () => {
+ boardsStore.addList(listObj);
+ const list = boardsStore.findListByLabelId(listObj.label.id);
+
+ expect(list.id).toBe(listObj.id);
+ });
+
it('gets issue when new list added', done => {
boardsStore.addList(listObj);
const list = boardsStore.findList('id', listObj.id);
diff --git a/spec/javascripts/boards/components/issue_due_date_spec.js b/spec/javascripts/boards/components/issue_due_date_spec.js
index 9e49330c052..054cf8c5b7d 100644
--- a/spec/javascripts/boards/components/issue_due_date_spec.js
+++ b/spec/javascripts/boards/components/issue_due_date_spec.js
@@ -49,10 +49,11 @@ describe('Issue Due Date component', () => {
it('should render month and day for other dates', () => {
date.setDate(date.getDate() + 17);
vm = createComponent(date);
+ const today = new Date();
+ const isDueInCurrentYear = today.getFullYear() === date.getFullYear();
+ const format = isDueInCurrentYear ? 'mmm d' : 'mmm d, yyyy';
- expect(vm.$el.querySelector('time').textContent.trim()).toEqual(
- dateFormat(date, 'mmm d', true),
- );
+ expect(vm.$el.querySelector('time').textContent.trim()).toEqual(dateFormat(date, format, true));
});
it('should contain the correct `.text-danger` css class for overdue issue', () => {
diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js
index 437ab4bb3df..54fb0e8228b 100644
--- a/spec/javascripts/boards/issue_spec.js
+++ b/spec/javascripts/boards/issue_spec.js
@@ -55,15 +55,27 @@ describe('Issue model', () => {
expect(issue.labels.length).toBe(2);
});
- it('does not add existing label', () => {
+ it('does not add label if label id exists', () => {
+ issue.addLabel({
+ id: 1,
+ title: 'test 2',
+ color: 'blue',
+ description: 'testing',
+ });
+
+ expect(issue.labels.length).toBe(1);
+ expect(issue.labels[0].color).toBe('red');
+ });
+
+ it('adds other label with same title', () => {
issue.addLabel({
id: 2,
title: 'test',
color: 'blue',
- description: 'bugs!',
+ description: 'other test',
});
- expect(issue.labels.length).toBe(1);
+ expect(issue.labels.length).toBe(2);
});
it('finds label', () => {
diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js
index c28e41ec175..14fff9223f4 100644
--- a/spec/javascripts/boards/mock_data.js
+++ b/spec/javascripts/boards/mock_data.js
@@ -1,5 +1,11 @@
import BoardService from '~/boards/services/board_service';
+export const boardObj = {
+ id: 1,
+ name: 'test',
+ milestone_id: null,
+};
+
export const listObj = {
id: 300,
position: 0,
@@ -40,6 +46,12 @@ export const BoardsMockData = {
},
],
},
+ '/test/issue-boards/milestones.json': [
+ {
+ id: 1,
+ title: 'test',
+ },
+ ],
},
POST: {
'/test/-/boards/1/lists': listObj,
@@ -70,3 +82,60 @@ export const mockBoardService = (opts = {}) => {
boardId,
});
};
+
+export const mockAssigneesList = [
+ {
+ id: 2,
+ name: 'Terrell Graham',
+ username: 'monserrate.gleichner',
+ state: 'active',
+ avatar_url: 'https://www.gravatar.com/avatar/598fd02741ac58b88854a99d16704309?s=80&d=identicon',
+ web_url: 'http://127.0.0.1:3001/monserrate.gleichner',
+ path: '/monserrate.gleichner',
+ },
+ {
+ id: 12,
+ name: 'Susy Johnson',
+ username: 'tana_harvey',
+ state: 'active',
+ avatar_url: 'https://www.gravatar.com/avatar/e021a7b0f3e4ae53b5068d487e68c031?s=80&d=identicon',
+ web_url: 'http://127.0.0.1:3001/tana_harvey',
+ path: '/tana_harvey',
+ },
+ {
+ id: 20,
+ name: 'Conchita Eichmann',
+ username: 'juliana_gulgowski',
+ state: 'active',
+ avatar_url: 'https://www.gravatar.com/avatar/c43c506cb6fd7b37017d3b54b94aa937?s=80&d=identicon',
+ web_url: 'http://127.0.0.1:3001/juliana_gulgowski',
+ path: '/juliana_gulgowski',
+ },
+ {
+ id: 6,
+ name: 'Bryce Turcotte',
+ username: 'melynda',
+ state: 'active',
+ avatar_url: 'https://www.gravatar.com/avatar/cc2518f2c6f19f8fac49e1a5ee092a9b?s=80&d=identicon',
+ web_url: 'http://127.0.0.1:3001/melynda',
+ path: '/melynda',
+ },
+ {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ web_url: 'http://127.0.0.1:3001/root',
+ path: '/root',
+ },
+];
+
+export const mockMilestone = {
+ id: 1,
+ state: 'active',
+ title: 'Milestone title',
+ description: 'Harum corporis aut consequatur quae dolorem error sequi quia.',
+ start_date: '2018-01-01',
+ due_date: '2019-12-31',
+};
diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
index 30b15011def..bef59b86d0c 100644
--- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
@@ -118,6 +118,8 @@ describe('VariableList', () => {
loadFixtures('projects/ci_cd_settings.html.raw');
$wrapper = $('.js-ci-variable-list-section');
+ $wrapper.find('.js-ci-variable-input-protected').attr('data-default', 'false');
+
variableList = new VariableList({
container: $wrapper,
formField: 'variables',
diff --git a/spec/javascripts/clusters/components/applications_spec.js b/spec/javascripts/clusters/components/applications_spec.js
index 928bf70f3a2..14ef1193984 100644
--- a/spec/javascripts/clusters/components/applications_spec.js
+++ b/spec/javascripts/clusters/components/applications_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import applications from '~/clusters/components/applications.vue';
+import { CLUSTER_TYPE } from '~/clusters/constants';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Applications', () => {
@@ -14,9 +15,10 @@ describe('Applications', () => {
vm.$destroy();
});
- describe('', () => {
+ describe('Project cluster applications', () => {
beforeEach(() => {
vm = mountComponent(Applications, {
+ type: CLUSTER_TYPE.PROJECT,
applications: {
helm: { title: 'Helm Tiller' },
ingress: { title: 'Ingress' },
@@ -30,31 +32,76 @@ describe('Applications', () => {
});
it('renders a row for Helm Tiller', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-helm')).toBeDefined();
+ 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')).toBeDefined();
+ 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')).toBeDefined();
+ 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')).toBeDefined();
+ 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')).toBeDefined();
+ 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.toBe(null);
+ 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.toBe(null);
+ expect(vm.$el.querySelector('.js-cluster-application-row-knative')).not.toBeNull();
+ });
+ });
+
+ describe('Group cluster applications', () => {
+ beforeEach(() => {
+ vm = mountComponent(Applications, {
+ type: CLUSTER_TYPE.GROUP,
+ applications: {
+ helm: { title: 'Helm Tiller' },
+ ingress: { title: 'Ingress' },
+ cert_manager: { title: 'Cert-Manager' },
+ runner: { title: 'GitLab Runner' },
+ prometheus: { title: 'Prometheus' },
+ jupyter: { title: 'JupyterHub' },
+ knative: { title: 'Knative' },
+ },
+ });
+ });
+
+ 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')).toBeNull();
+ });
+
+ it('renders a row for GitLab Runner', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-runner')).toBeNull();
+ });
+
+ it('renders a row for Jupyter', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-jupyter')).toBeNull();
+ });
+
+ it('renders a row for Knative', () => {
+ expect(vm.$el.querySelector('.js-cluster-application-row-knative')).toBeNull();
});
});
@@ -129,6 +176,54 @@ describe('Applications', () => {
});
});
+ describe('Cert-Manager application', () => {
+ describe('when not installed', () => {
+ it('renders email & allows editing', () => {
+ vm = mountComponent(Applications, {
+ applications: {
+ helm: { title: 'Helm Tiller', status: 'installed' },
+ ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' },
+ cert_manager: {
+ title: 'Cert-Manager',
+ email: 'before@example.com',
+ status: 'installable',
+ },
+ runner: { title: 'GitLab Runner' },
+ prometheus: { title: 'Prometheus' },
+ jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' },
+ knative: { title: 'Knative', hostname: '', status: 'installable' },
+ },
+ });
+
+ expect(vm.$el.querySelector('.js-email').value).toEqual('before@example.com');
+ expect(vm.$el.querySelector('.js-email').getAttribute('readonly')).toBe(null);
+ });
+ });
+
+ describe('when installed', () => {
+ it('renders email in readonly', () => {
+ vm = mountComponent(Applications, {
+ applications: {
+ helm: { title: 'Helm Tiller', status: 'installed' },
+ ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' },
+ cert_manager: {
+ title: 'Cert-Manager',
+ email: 'after@example.com',
+ status: 'installed',
+ },
+ runner: { title: 'GitLab Runner' },
+ prometheus: { title: 'Prometheus' },
+ jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' },
+ knative: { title: 'Knative', hostname: '', status: 'installable' },
+ },
+ });
+
+ expect(vm.$el.querySelector('.js-email').value).toEqual('after@example.com');
+ expect(vm.$el.querySelector('.js-email').getAttribute('readonly')).toEqual('readonly');
+ });
+ });
+ });
+
describe('Jupyter application', () => {
describe('with ingress installed with ip & jupyter installable', () => {
it('renders hostname active input', () => {
diff --git a/spec/javascripts/clusters/services/mock_data.js b/spec/javascripts/clusters/services/mock_data.js
index 540d7f30858..3c3d9977ffb 100644
--- a/spec/javascripts/clusters/services/mock_data.js
+++ b/spec/javascripts/clusters/services/mock_data.js
@@ -42,6 +42,7 @@ const CLUSTERS_MOCK_DATA = {
name: 'cert_manager',
status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect',
+ email: 'test@example.com',
},
],
},
@@ -86,6 +87,7 @@ const CLUSTERS_MOCK_DATA = {
name: 'cert_manager',
status: APPLICATION_STATUS.ERROR,
status_reason: 'Cannot connect',
+ email: 'test@example.com',
},
],
},
diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/javascripts/clusters/stores/clusters_store_spec.js
index 7ea0878ad45..1ca55549094 100644
--- a/spec/javascripts/clusters/stores/clusters_store_spec.js
+++ b/spec/javascripts/clusters/stores/clusters_store_spec.js
@@ -115,6 +115,7 @@ describe('Clusters Store', () => {
statusReason: mockResponseData.applications[6].status_reason,
requestStatus: null,
requestReason: null,
+ email: mockResponseData.applications[6].email,
},
},
});
diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js
index 1e2f7ff4fd8..a2cbc0f3c72 100644
--- a/spec/javascripts/diffs/components/app_spec.js
+++ b/spec/javascripts/diffs/components/app_spec.js
@@ -1,33 +1,44 @@
-import Vue from 'vue';
-import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import Vuex from 'vuex';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
import { TEST_HOST } from 'spec/test_constants';
import App from '~/diffs/components/app.vue';
+import NoChanges from '~/diffs/components/no_changes.vue';
+import DiffFile from '~/diffs/components/diff_file.vue';
import createDiffsStore from '../create_diffs_store';
describe('diffs/components/app', () => {
const oldMrTabs = window.mrTabs;
- const Component = Vue.extend(App);
-
+ let store;
let vm;
- beforeEach(() => {
- // setup globals (needed for component to mount :/)
- window.mrTabs = jasmine.createSpyObj('mrTabs', ['resetViewContainer']);
- window.mrTabs.expandViewContainer = jasmine.createSpy();
- window.location.hash = 'ABC_123';
+ function createComponent(props = {}, extendStore = () => {}) {
+ const localVue = createLocalVue();
- // setup component
- const store = createDiffsStore();
+ localVue.use(Vuex);
+
+ store = createDiffsStore();
store.state.diffs.isLoading = false;
- vm = mountComponentWithStore(Component, {
- store,
- props: {
+ extendStore(store);
+
+ vm = shallowMount(localVue.extend(App), {
+ localVue,
+ propsData: {
endpoint: `${TEST_HOST}/diff/endpoint`,
projectPath: 'namespace/project',
currentUser: {},
+ changesEmptyStateIllustration: '',
+ ...props,
},
+ store,
});
+ }
+
+ beforeEach(() => {
+ // setup globals (needed for component to mount :/)
+ window.mrTabs = jasmine.createSpyObj('mrTabs', ['resetViewContainer']);
+ window.mrTabs.expandViewContainer = jasmine.createSpy();
+ window.location.hash = 'ABC_123';
});
afterEach(() => {
@@ -35,21 +46,53 @@ describe('diffs/components/app', () => {
window.mrTabs = oldMrTabs;
// reset component
- vm.$destroy();
+ vm.destroy();
});
it('does not show commit info', () => {
- expect(vm.$el).not.toContainElement('.blob-commit-info');
+ createComponent();
+
+ expect(vm.contains('.blob-commit-info')).toBe(false);
});
it('sets highlighted row if hash exists in location object', done => {
- vm.$props.shouldShow = true;
-
- vm.$nextTick()
- .then(() => {
- expect(vm.$store.state.diffs.highlightedRow).toBe('ABC_123');
- })
- .then(done)
- .catch(done.fail);
+ createComponent({
+ shouldShow: true,
+ });
+
+ // Component uses $nextTick so we wait until that has finished
+ setTimeout(() => {
+ expect(store.state.diffs.highlightedRow).toBe('ABC_123');
+
+ done();
+ });
+ });
+
+ describe('empty state', () => {
+ it('renders empty state when no diff files exist', () => {
+ createComponent();
+
+ expect(vm.contains(NoChanges)).toBe(true);
+ });
+
+ it('does not render empty state when diff files exist', () => {
+ createComponent({}, () => {
+ store.state.diffs.diffFiles.push({
+ id: 1,
+ });
+ });
+
+ expect(vm.contains(NoChanges)).toBe(false);
+ expect(vm.findAll(DiffFile).length).toBe(1);
+ });
+
+ it('does not render empty state when versions match', () => {
+ createComponent({}, () => {
+ store.state.diffs.startVersion = { version_index: 1 };
+ store.state.diffs.mergeRequestDiff = { version_index: 1 };
+ });
+
+ expect(vm.contains(NoChanges)).toBe(false);
+ });
});
});
diff --git a/spec/javascripts/diffs/components/diff_content_spec.js b/spec/javascripts/diffs/components/diff_content_spec.js
index c25f6167163..9e158327a77 100644
--- a/spec/javascripts/diffs/components/diff_content_spec.js
+++ b/spec/javascripts/diffs/components/diff_content_spec.js
@@ -17,6 +17,7 @@ describe('DiffContent', () => {
current_user: {
can_create_note: false,
},
+ preview_note_path: 'path/to/preview',
};
vm = mountComponentWithStore(Component, {
@@ -49,6 +50,45 @@ describe('DiffContent', () => {
});
});
+ describe('empty files', () => {
+ beforeEach(() => {
+ vm.diffFile.empty = true;
+ vm.diffFile.highlighted_diff_lines = [];
+ vm.diffFile.parallel_diff_lines = [];
+ });
+
+ it('should render a message', done => {
+ vm.$nextTick(() => {
+ const block = vm.$el.querySelector('.diff-viewer .nothing-here-block');
+
+ expect(block).not.toBe(null);
+ expect(block.textContent.trim()).toContain('Empty file');
+
+ done();
+ });
+ });
+
+ it('should not render multiple messages', done => {
+ vm.diffFile.mode_changed = true;
+ vm.diffFile.b_mode = '100755';
+ vm.diffFile.viewer.name = 'mode_changed';
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelectorAll('.nothing-here-block').length).toBe(1);
+
+ done();
+ });
+ });
+
+ it('should not render diff table', done => {
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('table')).toBe(null);
+
+ done();
+ });
+ });
+ });
+
describe('Non-Text diffs', () => {
beforeEach(() => {
vm.diffFile.viewer.name = 'image';
diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js
index 51bb4807960..1af49282c36 100644
--- a/spec/javascripts/diffs/components/diff_file_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_spec.js
@@ -74,6 +74,32 @@ describe('DiffFile', () => {
});
});
+ it('should be collapsed for renamed files', done => {
+ vm.file.renderIt = true;
+ vm.file.collapsed = false;
+ vm.file.highlighted_diff_lines = null;
+ vm.file.renamed_file = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.innerText).not.toContain('This diff is collapsed');
+
+ done();
+ });
+ });
+
+ it('should be collapsed for mode changed files', done => {
+ vm.file.renderIt = true;
+ vm.file.collapsed = false;
+ vm.file.highlighted_diff_lines = null;
+ vm.file.mode_changed = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.innerText).not.toContain('This diff is collapsed');
+
+ done();
+ });
+ });
+
it('should have loading icon while loading a collapsed diffs', done => {
vm.file.collapsed = true;
vm.isLoadingCollapsedDiff = true;
diff --git a/spec/javascripts/diffs/components/no_changes_spec.js b/spec/javascripts/diffs/components/no_changes_spec.js
index 7237274eb43..e45d34bf9d5 100644
--- a/spec/javascripts/diffs/components/no_changes_spec.js
+++ b/spec/javascripts/diffs/components/no_changes_spec.js
@@ -1 +1,40 @@
-// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
+import { createStore } from '~/mr_notes/stores';
+import NoChanges from '~/diffs/components/no_changes.vue';
+
+describe('Diff no changes empty state', () => {
+ let vm;
+
+ function createComponent(extendStore = () => {}) {
+ const localVue = createLocalVue();
+ localVue.use(Vuex);
+
+ const store = createStore();
+ extendStore(store);
+
+ vm = shallowMount(localVue.extend(NoChanges), {
+ localVue,
+ store,
+ propsData: {
+ changesEmptyStateIllustration: '',
+ },
+ });
+ }
+
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it('prevents XSS', () => {
+ createComponent(store => {
+ // eslint-disable-next-line no-param-reassign
+ store.state.notes.noteableData = {
+ source_branch: '<script>alert("test");</script>',
+ target_branch: '<script>alert("test");</script>',
+ };
+ });
+
+ expect(vm.contains('script')).toBe(false);
+ });
+});
diff --git a/spec/javascripts/diffs/mock_data/diff_discussions.js b/spec/javascripts/diffs/mock_data/diff_discussions.js
index 5ffe5a366ba..c1e9f791925 100644
--- a/spec/javascripts/diffs/mock_data/diff_discussions.js
+++ b/spec/javascripts/diffs/mock_data/diff_discussions.js
@@ -487,10 +487,19 @@ export default {
],
},
diff_discussion: true,
- truncated_diff_lines:
- '<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="1">\n1\n</td>\n<td class="line_content new noteable_line"><span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n</td>\n</tr>\n<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="2">\n2\n</td>\n<td class="line_content new noteable_line"><span id="LC2" class="line" lang="plaintext"></span>\n</td>\n</tr>\n',
- image_diff_html:
- '<div class="image js-replaced-image" data="">\n<div class="two-up view">\n<div class="wrap">\n<div class="frame deleted">\n<img alt="CHANGELOG" src="http://localhost:3000/gitlab-org/gitlab-test/raw/e63f41fe459e62e1228fcef60d7189127aeba95a/CHANGELOG" />\n</div>\n<p class="image-info hide">\n<span class="meta-filesize">22.3 KB</span>\n|\n<strong>W:</strong>\n<span class="meta-width"></span>\n|\n<strong>H:</strong>\n<span class="meta-height"></span>\n</p>\n</div>\n<div class="wrap">\n<div class="added frame js-image-frame" data-note-type="DiffNote" data-position="{&quot;base_sha&quot;:&quot;e63f41fe459e62e1228fcef60d7189127aeba95a&quot;,&quot;start_sha&quot;:&quot;d9eaefe5a676b820c57ff18cf5b68316025f7962&quot;,&quot;head_sha&quot;:&quot;c48ee0d1bf3b30453f5b32250ce03134beaa6d13&quot;,&quot;old_path&quot;:&quot;CHANGELOG&quot;,&quot;new_path&quot;:&quot;CHANGELOG&quot;,&quot;position_type&quot;:&quot;text&quot;,&quot;old_line&quot;:null,&quot;new_line&quot;:2}">\n<img alt="CHANGELOG" draggable="false" src="http://localhost:3000/gitlab-org/gitlab-test/raw/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG" />\n</div>\n\n<p class="image-info hide">\n<span class="meta-filesize">22.3 KB</span>\n|\n<strong>W:</strong>\n<span class="meta-width"></span>\n|\n<strong>H:</strong>\n<span class="meta-height"></span>\n</p>\n</div>\n</div>\n<div class="swipe view hide">\n<div class="swipe-frame">\n<div class="frame deleted">\n<img alt="CHANGELOG" src="http://localhost:3000/gitlab-org/gitlab-test/raw/e63f41fe459e62e1228fcef60d7189127aeba95a/CHANGELOG" />\n</div>\n<div class="swipe-wrap">\n<div class="added frame js-image-frame" data-note-type="DiffNote" data-position="{&quot;base_sha&quot;:&quot;e63f41fe459e62e1228fcef60d7189127aeba95a&quot;,&quot;start_sha&quot;:&quot;d9eaefe5a676b820c57ff18cf5b68316025f7962&quot;,&quot;head_sha&quot;:&quot;c48ee0d1bf3b30453f5b32250ce03134beaa6d13&quot;,&quot;old_path&quot;:&quot;CHANGELOG&quot;,&quot;new_path&quot;:&quot;CHANGELOG&quot;,&quot;position_type&quot;:&quot;text&quot;,&quot;old_line&quot;:null,&quot;new_line&quot;:2}">\n<img alt="CHANGELOG" draggable="false" src="http://localhost:3000/gitlab-org/gitlab-test/raw/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG" />\n</div>\n\n</div>\n<span class="swipe-bar">\n<span class="top-handle"></span>\n<span class="bottom-handle"></span>\n</span>\n</div>\n</div>\n<div class="onion-skin view hide">\n<div class="onion-skin-frame">\n<div class="frame deleted">\n<img alt="CHANGELOG" src="http://localhost:3000/gitlab-org/gitlab-test/raw/e63f41fe459e62e1228fcef60d7189127aeba95a/CHANGELOG" />\n</div>\n<div class="added frame js-image-frame" data-note-type="DiffNote" data-position="{&quot;base_sha&quot;:&quot;e63f41fe459e62e1228fcef60d7189127aeba95a&quot;,&quot;start_sha&quot;:&quot;d9eaefe5a676b820c57ff18cf5b68316025f7962&quot;,&quot;head_sha&quot;:&quot;c48ee0d1bf3b30453f5b32250ce03134beaa6d13&quot;,&quot;old_path&quot;:&quot;CHANGELOG&quot;,&quot;new_path&quot;:&quot;CHANGELOG&quot;,&quot;position_type&quot;:&quot;text&quot;,&quot;old_line&quot;:null,&quot;new_line&quot;:2}">\n<img alt="CHANGELOG" draggable="false" src="http://localhost:3000/gitlab-org/gitlab-test/raw/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG" />\n</div>\n\n<div class="controls">\n<div class="transparent"></div>\n<div class="drag-track">\n<div class="dragger" style="left: 0px;"></div>\n</div>\n<div class="opaque"></div>\n</div>\n</div>\n</div>\n</div>\n<div class="view-modes hide">\n<ul class="view-modes-menu">\n<li class="two-up" data-mode="two-up">2-up</li>\n<li class="swipe" data-mode="swipe">Swipe</li>\n<li class="onion-skin" data-mode="onion-skin">Onion skin</li>\n</ul>\n</div>\n',
+ truncated_diff_lines: [
+ {
+ text: 'line',
+ rich_text:
+ '<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="1">\n1\n</td>\n<td class="line_content new noteable_line"><span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n</td>\n</tr>\n<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="2">\n2\n</td>\n<td class="line_content new noteable_line"><span id="LC2" class="line" lang="plaintext"></span>\n</td>\n</tr>\n',
+ can_receive_suggestion: true,
+ line_code: '6f209374f7e565f771b95720abf46024c41d1885_1_1',
+ type: 'new',
+ old_line: null,
+ new_line: 1,
+ meta_data: null,
+ },
+ ],
};
export const imageDiffDiscussions = [
diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js
index 5656ce16db0..033b5e86dbe 100644
--- a/spec/javascripts/diffs/store/actions_spec.js
+++ b/spec/javascripts/diffs/store/actions_spec.js
@@ -26,9 +26,12 @@ import actions, {
toggleTreeOpen,
scrollToFile,
toggleShowTreeList,
+ renderFileForDiscussionId,
} from '~/diffs/store/actions';
+import eventHub from '~/notes/event_hub';
import * as types from '~/diffs/store/mutation_types';
import axios from '~/lib/utils/axios_utils';
+import mockDiffFile from 'spec/diffs/mock_data/diff_file';
import testAction from '../../helpers/vuex_action_helper';
describe('DiffsStoreActions', () => {
@@ -382,24 +385,47 @@ describe('DiffsStoreActions', () => {
const file = { hash: 123, load_collapsed_diff_url: '/load/collapsed/diff/url' };
const data = { hash: 123, parallelDiffLines: [{ lineCode: 1 }] };
const mock = new MockAdapter(axios);
+ const commit = jasmine.createSpy('commit');
mock.onGet(file.loadCollapsedDiffUrl).reply(200, data);
- testAction(
- loadCollapsedDiff,
- file,
- {},
- [
- {
- type: types.ADD_COLLAPSED_DIFFS,
- payload: { file, data },
- },
- ],
- [],
- () => {
+ loadCollapsedDiff({ commit, getters: { commitId: null } }, file)
+ .then(() => {
+ expect(commit).toHaveBeenCalledWith(types.ADD_COLLAPSED_DIFFS, { file, data });
+
mock.restore();
done();
- },
- );
+ })
+ .catch(done.fail);
+ });
+
+ it('should fetch data without commit ID', () => {
+ const file = { load_collapsed_diff_url: '/load/collapsed/diff/url' };
+ const getters = {
+ commitId: null,
+ };
+
+ spyOn(axios, 'get').and.returnValue(Promise.resolve({ data: {} }));
+
+ loadCollapsedDiff({ commit() {}, getters }, file);
+
+ expect(axios.get).toHaveBeenCalledWith(file.load_collapsed_diff_url, {
+ params: { commit_id: null },
+ });
+ });
+
+ it('should fetch data with commit ID', () => {
+ const file = { load_collapsed_diff_url: '/load/collapsed/diff/url' };
+ const getters = {
+ commitId: '123',
+ };
+
+ spyOn(axios, 'get').and.returnValue(Promise.resolve({ data: {} }));
+
+ loadCollapsedDiff({ commit() {}, getters }, file);
+
+ expect(axios.get).toHaveBeenCalledWith(file.load_collapsed_diff_url, {
+ params: { commit_id: '123' },
+ });
});
});
@@ -584,11 +610,18 @@ describe('DiffsStoreActions', () => {
});
describe('saveDiffDiscussion', () => {
- beforeEach(() => {
- spyOnDependency(actions, 'getNoteFormData').and.returnValue('testData');
- });
-
it('dispatches actions', done => {
+ const commitId = 'something';
+ const formData = {
+ diffFile: { ...mockDiffFile },
+ noteableData: {},
+ };
+ const note = {};
+ const state = {
+ commit: {
+ id: commitId,
+ },
+ };
const dispatch = jasmine.createSpy('dispatch').and.callFake(name => {
switch (name) {
case 'saveNote':
@@ -602,11 +635,19 @@ describe('DiffsStoreActions', () => {
}
});
- saveDiffDiscussion({ dispatch }, { note: {}, formData: {} })
+ saveDiffDiscussion({ state, dispatch }, { note, formData })
.then(() => {
- expect(dispatch.calls.argsFor(0)).toEqual(['saveNote', 'testData', { root: true }]);
- expect(dispatch.calls.argsFor(1)).toEqual(['updateDiscussion', 'test', { root: true }]);
- expect(dispatch.calls.argsFor(2)).toEqual(['assignDiscussionsToDiff', ['discussion']]);
+ const { calls } = dispatch;
+
+ expect(calls.count()).toBe(5);
+ expect(calls.argsFor(0)).toEqual(['saveNote', jasmine.any(Object), { root: true }]);
+
+ const postData = calls.argsFor(0)[1];
+
+ expect(postData.data.note.commit_id).toBe(commitId);
+
+ expect(calls.argsFor(1)).toEqual(['updateDiscussion', 'test', { root: true }]);
+ expect(calls.argsFor(2)).toEqual(['assignDiscussionsToDiff', ['discussion']]);
})
.then(done)
.catch(done.fail);
@@ -696,4 +737,63 @@ describe('DiffsStoreActions', () => {
expect(localStorage.setItem).toHaveBeenCalledWith('mr_tree_show', true);
});
});
+
+ describe('renderFileForDiscussionId', () => {
+ const rootState = {
+ notes: {
+ discussions: [
+ {
+ id: '123',
+ diff_file: {
+ file_hash: 'HASH',
+ },
+ },
+ {
+ id: '456',
+ diff_file: {
+ file_hash: 'HASH',
+ },
+ },
+ ],
+ },
+ };
+ let commit;
+ let $emit;
+ let scrollToElement;
+ const state = ({ collapsed, renderIt }) => ({
+ diffFiles: [
+ {
+ file_hash: 'HASH',
+ collapsed,
+ renderIt,
+ },
+ ],
+ });
+
+ beforeEach(() => {
+ commit = jasmine.createSpy('commit');
+ scrollToElement = spyOnDependency(actions, 'scrollToElement').and.stub();
+ $emit = spyOn(eventHub, '$emit');
+ });
+
+ it('renders and expands file for the given discussion id', () => {
+ const localState = state({ collapsed: true, renderIt: false });
+
+ renderFileForDiscussionId({ rootState, state: localState, commit }, '123');
+
+ expect(commit).toHaveBeenCalledWith('RENDER_FILE', localState.diffFiles[0]);
+ expect($emit).toHaveBeenCalledTimes(1);
+ expect(scrollToElement).toHaveBeenCalledTimes(1);
+ });
+
+ it('jumps to discussion on already rendered and expanded file', () => {
+ const localState = state({ collapsed: false, renderIt: true });
+
+ renderFileForDiscussionId({ rootState, state: localState, commit }, '123');
+
+ expect(commit).not.toHaveBeenCalled();
+ expect($emit).toHaveBeenCalledTimes(1);
+ expect(scrollToElement).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
index 23e8761bc55..d8733941181 100644
--- a/spec/javascripts/diffs/store/mutations_spec.js
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -277,6 +277,152 @@ describe('DiffsStoreMutations', () => {
expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toEqual(1);
});
+ it('updates existing discussion', () => {
+ const diffPosition = {
+ base_sha: 'ed13df29948c41ba367caa757ab3ec4892509910',
+ head_sha: 'b921914f9a834ac47e6fd9420f78db0f83559130',
+ new_line: null,
+ new_path: '500-lines-4.txt',
+ old_line: 5,
+ old_path: '500-lines-4.txt',
+ start_sha: 'ed13df29948c41ba367caa757ab3ec4892509910',
+ };
+
+ const state = {
+ latestDiff: true,
+ diffFiles: [
+ {
+ file_hash: 'ABC',
+ parallel_diff_lines: [
+ {
+ left: {
+ line_code: 'ABC_1',
+ discussions: [],
+ },
+ right: {
+ line_code: 'ABC_1',
+ discussions: [],
+ },
+ },
+ ],
+ highlighted_diff_lines: [
+ {
+ line_code: 'ABC_1',
+ discussions: [],
+ },
+ ],
+ },
+ ],
+ };
+ const discussion = {
+ id: 1,
+ line_code: 'ABC_1',
+ diff_discussion: true,
+ resolvable: true,
+ original_position: diffPosition,
+ position: diffPosition,
+ diff_file: {
+ file_hash: state.diffFiles[0].file_hash,
+ },
+ };
+
+ const diffPositionByLineCode = {
+ ABC_1: diffPosition,
+ };
+
+ mutations[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, {
+ discussion,
+ diffPositionByLineCode,
+ });
+
+ expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions.length).toEqual(1);
+ expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].id).toEqual(1);
+ expect(state.diffFiles[0].parallel_diff_lines[0].right.discussions).toEqual([]);
+
+ expect(state.diffFiles[0].highlighted_diff_lines[0].discussions.length).toEqual(1);
+ expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].id).toEqual(1);
+
+ mutations[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, {
+ discussion: {
+ ...discussion,
+ resolved: true,
+ notes: ['test'],
+ },
+ diffPositionByLineCode,
+ });
+
+ expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].notes.length).toBe(1);
+ expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].notes.length).toBe(1);
+
+ expect(state.diffFiles[0].parallel_diff_lines[0].left.discussions[0].resolved).toBe(true);
+ expect(state.diffFiles[0].highlighted_diff_lines[0].discussions[0].resolved).toBe(true);
+ });
+
+ it('should not duplicate inline diff discussions', () => {
+ const diffPosition = {
+ base_sha: 'ed13df29948c41ba367caa757ab3ec4892509910',
+ head_sha: 'b921914f9a834ac47e6fd9420f78db0f83559130',
+ new_line: null,
+ new_path: '500-lines-4.txt',
+ old_line: 5,
+ old_path: '500-lines-4.txt',
+ start_sha: 'ed13df29948c41ba367caa757ab3ec4892509910',
+ };
+
+ const state = {
+ latestDiff: true,
+ diffFiles: [
+ {
+ file_hash: 'ABC',
+ highlighted_diff_lines: [
+ {
+ line_code: 'ABC_1',
+ discussions: [
+ {
+ id: 1,
+ line_code: 'ABC_1',
+ diff_discussion: true,
+ resolvable: true,
+ original_position: diffPosition,
+ position: diffPosition,
+ diff_file: {
+ file_hash: 'ABC',
+ },
+ },
+ ],
+ },
+ {
+ line_code: 'ABC_2',
+ discussions: [],
+ },
+ ],
+ },
+ ],
+ };
+ const discussion = {
+ id: 2,
+ line_code: 'ABC_2',
+ diff_discussion: true,
+ resolvable: true,
+ original_position: diffPosition,
+ position: diffPosition,
+ diff_file: {
+ file_hash: state.diffFiles[0].file_hash,
+ },
+ };
+
+ const diffPositionByLineCode = {
+ ABC_2: diffPosition,
+ };
+
+ mutations[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, {
+ discussion,
+ diffPositionByLineCode,
+ });
+
+ expect(state.diffFiles[0].highlighted_diff_lines[0].discussions.length).toBe(1);
+ });
+
it('should add legacy discussions to the given line', () => {
const diffPosition = {
base_sha: 'ed13df29948c41ba367caa757ab3ec4892509910',
@@ -356,10 +502,12 @@ describe('DiffsStoreMutations', () => {
{
id: 1,
line_code: 'ABC_1',
+ notes: [],
},
{
id: 2,
line_code: 'ABC_1',
+ notes: [],
},
],
},
@@ -376,10 +524,12 @@ describe('DiffsStoreMutations', () => {
{
id: 1,
line_code: 'ABC_1',
+ notes: [],
},
{
id: 2,
line_code: 'ABC_1',
+ notes: [],
},
],
},
diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js
index d4ef17c5ef8..4268634d302 100644
--- a/spec/javascripts/diffs/store/utils_spec.js
+++ b/spec/javascripts/diffs/store/utils_spec.js
@@ -150,7 +150,7 @@ describe('DiffsStoreUtils', () => {
note: {
noteable_type: options.noteableType,
noteable_id: options.noteableData.id,
- commit_id: '',
+ commit_id: undefined,
type: DIFF_NOTE_TYPE,
line_code: options.noteTargetLine.line_code,
note: options.note,
@@ -209,7 +209,7 @@ describe('DiffsStoreUtils', () => {
note: {
noteable_type: options.noteableType,
noteable_id: options.noteableData.id,
- commit_id: '',
+ commit_id: undefined,
type: LEGACY_DIFF_NOTE_TYPE,
line_code: options.noteTargetLine.line_code,
note: options.note,
@@ -294,10 +294,14 @@ describe('DiffsStoreUtils', () => {
});
describe('prepareDiffData', () => {
- it('sets the renderIt and collapsed attribute on files', () => {
- const preparedDiff = { diff_files: [getDiffFileMock()] };
+ let preparedDiff;
+
+ beforeEach(() => {
+ preparedDiff = { diff_files: [getDiffFileMock()] };
utils.prepareDiffData(preparedDiff);
+ });
+ it('sets the renderIt and collapsed attribute on files', () => {
const firstParallelDiffLine = preparedDiff.diff_files[0].parallel_diff_lines[2];
expect(firstParallelDiffLine.left.discussions.length).toBe(0);
@@ -323,6 +327,18 @@ describe('DiffsStoreUtils', () => {
expect(preparedDiff.diff_files[0].renderIt).toBeTruthy();
expect(preparedDiff.diff_files[0].collapsed).toBeFalsy();
});
+
+ it('adds line_code to all lines', () => {
+ expect(
+ preparedDiff.diff_files[0].parallel_diff_lines.filter(line => !line.line_code),
+ ).toHaveLength(0);
+ });
+
+ it('uses right line code if left has none', () => {
+ const firstLine = preparedDiff.diff_files[0].parallel_diff_lines[0];
+
+ expect(firstLine.line_code).toEqual(firstLine.right.line_code);
+ });
});
describe('isDiscussionApplicableToLine', () => {
@@ -559,4 +575,26 @@ describe('DiffsStoreUtils', () => {
]);
});
});
+
+ describe('getDiffMode', () => {
+ it('returns mode when matched in file', () => {
+ expect(
+ utils.getDiffMode({
+ renamed_file: true,
+ }),
+ ).toBe('renamed');
+ });
+
+ it('returns mode_changed if key has no match', () => {
+ expect(
+ utils.getDiffMode({
+ mode_changed: true,
+ }),
+ ).toBe('mode_changed');
+ });
+
+ it('defaults to replaced', () => {
+ expect(utils.getDiffMode({})).toBe('replaced');
+ });
+ });
});
diff --git a/spec/javascripts/environments/environment_terminal_button_spec.js b/spec/javascripts/environments/environment_terminal_button_spec.js
index f1576b19d1b..56e18db59c5 100644
--- a/spec/javascripts/environments/environment_terminal_button_spec.js
+++ b/spec/javascripts/environments/environment_terminal_button_spec.js
@@ -2,30 +2,46 @@ import Vue from 'vue';
import terminalComp from '~/environments/components/environment_terminal_button.vue';
describe('Stop Component', () => {
- let TerminalComponent;
let component;
const terminalPath = '/path';
- beforeEach(() => {
- TerminalComponent = Vue.extend(terminalComp);
-
+ const mountWithProps = props => {
+ const TerminalComponent = Vue.extend(terminalComp);
component = new TerminalComponent({
- propsData: {
- terminalPath,
- },
+ propsData: props,
}).$mount();
- });
+ };
+
+ describe('enabled', () => {
+ beforeEach(() => {
+ mountWithProps({ terminalPath });
+ });
+
+ describe('computed', () => {
+ it('title', () => {
+ expect(component.title).toEqual('Terminal');
+ });
+ });
- describe('computed', () => {
- it('title', () => {
- expect(component.title).toEqual('Terminal');
+ it('should render a link to open a web terminal with the provided path', () => {
+ expect(component.$el.tagName).toEqual('A');
+ expect(component.$el.getAttribute('data-original-title')).toEqual('Terminal');
+ expect(component.$el.getAttribute('aria-label')).toEqual('Terminal');
+ expect(component.$el.getAttribute('href')).toEqual(terminalPath);
+ });
+
+ it('should render a non-disabled button', () => {
+ expect(component.$el.classList).not.toContain('disabled');
});
});
- it('should render a link to open a web terminal with the provided path', () => {
- expect(component.$el.tagName).toEqual('A');
- expect(component.$el.getAttribute('data-original-title')).toEqual('Terminal');
- expect(component.$el.getAttribute('aria-label')).toEqual('Terminal');
- expect(component.$el.getAttribute('href')).toEqual(terminalPath);
+ describe('disabled', () => {
+ beforeEach(() => {
+ mountWithProps({ terminalPath, disabled: true });
+ });
+
+ it('should render a disabled button', () => {
+ expect(component.$el.classList).toContain('disabled');
+ });
});
});
diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js
index 6605b0a30d7..cfd0b96ec43 100644
--- a/spec/javascripts/filtered_search/dropdown_utils_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js
@@ -211,132 +211,6 @@ describe('Dropdown Utils', () => {
});
});
- describe('mergeDuplicateLabels', () => {
- const dataMap = {
- label: {
- title: 'label',
- color: '#FFFFFF',
- },
- };
-
- it('should add label to dataMap if it is not a duplicate', () => {
- const newLabel = {
- title: 'new-label',
- color: '#000000',
- };
-
- const updated = DropdownUtils.mergeDuplicateLabels(dataMap, newLabel);
-
- expect(updated[newLabel.title]).toEqual(newLabel);
- });
-
- it('should merge colors if label is a duplicate', () => {
- const duplicate = {
- title: 'label',
- color: '#000000',
- };
-
- const updated = DropdownUtils.mergeDuplicateLabels(dataMap, duplicate);
-
- expect(updated.label.multipleColors).toEqual([dataMap.label.color, duplicate.color]);
- });
- });
-
- describe('duplicateLabelColor', () => {
- it('should linear-gradient 2 colors', () => {
- const gradient = DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000']);
-
- expect(gradient).toEqual(
- 'linear-gradient(#FFFFFF 0%, #FFFFFF 50%, #000000 50%, #000000 100%)',
- );
- });
-
- it('should linear-gradient 3 colors', () => {
- const gradient = DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000', '#333333']);
-
- expect(gradient).toEqual(
- 'linear-gradient(#FFFFFF 0%, #FFFFFF 33%, #000000 33%, #000000 66%, #333333 66%, #333333 100%)',
- );
- });
-
- it('should linear-gradient 4 colors', () => {
- const gradient = DropdownUtils.duplicateLabelColor([
- '#FFFFFF',
- '#000000',
- '#333333',
- '#DDDDDD',
- ]);
-
- expect(gradient).toEqual(
- 'linear-gradient(#FFFFFF 0%, #FFFFFF 25%, #000000 25%, #000000 50%, #333333 50%, #333333 75%, #DDDDDD 75%, #DDDDDD 100%)',
- );
- });
-
- it('should not linear-gradient more than 4 colors', () => {
- const gradient = DropdownUtils.duplicateLabelColor([
- '#FFFFFF',
- '#000000',
- '#333333',
- '#DDDDDD',
- '#EEEEEE',
- ]);
-
- expect(gradient.indexOf('#EEEEEE')).toBe(-1);
- });
- });
-
- describe('duplicateLabelPreprocessing', () => {
- it('should set preprocessed to true', () => {
- const results = DropdownUtils.duplicateLabelPreprocessing([]);
-
- expect(results.preprocessed).toEqual(true);
- });
-
- it('should not mutate existing data if there are no duplicates', () => {
- const data = [
- {
- title: 'label1',
- color: '#FFFFFF',
- },
- {
- title: 'label2',
- color: '#000000',
- },
- ];
- const results = DropdownUtils.duplicateLabelPreprocessing(data);
-
- expect(results.length).toEqual(2);
- expect(results[0]).toEqual(data[0]);
- expect(results[1]).toEqual(data[1]);
- });
-
- describe('duplicate labels', () => {
- const data = [
- {
- title: 'label',
- color: '#FFFFFF',
- },
- {
- title: 'label',
- color: '#000000',
- },
- ];
- const results = DropdownUtils.duplicateLabelPreprocessing(data);
-
- it('should merge duplicate labels', () => {
- expect(results.length).toEqual(1);
- });
-
- it('should convert multiple colored labels into linear-gradient', () => {
- expect(results[0].color).toEqual(DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000']));
- });
-
- it('should set multiple colored label text color to black', () => {
- expect(results[0].text_color).toEqual('#000000');
- });
- });
- });
-
describe('setDataValueIfSelected', () => {
beforeEach(() => {
spyOn(FilteredSearchDropdownManager, 'addWordToInput').and.callFake(() => {});
diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
index 4f561df7943..9aa3cbaa231 100644
--- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
@@ -909,16 +909,6 @@ describe('Filtered Search Visual Tokens', () => {
expect(token.style.backgroundColor).not.toEqual(originalBackgroundColor);
});
- it('should not set backgroundColor when it is a linear-gradient', () => {
- const token = subject.setTokenStyle(
- bugLabelToken,
- 'linear-gradient(135deg, red, blue)',
- 'white',
- );
-
- expect(token.style.backgroundColor).toEqual(bugLabelToken.style.backgroundColor);
- });
-
it('should set textColor', () => {
const token = subject.setTokenStyle(bugLabelToken, 'white', 'black');
@@ -935,39 +925,6 @@ describe('Filtered Search Visual Tokens', () => {
});
});
- describe('preprocessLabel', () => {
- const endpoint = 'endpoint';
-
- it('does not preprocess more than once', () => {
- let labels = [];
-
- spyOn(DropdownUtils, 'duplicateLabelPreprocessing').and.callFake(() => []);
-
- labels = FilteredSearchVisualTokens.preprocessLabel(endpoint, labels);
- FilteredSearchVisualTokens.preprocessLabel(endpoint, labels);
-
- expect(DropdownUtils.duplicateLabelPreprocessing.calls.count()).toEqual(1);
- });
-
- describe('not preprocessed before', () => {
- it('returns preprocessed labels', () => {
- let labels = [];
-
- expect(labels.preprocessed).not.toEqual(true);
- labels = FilteredSearchVisualTokens.preprocessLabel(endpoint, labels);
-
- expect(labels.preprocessed).toEqual(true);
- });
-
- it('overrides AjaxCache with preprocessed results', () => {
- spyOn(AjaxCache, 'override').and.callFake(() => {});
- FilteredSearchVisualTokens.preprocessLabel(endpoint, []);
-
- expect(AjaxCache.override.calls.count()).toEqual(1);
- });
- });
- });
-
describe('updateLabelTokenColor', () => {
const jsonFixtureName = 'labels/project_labels.json';
const dummyEndpoint = '/dummy/endpoint';
diff --git a/spec/javascripts/fixtures/blob.rb b/spec/javascripts/fixtures/blob.rb
index 81e8a51a902..1b2a3b484bb 100644
--- a/spec/javascripts/fixtures/blob.rb
+++ b/spec/javascripts/fixtures/blob.rb
@@ -22,10 +22,11 @@ describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do
end
it 'blob/show.html.raw' do |example|
- get(:show,
- namespace_id: project.namespace,
- project_id: project,
- id: 'add-ipython-files/files/ipython/basic.ipynb')
+ get(:show, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: 'add-ipython-files/files/ipython/basic.ipynb'
+ })
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/fixtures/boards.rb b/spec/javascripts/fixtures/boards.rb
index 494c9cabdcc..1d675e008ba 100644
--- a/spec/javascripts/fixtures/boards.rb
+++ b/spec/javascripts/fixtures/boards.rb
@@ -18,9 +18,10 @@ describe Projects::BoardsController, '(JavaScript fixtures)', type: :controller
end
it 'boards/show.html.raw' do |example|
- get(:index,
- namespace_id: project.namespace,
- project_id: project)
+ get(:index, params: {
+ namespace_id: project.namespace,
+ project_id: project
+ })
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/fixtures/branches.rb b/spec/javascripts/fixtures/branches.rb
index 4fc072d2585..3cc713ef90f 100644
--- a/spec/javascripts/fixtures/branches.rb
+++ b/spec/javascripts/fixtures/branches.rb
@@ -22,9 +22,10 @@ describe Projects::BranchesController, '(JavaScript fixtures)', type: :controlle
end
it 'branches/new_branch.html.raw' do |example|
- get :new,
+ get :new, params: {
namespace_id: project.namespace.to_param,
project_id: project
+ }
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/javascripts/fixtures/clusters.rb
index 8e74c4f859c..69dbe54ffc2 100644
--- a/spec/javascripts/fixtures/clusters.rb
+++ b/spec/javascripts/fixtures/clusters.rb
@@ -23,10 +23,11 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle
end
it 'clusters/show_cluster.html.raw' do |example|
- get :show,
+ get :show, params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: cluster
+ }
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/fixtures/commit.rb b/spec/javascripts/fixtures/commit.rb
index 24ab8159a18..f0e4bb50c67 100644
--- a/spec/javascripts/fixtures/commit.rb
+++ b/spec/javascripts/fixtures/commit.rb
@@ -25,7 +25,7 @@ describe Projects::CommitController, '(JavaScript fixtures)', type: :controller
id: commit.id
}
- get :show, params
+ get :show, params: params
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/fixtures/deploy_keys.rb b/spec/javascripts/fixtures/deploy_keys.rb
index 24699c3043a..efbda955972 100644
--- a/spec/javascripts/fixtures/deploy_keys.rb
+++ b/spec/javascripts/fixtures/deploy_keys.rb
@@ -33,10 +33,10 @@ describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :control
create(:deploy_keys_project, project: project3, deploy_key: project_key)
create(:deploy_keys_project, project: project4, deploy_key: project_key)
- get :index,
+ get :index, params: {
namespace_id: project.namespace.to_param,
- project_id: project,
- format: :json
+ project_id: project
+ }, format: :json
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/fixtures/groups.rb b/spec/javascripts/fixtures/groups.rb
index b42f442557c..f8d55fc97c3 100644
--- a/spec/javascripts/fixtures/groups.rb
+++ b/spec/javascripts/fixtures/groups.rb
@@ -19,8 +19,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do
describe GroupsController, '(JavaScript fixtures)', type: :controller do
it 'groups/edit.html.raw' do |example|
- get :edit,
- id: group
+ get :edit, params: { id: group }
expect(response).to be_success
store_frontend_fixture(response, example.description)
@@ -29,8 +28,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do
describe Groups::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do
it 'groups/ci_cd_settings.html.raw' do |example|
- get :show,
- group_id: group
+ get :show, params: { group_id: group }
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb
index 0ee2f82dfd6..18fb1bebf8b 100644
--- a/spec/javascripts/fixtures/issues.rb
+++ b/spec/javascripts/fixtures/issues.rb
@@ -43,9 +43,10 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
it 'issues/issue_list.html.raw' do |example|
create(:issue, project: project)
- get :index,
+ get :index, params: {
namespace_id: project.namespace.to_param,
project_id: project
+ }
expect(response).to be_success
store_frontend_fixture(response, example.description)
@@ -54,10 +55,11 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
private
def render_issue(fixture_file_name, issue)
- get :show,
+ get :show, params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: issue.to_param
+ }
expect(response).to be_success
store_frontend_fixture(response, fixture_file_name)
diff --git a/spec/javascripts/fixtures/jobs.rb b/spec/javascripts/fixtures/jobs.rb
index 82d7a5e394e..d6b5349594d 100644
--- a/spec/javascripts/fixtures/jobs.rb
+++ b/spec/javascripts/fixtures/jobs.rb
@@ -34,21 +34,22 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
end
it 'builds/build-with-artifacts.html.raw' do |example|
- get :show,
+ get :show, params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: build_with_artifacts.to_param
+ }
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
it 'jobs/delayed.json' do |example|
- get :show,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: delayed_job.to_param,
- format: :json
+ get :show, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: delayed_job.to_param
+ }, format: :json
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/fixtures/labels.rb b/spec/javascripts/fixtures/labels.rb
index b730d557e21..9420194e675 100644
--- a/spec/javascripts/fixtures/labels.rb
+++ b/spec/javascripts/fixtures/labels.rb
@@ -31,9 +31,9 @@ describe 'Labels (JavaScript fixtures)' do
end
it 'labels/group_labels.json' do |example|
- get :index,
- group_id: group,
- format: 'json'
+ get :index, params: {
+ group_id: group
+ }, format: 'json'
expect(response).to be_success
store_frontend_fixture(response, example.description)
@@ -48,10 +48,10 @@ describe 'Labels (JavaScript fixtures)' do
end
it 'labels/project_labels.json' do |example|
- get :index,
+ get :index, params: {
namespace_id: group,
- project_id: project,
- format: 'json'
+ project_id: project
+ }, format: 'json'
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
index 7257d0c8556..26e81f06c0b 100644
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ b/spec/javascripts/fixtures/merge_requests.rb
@@ -112,21 +112,21 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
private
def render_discussions_json(merge_request, fixture_file_name)
- get :discussions,
+ get :discussions, params: {
namespace_id: project.namespace.to_param,
project_id: project,
- id: merge_request.to_param,
- format: :json
+ id: merge_request.to_param
+ }, format: :json
store_frontend_fixture(response, fixture_file_name)
end
def render_merge_request(fixture_file_name, merge_request)
- get :show,
+ get :show, params: {
namespace_id: project.namespace.to_param,
project_id: project,
- id: merge_request.to_param,
- format: :html
+ id: merge_request.to_param
+ }, format: :html
expect(response).to be_success
store_frontend_fixture(response, fixture_file_name)
diff --git a/spec/javascripts/fixtures/merge_requests_diffs.rb b/spec/javascripts/fixtures/merge_requests_diffs.rb
index afe34b834b0..57462e74bb2 100644
--- a/spec/javascripts/fixtures/merge_requests_diffs.rb
+++ b/spec/javascripts/fixtures/merge_requests_diffs.rb
@@ -57,13 +57,13 @@ describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type
private
def render_merge_request(fixture_file_name, merge_request, view: 'inline', **extra_params)
- get :show,
+ get :show, params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: merge_request.to_param,
- format: :json,
view: view,
**extra_params
+ }, format: :json
expect(response).to be_success
store_frontend_fixture(response, fixture_file_name)
diff --git a/spec/javascripts/fixtures/pipeline_schedules.rb b/spec/javascripts/fixtures/pipeline_schedules.rb
index 56f27ea7df1..05d79ec8de9 100644
--- a/spec/javascripts/fixtures/pipeline_schedules.rb
+++ b/spec/javascripts/fixtures/pipeline_schedules.rb
@@ -22,20 +22,22 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: :
end
it 'pipeline_schedules/edit.html.raw' do |example|
- get :edit,
+ get :edit, params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: pipeline_schedule.id
+ }
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
it 'pipeline_schedules/edit_with_variables.html.raw' do |example|
- get :edit,
+ get :edit, params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: pipeline_schedule_populated.id
+ }
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/fixtures/pipelines.rb b/spec/javascripts/fixtures/pipelines.rb
index bb85da50f0f..42b552e81c0 100644
--- a/spec/javascripts/fixtures/pipelines.rb
+++ b/spec/javascripts/fixtures/pipelines.rb
@@ -24,10 +24,10 @@ describe Projects::PipelinesController, '(JavaScript fixtures)', type: :controll
end
it 'pipelines/pipelines.json' do |example|
- get :index,
+ get :index, params: {
namespace_id: namespace,
- project_id: project,
- format: :json
+ project_id: project
+ }, format: :json
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb
index d98f7f55b20..9b48646f8f0 100644
--- a/spec/javascripts/fixtures/projects.rb
+++ b/spec/javascripts/fixtures/projects.rb
@@ -28,27 +28,30 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
describe ProjectsController, '(JavaScript fixtures)', type: :controller do
it 'projects/dashboard.html.raw' do |example|
- get :show,
+ get :show, params: {
namespace_id: project.namespace.to_param,
id: project
+ }
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
it 'projects/overview.html.raw' do |example|
- get :show,
+ get :show, params: {
namespace_id: project_with_repo.namespace.to_param,
id: project_with_repo
+ }
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
it 'projects/edit.html.raw' do |example|
- get :edit,
+ get :edit, params: {
namespace_id: project.namespace.to_param,
id: project
+ }
expect(response).to be_success
store_frontend_fixture(response, example.description)
@@ -57,18 +60,20 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
describe Projects::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do
it 'projects/ci_cd_settings.html.raw' do |example|
- get :show,
+ get :show, params: {
namespace_id: project.namespace.to_param,
project_id: project
+ }
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
it 'projects/ci_cd_settings_with_variables.html.raw' do |example|
- get :show,
+ get :show, params: {
namespace_id: project_variable_populated.namespace.to_param,
project_id: project_variable_populated
+ }
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/fixtures/prometheus_service.rb b/spec/javascripts/fixtures/prometheus_service.rb
index f95f8038ffb..746fbfd66dd 100644
--- a/spec/javascripts/fixtures/prometheus_service.rb
+++ b/spec/javascripts/fixtures/prometheus_service.rb
@@ -23,10 +23,11 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
end
it 'services/prometheus/prometheus_service.html.raw' do |example|
- get :edit,
+ get :edit, params: {
namespace_id: namespace,
project_id: project,
id: service.to_param
+ }
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/fixtures/services.rb b/spec/javascripts/fixtures/services.rb
index 9280ed5a7f1..6ccd74a07ff 100644
--- a/spec/javascripts/fixtures/services.rb
+++ b/spec/javascripts/fixtures/services.rb
@@ -23,10 +23,11 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
end
it 'services/edit_service.html.raw' do |example|
- get :edit,
+ get :edit, params: {
namespace_id: namespace,
project_id: project,
id: service.to_param
+ }
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/fixtures/snippet.rb b/spec/javascripts/fixtures/snippet.rb
index 38fc963caf7..a14837e4d4a 100644
--- a/spec/javascripts/fixtures/snippet.rb
+++ b/spec/javascripts/fixtures/snippet.rb
@@ -24,7 +24,7 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do
end
it 'snippets/show.html.raw' do |example|
- get(:show, id: snippet.to_param)
+ get(:show, params: { id: snippet.to_param })
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/fixtures/todos.rb b/spec/javascripts/fixtures/todos.rb
index 426b854fe8b..b5f6620873b 100644
--- a/spec/javascripts/fixtures/todos.rb
+++ b/spec/javascripts/fixtures/todos.rb
@@ -42,12 +42,12 @@ describe 'Todos (JavaScript fixtures)' do
end
it 'todos/todos.json' do |example|
- post :create,
+ post :create, params: {
namespace_id: namespace,
project_id: project,
issuable_type: 'issue',
- issuable_id: issue_2.id,
- format: 'json'
+ issuable_id: issue_2.id
+ }, format: 'json'
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/javascripts/fixtures/u2f.rb
index e3d7986f2cf..f0aa874bf75 100644
--- a/spec/javascripts/fixtures/u2f.rb
+++ b/spec/javascripts/fixtures/u2f.rb
@@ -21,7 +21,7 @@ context 'U2F' do
it 'u2f/authenticate.html.raw' do |example|
allow(controller).to receive(:find_user).and_return(user)
- post :create, user: { login: user.username, password: user.password }
+ post :create, params: { user: { login: user.username, password: user.password } }
expect(response).to be_success
store_frontend_fixture(response, example.description)
diff --git a/spec/javascripts/image_diff/helpers/badge_helper_spec.js b/spec/javascripts/image_diff/helpers/badge_helper_spec.js
index 8ea05203d00..b3001d45e3c 100644
--- a/spec/javascripts/image_diff/helpers/badge_helper_spec.js
+++ b/spec/javascripts/image_diff/helpers/badge_helper_spec.js
@@ -61,6 +61,10 @@ describe('badge helper', () => {
expect(buttonEl).toBeDefined();
});
+ it('should add badge classes', () => {
+ expect(buttonEl.className).toContain('badge badge-pill');
+ });
+
it('should set the badge text', () => {
expect(buttonEl.innerText).toEqual(badgeText);
});
diff --git a/spec/javascripts/jobs/components/artifacts_block_spec.js b/spec/javascripts/jobs/components/artifacts_block_spec.js
index 2fa7ff653fe..27d480ef2ea 100644
--- a/spec/javascripts/jobs/components/artifacts_block_spec.js
+++ b/spec/javascripts/jobs/components/artifacts_block_spec.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import { getTimeago } from '~/lib/utils/datetime_utility';
import component from '~/jobs/components/artifacts_block.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
+import { trimText } from '../../helpers/vue_component_helper';
describe('Artifacts block', () => {
const Component = Vue.extend(component);
@@ -9,7 +10,7 @@ describe('Artifacts block', () => {
const expireAt = '2018-08-14T09:38:49.157Z';
const timeago = getTimeago();
- const formatedDate = timeago.format(expireAt);
+ const formattedDate = timeago.format(expireAt);
const expiredArtifact = {
expire_at: expireAt,
@@ -36,9 +37,8 @@ describe('Artifacts block', () => {
expect(vm.$el.querySelector('.js-artifacts-removed')).not.toBeNull();
expect(vm.$el.querySelector('.js-artifacts-will-be-removed')).toBeNull();
- expect(vm.$el.textContent).toContain(formatedDate);
- expect(vm.$el.querySelector('.js-artifacts-removed').textContent.trim()).toEqual(
- 'The artifacts were removed',
+ expect(trimText(vm.$el.querySelector('.js-artifacts-removed').textContent)).toEqual(
+ `The artifacts were removed ${formattedDate}`,
);
});
});
@@ -51,9 +51,8 @@ describe('Artifacts block', () => {
expect(vm.$el.querySelector('.js-artifacts-removed')).toBeNull();
expect(vm.$el.querySelector('.js-artifacts-will-be-removed')).not.toBeNull();
- expect(vm.$el.textContent).toContain(formatedDate);
- expect(vm.$el.querySelector('.js-artifacts-will-be-removed').textContent.trim()).toEqual(
- 'The artifacts will be removed in',
+ expect(trimText(vm.$el.querySelector('.js-artifacts-will-be-removed').textContent)).toEqual(
+ `The artifacts will be removed ${formattedDate}`,
);
});
});
diff --git a/spec/javascripts/jobs/components/sidebar_spec.js b/spec/javascripts/jobs/components/sidebar_spec.js
index 424092d2d88..b0bc16d7c64 100644
--- a/spec/javascripts/jobs/components/sidebar_spec.js
+++ b/spec/javascripts/jobs/components/sidebar_spec.js
@@ -79,14 +79,6 @@ describe('Sidebar details block', () => {
});
describe('information', () => {
- it('should render merge request link', () => {
- expect(trimText(vm.$el.querySelector('.js-job-mr').textContent)).toEqual('Merge Request: !2');
-
- expect(vm.$el.querySelector('.js-job-mr a').getAttribute('href')).toEqual(
- job.merge_request.path,
- );
- });
-
it('should render job duration', () => {
expect(trimText(vm.$el.querySelector('.js-job-duration').textContent)).toEqual(
'Duration: 6 seconds',
diff --git a/spec/javascripts/jobs/components/trigger_block_spec.js b/spec/javascripts/jobs/components/trigger_block_spec.js
index 7254851a9e7..448197b82c0 100644
--- a/spec/javascripts/jobs/components/trigger_block_spec.js
+++ b/spec/javascripts/jobs/components/trigger_block_spec.js
@@ -31,8 +31,8 @@ describe('Trigger block', () => {
});
describe('with variables', () => {
- describe('reveal variables', () => {
- it('reveals variables on click', done => {
+ describe('hide/reveal variables', () => {
+ it('should toggle variables on click', done => {
vm = mountComponent(Component, {
trigger: {
short_token: 'bd7e',
@@ -48,6 +48,10 @@ describe('Trigger block', () => {
vm.$nextTick()
.then(() => {
expect(vm.$el.querySelector('.js-build-variables')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-reveal-variables').textContent.trim()).toEqual(
+ 'Hide values',
+ );
+
expect(vm.$el.querySelector('.js-build-variables').textContent).toContain(
'UPLOAD_TO_GCS',
);
@@ -58,6 +62,26 @@ describe('Trigger block', () => {
);
expect(vm.$el.querySelector('.js-build-variables').textContent).toContain('true');
+
+ vm.$el.querySelector('.js-reveal-variables').click();
+ })
+ .then(vm.$nextTick)
+ .then(() => {
+ expect(vm.$el.querySelector('.js-reveal-variables').textContent.trim()).toEqual(
+ 'Reveal values',
+ );
+
+ expect(vm.$el.querySelector('.js-build-variables').textContent).toContain(
+ 'UPLOAD_TO_GCS',
+ );
+
+ expect(vm.$el.querySelector('.js-build-value').textContent).toContain('••••••');
+
+ expect(vm.$el.querySelector('.js-build-variables').textContent).toContain(
+ 'UPLOAD_TO_S3',
+ );
+
+ expect(vm.$el.querySelector('.js-build-value').textContent).toContain('••••••');
})
.then(done)
.catch(done.fail);
diff --git a/spec/javascripts/lib/utils/dom_utils_spec.js b/spec/javascripts/lib/utils/dom_utils_spec.js
index 1fb2e4584a0..2bcf37f35c7 100644
--- a/spec/javascripts/lib/utils/dom_utils_spec.js
+++ b/spec/javascripts/lib/utils/dom_utils_spec.js
@@ -1,4 +1,6 @@
-import { addClassIfElementExists } from '~/lib/utils/dom_utils';
+import { addClassIfElementExists, canScrollUp, canScrollDown } from '~/lib/utils/dom_utils';
+
+const TEST_MARGIN = 5;
describe('DOM Utils', () => {
describe('addClassIfElementExists', () => {
@@ -34,4 +36,54 @@ describe('DOM Utils', () => {
addClassIfElementExists(childElement, className);
});
});
+
+ describe('canScrollUp', () => {
+ [1, 100].forEach(scrollTop => {
+ it(`is true if scrollTop is > 0 (${scrollTop})`, () => {
+ expect(canScrollUp({ scrollTop })).toBe(true);
+ });
+ });
+
+ [0, -10].forEach(scrollTop => {
+ it(`is false if scrollTop is <= 0 (${scrollTop})`, () => {
+ expect(canScrollUp({ scrollTop })).toBe(false);
+ });
+ });
+
+ it('is true if scrollTop is > margin', () => {
+ expect(canScrollUp({ scrollTop: TEST_MARGIN + 1 }, TEST_MARGIN)).toBe(true);
+ });
+
+ it('is false if scrollTop is <= margin', () => {
+ expect(canScrollUp({ scrollTop: TEST_MARGIN }, TEST_MARGIN)).toBe(false);
+ });
+ });
+
+ describe('canScrollDown', () => {
+ let element;
+
+ beforeEach(() => {
+ element = { scrollTop: 7, offsetHeight: 22, scrollHeight: 30 };
+ });
+
+ it('is true if element can be scrolled down', () => {
+ expect(canScrollDown(element)).toBe(true);
+ });
+
+ it('is false if element cannot be scrolled down', () => {
+ element.scrollHeight -= 1;
+
+ expect(canScrollDown(element)).toBe(false);
+ });
+
+ it('is true if element can be scrolled down, with margin given', () => {
+ element.scrollHeight += TEST_MARGIN;
+
+ expect(canScrollDown(element, TEST_MARGIN)).toBe(true);
+ });
+
+ it('is false if element cannot be scrolled down, with margin given', () => {
+ expect(canScrollDown(element, TEST_MARGIN)).toBe(false);
+ });
+ });
});
diff --git a/spec/javascripts/lib/utils/file_upload_spec.js b/spec/javascripts/lib/utils/file_upload_spec.js
new file mode 100644
index 00000000000..92c9cc70aaf
--- /dev/null
+++ b/spec/javascripts/lib/utils/file_upload_spec.js
@@ -0,0 +1,36 @@
+import fileUpload from '~/lib/utils/file_upload';
+
+describe('File upload', () => {
+ beforeEach(() => {
+ setFixtures(`
+ <form>
+ <button class="js-button" type="button">Click me!</button>
+ <input type="text" class="js-input" />
+ <span class="js-filename"></span>
+ </form>
+ `);
+
+ fileUpload('.js-button', '.js-input');
+ });
+
+ it('clicks file input after clicking button', () => {
+ const btn = document.querySelector('.js-button');
+ const input = document.querySelector('.js-input');
+
+ spyOn(input, 'click');
+
+ btn.click();
+
+ expect(input.click).toHaveBeenCalled();
+ });
+
+ it('updates file name text', () => {
+ const input = document.querySelector('.js-input');
+
+ input.value = 'path/to/file/index.js';
+
+ input.dispatchEvent(new CustomEvent('change'));
+
+ expect(document.querySelector('.js-filename').textContent).toEqual('index.js');
+ });
+});
diff --git a/spec/javascripts/lib/utils/users_cache_spec.js b/spec/javascripts/lib/utils/users_cache_spec.js
index 6adc19bdd51..acb5e024acd 100644
--- a/spec/javascripts/lib/utils/users_cache_spec.js
+++ b/spec/javascripts/lib/utils/users_cache_spec.js
@@ -3,7 +3,9 @@ import UsersCache from '~/lib/utils/users_cache';
describe('UsersCache', () => {
const dummyUsername = 'win';
- const dummyUser = 'has a farm';
+ const dummyUserId = 123;
+ const dummyUser = { name: 'has a farm', username: 'farmer' };
+ const dummyUserStatus = 'my status';
beforeEach(() => {
UsersCache.internalStorage = {};
@@ -135,4 +137,110 @@ describe('UsersCache', () => {
.catch(done.fail);
});
});
+
+ describe('retrieveById', () => {
+ let apiSpy;
+
+ beforeEach(() => {
+ spyOn(Api, 'user').and.callFake(id => apiSpy(id));
+ });
+
+ it('stores and returns data from API call if cache is empty', done => {
+ apiSpy = id => {
+ expect(id).toBe(dummyUserId);
+ return Promise.resolve({
+ data: dummyUser,
+ });
+ };
+
+ UsersCache.retrieveById(dummyUserId)
+ .then(user => {
+ expect(user).toBe(dummyUser);
+ expect(UsersCache.internalStorage[dummyUserId]).toBe(dummyUser);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('returns undefined if Ajax call fails and cache is empty', done => {
+ const dummyError = new Error('server exploded');
+ apiSpy = id => {
+ expect(id).toBe(dummyUserId);
+ return Promise.reject(dummyError);
+ };
+
+ UsersCache.retrieveById(dummyUserId)
+ .then(user => fail(`Received unexpected user: ${JSON.stringify(user)}`))
+ .catch(error => {
+ expect(error).toBe(dummyError);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('makes no Ajax call if matching data exists', done => {
+ UsersCache.internalStorage[dummyUserId] = dummyUser;
+ apiSpy = () => fail(new Error('expected no Ajax call!'));
+
+ UsersCache.retrieveById(dummyUserId)
+ .then(user => {
+ expect(user).toBe(dummyUser);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('retrieveStatusById', () => {
+ let apiSpy;
+
+ beforeEach(() => {
+ spyOn(Api, 'userStatus').and.callFake(id => apiSpy(id));
+ });
+
+ it('stores and returns data from API call if cache is empty', done => {
+ apiSpy = id => {
+ expect(id).toBe(dummyUserId);
+ return Promise.resolve({
+ data: dummyUserStatus,
+ });
+ };
+
+ UsersCache.retrieveStatusById(dummyUserId)
+ .then(userStatus => {
+ expect(userStatus).toBe(dummyUserStatus);
+ expect(UsersCache.internalStorage[dummyUserId].status).toBe(dummyUserStatus);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('returns undefined if Ajax call fails and cache is empty', done => {
+ const dummyError = new Error('server exploded');
+ apiSpy = id => {
+ expect(id).toBe(dummyUserId);
+ return Promise.reject(dummyError);
+ };
+
+ UsersCache.retrieveStatusById(dummyUserId)
+ .then(userStatus => fail(`Received unexpected user: ${JSON.stringify(userStatus)}`))
+ .catch(error => {
+ expect(error).toBe(dummyError);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('makes no Ajax call if matching data exists', done => {
+ UsersCache.internalStorage[dummyUserId] = { status: dummyUserStatus };
+ apiSpy = () => fail(new Error('expected no Ajax call!'));
+
+ UsersCache.retrieveStatusById(dummyUserId)
+ .then(userStatus => {
+ expect(userStatus).toBe(dummyUserStatus);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
});
diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js
index 0081f42c330..22bee049f9c 100644
--- a/spec/javascripts/notes/components/note_app_spec.js
+++ b/spec/javascripts/notes/components/note_app_spec.js
@@ -30,6 +30,8 @@ describe('note_app', () => {
jasmine.addMatchers(vueMatchers);
$('body').attr('data-page', 'projects:merge_requests:show');
+ setFixtures('<div class="js-vue-notes-event"><div id="app"></div></div>');
+
const IssueNotesApp = Vue.extend(notesApp);
store = createStore();
@@ -43,6 +45,7 @@ describe('note_app', () => {
return mountComponentWithStore(IssueNotesApp, {
props,
store,
+ el: document.getElementById('app'),
});
};
});
@@ -283,4 +286,24 @@ describe('note_app', () => {
}, 0);
});
});
+
+ describe('emoji awards', () => {
+ it('dispatches toggleAward after toggleAward event', () => {
+ const toggleAwardEvent = new CustomEvent('toggleAward', {
+ detail: {
+ awardName: 'test',
+ noteId: 1,
+ },
+ });
+
+ spyOn(vm.$store, 'dispatch');
+
+ vm.$el.parentElement.dispatchEvent(toggleAwardEvent);
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith('toggleAward', {
+ awardName: 'test',
+ noteId: 1,
+ });
+ });
+ });
});
diff --git a/spec/javascripts/notes/components/note_edited_text_spec.js b/spec/javascripts/notes/components/note_edited_text_spec.js
index e0b991c32ec..e4c8d954d50 100644
--- a/spec/javascripts/notes/components/note_edited_text_spec.js
+++ b/spec/javascripts/notes/components/note_edited_text_spec.js
@@ -39,7 +39,7 @@ describe('note_edited_text', () => {
});
it('should render provided user information', () => {
- const authorLink = vm.$el.querySelector('.js-vue-author');
+ const authorLink = vm.$el.querySelector('.js-user-link');
expect(authorLink.getAttribute('href')).toEqual(props.editedBy.path);
expect(authorLink.textContent.trim()).toEqual(props.editedBy.name);
diff --git a/spec/javascripts/notes/components/note_header_spec.js b/spec/javascripts/notes/components/note_header_spec.js
index 379780f43a0..6d1a7ef370f 100644
--- a/spec/javascripts/notes/components/note_header_spec.js
+++ b/spec/javascripts/notes/components/note_header_spec.js
@@ -42,6 +42,9 @@ describe('note_header component', () => {
it('should render user information', () => {
expect(vm.$el.querySelector('.note-header-author-name').textContent.trim()).toEqual('Root');
expect(vm.$el.querySelector('.note-header-info a').getAttribute('href')).toEqual('/root');
+ expect(vm.$el.querySelector('.note-header-info a').dataset.userId).toEqual('1');
+ expect(vm.$el.querySelector('.note-header-info a').dataset.username).toEqual('root');
+ expect(vm.$el.querySelector('.note-header-info a').classList).toContain('js-user-link');
});
it('should render timestamp link', () => {
diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js
index ab9c52346d6..3aff2dd0641 100644
--- a/spec/javascripts/notes/components/noteable_discussion_spec.js
+++ b/spec/javascripts/notes/components/noteable_discussion_spec.js
@@ -42,12 +42,14 @@ describe('noteable_discussion component', () => {
const discussion = { ...discussionMock };
discussion.diff_file = mockDiffFile;
discussion.diff_discussion = true;
- const diffDiscussionVm = new Component({
+
+ vm.$destroy();
+ vm = new Component({
store,
propsData: { discussion },
}).$mount();
- expect(diffDiscussionVm.$el.querySelector('.discussion-header')).not.toBeNull();
+ expect(vm.$el.querySelector('.discussion-header')).not.toBeNull();
});
describe('actions', () => {
@@ -83,6 +85,7 @@ describe('noteable_discussion component', () => {
it('expands next unresolved discussion', done => {
const discussion2 = getJSONFixture(discussionWithTwoUnresolvedNotes)[0];
discussion2.resolved = false;
+ discussion2.active = true;
discussion2.id = 'next'; // prepare this for being identified as next one (to be jumped to)
vm.$store.dispatch('setInitialNotes', [discussionMock, discussion2]);
window.mrTabs.currentAction = 'show';
@@ -129,4 +132,101 @@ describe('noteable_discussion component', () => {
expect(note).toEqual(data);
});
});
+
+ describe('action text', () => {
+ const commitId = 'razupaltuff';
+ const truncatedCommitId = commitId.substr(0, 8);
+ let commitElement;
+
+ beforeEach(() => {
+ vm.$destroy();
+
+ store.state.diffs = {
+ projectPath: 'something',
+ };
+
+ vm = new Component({
+ propsData: {
+ discussion: {
+ ...discussionMock,
+ for_commit: true,
+ commit_id: commitId,
+ diff_discussion: true,
+ diff_file: {
+ ...mockDiffFile,
+ },
+ },
+ renderDiffFile: true,
+ },
+ store,
+ }).$mount();
+
+ commitElement = vm.$el.querySelector('.commit-sha');
+ });
+
+ describe('for commit discussions', () => {
+ it('should display a monospace started a discussion on commit', () => {
+ expect(vm.$el).toContainText(`started a discussion on commit ${truncatedCommitId}`);
+ expect(commitElement).not.toBe(null);
+ expect(commitElement).toHaveText(truncatedCommitId);
+ });
+ });
+
+ describe('for diff discussion with a commit id', () => {
+ it('should display started discussion on commit header', done => {
+ vm.discussion.for_commit = false;
+
+ vm.$nextTick(() => {
+ expect(vm.$el).toContainText(`started a discussion on commit ${truncatedCommitId}`);
+ expect(commitElement).not.toBe(null);
+
+ done();
+ });
+ });
+
+ it('should display outdated change on commit header', done => {
+ vm.discussion.for_commit = false;
+ vm.discussion.active = false;
+
+ vm.$nextTick(() => {
+ expect(vm.$el).toContainText(
+ `started a discussion on an outdated change in commit ${truncatedCommitId}`,
+ );
+
+ expect(commitElement).not.toBe(null);
+
+ done();
+ });
+ });
+ });
+
+ describe('for diff discussions without a commit id', () => {
+ it('should show started a discussion on the diff text', done => {
+ Object.assign(vm.discussion, {
+ for_commit: false,
+ commit_id: null,
+ });
+
+ vm.$nextTick(() => {
+ expect(vm.$el).toContainText('started a discussion on the diff');
+
+ done();
+ });
+ });
+
+ it('should show discussion on older version text', done => {
+ Object.assign(vm.discussion, {
+ for_commit: false,
+ commit_id: null,
+ active: false,
+ });
+
+ vm.$nextTick(() => {
+ expect(vm.$el).toContainText('started a discussion on an old version of the diff');
+
+ done();
+ });
+ });
+ });
+ });
});
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index ad0e793b915..7ae45c40c28 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -305,6 +305,7 @@ export const discussionMock = {
],
individual_note: false,
resolvable: true,
+ active: true,
};
export const loggedOutnoteableData = {
@@ -1173,6 +1174,7 @@ export const discussion1 = {
id: 'abc1',
resolvable: true,
resolved: false,
+ active: true,
diff_file: {
file_path: 'about.md',
},
@@ -1209,6 +1211,7 @@ export const discussion2 = {
id: 'abc2',
resolvable: true,
resolved: false,
+ active: true,
diff_file: {
file_path: 'README.md',
},
@@ -1226,6 +1229,7 @@ export const discussion2 = {
export const discussion3 = {
id: 'abc3',
resolvable: true,
+ active: true,
resolved: false,
diff_file: {
file_path: 'README.md',
diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js
index 24c2b3e6570..2e3cd5e8f36 100644
--- a/spec/javascripts/notes/stores/actions_spec.js
+++ b/spec/javascripts/notes/stores/actions_spec.js
@@ -124,7 +124,7 @@ describe('Actions Notes Store', () => {
{ discussionId: discussionMock.id },
{ notes: [discussionMock] },
[{ type: 'EXPAND_DISCUSSION', payload: { discussionId: discussionMock.id } }],
- [],
+ [{ type: 'diffs/renderFileForDiscussionId', payload: discussionMock.id }],
done,
);
});
diff --git a/spec/javascripts/notes/stores/mutation_spec.js b/spec/javascripts/notes/stores/mutation_spec.js
index 52cdc16353a..3fbae82f16c 100644
--- a/spec/javascripts/notes/stores/mutation_spec.js
+++ b/spec/javascripts/notes/stores/mutation_spec.js
@@ -9,6 +9,11 @@ import {
individualNote,
} from '../mock_data';
+const RESOLVED_NOTE = { resolvable: true, resolved: true };
+const UNRESOLVED_NOTE = { resolvable: true, resolved: false };
+const SYSTEM_NOTE = { resolvable: false, resolved: false };
+const WEIRD_NOTE = { resolvable: false, resolved: true };
+
describe('Notes Store mutations', () => {
describe('ADD_NEW_NOTE', () => {
let state;
@@ -449,49 +454,61 @@ describe('Notes Store mutations', () => {
});
describe('UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS', () => {
- it('updates resolvableDiscussionsCount', () => {
- const state = {
- discussions: [
- { individual_note: false, resolvable: true, notes: [] },
- { individual_note: true, resolvable: true, notes: [] },
- { individual_note: false, resolvable: false, notes: [] },
- ],
- resolvableDiscussionsCount: 0,
- };
-
- mutations.UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS(state);
-
- expect(state.resolvableDiscussionsCount).toBe(1);
- });
-
- it('updates unresolvedDiscussionsCount', () => {
+ it('with unresolvable discussions, updates state', () => {
const state = {
discussions: [
- { individual_note: false, resolvable: true, notes: [{ resolved: false }] },
- { individual_note: true, resolvable: true, notes: [{ resolved: false }] },
- { individual_note: false, resolvable: false, notes: [{ resolved: false }] },
+ { individual_note: false, resolvable: true, notes: [UNRESOLVED_NOTE] },
+ { individual_note: true, resolvable: true, notes: [UNRESOLVED_NOTE] },
+ { individual_note: false, resolvable: false, notes: [UNRESOLVED_NOTE] },
],
- unresolvedDiscussionsCount: 0,
};
mutations.UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS(state);
- expect(state.unresolvedDiscussionsCount).toBe(1);
+ expect(state).toEqual(
+ jasmine.objectContaining({
+ resolvableDiscussionsCount: 1,
+ unresolvedDiscussionsCount: 1,
+ hasUnresolvedDiscussions: false,
+ }),
+ );
});
- it('updates hasUnresolvedDiscussions', () => {
+ it('with resolvable discussions, updates state', () => {
const state = {
discussions: [
- { individual_note: false, resolvable: true, notes: [{ resolved: false }] },
- { individual_note: false, resolvable: true, notes: [{ resolved: false }] },
- { individual_note: false, resolvable: false, notes: [{ resolved: false }] },
+ {
+ individual_note: false,
+ resolvable: true,
+ notes: [RESOLVED_NOTE, SYSTEM_NOTE, RESOLVED_NOTE],
+ },
+ {
+ individual_note: false,
+ resolvable: true,
+ notes: [RESOLVED_NOTE, SYSTEM_NOTE, WEIRD_NOTE],
+ },
+ {
+ individual_note: false,
+ resolvable: true,
+ notes: [SYSTEM_NOTE, RESOLVED_NOTE, WEIRD_NOTE, UNRESOLVED_NOTE],
+ },
+ {
+ individual_note: false,
+ resolvable: true,
+ notes: [UNRESOLVED_NOTE],
+ },
],
- hasUnresolvedDiscussions: 0,
};
mutations.UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS(state);
- expect(state.hasUnresolvedDiscussions).toBe(true);
+ expect(state).toEqual(
+ jasmine.objectContaining({
+ resolvableDiscussionsCount: 4,
+ unresolvedDiscussionsCount: 2,
+ hasUnresolvedDiscussions: true,
+ }),
+ );
});
});
});
diff --git a/spec/javascripts/pipelines/pipeline_url_spec.js b/spec/javascripts/pipelines/pipeline_url_spec.js
index d6c44f4c976..ea917b36526 100644
--- a/spec/javascripts/pipelines/pipeline_url_spec.js
+++ b/spec/javascripts/pipelines/pipeline_url_spec.js
@@ -90,7 +90,7 @@ describe('Pipeline Url Component', () => {
expect(component.$el.querySelector('.js-pipeline-url-api').textContent).toContain('API');
});
- it('should render latest, yaml invalid and stuck flags when provided', () => {
+ it('should render latest, yaml invalid, merge request, and stuck flags when provided', () => {
const component = new PipelineUrlComponent({
propsData: {
pipeline: {
@@ -100,6 +100,7 @@ describe('Pipeline Url Component', () => {
latest: true,
yaml_errors: true,
stuck: true,
+ merge_request: true,
},
},
autoDevopsHelpPath: 'foo',
@@ -111,6 +112,10 @@ describe('Pipeline Url Component', () => {
'yaml invalid',
);
+ expect(component.$el.querySelector('.js-pipeline-url-mergerequest').textContent).toContain(
+ 'merge request',
+ );
+
expect(component.$el.querySelector('.js-pipeline-url-stuck').textContent).toContain('stuck');
});
diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js
index 92ff960277a..67118ac03a5 100644
--- a/spec/javascripts/registry/components/app_spec.js
+++ b/spec/javascripts/registry/components/app_spec.js
@@ -1,37 +1,30 @@
-import _ from 'underscore';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import Vue from 'vue';
import registry from '~/registry/components/app.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { TEST_HOST } from 'spec/test_constants';
import { reposServerResponse } from '../mock_data';
describe('Registry List', () => {
+ const Component = Vue.extend(registry);
let vm;
- let Component;
+ let mock;
beforeEach(() => {
- Component = Vue.extend(registry);
+ mock = new MockAdapter(axios);
});
afterEach(() => {
+ mock.restore();
vm.$destroy();
});
describe('with data', () => {
- const interceptor = (request, next) => {
- next(
- request.respondWith(JSON.stringify(reposServerResponse), {
- status: 200,
- }),
- );
- };
-
beforeEach(() => {
- Vue.http.interceptors.push(interceptor);
- vm = mountComponent(Component, { endpoint: 'foo' });
- });
+ mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, reposServerResponse);
- afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
+ vm = mountComponent(Component, { endpoint: `${TEST_HOST}/foo` });
});
it('should render a list of repos', done => {
@@ -64,9 +57,9 @@ describe('Registry List', () => {
Vue.nextTick(() => {
vm.$el.querySelector('.js-toggle-repo').click();
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.js-toggle-repo i').className).toEqual(
- 'fa fa-chevron-up',
- );
+ expect(
+ vm.$el.querySelector('.js-toggle-repo use').getAttribute('xlink:href'),
+ ).toContain('angle-up');
done();
});
});
@@ -76,21 +69,10 @@ describe('Registry List', () => {
});
describe('without data', () => {
- const interceptor = (request, next) => {
- next(
- request.respondWith(JSON.stringify([]), {
- status: 200,
- }),
- );
- };
-
beforeEach(() => {
- Vue.http.interceptors.push(interceptor);
- vm = mountComponent(Component, { endpoint: 'foo' });
- });
+ mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []);
- afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
+ vm = mountComponent(Component, { endpoint: `${TEST_HOST}/foo` });
});
it('should render empty message', done => {
@@ -109,21 +91,10 @@ describe('Registry List', () => {
});
describe('while loading data', () => {
- const interceptor = (request, next) => {
- next(
- request.respondWith(JSON.stringify(reposServerResponse), {
- status: 200,
- }),
- );
- };
-
beforeEach(() => {
- Vue.http.interceptors.push(interceptor);
- vm = mountComponent(Component, { endpoint: 'foo' });
- });
+ mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []);
- afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
+ vm = mountComponent(Component, { endpoint: `${TEST_HOST}/foo` });
});
it('should render a loading spinner', done => {
diff --git a/spec/javascripts/registry/components/collapsible_container_spec.js b/spec/javascripts/registry/components/collapsible_container_spec.js
index 256a242f784..a3f7ff76dc7 100644
--- a/spec/javascripts/registry/components/collapsible_container_spec.js
+++ b/spec/javascripts/registry/components/collapsible_container_spec.js
@@ -1,14 +1,24 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import Vue from 'vue';
import collapsibleComponent from '~/registry/components/collapsible_container.vue';
import store from '~/registry/stores';
-import { repoPropsData } from '../mock_data';
+import * as types from '~/registry/stores/mutation_types';
+
+import { repoPropsData, registryServerResponse, reposServerResponse } from '../mock_data';
describe('collapsible registry container', () => {
let vm;
- let Component;
+ let mock;
+ const Component = Vue.extend(collapsibleComponent);
beforeEach(() => {
- Component = Vue.extend(collapsibleComponent);
+ mock = new MockAdapter(axios);
+
+ mock.onGet(repoPropsData.tagsPath).replyOnce(200, registryServerResponse, {});
+
+ store.commit(types.SET_REPOS_LIST, reposServerResponse);
+
vm = new Component({
store,
propsData: {
@@ -18,24 +28,23 @@ describe('collapsible registry container', () => {
});
afterEach(() => {
+ mock.restore();
vm.$destroy();
});
describe('toggle', () => {
it('should be closed by default', () => {
expect(vm.$el.querySelector('.container-image-tags')).toBe(null);
- expect(vm.$el.querySelector('.container-image-head i').className).toEqual(
- 'fa fa-chevron-right',
- );
+ expect(vm.iconName).toEqual('angle-right');
});
it('should be open when user clicks on closed repo', done => {
vm.$el.querySelector('.js-toggle-repo').click();
+
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.container-image-tags')).toBeDefined();
- expect(vm.$el.querySelector('.container-image-head i').className).toEqual(
- 'fa fa-chevron-up',
- );
+ expect(vm.$el.querySelector('.container-image-tags')).not.toBeNull();
+ expect(vm.iconName).toEqual('angle-up');
+
done();
});
});
@@ -45,12 +54,12 @@ describe('collapsible registry container', () => {
Vue.nextTick(() => {
vm.$el.querySelector('.js-toggle-repo').click();
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.container-image-tags')).toBe(null);
- expect(vm.$el.querySelector('.container-image-head i').className).toEqual(
- 'fa fa-chevron-right',
- );
- done();
+ setTimeout(() => {
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.container-image-tags')).toBe(null);
+ expect(vm.iconName).toEqual('angle-right');
+ done();
+ });
});
});
});
@@ -58,7 +67,7 @@ describe('collapsible registry container', () => {
describe('delete repo', () => {
it('should be possible to delete a repo', () => {
- expect(vm.$el.querySelector('.js-remove-repo')).toBeDefined();
+ expect(vm.$el.querySelector('.js-remove-repo')).not.toBeNull();
});
});
});
diff --git a/spec/javascripts/registry/stores/actions_spec.js b/spec/javascripts/registry/stores/actions_spec.js
index bc4c444655a..c9aa82dba90 100644
--- a/spec/javascripts/registry/stores/actions_spec.js
+++ b/spec/javascripts/registry/stores/actions_spec.js
@@ -1,42 +1,34 @@
-import Vue from 'vue';
-import VueResource from 'vue-resource';
-import _ from 'underscore';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import * as actions from '~/registry/stores/actions';
import * as types from '~/registry/stores/mutation_types';
+import state from '~/registry/stores/state';
+import { TEST_HOST } from 'spec/test_constants';
import testAction from '../../helpers/vuex_action_helper';
import {
- defaultState,
reposServerResponse,
registryServerResponse,
parsedReposServerResponse,
} from '../mock_data';
-Vue.use(VueResource);
-
describe('Actions Registry Store', () => {
- let interceptor;
let mockedState;
+ let mock;
beforeEach(() => {
- mockedState = defaultState;
+ mockedState = state();
+ mockedState.endpoint = `${TEST_HOST}/endpoint.json`;
+ mock = new MockAdapter(axios);
});
- describe('server requests', () => {
- afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
- });
+ afterEach(() => {
+ mock.restore();
+ });
+ describe('server requests', () => {
describe('fetchRepos', () => {
beforeEach(() => {
- interceptor = (request, next) => {
- next(
- request.respondWith(JSON.stringify(reposServerResponse), {
- status: 200,
- }),
- );
- };
-
- Vue.http.interceptors.push(interceptor);
+ mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, reposServerResponse, {});
});
it('should set receveived repos', done => {
@@ -56,23 +48,15 @@ describe('Actions Registry Store', () => {
});
describe('fetchList', () => {
+ let repo;
beforeEach(() => {
- interceptor = (request, next) => {
- next(
- request.respondWith(JSON.stringify(registryServerResponse), {
- status: 200,
- }),
- );
- };
+ mockedState.repos = parsedReposServerResponse;
+ [, repo] = mockedState.repos;
- Vue.http.interceptors.push(interceptor);
+ mock.onGet(repo.tagsPath).replyOnce(200, registryServerResponse, {});
});
it('should set received list', done => {
- mockedState.repos = parsedReposServerResponse;
-
- const repo = mockedState.repos[1];
-
testAction(
actions.fetchList,
{ repo },
diff --git a/spec/javascripts/releases/components/app_spec.js b/spec/javascripts/releases/components/app_spec.js
new file mode 100644
index 00000000000..f30c7685e34
--- /dev/null
+++ b/spec/javascripts/releases/components/app_spec.js
@@ -0,0 +1,79 @@
+import Vue from 'vue';
+import app from '~/releases/components/app.vue';
+import createStore from '~/releases/store';
+import api from '~/api';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { resetStore } from '../store/helpers';
+import { releases } from '../mock_data';
+
+describe('Releases App ', () => {
+ const Component = Vue.extend(app);
+ let store;
+ let vm;
+
+ const props = {
+ projectId: 'gitlab-ce',
+ documentationLink: 'help/releases',
+ illustrationPath: 'illustration/path',
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ });
+
+ afterEach(() => {
+ resetStore(store);
+ vm.$destroy();
+ });
+
+ describe('while loading', () => {
+ beforeEach(() => {
+ spyOn(api, 'releases').and.returnValue(Promise.resolve({ data: [] }));
+ vm = mountComponentWithStore(Component, { props, store });
+ });
+
+ it('renders loading icon', done => {
+ expect(vm.$el.querySelector('.js-loading')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-empty-state')).toBeNull();
+ expect(vm.$el.querySelector('.js-success-state')).toBeNull();
+
+ setTimeout(() => {
+ done();
+ }, 0);
+ });
+ });
+
+ describe('with successful request', () => {
+ beforeEach(() => {
+ spyOn(api, 'releases').and.returnValue(Promise.resolve({ data: releases }));
+ vm = mountComponentWithStore(Component, { props, store });
+ });
+
+ it('renders success state', done => {
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.js-loading')).toBeNull();
+ expect(vm.$el.querySelector('.js-empty-state')).toBeNull();
+ expect(vm.$el.querySelector('.js-success-state')).not.toBeNull();
+
+ done();
+ }, 0);
+ });
+ });
+
+ describe('with empty request', () => {
+ beforeEach(() => {
+ spyOn(api, 'releases').and.returnValue(Promise.resolve({ data: [] }));
+ vm = mountComponentWithStore(Component, { props, store });
+ });
+
+ it('renders empty state', done => {
+ setTimeout(() => {
+ expect(vm.$el.querySelector('.js-loading')).toBeNull();
+ expect(vm.$el.querySelector('.js-empty-state')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-success-state')).toBeNull();
+
+ done();
+ }, 0);
+ });
+ });
+});
diff --git a/spec/javascripts/releases/components/release_block_spec.js b/spec/javascripts/releases/components/release_block_spec.js
new file mode 100644
index 00000000000..29420216bc4
--- /dev/null
+++ b/spec/javascripts/releases/components/release_block_spec.js
@@ -0,0 +1,136 @@
+import Vue from 'vue';
+import component from '~/releases/components/release_block.vue';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('Release block', () => {
+ const Component = Vue.extend(component);
+
+ const release = {
+ name: 'Bionic Beaver',
+ tag_name: '18.04',
+ description: '## changelog\n\n* line 1\n* line2',
+ description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>',
+ author_name: 'Release bot',
+ author_email: 'release-bot@example.com',
+ created_at: '2012-05-28T05:00:00-07:00',
+ author: {
+ avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png',
+ id: 482476,
+ name: 'John Doe',
+ path: '/johndoe',
+ state: 'active',
+ status_tooltip_html: null,
+ username: 'johndoe',
+ web_url: 'https://gitlab.com/johndoe',
+ },
+ commit: {
+ id: '2695effb5807a22ff3d138d593fd856244e155e7',
+ short_id: '2695effb',
+ title: 'Initial commit',
+ created_at: '2017-07-26T11:08:53.000+02:00',
+ parent_ids: ['2a4b78934375d7f53875269ffd4f45fd83a84ebe'],
+ message: 'Initial commit',
+ author_name: 'John Smith',
+ author_email: 'john@example.com',
+ authored_date: '2012-05-28T04:42:42-07:00',
+ committer_name: 'Jack Smith',
+ committer_email: 'jack@example.com',
+ committed_date: '2012-05-28T04:42:42-07:00',
+ },
+ assets: {
+ count: 6,
+ sources: [
+ {
+ format: 'zip',
+ url: 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.zip',
+ },
+ {
+ format: 'tar.gz',
+ url:
+ 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.gz',
+ },
+ {
+ format: 'tar.bz2',
+ url:
+ 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.bz2',
+ },
+ {
+ format: 'tar',
+ url: 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar',
+ },
+ ],
+ links: [
+ {
+ name: 'release-18.04.dmg',
+ url: 'https://my-external-hosting.example.com/scrambled-url/',
+ external: true,
+ },
+ {
+ name: 'binary-linux-amd64',
+ url:
+ 'https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50',
+ external: false,
+ },
+ ],
+ },
+ };
+ let vm;
+
+ beforeEach(() => {
+ vm = mountComponent(Component, { release });
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders release name', () => {
+ expect(vm.$el.textContent).toContain(release.name);
+ });
+
+ it('renders commit sha', () => {
+ expect(vm.$el.textContent).toContain(release.commit.short_id);
+ });
+
+ it('renders tag name', () => {
+ expect(vm.$el.textContent).toContain(release.tag_name);
+ });
+
+ it('renders release date', () => {
+ expect(vm.$el.textContent).toContain(timeagoMixin.methods.timeFormated(release.created_at));
+ });
+
+ it('renders number of assets provided', () => {
+ expect(vm.$el.querySelector('.js-assets-count').textContent).toContain(release.assets.count);
+ });
+
+ it('renders dropdown with the sources', () => {
+ expect(vm.$el.querySelectorAll('.js-sources-dropdown li').length).toEqual(
+ release.assets.sources.length,
+ );
+
+ expect(vm.$el.querySelector('.js-sources-dropdown li a').getAttribute('href')).toEqual(
+ release.assets.sources[0].url,
+ );
+
+ expect(vm.$el.querySelector('.js-sources-dropdown li a').textContent).toContain(
+ release.assets.sources[0].format,
+ );
+ });
+
+ it('renders list with the links provided', () => {
+ expect(vm.$el.querySelectorAll('.js-assets-list li').length).toEqual(
+ release.assets.links.length,
+ );
+
+ expect(vm.$el.querySelector('.js-assets-list li a').getAttribute('href')).toEqual(
+ release.assets.links[0].url,
+ );
+
+ expect(vm.$el.querySelector('.js-assets-list li a').textContent).toContain(
+ release.assets.links[0].name,
+ );
+ });
+});
diff --git a/spec/javascripts/releases/mock_data.js b/spec/javascripts/releases/mock_data.js
new file mode 100644
index 00000000000..2855eca1711
--- /dev/null
+++ b/spec/javascripts/releases/mock_data.js
@@ -0,0 +1,128 @@
+export const release = {
+ name: 'Bionic Beaver',
+ tag_name: '18.04',
+ description: '## changelog\n\n* line 1\n* line2',
+ description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>',
+ author_name: 'Release bot',
+ author_email: 'release-bot@example.com',
+ created_at: '2012-05-28T05:00:00-07:00',
+ commit: {
+ id: '2695effb5807a22ff3d138d593fd856244e155e7',
+ short_id: '2695effb',
+ title: 'Initial commit',
+ created_at: '2017-07-26T11:08:53.000+02:00',
+ parent_ids: ['2a4b78934375d7f53875269ffd4f45fd83a84ebe'],
+ message: 'Initial commit',
+ author: {
+ avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png',
+ id: 482476,
+ name: 'John Doe',
+ path: '/johndoe',
+ state: 'active',
+ status_tooltip_html: null,
+ username: 'johndoe',
+ web_url: 'https://gitlab.com/johndoe',
+ },
+ authored_date: '2012-05-28T04:42:42-07:00',
+ committer_name: 'Jack Smith',
+ committer_email: 'jack@example.com',
+ committed_date: '2012-05-28T04:42:42-07:00',
+ },
+ assets: {
+ count: 6,
+ sources: [
+ {
+ format: 'zip',
+ url: 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.zip',
+ },
+ {
+ format: 'tar.gz',
+ url: 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.gz',
+ },
+ {
+ format: 'tar.bz2',
+ url:
+ 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.bz2',
+ },
+ {
+ format: 'tar',
+ url: 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar',
+ },
+ ],
+ links: [
+ {
+ name: 'release-18.04.dmg',
+ url: 'https://my-external-hosting.example.com/scrambled-url/',
+ external: true,
+ },
+ {
+ name: 'binary-linux-amd64',
+ url:
+ 'https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50',
+ external: false,
+ },
+ ],
+ },
+};
+
+export const releases = [
+ release,
+ {
+ name: 'JoJos Bizarre Adventure',
+ tag_name: '19.00',
+ description: '## changelog\n\n* line 1\n* line2',
+ description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>',
+ author_name: 'Release bot',
+ author_email: 'release-bot@example.com',
+ created_at: '2012-05-28T05:00:00-07:00',
+ commit: {
+ id: '2695effb5807a22ff3d138d593fd856244e155e7',
+ short_id: '2695effb',
+ title: 'Initial commit',
+ created_at: '2017-07-26T11:08:53.000+02:00',
+ parent_ids: ['2a4b78934375d7f53875269ffd4f45fd83a84ebe'],
+ message: 'Initial commit',
+ author: {
+ avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png',
+ id: 482476,
+ name: 'John Doe',
+ path: '/johndoe',
+ state: 'active',
+ status_tooltip_html: null,
+ username: 'johndoe',
+ web_url: 'https://gitlab.com/johndoe',
+ },
+ authored_date: '2012-05-28T04:42:42-07:00',
+ committer_name: 'Jack Smith',
+ committer_email: 'jack@example.com',
+ committed_date: '2012-05-28T04:42:42-07:00',
+ },
+ assets: {
+ count: 4,
+ sources: [
+ {
+ format: 'tar.gz',
+ url:
+ 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.gz',
+ },
+ {
+ format: 'tar.bz2',
+ url:
+ 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.bz2',
+ },
+ {
+ format: 'tar',
+ url: 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar',
+ },
+ ],
+ links: [
+ {
+ name: 'binary-linux-amd64',
+ url:
+ 'https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50',
+ external: false,
+ },
+ ],
+ },
+ },
+];
diff --git a/spec/javascripts/releases/store/actions_spec.js b/spec/javascripts/releases/store/actions_spec.js
new file mode 100644
index 00000000000..6eb8e681be9
--- /dev/null
+++ b/spec/javascripts/releases/store/actions_spec.js
@@ -0,0 +1,98 @@
+import {
+ requestReleases,
+ fetchReleases,
+ receiveReleasesSuccess,
+ receiveReleasesError,
+} from '~/releases/store/actions';
+import state from '~/releases/store/state';
+import * as types from '~/releases/store/mutation_types';
+import api from '~/api';
+import testAction from 'spec/helpers/vuex_action_helper';
+import { releases } from '../mock_data';
+
+describe('Releases State actions', () => {
+ let mockedState;
+
+ beforeEach(() => {
+ mockedState = state();
+ });
+
+ describe('requestReleases', () => {
+ it('should commit REQUEST_RELEASES mutation', done => {
+ testAction(requestReleases, null, mockedState, [{ type: types.REQUEST_RELEASES }], [], done);
+ });
+ });
+
+ describe('fetchReleases', () => {
+ describe('success', () => {
+ it('dispatches requestReleases and receiveReleasesSuccess ', done => {
+ spyOn(api, 'releases').and.returnValue(Promise.resolve({ data: releases }));
+
+ testAction(
+ fetchReleases,
+ releases,
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestReleases',
+ },
+ {
+ payload: releases,
+ type: 'receiveReleasesSuccess',
+ },
+ ],
+ done,
+ );
+ });
+ });
+
+ describe('error', () => {
+ it('dispatches requestReleases and receiveReleasesError ', done => {
+ spyOn(api, 'releases').and.returnValue(Promise.reject());
+
+ testAction(
+ fetchReleases,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestReleases',
+ },
+ {
+ type: 'receiveReleasesError',
+ },
+ ],
+ done,
+ );
+ });
+ });
+ });
+
+ describe('receiveReleasesSuccess', () => {
+ it('should commit RECEIVE_RELEASES_SUCCESS mutation', done => {
+ testAction(
+ receiveReleasesSuccess,
+ releases,
+ mockedState,
+ [{ type: types.RECEIVE_RELEASES_SUCCESS, payload: releases }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('receiveReleasesError', () => {
+ it('should commit RECEIVE_RELEASES_ERROR mutation', done => {
+ testAction(
+ receiveReleasesError,
+ null,
+ mockedState,
+ [{ type: types.RECEIVE_RELEASES_ERROR }],
+ [],
+ done,
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/releases/store/helpers.js b/spec/javascripts/releases/store/helpers.js
new file mode 100644
index 00000000000..e962b254377
--- /dev/null
+++ b/spec/javascripts/releases/store/helpers.js
@@ -0,0 +1,6 @@
+import state from '~/releases/store/state';
+
+// eslint-disable-next-line import/prefer-default-export
+export const resetStore = store => {
+ store.replaceState(state());
+};
diff --git a/spec/javascripts/releases/store/mutations_spec.js b/spec/javascripts/releases/store/mutations_spec.js
new file mode 100644
index 00000000000..72b98529fe9
--- /dev/null
+++ b/spec/javascripts/releases/store/mutations_spec.js
@@ -0,0 +1,47 @@
+import state from '~/releases/store/state';
+import mutations from '~/releases/store/mutations';
+import * as types from '~/releases/store/mutation_types';
+import { releases } from '../mock_data';
+
+describe('Releases Store Mutations', () => {
+ let stateCopy;
+
+ beforeEach(() => {
+ stateCopy = state();
+ });
+
+ describe('REQUEST_RELEASES', () => {
+ it('sets isLoading to true', () => {
+ mutations[types.REQUEST_RELEASES](stateCopy);
+
+ expect(stateCopy.isLoading).toEqual(true);
+ });
+ });
+
+ describe('RECEIVE_RELEASES_SUCCESS', () => {
+ beforeEach(() => {
+ mutations[types.RECEIVE_RELEASES_SUCCESS](stateCopy, releases);
+ });
+
+ it('sets is loading to false', () => {
+ expect(stateCopy.isLoading).toEqual(false);
+ });
+
+ it('sets hasError to false', () => {
+ expect(stateCopy.hasError).toEqual(false);
+ });
+
+ it('sets data', () => {
+ expect(stateCopy.releases).toEqual(releases);
+ });
+ });
+
+ describe('RECEIVE_RELEASES_ERROR', () => {
+ it('resets data', () => {
+ mutations[types.RECEIVE_RELEASES_ERROR](stateCopy);
+
+ expect(stateCopy.isLoading).toEqual(false);
+ expect(stateCopy.releases).toEqual([]);
+ });
+ });
+});
diff --git a/spec/javascripts/sidebar/mock_data.js b/spec/javascripts/sidebar/mock_data.js
index fcd7bea3f6d..7f20b0da991 100644
--- a/spec/javascripts/sidebar/mock_data.js
+++ b/spec/javascripts/sidebar/mock_data.js
@@ -66,7 +66,7 @@ const RESPONSE_MAP = {
},
labels: [],
},
- '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar': {
+ '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar_extras': {
assignees: [
{
name: 'User 0',
@@ -181,7 +181,7 @@ const RESPONSE_MAP = {
const mockData = {
responseMap: RESPONSE_MAP,
mediator: {
- endpoint: '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar',
+ endpoint: '/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar_extras',
toggleSubscriptionEndpoint: '/gitlab-org/gitlab-shell/issues/5/toggle_subscription',
moveIssueEndpoint: '/gitlab-org/gitlab-shell/issues/5/move',
projectsAutocompleteEndpoint: '/autocomplete/projects?project_id=15',
diff --git a/spec/javascripts/sidebar/sidebar_mediator_spec.js b/spec/javascripts/sidebar/sidebar_mediator_spec.js
index 2d853970fc4..6c69c08e733 100644
--- a/spec/javascripts/sidebar/sidebar_mediator_spec.js
+++ b/spec/javascripts/sidebar/sidebar_mediator_spec.js
@@ -37,7 +37,7 @@ describe('Sidebar mediator', function() {
it('fetches the data', done => {
const mockData =
- Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar'];
+ Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar_extras'];
spyOn(this.mediator, 'processFetchedData').and.callThrough();
this.mediator
@@ -51,7 +51,7 @@ describe('Sidebar mediator', function() {
it('processes fetched data', () => {
const mockData =
- Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar'];
+ Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar_extras'];
this.mediator.processFetchedData(mockData);
expect(this.mediator.store.assignees).toEqual(mockData.assignees);
diff --git a/spec/javascripts/user_popovers_spec.js b/spec/javascripts/user_popovers_spec.js
new file mode 100644
index 00000000000..6cf8dd81b36
--- /dev/null
+++ b/spec/javascripts/user_popovers_spec.js
@@ -0,0 +1,66 @@
+import initUserPopovers from '~/user_popovers';
+import UsersCache from '~/lib/utils/users_cache';
+
+describe('User Popovers', () => {
+ const selector = '.js-user-link';
+
+ const dummyUser = { name: 'root' };
+ const dummyUserStatus = { message: 'active' };
+
+ const triggerEvent = (eventName, el) => {
+ const event = document.createEvent('MouseEvents');
+ event.initMouseEvent(eventName, true, true, window);
+
+ el.dispatchEvent(event);
+ };
+
+ beforeEach(() => {
+ setFixtures(`
+ <a href="/root" data-user-id="1" class="js-user-link" data-username="root" data-original-title="" title="">
+ Root
+ </a>
+ `);
+
+ const usersCacheSpy = () => Promise.resolve(dummyUser);
+ spyOn(UsersCache, 'retrieveById').and.callFake(userId => usersCacheSpy(userId));
+
+ const userStatusCacheSpy = () => Promise.resolve(dummyUserStatus);
+ spyOn(UsersCache, 'retrieveStatusById').and.callFake(userId => userStatusCacheSpy(userId));
+
+ initUserPopovers(document.querySelectorAll('.js-user-link'));
+ });
+
+ it('Should Show+Hide Popover on mouseenter and mouseleave', done => {
+ triggerEvent('mouseenter', document.querySelector(selector));
+
+ setTimeout(() => {
+ const shownPopover = document.querySelector('.popover');
+
+ expect(shownPopover).not.toBeNull();
+
+ expect(shownPopover.innerHTML).toContain(dummyUser.name);
+ expect(UsersCache.retrieveById).toHaveBeenCalledWith('1');
+
+ triggerEvent('mouseleave', document.querySelector(selector));
+
+ setTimeout(() => {
+ // After Mouse leave it should be hidden now
+ expect(document.querySelector('.popover')).toBeNull();
+ done();
+ });
+ }, 210); // We need to wait until the 200ms mouseover delay is over, only then the popover will be visible
+ });
+
+ it('Should Not show a popover on short mouse over', done => {
+ triggerEvent('mouseenter', document.querySelector(selector));
+
+ setTimeout(() => {
+ expect(document.querySelector('.popover')).toBeNull();
+ expect(UsersCache.retrieveById).not.toHaveBeenCalledWith('1');
+
+ triggerEvent('mouseleave', document.querySelector(selector));
+
+ done();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js
index 300133dc602..212519743aa 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js
@@ -114,7 +114,7 @@ describe('Merge request widget rebase component', () => {
// Wait for the eventHub to be called
.then(vm.$nextTick())
.then(() => {
- expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
+ expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetRebaseSuccess');
})
.then(done)
.catch(done.fail);
diff --git a/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js b/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js
index 9d34bdd1084..61ef26cd080 100644
--- a/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js
+++ b/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js
@@ -35,7 +35,7 @@ describe('getStateKey', () => {
expect(bound()).toEqual('mergeWhenPipelineSucceeds');
- context.hasSHAChanged = true;
+ context.isSHAMismatch = true;
expect(bound()).toEqual('shaMismatch');
diff --git a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
index f5079147f60..c226704694c 100644
--- a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
+++ b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
@@ -3,23 +3,30 @@ import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
import mockData from '../mock_data';
describe('MergeRequestStore', () => {
- describe('setData', () => {
- let store;
+ let store;
- beforeEach(() => {
- store = new MergeRequestStore(mockData);
- });
+ beforeEach(() => {
+ store = new MergeRequestStore(mockData);
+ });
- it('should set hasSHAChanged when the diff SHA changes', () => {
+ describe('setData', () => {
+ it('should set isSHAMismatch when the diff SHA changes', () => {
store.setData({ ...mockData, diff_head_sha: 'a-different-string' });
- expect(store.hasSHAChanged).toBe(true);
+ expect(store.isSHAMismatch).toBe(true);
});
- it('should not set hasSHAChanged when other data changes', () => {
+ it('should not set isSHAMismatch when other data changes', () => {
store.setData({ ...mockData, work_in_progress: !mockData.work_in_progress });
- expect(store.hasSHAChanged).toBe(false);
+ expect(store.isSHAMismatch).toBe(false);
+ });
+
+ it('should update cached sha after rebasing', () => {
+ store.setData({ ...mockData, diff_head_sha: 'abc123' }, true);
+
+ expect(store.isSHAMismatch).toBe(false);
+ expect(store.sha).toBe('abc123');
});
describe('isPipelinePassing', () => {
diff --git a/spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js b/spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js
index 67a3a2e08bc..6add6cdac4d 100644
--- a/spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js
+++ b/spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js
@@ -68,4 +68,30 @@ describe('DiffViewer', () => {
done();
});
});
+
+ it('renders renamed component', () => {
+ createComponent({
+ diffMode: 'renamed',
+ newPath: 'test.abc',
+ newSha: 'ABC',
+ oldPath: 'testold.abc',
+ oldSha: 'DEF',
+ });
+
+ expect(vm.$el.textContent).toContain('File moved');
+ });
+
+ it('renders mode changed component', () => {
+ createComponent({
+ diffMode: 'mode_changed',
+ newPath: 'test.abc',
+ newSha: 'ABC',
+ oldPath: 'testold.abc',
+ oldSha: 'DEF',
+ aMode: '123',
+ bMode: '321',
+ });
+
+ expect(vm.$el.textContent).toContain('File mode changed from 123 to 321');
+ });
});
diff --git a/spec/javascripts/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js b/spec/javascripts/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js
new file mode 100644
index 00000000000..c4358f0d9cb
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js
@@ -0,0 +1,23 @@
+import { shallowMount } from '@vue/test-utils';
+import ModeChanged from '~/vue_shared/components/diff_viewer/viewers/mode_changed.vue';
+
+describe('Diff viewer mode changed component', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = shallowMount(ModeChanged, {
+ propsData: {
+ aMode: '123',
+ bMode: '321',
+ },
+ });
+ });
+
+ afterEach(() => {
+ vm.destroy();
+ });
+
+ it('renders aMode & bMode', () => {
+ expect(vm.text()).toContain('File mode changed from 123 to 321');
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/issue/issue_assignees_spec.js b/spec/javascripts/vue_shared/components/issue/issue_assignees_spec.js
new file mode 100644
index 00000000000..9eac75fac96
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/issue/issue_assignees_spec.js
@@ -0,0 +1,114 @@
+import Vue from 'vue';
+
+import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
+
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { mockAssigneesList } from 'spec/boards/mock_data';
+
+const createComponent = (assignees = mockAssigneesList, cssClass = '') => {
+ const Component = Vue.extend(IssueAssignees);
+
+ return mountComponent(Component, {
+ assignees,
+ cssClass,
+ });
+};
+
+describe('IssueAssigneesComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('data', () => {
+ it('returns default data props', () => {
+ expect(vm.maxVisibleAssignees).toBe(2);
+ expect(vm.maxAssigneeAvatars).toBe(3);
+ expect(vm.maxAssignees).toBe(99);
+ });
+ });
+
+ describe('computed', () => {
+ describe('countOverLimit', () => {
+ it('should return difference between assignees count and maxVisibleAssignees', () => {
+ expect(vm.countOverLimit).toBe(mockAssigneesList.length - vm.maxVisibleAssignees);
+ });
+ });
+
+ describe('assigneesToShow', () => {
+ it('should return assignees containing only 2 items when count more than maxAssigneeAvatars', () => {
+ expect(vm.assigneesToShow.length).toBe(2);
+ });
+
+ it('should return all assignees as it is when count less than maxAssigneeAvatars', () => {
+ vm.assignees = mockAssigneesList.slice(0, 3); // Set 3 Assignees
+
+ expect(vm.assigneesToShow.length).toBe(3);
+ });
+ });
+
+ describe('assigneesCounterTooltip', () => {
+ it('should return string containing count of remaining assignees when count more than maxAssigneeAvatars', () => {
+ expect(vm.assigneesCounterTooltip).toBe('3 more assignees');
+ });
+ });
+
+ describe('shouldRenderAssigneesCounter', () => {
+ it('should return `false` when assignees count less than maxAssigneeAvatars', () => {
+ vm.assignees = mockAssigneesList.slice(0, 3); // Set 3 Assignees
+
+ expect(vm.shouldRenderAssigneesCounter).toBe(false);
+ });
+
+ it('should return `true` when assignees count more than maxAssigneeAvatars', () => {
+ expect(vm.shouldRenderAssigneesCounter).toBe(true);
+ });
+ });
+
+ describe('assigneeCounterLabel', () => {
+ it('should return count of additional assignees total assignees count more than maxAssigneeAvatars', () => {
+ expect(vm.assigneeCounterLabel).toBe('+3');
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('avatarUrlTitle', () => {
+ it('returns string containing alt text for assignee avatar', () => {
+ expect(vm.avatarUrlTitle(mockAssigneesList[0])).toBe('Avatar for Terrell Graham');
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders component root element with class `issue-assignees`', () => {
+ expect(vm.$el.classList.contains('issue-assignees')).toBe(true);
+ });
+
+ it('renders assignee avatars', () => {
+ expect(vm.$el.querySelectorAll('.user-avatar-link').length).toBe(2);
+ });
+
+ it('renders assignee tooltips', () => {
+ const tooltipText = vm.$el
+ .querySelectorAll('.user-avatar-link')[0]
+ .querySelector('.js-assignee-tooltip').innerText;
+
+ expect(tooltipText).toContain('Assignee');
+ expect(tooltipText).toContain('Terrell Graham');
+ expect(tooltipText).toContain('@monserrate.gleichner');
+ });
+
+ it('renders additional assignees count', () => {
+ const avatarCounterEl = vm.$el.querySelector('.avatar-counter');
+
+ expect(avatarCounterEl.innerText.trim()).toBe('+3');
+ expect(avatarCounterEl.getAttribute('data-original-title')).toBe('3 more assignees');
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/issue/issue_milestone_spec.js b/spec/javascripts/vue_shared/components/issue/issue_milestone_spec.js
new file mode 100644
index 00000000000..8fca2637326
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/issue/issue_milestone_spec.js
@@ -0,0 +1,234 @@
+import Vue from 'vue';
+
+import IssueMilestone from '~/vue_shared/components/issue/issue_milestone.vue';
+
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { mockMilestone } from 'spec/boards/mock_data';
+
+const createComponent = (milestone = mockMilestone) => {
+ const Component = Vue.extend(IssueMilestone);
+
+ return mountComponent(Component, {
+ milestone,
+ });
+};
+
+describe('IssueMilestoneComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('isMilestoneStarted', () => {
+ it('should return `false` when milestoneStart prop is not defined', done => {
+ const vmStartUndefined = createComponent(
+ Object.assign({}, mockMilestone, {
+ start_date: '',
+ }),
+ );
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vmStartUndefined.isMilestoneStarted).toBe(false);
+ })
+ .then(done)
+ .catch(done.fail);
+
+ vmStartUndefined.$destroy();
+ });
+
+ it('should return `true` when milestone start date is past current date', done => {
+ const vmStarted = createComponent(
+ Object.assign({}, mockMilestone, {
+ start_date: '1990-07-22',
+ }),
+ );
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vmStarted.isMilestoneStarted).toBe(true);
+ })
+ .then(done)
+ .catch(done.fail);
+
+ vmStarted.$destroy();
+ });
+ });
+
+ describe('isMilestonePastDue', () => {
+ it('should return `false` when milestoneDue prop is not defined', done => {
+ const vmDueUndefined = createComponent(
+ Object.assign({}, mockMilestone, {
+ due_date: '',
+ }),
+ );
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vmDueUndefined.isMilestonePastDue).toBe(false);
+ })
+ .then(done)
+ .catch(done.fail);
+
+ vmDueUndefined.$destroy();
+ });
+
+ it('should return `true` when milestone due is past current date', done => {
+ const vmPastDue = createComponent(
+ Object.assign({}, mockMilestone, {
+ due_date: '1990-07-22',
+ }),
+ );
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vmPastDue.isMilestonePastDue).toBe(true);
+ })
+ .then(done)
+ .catch(done.fail);
+
+ vmPastDue.$destroy();
+ });
+ });
+
+ describe('milestoneDatesAbsolute', () => {
+ it('returns string containing absolute milestone due date', () => {
+ expect(vm.milestoneDatesAbsolute).toBe('(December 31, 2019)');
+ });
+
+ it('returns string containing absolute milestone start date when due date is not present', done => {
+ const vmDueUndefined = createComponent(
+ Object.assign({}, mockMilestone, {
+ due_date: '',
+ }),
+ );
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vmDueUndefined.milestoneDatesAbsolute).toBe('(January 1, 2018)');
+ })
+ .then(done)
+ .catch(done.fail);
+
+ vmDueUndefined.$destroy();
+ });
+
+ it('returns empty string when both milestone start and due dates are not present', done => {
+ const vmDatesUndefined = createComponent(
+ Object.assign({}, mockMilestone, {
+ start_date: '',
+ due_date: '',
+ }),
+ );
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vmDatesUndefined.milestoneDatesAbsolute).toBe('');
+ })
+ .then(done)
+ .catch(done.fail);
+
+ vmDatesUndefined.$destroy();
+ });
+ });
+
+ describe('milestoneDatesHuman', () => {
+ it('returns string containing milestone due date when date is yet to be due', done => {
+ const vmFuture = createComponent(
+ Object.assign({}, mockMilestone, {
+ due_date: `${new Date().getFullYear() + 10}-01-01`,
+ }),
+ );
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vmFuture.milestoneDatesHuman).toContain('years remaining');
+ })
+ .then(done)
+ .catch(done.fail);
+
+ vmFuture.$destroy();
+ });
+
+ it('returns string containing milestone start date when date has already started and due date is not present', done => {
+ const vmStarted = createComponent(
+ Object.assign({}, mockMilestone, {
+ start_date: '1990-07-22',
+ due_date: '',
+ }),
+ );
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vmStarted.milestoneDatesHuman).toContain('Started');
+ })
+ .then(done)
+ .catch(done.fail);
+
+ vmStarted.$destroy();
+ });
+
+ it('returns string containing milestone start date when date is yet to start and due date is not present', done => {
+ const vmStarts = createComponent(
+ Object.assign({}, mockMilestone, {
+ start_date: `${new Date().getFullYear() + 10}-01-01`,
+ due_date: '',
+ }),
+ );
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vmStarts.milestoneDatesHuman).toContain('Starts');
+ })
+ .then(done)
+ .catch(done.fail);
+
+ vmStarts.$destroy();
+ });
+
+ it('returns empty string when milestone start and due dates are not present', done => {
+ const vmDatesUndefined = createComponent(
+ Object.assign({}, mockMilestone, {
+ start_date: '',
+ due_date: '',
+ }),
+ );
+
+ Vue.nextTick()
+ .then(() => {
+ expect(vmDatesUndefined.milestoneDatesHuman).toBe('');
+ })
+ .then(done)
+ .catch(done.fail);
+
+ vmDatesUndefined.$destroy();
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders component root element with class `issue-milestone-details`', () => {
+ expect(vm.$el.classList.contains('issue-milestone-details')).toBe(true);
+ });
+
+ it('renders milestone icon', () => {
+ expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('clock');
+ });
+
+ it('renders milestone title', () => {
+ expect(vm.$el.querySelector('.milestone-title').innerText.trim()).toBe(mockMilestone.title);
+ });
+
+ it('renders milestone tooltip', () => {
+ expect(vm.$el.querySelector('.js-item-milestone').innerText.trim()).toContain(
+ mockMilestone.title,
+ );
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/markdown/field_spec.js b/spec/javascripts/vue_shared/components/markdown/field_spec.js
index abb17440c0e..79e0e756a7a 100644
--- a/spec/javascripts/vue_shared/components/markdown/field_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/field_spec.js
@@ -80,7 +80,7 @@ describe('Markdown field component', () => {
previewLink.click();
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.md-preview').textContent.trim()).toContain('Loading...');
+ expect(vm.$el.querySelector('.md-preview').textContent.trim()).toContain('Loading…');
done();
});
diff --git a/spec/javascripts/vue_shared/components/markdown/header_spec.js b/spec/javascripts/vue_shared/components/markdown/header_spec.js
index 59613faa49f..e733a95288e 100644
--- a/spec/javascripts/vue_shared/components/markdown/header_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/header_spec.js
@@ -28,6 +28,7 @@ describe('Markdown field header component', () => {
'Add a numbered list',
'Add a task list',
'Add a table',
+ 'Insert suggestion',
'Go full screen',
];
const elements = vm.$el.querySelectorAll('.toolbar-btn');
@@ -93,4 +94,18 @@ describe('Markdown field header component', () => {
'| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |',
);
});
+
+ it('renders suggestion template', () => {
+ vm.lineContent = 'Some content';
+
+ expect(vm.mdSuggestion).toEqual('```suggestion\n{text}\n```');
+ });
+
+ it('does not render suggestion button if `canSuggest` is set to false', () => {
+ vm.canSuggest = false;
+
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.qa-suggestion-btn')).toBe(null);
+ });
+ });
});
diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js
new file mode 100644
index 00000000000..8187b3204b1
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js
@@ -0,0 +1,69 @@
+import Vue from 'vue';
+import SuggestionDiffHeaderComponent from '~/vue_shared/components/markdown/suggestion_diff_header.vue';
+
+const MOCK_DATA = {
+ canApply: true,
+ isApplied: false,
+ helpPagePath: 'path_to_docs',
+};
+
+describe('Suggestion Diff component', () => {
+ let vm;
+
+ function createComponent(propsData) {
+ const Component = Vue.extend(SuggestionDiffHeaderComponent);
+
+ return new Component({
+ propsData,
+ }).$mount();
+ }
+
+ beforeEach(done => {
+ vm = createComponent(MOCK_DATA);
+ Vue.nextTick(done);
+ });
+
+ describe('init', () => {
+ it('renders a suggestion header', () => {
+ const header = vm.$el.querySelector('.qa-suggestion-diff-header');
+
+ expect(header).not.toBeNull();
+ expect(header.innerHTML.includes('Suggested change')).toBe(true);
+ });
+
+ it('renders an apply button', () => {
+ const applyBtn = vm.$el.querySelector('.qa-apply-btn');
+
+ expect(applyBtn).not.toBeNull();
+ expect(applyBtn.innerHTML.includes('Apply suggestion')).toBe(true);
+ });
+
+ it('does not render an apply button if `canApply` is set to false', () => {
+ const props = Object.assign(MOCK_DATA, { canApply: false });
+
+ vm = createComponent(props);
+
+ expect(vm.$el.querySelector('.qa-apply-btn')).toBeNull();
+ });
+ });
+
+ describe('applySuggestion', () => {
+ it('emits when the apply button is clicked', () => {
+ const props = Object.assign(MOCK_DATA, { canApply: true });
+
+ vm = createComponent(props);
+ spyOn(vm, '$emit');
+ vm.applySuggestion();
+
+ expect(vm.$emit).toHaveBeenCalled();
+ });
+
+ it('does not emit when the canApply is set to false', () => {
+ spyOn(vm, '$emit');
+ vm.canApply = false;
+ vm.applySuggestion();
+
+ expect(vm.$emit).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
new file mode 100644
index 00000000000..d4ed8f2f7a4
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
@@ -0,0 +1,79 @@
+import Vue from 'vue';
+import SuggestionDiffComponent from '~/vue_shared/components/markdown/suggestion_diff.vue';
+
+const MOCK_DATA = {
+ canApply: true,
+ newLines: [
+ { content: 'Line 1\n', lineNumber: 1 },
+ { content: 'Line 2\n', lineNumber: 2 },
+ { content: 'Line 3\n', lineNumber: 3 },
+ ],
+ fromLine: 1,
+ fromContent: 'Old content',
+ suggestion: {
+ id: 1,
+ },
+ helpPagePath: 'path_to_docs',
+};
+
+describe('Suggestion Diff component', () => {
+ let vm;
+
+ beforeEach(done => {
+ const Component = Vue.extend(SuggestionDiffComponent);
+
+ vm = new Component({
+ propsData: MOCK_DATA,
+ }).$mount();
+
+ Vue.nextTick(done);
+ });
+
+ describe('init', () => {
+ it('renders a suggestion header', () => {
+ expect(vm.$el.querySelector('.qa-suggestion-diff-header')).not.toBeNull();
+ });
+
+ it('renders a diff table', () => {
+ expect(vm.$el.querySelector('table.md-suggestion-diff')).not.toBeNull();
+ });
+
+ it('renders the oldLineNumber', () => {
+ const fromLine = vm.$el.querySelector('.qa-old-diff-line-number').innerHTML;
+
+ expect(parseInt(fromLine, 10)).toBe(vm.fromLine);
+ });
+
+ it('renders the oldLineContent', () => {
+ const fromContent = vm.$el.querySelector('.line_content.old').innerHTML;
+
+ expect(fromContent.includes(vm.fromContent)).toBe(true);
+ });
+
+ it('renders the contents of newLines', () => {
+ const newLines = vm.$el.querySelectorAll('.line_holder.new');
+
+ newLines.forEach((line, i) => {
+ expect(newLines[i].innerHTML.includes(vm.newLines[i].content)).toBe(true);
+ });
+ });
+
+ it('renders a line number for each line', () => {
+ const newLineNumbers = vm.$el.querySelectorAll('.qa-new-diff-line-number');
+
+ newLineNumbers.forEach((line, i) => {
+ expect(newLineNumbers[i].innerHTML.includes(vm.newLines[i].lineNumber)).toBe(true);
+ });
+ });
+ });
+
+ describe('applySuggestion', () => {
+ it('emits apply event when applySuggestion is called', () => {
+ const callback = () => {};
+ spyOn(vm, '$emit');
+ vm.applySuggestion(callback);
+
+ expect(vm.$emit).toHaveBeenCalledWith('apply', { suggestionId: vm.suggestion.id, callback });
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js
new file mode 100644
index 00000000000..ab1b747c360
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/markdown/suggestions_spec.js
@@ -0,0 +1,125 @@
+import Vue from 'vue';
+import SuggestionsComponent from '~/vue_shared/components/markdown/suggestions.vue';
+
+const MOCK_DATA = {
+ fromLine: 1,
+ fromContent: 'Old content',
+ suggestions: [],
+ noteHtml: `
+ <div class="suggestion">
+ <div class="line">Suggestion 1</div>
+ </div>
+
+ <div class="suggestion">
+ <div class="line">Suggestion 2</div>
+ </div>
+ `,
+ isApplied: false,
+ helpPagePath: 'path_to_docs',
+};
+
+const generateLine = content => {
+ const line = document.createElement('div');
+ line.className = 'line';
+ line.innerHTML = content;
+
+ return line;
+};
+
+const generateMockLines = () => {
+ const line1 = generateLine('Line 1');
+ const line2 = generateLine('Line 2');
+ const line3 = generateLine('Line 3');
+ const container = document.createElement('div');
+
+ container.appendChild(line1);
+ container.appendChild(line2);
+ container.appendChild(line3);
+
+ return container;
+};
+
+describe('Suggestion component', () => {
+ let vm;
+ let extractedLines;
+ let diffTable;
+
+ beforeEach(done => {
+ const Component = Vue.extend(SuggestionsComponent);
+
+ vm = new Component({
+ propsData: MOCK_DATA,
+ }).$mount();
+
+ extractedLines = vm.extractNewLines(generateMockLines());
+ diffTable = vm.generateDiff(extractedLines).$mount().$el;
+
+ spyOn(vm, 'renderSuggestions');
+ vm.renderSuggestions();
+ Vue.nextTick(done);
+ });
+
+ describe('mounted', () => {
+ it('renders a flash container', () => {
+ expect(vm.$el.querySelector('.flash-container')).not.toBeNull();
+ });
+
+ it('renders a container for suggestions', () => {
+ expect(vm.$refs.container).not.toBeNull();
+ });
+
+ it('renders suggestions', () => {
+ expect(vm.renderSuggestions).toHaveBeenCalled();
+ expect(vm.$el.innerHTML.includes('Suggestion 1')).toBe(true);
+ expect(vm.$el.innerHTML.includes('Suggestion 2')).toBe(true);
+ });
+ });
+
+ describe('extractNewLines', () => {
+ it('extracts suggested lines', () => {
+ const expectedReturn = [
+ { content: 'Line 1\n', lineNumber: 1 },
+ { content: 'Line 2\n', lineNumber: 2 },
+ { content: 'Line 3\n', lineNumber: 3 },
+ ];
+
+ expect(vm.extractNewLines(generateMockLines())).toEqual(expectedReturn);
+ });
+
+ it('increments line number for each extracted line', () => {
+ expect(extractedLines[0].lineNumber).toEqual(1);
+ expect(extractedLines[1].lineNumber).toEqual(2);
+ expect(extractedLines[2].lineNumber).toEqual(3);
+ });
+
+ it('returns empty array if no lines are found', () => {
+ const el = document.createElement('div');
+
+ expect(vm.extractNewLines(el)).toEqual([]);
+ });
+ });
+
+ describe('generateDiff', () => {
+ it('generates a diff table', () => {
+ expect(diffTable.querySelector('.md-suggestion-diff')).not.toBeNull();
+ });
+
+ it('generates a diff table that contains contents of `oldLineContent`', () => {
+ expect(diffTable.innerHTML.includes(vm.fromContent)).toBe(true);
+ });
+
+ it('generates a diff table that contains contents the suggested lines', () => {
+ extractedLines.forEach((line, i) => {
+ expect(diffTable.innerHTML.includes(extractedLines[i].content)).toBe(true);
+ });
+ });
+
+ it('generates a diff table with the correct line number for each suggested line', () => {
+ const lines = diffTable.getElementsByClassName('qa-new-diff-line-number');
+
+ expect([...lines][0].innerHTML).toBe('1');
+ expect([...lines][1].innerHTML).toBe('2');
+ expect([...lines][2].innerHTML).toBe('3');
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_image_spec.js b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_image_spec.js
index 5c4aa7cf844..c5045afc5b0 100644
--- a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_image_spec.js
+++ b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_image_spec.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import { placeholderImage } from '~/lazy_loader';
import userAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
import mountComponent, { mountComponentWithSlots } from 'spec/helpers/vue_mount_component_helper';
+import defaultAvatarUrl from '~/../images/no_avatar.png';
const DEFAULT_PROPS = {
size: 99,
@@ -76,6 +77,18 @@ describe('User Avatar Image Component', function() {
});
});
+ describe('Initialization without src', function() {
+ beforeEach(function() {
+ vm = mountComponent(UserAvatarImage);
+ });
+
+ it('should have default avatar image', function() {
+ const imageElement = vm.$el.querySelector('img');
+
+ expect(imageElement.getAttribute('src')).toBe(defaultAvatarUrl);
+ });
+ });
+
describe('dynamic tooltip content', () => {
const props = DEFAULT_PROPS;
const slots = {
diff --git a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js
index 0151ad23ba2..f2472fd377c 100644
--- a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js
+++ b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_link_spec.js
@@ -74,9 +74,7 @@ describe('User Avatar Link Component', function() {
describe('username', function() {
it('should not render avatar image tooltip', function() {
- expect(
- this.userAvatarLink.$el.querySelector('.js-user-avatar-image-toolip').innerText.trim(),
- ).toEqual('');
+ expect(this.userAvatarLink.$el.querySelector('.js-user-avatar-image-toolip')).toBeNull();
});
it('should render username prop in <span>', function() {
diff --git a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js
new file mode 100644
index 00000000000..25b6e3b6bc8
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js
@@ -0,0 +1,150 @@
+import Vue from 'vue';
+import userPopover from '~/vue_shared/components/user_popover/user_popover.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+const DEFAULT_PROPS = {
+ loaded: true,
+ user: {
+ username: 'root',
+ name: 'Administrator',
+ location: 'Vienna',
+ bio: null,
+ organization: null,
+ status: null,
+ },
+};
+
+const UserPopover = Vue.extend(userPopover);
+
+describe('User Popover Component', () => {
+ let vm;
+
+ beforeEach(() => {
+ setFixtures(`
+ <a href="/root" data-user-id="1" class="js-user-link" title="testuser">
+ Root
+ </a>
+ `);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('Empty', () => {
+ beforeEach(() => {
+ vm = mountComponent(UserPopover, {
+ target: document.querySelector('.js-user-link'),
+ user: {
+ name: null,
+ username: null,
+ location: null,
+ bio: null,
+ organization: null,
+ status: null,
+ },
+ });
+ });
+
+ it('should return skeleton loaders', () => {
+ expect(vm.$el.querySelectorAll('.animation-container').length).toBe(4);
+ });
+ });
+
+ describe('basic data', () => {
+ it('should show basic fields', () => {
+ vm = mountComponent(UserPopover, {
+ ...DEFAULT_PROPS,
+ target: document.querySelector('.js-user-link'),
+ });
+
+ expect(vm.$el.textContent).toContain(DEFAULT_PROPS.user.name);
+ expect(vm.$el.textContent).toContain(DEFAULT_PROPS.user.username);
+ expect(vm.$el.textContent).toContain(DEFAULT_PROPS.user.location);
+ });
+ });
+
+ describe('job data', () => {
+ it('should show only bio if no organization is available', () => {
+ const testProps = Object.assign({}, DEFAULT_PROPS);
+ testProps.user.bio = 'Engineer';
+
+ vm = mountComponent(UserPopover, {
+ ...testProps,
+ target: document.querySelector('.js-user-link'),
+ });
+
+ expect(vm.$el.textContent).toContain('Engineer');
+ });
+
+ it('should show only organization if no bio is available', () => {
+ const testProps = Object.assign({}, DEFAULT_PROPS);
+ testProps.user.organization = 'GitLab';
+
+ vm = mountComponent(UserPopover, {
+ ...testProps,
+ target: document.querySelector('.js-user-link'),
+ });
+
+ expect(vm.$el.textContent).toContain('GitLab');
+ });
+
+ it('should display bio and organization in separate lines', () => {
+ const testProps = Object.assign({}, DEFAULT_PROPS);
+ testProps.user.bio = 'Engineer';
+ testProps.user.organization = 'GitLab';
+
+ vm = mountComponent(UserPopover, {
+ ...DEFAULT_PROPS,
+ target: document.querySelector('.js-user-link'),
+ });
+
+ expect(vm.$el.querySelector('.js-bio').textContent).toContain('Engineer');
+ expect(vm.$el.querySelector('.js-organization').textContent).toContain('GitLab');
+ });
+
+ it('should not encode special characters in bio and organization', () => {
+ const testProps = Object.assign({}, DEFAULT_PROPS);
+ testProps.user.bio = 'Manager & Team Lead';
+ testProps.user.organization = 'Me & my <funky> Company';
+
+ vm = mountComponent(UserPopover, {
+ ...DEFAULT_PROPS,
+ target: document.querySelector('.js-user-link'),
+ });
+
+ expect(vm.$el.querySelector('.js-bio').textContent).toContain('Manager & Team Lead');
+ expect(vm.$el.querySelector('.js-organization').textContent).toContain(
+ 'Me & my <funky> Company',
+ );
+ });
+ });
+
+ describe('status data', () => {
+ it('should show only message', () => {
+ const testProps = Object.assign({}, DEFAULT_PROPS);
+ testProps.user.status = { message: 'Hello World' };
+
+ vm = mountComponent(UserPopover, {
+ ...DEFAULT_PROPS,
+ target: document.querySelector('.js-user-link'),
+ });
+
+ expect(vm.$el.textContent).toContain('Hello World');
+ });
+
+ it('should show message and emoji', () => {
+ const testProps = Object.assign({}, DEFAULT_PROPS);
+ testProps.user.status = { emoji: 'basketball_player', message: 'Hello World' };
+
+ vm = mountComponent(UserPopover, {
+ ...DEFAULT_PROPS,
+ target: document.querySelector('.js-user-link'),
+ status: { emoji: 'basketball_player', message: 'Hello World' },
+ });
+
+ expect(vm.$el.textContent).toContain('Hello World');
+ expect(vm.$el.innerHTML).toContain('<gl-emoji data-name="basketball_player"');
+ });
+ });
+});
diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb
index fdeea814bb2..5ace5c5b1a2 100644
--- a/spec/lib/backup/repository_spec.rb
+++ b/spec/lib/backup/repository_spec.rb
@@ -67,6 +67,19 @@ describe Backup::Repository do
end
end
end
+
+ context 'restoring object pools' do
+ it 'schedules restoring of the pool' do
+ pool_repository = create(:pool_repository, :failed)
+ pool_repository.delete_object_pool
+
+ subject.restore
+
+ pool_repository.reload
+ expect(pool_repository).not_to be_failed
+ expect(pool_repository.object_pool.exists?).to be(true)
+ end
+ end
end
describe '#prepare_directories', :seed_helper do
diff --git a/spec/lib/banzai/filter/front_matter_filter_spec.rb b/spec/lib/banzai/filter/front_matter_filter_spec.rb
new file mode 100644
index 00000000000..3071dc7cf21
--- /dev/null
+++ b/spec/lib/banzai/filter/front_matter_filter_spec.rb
@@ -0,0 +1,140 @@
+require 'rails_helper'
+
+describe Banzai::Filter::FrontMatterFilter do
+ include FilterSpecHelper
+
+ it 'allows for `encoding:` before the front matter' do
+ content = <<~MD
+ # encoding: UTF-8
+ ---
+ foo: foo
+ bar: bar
+ ---
+
+ # Header
+
+ Content
+ MD
+
+ output = filter(content)
+
+ expect(output).not_to match 'encoding'
+ end
+
+ it 'converts YAML front matter to a fenced code block' do
+ content = <<~MD
+ ---
+ foo: :foo_symbol
+ bar: :bar_symbol
+ ---
+
+ # Header
+
+ Content
+ MD
+
+ output = filter(content)
+
+ aggregate_failures do
+ expect(output).not_to include '---'
+ expect(output).to include "```yaml\nfoo: :foo_symbol\n"
+ end
+ end
+
+ it 'converts TOML frontmatter to a fenced code block' do
+ content = <<~MD
+ +++
+ foo = :foo_symbol
+ bar = :bar_symbol
+ +++
+
+ # Header
+
+ Content
+ MD
+
+ output = filter(content)
+
+ aggregate_failures do
+ expect(output).not_to include '+++'
+ expect(output).to include "```toml\nfoo = :foo_symbol\n"
+ end
+ end
+
+ it 'converts JSON front matter to a fenced code block' do
+ content = <<~MD
+ ;;;
+ {
+ "foo": ":foo_symbol",
+ "bar": ":bar_symbol"
+ }
+ ;;;
+
+ # Header
+
+ Content
+ MD
+
+ output = filter(content)
+
+ aggregate_failures do
+ expect(output).not_to include ';;;'
+ expect(output).to include "```json\n{\n \"foo\": \":foo_symbol\",\n"
+ end
+ end
+
+ it 'converts arbitrary front matter to a fenced code block' do
+ content = <<~MD
+ ---arbitrary
+ foo = :foo_symbol
+ bar = :bar_symbol
+ ---
+
+ # Header
+
+ Content
+ MD
+
+ output = filter(content)
+
+ aggregate_failures do
+ expect(output).not_to include '---arbitrary'
+ expect(output).to include "```arbitrary\nfoo = :foo_symbol\n"
+ end
+ end
+
+ context 'on content without front matter' do
+ it 'returns the content unmodified' do
+ content = <<~MD
+ # This is some Markdown
+
+ It has no YAML front matter to parse.
+ MD
+
+ expect(filter(content)).to eq content
+ end
+ end
+
+ context 'on front matter without content' do
+ it 'converts YAML front matter to a fenced code block' do
+ content = <<~MD
+ ---
+ foo: :foo_symbol
+ bar: :bar_symbol
+ ---
+ MD
+
+ output = filter(content)
+
+ aggregate_failures do
+ expect(output).to eq <<~MD
+ ```yaml
+ foo: :foo_symbol
+ bar: :bar_symbol
+ ```
+
+ MD
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index 91d4a60ba95..4c94e4fdae0 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -59,7 +59,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do
it 'links with adjacent text' do
doc = reference_filter("Milestone (#{reference}.)")
- expect(doc.to_html).to match(%r(\(<a.+>#{milestone.name}</a>\.\)))
+ expect(doc.to_html).to match(%r(\(<a.+>#{milestone.reference_link_text}</a>\.\)))
end
it 'ignores invalid milestone IIDs' do
@@ -80,12 +80,12 @@ describe Banzai::Filter::MilestoneReferenceFilter do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.milestone_url(milestone)
- expect(doc.text).to eq 'See gfm'
+ expect(doc.text).to eq "See #{milestone.reference_link_text}"
end
it 'links with adjacent text' do
doc = reference_filter("Milestone (#{reference}.)")
- expect(doc.to_html).to match(%r(\(<a.+>#{milestone.name}</a>\.\)))
+ expect(doc.to_html).to match(%r(\(<a.+>#{milestone.reference_link_text}</a>\.\)))
end
it 'ignores invalid milestone names' do
@@ -106,12 +106,12 @@ describe Banzai::Filter::MilestoneReferenceFilter do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.milestone_url(milestone)
- expect(doc.text).to eq 'See gfm references'
+ expect(doc.text).to eq "See #{milestone.reference_link_text}"
end
it 'links with adjacent text' do
doc = reference_filter("Milestone (#{reference}.)")
- expect(doc.to_html).to match(%r(\(<a.+>#{milestone.name}</a>\.\)))
+ expect(doc.to_html).to match(%r(\(<a.+>#{milestone.reference_link_text}</a>\.\)))
end
it 'ignores invalid milestone names' do
@@ -201,14 +201,14 @@ describe Banzai::Filter::MilestoneReferenceFilter do
doc = reference_filter("See (#{reference}.)")
expect(doc.css('a').first.text)
- .to eq("#{milestone.name} in #{another_project.full_path}")
+ .to eq("#{milestone.reference_link_text} in #{another_project.full_path}")
end
it 'has valid text' do
doc = reference_filter("See (#{reference}.)")
expect(doc.text)
- .to eq("See (#{milestone.name} in #{another_project.full_path}.)")
+ .to eq("See (#{milestone.reference_link_text} in #{another_project.full_path}.)")
end
it 'escapes the name attribute' do
@@ -217,7 +217,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.text)
- .to eq "#{milestone.name} in #{another_project.full_path}"
+ .to eq "#{milestone.reference_link_text} in #{another_project.full_path}"
end
end
@@ -238,14 +238,14 @@ describe Banzai::Filter::MilestoneReferenceFilter do
doc = reference_filter("See (#{reference}.)")
expect(doc.css('a').first.text)
- .to eq("#{milestone.name} in #{another_project.path}")
+ .to eq("#{milestone.reference_link_text} in #{another_project.path}")
end
it 'has valid text' do
doc = reference_filter("See (#{reference}.)")
expect(doc.text)
- .to eq("See (#{milestone.name} in #{another_project.path}.)")
+ .to eq("See (#{milestone.reference_link_text} in #{another_project.path}.)")
end
it 'escapes the name attribute' do
@@ -254,7 +254,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.text)
- .to eq "#{milestone.name} in #{another_project.path}"
+ .to eq "#{milestone.reference_link_text} in #{another_project.path}"
end
end
@@ -275,14 +275,14 @@ describe Banzai::Filter::MilestoneReferenceFilter do
doc = reference_filter("See (#{reference}.)")
expect(doc.css('a').first.text)
- .to eq("#{milestone.name} in #{another_project.path}")
+ .to eq("#{milestone.reference_link_text} in #{another_project.path}")
end
it 'has valid text' do
doc = reference_filter("See (#{reference}.)")
expect(doc.text)
- .to eq("See (#{milestone.name} in #{another_project.path}.)")
+ .to eq("See (#{milestone.reference_link_text} in #{another_project.path}.)")
end
it 'escapes the name attribute' do
@@ -291,7 +291,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.text)
- .to eq "#{milestone.name} in #{another_project.path}"
+ .to eq "#{milestone.reference_link_text} in #{another_project.path}"
end
end
@@ -346,26 +346,55 @@ describe Banzai::Filter::MilestoneReferenceFilter do
milestone.update!(group: parent_group)
doc = reference_filter("See #{reference}")
- expect(doc.css('a').first.text).to eq(milestone.name)
+ expect(doc.css('a').first.text).to eq(milestone.reference_link_text)
end
end
context 'group context' do
- let(:context) { { project: nil, group: create(:group) } }
- let(:milestone) { create(:milestone, project: project) }
+ let(:group) { create(:group) }
+ let(:context) { { project: nil, group: group } }
- it 'links to a valid reference' do
- reference = "#{project.full_path}%#{milestone.iid}"
+ context 'when project milestone' do
+ let(:milestone) { create(:milestone, project: project) }
- result = reference_filter("See #{reference}", context)
+ it 'links to a valid reference' do
+ reference = "#{project.full_path}%#{milestone.iid}"
- expect(result.css('a').first.attr('href')).to eq(urls.milestone_url(milestone))
+ result = reference_filter("See #{reference}", context)
+
+ expect(result.css('a').first.attr('href')).to eq(urls.milestone_url(milestone))
+ end
+
+ it 'ignores internal references' do
+ exp = act = "See %#{milestone.iid}"
+
+ expect(reference_filter(act, context).to_html).to eq exp
+ end
end
- it 'ignores internal references' do
- exp = act = "See %#{milestone.iid}"
+ context 'when group milestone' do
+ let(:group_milestone) { create(:milestone, title: 'group_milestone', group: group) }
- expect(reference_filter(act, context).to_html).to eq exp
+ context 'for subgroups', :nested_groups do
+ let(:sub_group) { create(:group, parent: group) }
+ let(:sub_group_milestone) { create(:milestone, title: 'sub_group_milestone', group: sub_group) }
+
+ it 'links to a valid reference of subgroup and group milestones' do
+ [group_milestone, sub_group_milestone].each do |milestone|
+ reference = "%#{milestone.title}"
+
+ result = reference_filter("See #{reference}", { project: nil, group: sub_group })
+
+ expect(result.css('a').first.attr('href')).to eq(urls.milestone_url(milestone))
+ end
+ end
+ end
+
+ it 'ignores internal references' do
+ exp = act = "See %#{group_milestone.iid}"
+
+ expect(reference_filter(act, context).to_html).to eq exp
+ end
end
end
diff --git a/spec/lib/banzai/filter/suggestion_filter_spec.rb b/spec/lib/banzai/filter/suggestion_filter_spec.rb
new file mode 100644
index 00000000000..b13c90b54bd
--- /dev/null
+++ b/spec/lib/banzai/filter/suggestion_filter_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::Filter::SuggestionFilter do
+ include FilterSpecHelper
+
+ let(:input) { "<pre class='code highlight js-syntax-highlight suggestion'><code>foo\n</code></pre>" }
+ let(:default_context) do
+ { suggestions_filter_enabled: true }
+ end
+
+ it 'includes `js-render-suggestion` class' do
+ doc = filter(input, default_context)
+ result = doc.css('code').first
+
+ expect(result[:class]).to include('js-render-suggestion')
+ end
+
+ it 'includes no `js-render-suggestion` when filter is disabled' do
+ doc = filter(input)
+ result = doc.css('code').first
+
+ expect(result[:class]).to be_nil
+ end
+end
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 334d29a5368..1e8a44b4549 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -120,7 +120,7 @@ describe Banzai::Filter::UserReferenceFilter do
it 'includes default classes' do
doc = reference_filter("Hey #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member has-tooltip'
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member'
end
context 'when a project is not specified' do
diff --git a/spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb b/spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb
deleted file mode 100644
index 9f1b862ef19..00000000000
--- a/spec/lib/banzai/filter/yaml_front_matter_filter_spec.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-require 'rails_helper'
-
-describe Banzai::Filter::YamlFrontMatterFilter do
- include FilterSpecHelper
-
- it 'allows for `encoding:` before the frontmatter' do
- content = <<-MD.strip_heredoc
- # encoding: UTF-8
- ---
- foo: foo
- ---
-
- # Header
-
- Content
- MD
-
- output = filter(content)
-
- expect(output).not_to match 'encoding'
- end
-
- it 'converts YAML frontmatter to a fenced code block' do
- content = <<-MD.strip_heredoc
- ---
- bar: :bar_symbol
- ---
-
- # Header
-
- Content
- MD
-
- output = filter(content)
-
- aggregate_failures do
- expect(output).not_to include '---'
- expect(output).to include "```yaml\nbar: :bar_symbol\n```"
- end
- end
-
- context 'on content without frontmatter' do
- it 'returns the content unmodified' do
- content = <<-MD.strip_heredoc
- # This is some Markdown
-
- It has no YAML frontmatter to parse.
- MD
-
- expect(filter(content)).to eq content
- end
- end
-end
diff --git a/spec/lib/banzai/suggestions_parser_spec.rb b/spec/lib/banzai/suggestions_parser_spec.rb
new file mode 100644
index 00000000000..79658d710ce
--- /dev/null
+++ b/spec/lib/banzai/suggestions_parser_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::SuggestionsParser do
+ describe '.parse' do
+ it 'returns a list of suggestion contents' do
+ markdown = <<-MARKDOWN.strip_heredoc
+ ```suggestion
+ foo
+ bar
+ ```
+
+ ```
+ nothing
+ ```
+
+ ```suggestion
+ xpto
+ baz
+ ```
+
+ ```thing
+ this is not a suggestion, it's a thing
+ ```
+ MARKDOWN
+
+ expect(described_class.parse(markdown)).to eq([" foo\n bar",
+ " xpto\n baz"])
+ end
+ end
+end
diff --git a/spec/lib/constraints/feature_constrainer_spec.rb b/spec/lib/constraints/feature_constrainer_spec.rb
new file mode 100644
index 00000000000..42efc164f81
--- /dev/null
+++ b/spec/lib/constraints/feature_constrainer_spec.rb
@@ -0,0 +1,11 @@
+require 'spec_helper'
+
+describe Constraints::FeatureConstrainer do
+ describe '#matches' do
+ it 'calls Feature.enabled? with the correct arguments' do
+ expect(Feature).to receive(:enabled?).with(:feature_name, "an object", default_enabled: true)
+
+ described_class.new(:feature_name, "an object", default_enabled: true).matches?(double('request'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb
new file mode 100644
index 00000000000..e802613490b
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_hashed_project_repositories_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::BackfillHashedProjectRepositories, :migration, schema: 20181130102132 do
+ it_behaves_like 'backfill migration for project repositories', :hashed
+end
diff --git a/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb
new file mode 100644
index 00000000000..ae4b53d62e6
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_legacy_project_repositories_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::BackfillLegacyProjectRepositories, :migration, schema: 20181218192239 do
+ it_behaves_like 'backfill migration for project repositories', :legacy
+end
diff --git a/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
new file mode 100644
index 00000000000..53c071f0268
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
+ let(:group) { create(:group, name: 'foo', path: 'foo') }
+
+ describe described_class::ShardFinder do
+ let(:shard) { create(:shard, name: 'default') }
+
+ describe '#find_shard_id' do
+ it 'creates a new shard when it does not exist yet' do
+ expect { subject.find_shard_id('other') }.to change(Shard, :count).by(1)
+ end
+
+ it 'returns the shard when it exists' do
+ other_shard = create(:shard, name: 'other')
+
+ shard_id = subject.find_shard_id('other')
+
+ expect(shard_id).to eq(other_shard.id)
+ end
+
+ it 'only queries the database once to retrieve shards' do
+ subject.find_shard_id('default')
+
+ expect { subject.find_shard_id('default') }.not_to exceed_query_limit(0)
+ end
+ end
+ end
+
+ describe described_class::Project do
+ let!(:project_hashed_storage_1) { create(:project, name: 'foo', path: 'foo', namespace: group, storage_version: 1) }
+ let!(:project_hashed_storage_2) { create(:project, name: 'bar', path: 'bar', namespace: group, storage_version: 2) }
+ let!(:project_legacy_storage_3) { create(:project, name: 'baz', path: 'baz', namespace: group, storage_version: 0) }
+ let!(:project_legacy_storage_4) { create(:project, name: 'zoo', path: 'zoo', namespace: group, storage_version: nil) }
+
+ describe '.on_hashed_storage' do
+ it 'finds projects with repository on hashed storage' do
+ projects = described_class.on_hashed_storage.pluck(:id)
+
+ expect(projects).to match_array([project_hashed_storage_1.id, project_hashed_storage_2.id])
+ end
+ end
+
+ describe '.on_legacy_storage' do
+ it 'finds projects with repository on legacy storage' do
+ projects = described_class.on_legacy_storage.pluck(:id)
+
+ expect(projects).to match_array([project_legacy_storage_3.id, project_legacy_storage_4.id])
+ end
+ end
+
+ describe '.without_project_repository' do
+ it 'finds projects which do not have a projects_repositories entry' do
+ create(:project_repository, project: project_hashed_storage_1)
+ create(:project_repository, project: project_legacy_storage_3)
+
+ projects = described_class.without_project_repository.pluck(:id)
+
+ expect(projects).to contain_exactly(project_hashed_storage_2.id, project_legacy_storage_4.id)
+ end
+ end
+
+ describe '#disk_path' do
+ context 'for projects on hashed storage' do
+ it 'returns the correct disk_path' do
+ project = described_class.find(project_hashed_storage_1.id)
+
+ expect(project.disk_path).to eq(project_hashed_storage_1.disk_path)
+ end
+ end
+
+ context 'for projects on legacy storage' do
+ it 'returns the correct disk_path' do
+ project = described_class.find(project_legacy_storage_3.id)
+
+ expect(project.disk_path).to eq(project_legacy_storage_3.disk_path)
+ end
+
+ it 'raises OrphanedNamespaceError when any parent namespace does not exist' do
+ subgroup = create(:group, parent: group)
+ project_orphaned_namespace = create(:project, name: 'baz', path: 'baz', namespace: subgroup, storage_version: nil)
+ subgroup.update_column(:parent_id, Namespace.maximum(:id).succ)
+
+ project = described_class.find(project_orphaned_namespace.id)
+
+ expect { project.disk_path }
+ .to raise_error(Gitlab::BackgroundMigration::BackfillProjectRepositories::OrphanedNamespaceError)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
index 5ce84c61042..7c7e58d6bb7 100644
--- a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
@@ -6,8 +6,18 @@ describe Gitlab::BackgroundMigration::MigrateBuildStage, :migration, schema: 201
let(:stages) { table(:ci_stages) }
let(:jobs) { table(:ci_builds) }
- STATUSES = { created: 0, pending: 1, running: 2, success: 3,
- failed: 4, canceled: 5, skipped: 6, manual: 7 }.freeze
+ let(:statuses) do
+ {
+ created: 0,
+ pending: 1,
+ running: 2,
+ success: 3,
+ failed: 4,
+ canceled: 5,
+ skipped: 6,
+ manual: 7
+ }
+ end
before do
projects.create!(id: 123, name: 'gitlab', path: 'gitlab-ce')
@@ -36,9 +46,9 @@ describe Gitlab::BackgroundMigration::MigrateBuildStage, :migration, schema: 201
expect(stages.all.pluck(:name)).to match_array %w[test build deploy]
expect(jobs.where(stage_id: nil)).to be_one
expect(jobs.find_by(stage_id: nil).id).to eq 6
- expect(stages.all.pluck(:status)).to match_array [STATUSES[:success],
- STATUSES[:failed],
- STATUSES[:pending]]
+ expect(stages.all.pluck(:status)).to match_array [statuses[:success],
+ statuses[:failed],
+ statuses[:pending]]
end
it 'recovers from unique constraint violation only twice' do
diff --git a/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb b/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb
index 878158910be..89b56906ed0 100644
--- a/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20170711145320 do
@@ -6,8 +8,18 @@ describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20
let(:stages) { table(:ci_stages) }
let(:jobs) { table(:ci_builds) }
- STATUSES = { created: 0, pending: 1, running: 2, success: 3,
- failed: 4, canceled: 5, skipped: 6, manual: 7 }.freeze
+ let(:statuses) do
+ {
+ created: 0,
+ pending: 1,
+ running: 2,
+ success: 3,
+ failed: 4,
+ canceled: 5,
+ skipped: 6,
+ manual: 7
+ }
+ end
before do
projects.create!(id: 1, name: 'gitlab1', path: 'gitlab1')
@@ -26,8 +38,8 @@ describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20
it 'sets a correct stage status' do
described_class.new.perform(1, 2)
- expect(stages.first.status).to eq STATUSES[:running]
- expect(stages.second.status).to eq STATUSES[:failed]
+ expect(stages.first.status).to eq statuses[:running]
+ expect(stages.second.status).to eq statuses[:failed]
end
end
@@ -35,8 +47,8 @@ describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20
it 'sets a skipped stage status' do
described_class.new.perform(1, 2)
- expect(stages.first.status).to eq STATUSES[:skipped]
- expect(stages.second.status).to eq STATUSES[:skipped]
+ expect(stages.first.status).to eq statuses[:skipped]
+ expect(stages.second.status).to eq statuses[:skipped]
end
end
@@ -50,8 +62,8 @@ describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20
it 'sets a correct stage status' do
described_class.new.perform(1, 2)
- expect(stages.first.status).to eq STATUSES[:canceled]
- expect(stages.second.status).to eq STATUSES[:success]
+ expect(stages.first.status).to eq statuses[:canceled]
+ expect(stages.second.status).to eq statuses[:success]
end
end
@@ -65,8 +77,8 @@ describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20
it 'sets a correct stage status' do
described_class.new.perform(1, 2)
- expect(stages.first.status).to eq STATUSES[:manual]
- expect(stages.second.status).to eq STATUSES[:success]
+ expect(stages.first.status).to eq statuses[:manual]
+ expect(stages.second.status).to eq statuses[:success]
end
end
diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_improved_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_improved_spec.rb
new file mode 100644
index 00000000000..d1d64574627
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_improved_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsDataImproved, :migration, schema: 20181204154019 do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:users) { table(:users) }
+ let(:events) { table(:events) }
+
+ let(:user) { users.create!(email: 'test@example.com', projects_limit: 100, username: 'test') }
+
+ let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') }
+ let(:merge_requests) { table(:merge_requests) }
+
+ def create_merge_request(id, params = {})
+ params.merge!(id: id,
+ target_project_id: project.id,
+ target_branch: 'master',
+ source_project_id: project.id,
+ source_branch: 'mr name',
+ title: "mr name#{id}")
+
+ merge_requests.create(params)
+ end
+
+ def create_merge_request_event(id, params = {})
+ params.merge!(id: id,
+ project_id: project.id,
+ author_id: user.id,
+ target_type: 'MergeRequest')
+
+ events.create(params)
+ end
+
+ describe '#perform' do
+ it 'creates and updates closed and merged events' do
+ timestamp = Time.new('2018-01-01 12:00:00').utc
+
+ create_merge_request(1)
+ create_merge_request_event(1, target_id: 1, action: 3, updated_at: timestamp)
+ create_merge_request_event(2, target_id: 1, action: 3, updated_at: timestamp + 10.seconds)
+
+ create_merge_request_event(3, target_id: 1, action: 7, updated_at: timestamp)
+ create_merge_request_event(4, target_id: 1, action: 7, updated_at: timestamp + 10.seconds)
+
+ subject.perform(1, 1)
+
+ merge_request = MergeRequest.first
+
+ expect(merge_request.metrics).to have_attributes(latest_closed_by_id: user.id,
+ latest_closed_at: timestamp + 10.seconds,
+ merged_by_id: user.id)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
index 3c63e601abc..f4759b69538 100644
--- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
@@ -192,7 +192,7 @@ describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
let(:project_path) { 'a-group/a-sub-group/a-project' }
before do
- expect(Group).to receive(:supports_nested_groups?) { false }
+ expect(Group).to receive(:supports_nested_objects?) { false }
end
describe '#create_project_if_needed' do
diff --git a/spec/lib/gitlab/branch_push_merge_commit_analyzer_spec.rb b/spec/lib/gitlab/branch_push_merge_commit_analyzer_spec.rb
new file mode 100644
index 00000000000..1e969542975
--- /dev/null
+++ b/spec/lib/gitlab/branch_push_merge_commit_analyzer_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BranchPushMergeCommitAnalyzer do
+ let(:project) { create(:project, :repository) }
+ let(:oldrev) { 'merge-commit-analyze-before' }
+ let(:newrev) { 'merge-commit-analyze-after' }
+ let(:commits) { project.repository.commits_between(oldrev, newrev).reverse }
+
+ subject { described_class.new(commits) }
+
+ describe '#get_merge_commit' do
+ let(:expected_merge_commits) do
+ {
+ '646ece5cfed840eca0a4feb21bcd6a81bb19bda3' => '646ece5cfed840eca0a4feb21bcd6a81bb19bda3',
+ '29284d9bcc350bcae005872d0be6edd016e2efb5' => '29284d9bcc350bcae005872d0be6edd016e2efb5',
+ '5f82584f0a907f3b30cfce5bb8df371454a90051' => '29284d9bcc350bcae005872d0be6edd016e2efb5',
+ '8a994512e8c8f0dfcf22bb16df6e876be7a61036' => '29284d9bcc350bcae005872d0be6edd016e2efb5',
+ '689600b91aabec706e657e38ea706ece1ee8268f' => '29284d9bcc350bcae005872d0be6edd016e2efb5',
+ 'db46a1c5a5e474aa169b6cdb7a522d891bc4c5f9' => 'db46a1c5a5e474aa169b6cdb7a522d891bc4c5f9'
+ }
+ end
+
+ it 'returns correct merge commit SHA for each commit' do
+ expected_merge_commits.each do |commit, merge_commit|
+ expect(subject.get_merge_commit(commit)).to eq(merge_commit)
+ end
+ end
+
+ context 'when one parent has two children' do
+ let(:oldrev) { '1adbdefe31288f3bbe4b614853de4908a0b6f792' }
+ let(:newrev) { '5f82584f0a907f3b30cfce5bb8df371454a90051' }
+
+ let(:expected_merge_commits) do
+ {
+ '5f82584f0a907f3b30cfce5bb8df371454a90051' => '5f82584f0a907f3b30cfce5bb8df371454a90051',
+ '8a994512e8c8f0dfcf22bb16df6e876be7a61036' => '5f82584f0a907f3b30cfce5bb8df371454a90051',
+ '689600b91aabec706e657e38ea706ece1ee8268f' => '689600b91aabec706e657e38ea706ece1ee8268f'
+ }
+ end
+
+ it 'returns correct merge commit SHA for each commit' do
+ expected_merge_commits.each do |commit, merge_commit|
+ expect(subject.get_merge_commit(commit)).to eq(merge_commit)
+ end
+ end
+ end
+
+ context 'when relevant_commit_ids is provided' do
+ let(:relevant_commit_id) { '8a994512e8c8f0dfcf22bb16df6e876be7a61036' }
+ subject { described_class.new(commits, relevant_commit_ids: [relevant_commit_id]) }
+
+ it 'returns correct merge commit' do
+ expected_merge_commits.each do |commit, merge_commit|
+ subject = described_class.new(commits, relevant_commit_ids: [commit])
+ expect(subject.get_merge_commit(commit)).to eq(merge_commit)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/except_policy_spec.rb b/spec/lib/gitlab/ci/config/entry/except_policy_spec.rb
deleted file mode 100644
index d036bf2f4d1..00000000000
--- a/spec/lib/gitlab/ci/config/entry/except_policy_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::Ci::Config::Entry::ExceptPolicy do
- let(:entry) { described_class.new(config) }
-
- it_behaves_like 'correct only except policy'
-
- describe '.default' do
- it 'does not have a default value' do
- expect(described_class.default).to be_nil
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb
index 12f4b9dc624..61d78f86b51 100644
--- a/spec/lib/gitlab/ci/config/entry/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb
@@ -161,7 +161,8 @@ describe Gitlab::Ci::Config::Entry::Global do
variables: { 'VAR' => 'value' },
ignore: false,
after_script: ['make clean'],
- only: { refs: %w[branches tags] } },
+ only: { refs: %w[branches tags] },
+ except: {} },
spinach: { name: :spinach,
before_script: [],
script: %w[spinach],
@@ -173,7 +174,8 @@ describe Gitlab::Ci::Config::Entry::Global do
variables: {},
ignore: false,
after_script: ['make clean'],
- only: { refs: %w[branches tags] } }
+ only: { refs: %w[branches tags] },
+ except: {} }
)
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index c1f4a060063..8e32cede3b5 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -259,7 +259,8 @@ describe Gitlab::Ci::Config::Entry::Job do
stage: 'test',
ignore: false,
after_script: %w[cleanup],
- only: { refs: %w[branches tags] })
+ only: { refs: %w[branches tags] },
+ except: {})
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
index 2a753408f54..1a2c30d3571 100644
--- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
@@ -68,13 +68,15 @@ describe Gitlab::Ci::Config::Entry::Jobs do
commands: 'rspec',
ignore: false,
stage: 'test',
- only: { refs: %w[branches tags] } },
+ only: { refs: %w[branches tags] },
+ except: {} },
spinach: { name: :spinach,
script: %w[spinach],
commands: 'spinach',
ignore: false,
stage: 'test',
- only: { refs: %w[branches tags] } })
+ only: { refs: %w[branches tags] },
+ except: {} })
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/only_policy_spec.rb b/spec/lib/gitlab/ci/config/entry/only_policy_spec.rb
deleted file mode 100644
index 5518b68e51a..00000000000
--- a/spec/lib/gitlab/ci/config/entry/only_policy_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::Ci::Config::Entry::OnlyPolicy do
- let(:entry) { described_class.new(config) }
-
- it_behaves_like 'correct only except policy'
-
- describe '.default' do
- it 'haa a default value' do
- expect(described_class.default).to eq( { refs: %w[branches tags] } )
- end
- end
-end
diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
index cf40a22af2e..83001b7fdd8 100644
--- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
@@ -1,8 +1,173 @@
-require 'spec_helper'
+require 'fast_spec_helper'
+require_dependency 'active_model'
describe Gitlab::Ci::Config::Entry::Policy do
let(:entry) { described_class.new(config) }
+ context 'when using simplified policy' do
+ describe 'validations' do
+ context 'when entry config value is valid' do
+ context 'when config is a branch or tag name' do
+ let(:config) { %w[master feature/branch] }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+
+ describe '#value' do
+ it 'returns refs hash' do
+ expect(entry.value).to eq(refs: config)
+ end
+ end
+ end
+
+ context 'when config is a regexp' do
+ let(:config) { ['/^issue-.*$/'] }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'when config is a special keyword' do
+ let(:config) { %w[tags triggers branches] }
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+ end
+
+ context 'when entry value is not valid' do
+ let(:config) { [1] }
+
+ describe '#errors' do
+ it 'saves errors' do
+ expect(entry.errors)
+ .to include /policy config should be an array of strings or regexps/
+ end
+ end
+ end
+ end
+ end
+
+ context 'when using complex policy' do
+ context 'when specifying refs policy' do
+ let(:config) { { refs: ['master'] } }
+
+ it 'is a correct configuraton' do
+ expect(entry).to be_valid
+ expect(entry.value).to eq(refs: %w[master])
+ end
+ end
+
+ context 'when specifying kubernetes policy' do
+ let(:config) { { kubernetes: 'active' } }
+
+ it 'is a correct configuraton' do
+ expect(entry).to be_valid
+ expect(entry.value).to eq(kubernetes: 'active')
+ end
+ end
+
+ context 'when specifying invalid kubernetes policy' do
+ let(:config) { { kubernetes: 'something' } }
+
+ it 'reports an error about invalid policy' do
+ expect(entry.errors).to include /unknown value: something/
+ end
+ end
+
+ context 'when specifying valid variables expressions policy' do
+ let(:config) { { variables: ['$VAR == null'] } }
+
+ it 'is a correct configuraton' do
+ expect(entry).to be_valid
+ expect(entry.value).to eq(config)
+ end
+ end
+
+ context 'when specifying variables expressions in invalid format' do
+ let(:config) { { variables: '$MY_VAR' } }
+
+ it 'reports an error about invalid format' do
+ expect(entry.errors).to include /should be an array of strings/
+ end
+ end
+
+ context 'when specifying invalid variables expressions statement' do
+ let(:config) { { variables: ['$MY_VAR =='] } }
+
+ it 'reports an error about invalid statement' do
+ expect(entry.errors).to include /invalid expression syntax/
+ end
+ end
+
+ context 'when specifying invalid variables expressions token' do
+ let(:config) { { variables: ['$MY_VAR == 123'] } }
+
+ it 'reports an error about invalid expression' do
+ expect(entry.errors).to include /invalid expression syntax/
+ end
+ end
+
+ context 'when using invalid variables expressions regexp' do
+ let(:config) { { variables: ['$MY_VAR =~ /some ( thing/'] } }
+
+ it 'reports an error about invalid expression' do
+ expect(entry.errors).to include /invalid expression syntax/
+ end
+ end
+
+ context 'when specifying a valid changes policy' do
+ let(:config) { { changes: %w[some/* paths/**/*.rb] } }
+
+ it 'is a correct configuraton' do
+ expect(entry).to be_valid
+ expect(entry.value).to eq(config)
+ end
+ end
+
+ context 'when changes policy is invalid' do
+ let(:config) { { changes: [1, 2] } }
+
+ it 'returns errors' do
+ expect(entry.errors).to include /changes should be an array of strings/
+ end
+ end
+
+ context 'when specifying unknown policy' do
+ let(:config) { { refs: ['master'], invalid: :something } }
+
+ it 'returns error about invalid key' do
+ expect(entry.errors).to include /unknown keys: invalid/
+ end
+ end
+
+ context 'when policy is empty' do
+ let(:config) { {} }
+
+ it 'is not a valid configuration' do
+ expect(entry.errors).to include /can't be blank/
+ end
+ end
+ end
+
+ context 'when policy strategy does not match' do
+ let(:config) { 'string strategy' }
+
+ it 'returns information about errors' do
+ expect(entry.errors)
+ .to include /has to be either an array of conditions or a hash/
+ end
+ end
+
describe '.default' do
it 'does not have a default value' do
expect(described_class.default).to be_nil
diff --git a/spec/lib/gitlab/ci/parsers/test_spec.rb b/spec/lib/gitlab/ci/parsers_spec.rb
index 0b85b432677..4b647bffe59 100644
--- a/spec/lib/gitlab/ci/parsers/test_spec.rb
+++ b/spec/lib/gitlab/ci/parsers_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe Gitlab::Ci::Parsers::Test do
+describe Gitlab::Ci::Parsers do
describe '.fabricate!' do
subject { described_class.fabricate!(file_type) }
@@ -8,7 +10,7 @@ describe Gitlab::Ci::Parsers::Test do
let(:file_type) { 'junit' }
it 'fabricates the class' do
- is_expected.to be_a(described_class::Junit)
+ is_expected.to be_a(described_class::Test::Junit)
end
end
@@ -16,7 +18,7 @@ describe Gitlab::Ci::Parsers::Test do
let(:file_type) { 'undefined' }
it 'raises an error' do
- expect { subject }.to raise_error(Gitlab::Ci::Parsers::Test::ParserNotFoundError)
+ expect { subject }.to raise_error(Gitlab::Ci::Parsers::ParserNotFoundError)
end
end
end
diff --git a/spec/lib/gitlab/correlation_id_spec.rb b/spec/lib/gitlab/correlation_id_spec.rb
new file mode 100644
index 00000000000..584d1f48386
--- /dev/null
+++ b/spec/lib/gitlab/correlation_id_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::CorrelationId do
+ describe '.use_id' do
+ it 'yields when executed' do
+ expect { |blk| described_class.use_id('id', &blk) }.to yield_control
+ end
+
+ it 'stacks correlation ids' do
+ described_class.use_id('id1') do
+ described_class.use_id('id2') do |current_id|
+ expect(current_id).to eq('id2')
+ end
+ end
+ end
+
+ it 'for missing correlation id it generates random one' do
+ described_class.use_id('id1') do
+ described_class.use_id(nil) do |current_id|
+ expect(current_id).not_to be_empty
+ expect(current_id).not_to eq('id1')
+ end
+ end
+ end
+ end
+
+ describe '.current_id' do
+ subject { described_class.current_id }
+
+ it 'returns last correlation id' do
+ described_class.use_id('id1') do
+ described_class.use_id('id2') do
+ is_expected.to eq('id2')
+ end
+ end
+ end
+ end
+
+ describe '.current_or_new_id' do
+ subject { described_class.current_or_new_id }
+
+ context 'when correlation id is set' do
+ it 'returns last correlation id' do
+ described_class.use_id('id1') do
+ is_expected.to eq('id1')
+ end
+ end
+ end
+
+ context 'when correlation id is missing' do
+ it 'returns a new correlation id' do
+ expect(described_class).to receive(:new_id)
+ .and_call_original
+
+ is_expected.not_to be_empty
+ end
+ end
+ end
+
+ describe '.ids' do
+ subject { described_class.send(:ids) }
+
+ it 'returns empty list if not correlation is used' do
+ is_expected.to be_empty
+ end
+
+ it 'returns list if correlation ids are used' do
+ described_class.use_id('id1') do
+ described_class.use_id('id2') do
+ is_expected.to eq(%w(id1 id2))
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index 55490f37ac7..caf9fc5442c 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -54,7 +54,7 @@ describe Gitlab::CurrentSettings do
expect(ApplicationSetting).not_to receive(:current)
end
- it 'returns an in-memory ApplicationSetting object' do
+ it 'returns a FakeApplicationSettings object' do
expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings)
end
@@ -91,6 +91,14 @@ describe Gitlab::CurrentSettings do
allow(ActiveRecord::Base.connection).to receive(:cached_table_exists?).with('application_settings').and_return(true)
end
+ context 'with RequestStore enabled', :request_store do
+ it 'fetches the settings from DB only once' do
+ described_class.current_application_settings # warm the cache
+
+ expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).to eq(0)
+ end
+ end
+
it 'creates default ApplicationSettings if none are present' do
settings = described_class.current_application_settings
@@ -99,34 +107,45 @@ describe Gitlab::CurrentSettings do
expect(settings).to have_attributes(settings_from_defaults)
end
- context 'with migrations pending' do
+ context 'with pending migrations' do
before do
expect(ActiveRecord::Migrator).to receive(:needs_migration?).and_return(true)
end
- it 'returns an in-memory ApplicationSetting object' do
- settings = described_class.current_application_settings
+ shared_examples 'a non-persisted ApplicationSetting object' do
+ let(:current_settings) { described_class.current_application_settings }
+
+ it 'returns a non-persisted ApplicationSetting object' do
+ expect(current_settings).to be_a(ApplicationSetting)
+ expect(current_settings).not_to be_persisted
+ end
+
+ it 'uses the default value from ApplicationSetting.defaults' do
+ expect(current_settings.signup_enabled).to eq(ApplicationSetting.defaults[:signup_enabled])
+ end
+
+ it 'uses the default value from custom ApplicationSetting accessors' do
+ expect(current_settings.commit_email_hostname).to eq(ApplicationSetting.default_commit_email_hostname)
+ end
+
+ it 'responds to predicate methods' do
+ expect(current_settings.signup_enabled?).to eq(current_settings.signup_enabled)
+ end
+ end
- expect(settings).to be_a(Gitlab::FakeApplicationSettings)
- expect(settings.sign_in_enabled?).to eq(settings.sign_in_enabled)
- expect(settings.sign_up_enabled?).to eq(settings.sign_up_enabled)
+ context 'with no ApplicationSetting DB record' do
+ it_behaves_like 'a non-persisted ApplicationSetting object'
end
- it 'uses the existing database settings and falls back to defaults' do
- db_settings = create(:application_setting,
- home_page_url: 'http://mydomain.com',
- signup_enabled: false)
- settings = described_class.current_application_settings
- app_defaults = ApplicationSetting.last
-
- expect(settings).to be_a(Gitlab::FakeApplicationSettings)
- expect(settings.home_page_url).to eq(db_settings.home_page_url)
- expect(settings.signup_enabled?).to be_falsey
- expect(settings.signup_enabled).to be_falsey
-
- # Check that unspecified values use the defaults
- settings.reject! { |key, _| [:home_page_url, :signup_enabled].include? key }
- settings.each { |key, _| expect(settings[key]).to eq(app_defaults[key]) }
+ context 'with an existing ApplicationSetting DB record' do
+ let!(:db_settings) { ApplicationSetting.build_from_defaults(home_page_url: 'http://mydomain.com').save! && ApplicationSetting.last }
+ let(:current_settings) { described_class.current_application_settings }
+
+ it_behaves_like 'a non-persisted ApplicationSetting object'
+
+ it 'uses the value from the DB attribute if present and not overriden by an accessor' do
+ expect(current_settings.home_page_url).to eq(db_settings.home_page_url)
+ end
end
end
@@ -138,17 +157,12 @@ describe Gitlab::CurrentSettings do
end
end
- context 'when the application_settings table does not exists' do
- it 'returns an in-memory ApplicationSetting object' do
- expect(ApplicationSetting).to receive(:create_from_defaults).and_raise(ActiveRecord::StatementInvalid)
-
- expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings)
- end
- end
-
- context 'when the application_settings table is not fully migrated' do
- it 'returns an in-memory ApplicationSetting object' do
- expect(ApplicationSetting).to receive(:create_from_defaults).and_raise(ActiveRecord::UnknownAttributeError)
+ context 'when the application_settings table does not exist' do
+ it 'returns a FakeApplicationSettings object' do
+ expect(Gitlab::Database)
+ .to receive(:cached_table_exists?)
+ .with('application_settings')
+ .and_return(false)
expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings)
end
diff --git a/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb b/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb
index f518bb3dc3e..3991c737a26 100644
--- a/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb
@@ -16,6 +16,12 @@ describe Gitlab::Database::Count::ExactCountStrategy do
expect(subject).to eq({ Project => 3, Identity => 1 })
end
+
+ it 'returns default value if count times out' do
+ allow(models.first).to receive(:count).and_raise(ActiveRecord::StatementInvalid.new(''))
+
+ expect(subject).to eq({})
+ end
end
describe '.enabled?' do
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 23f27939dd2..4e83b27e4a5 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -1338,12 +1338,7 @@ describe Gitlab::Database::MigrationHelpers do
end
describe '#index_exists_by_name?' do
- # TODO: remove rails5-only after removing rails4 tests
- # rails 4 can not handle multiple indexes on the same column set if
- # index was added by 't.index' - t.index is used by default in schema.rb in
- # rails 5. Let's run this test only in rails 5 env:
- # see https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21492#note_113602758
- it 'returns true if an index exists', :rails5 do
+ it 'returns true if an index exists' do
expect(model.index_exists_by_name?(:projects, 'index_projects_on_path'))
.to be_truthy
end
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index fc295b2deff..60106ee3c0b 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -400,13 +400,8 @@ describe Gitlab::Database do
describe '.cached_table_exists?' do
it 'only retrieves data once per table' do
- if Gitlab.rails5?
- expect(ActiveRecord::Base.connection).to receive(:data_source_exists?).with(:projects).once.and_call_original
- expect(ActiveRecord::Base.connection).to receive(:data_source_exists?).with(:bogus_table_name).once.and_call_original
- else
- expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:projects).once.and_call_original
- expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:bogus_table_name).once.and_call_original
- end
+ expect(ActiveRecord::Base.connection).to receive(:data_source_exists?).with(:projects).once.and_call_original
+ expect(ActiveRecord::Base.connection).to receive(:data_source_exists?).with(:bogus_table_name).once.and_call_original
2.times do
expect(described_class.cached_table_exists?(:projects)).to be_truthy
@@ -462,8 +457,7 @@ describe Gitlab::Database do
expect(described_class.db_read_only?).to be_truthy
end
- # TODO: remove rails5-only tag after removing rails4 tests
- it 'detects a read only database', :rails5 do
+ it 'detects a read only database' do
allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => true }])
expect(described_class.db_read_only?).to be_truthy
@@ -475,8 +469,7 @@ describe Gitlab::Database do
expect(described_class.db_read_only?).to be_falsey
end
- # TODO: remove rails5-only tag after removing rails4 tests
- it 'detects a read write database', :rails5 do
+ it 'detects a read write database' do
allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => false }])
expect(described_class.db_read_only?).to be_falsey
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index 3417896e259..b15d22c634a 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -583,6 +583,12 @@ describe Gitlab::Diff::File do
end
end
+ describe '#empty?' do
+ it 'returns true' do
+ expect(diff_file.empty?).to be_truthy
+ end
+ end
+
describe '#different_type?' do
it 'returns false' do
expect(diff_file).not_to be_different_type
@@ -662,4 +668,87 @@ describe Gitlab::Diff::File do
end
end
end
+
+ describe '#empty?' do
+ let(:project) do
+ create(:project, :custom_repo, files: {})
+ end
+ let(:branch_name) { 'master' }
+
+ def create_file(file_name, content)
+ Files::CreateService.new(
+ project,
+ project.owner,
+ commit_message: 'Update',
+ start_branch: branch_name,
+ branch_name: branch_name,
+ file_path: file_name,
+ file_content: content
+ ).execute
+
+ project.commit(branch_name).diffs.diff_files.first
+ end
+
+ def update_file(file_name, content)
+ Files::UpdateService.new(
+ project,
+ project.owner,
+ commit_message: 'Update',
+ start_branch: branch_name,
+ branch_name: branch_name,
+ file_path: file_name,
+ file_content: content
+ ).execute
+
+ project.commit(branch_name).diffs.diff_files.first
+ end
+
+ def delete_file(file_name)
+ Files::DeleteService.new(
+ project,
+ project.owner,
+ commit_message: 'Update',
+ start_branch: branch_name,
+ branch_name: branch_name,
+ file_path: file_name
+ ).execute
+
+ project.commit(branch_name).diffs.diff_files.first
+ end
+
+ context 'when empty file is created' do
+ it 'returns true' do
+ diff_file = create_file('empty.md', '')
+
+ expect(diff_file.empty?).to be_truthy
+ end
+ end
+
+ context 'when empty file is deleted' do
+ it 'returns true' do
+ create_file('empty.md', '')
+ diff_file = delete_file('empty.md')
+
+ expect(diff_file.empty?).to be_truthy
+ end
+ end
+
+ context 'when file with content is truncated' do
+ it 'returns false' do
+ create_file('with-content.md', 'file content')
+ diff_file = update_file('with-content.md', '')
+
+ expect(diff_file.empty?).to be_falsey
+ end
+ end
+
+ context 'when empty file has content added' do
+ it 'returns false' do
+ create_file('empty.md', '')
+ diff_file = update_file('empty.md', 'new content')
+
+ expect(diff_file.empty?).to be_falsey
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/discussions_diff/file_collection_spec.rb b/spec/lib/gitlab/discussions_diff/file_collection_spec.rb
new file mode 100644
index 00000000000..0489206458b
--- /dev/null
+++ b/spec/lib/gitlab/discussions_diff/file_collection_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::DiscussionsDiff::FileCollection do
+ let(:merge_request) { create(:merge_request) }
+ let!(:diff_note_a) { create(:diff_note_on_merge_request, project: merge_request.project, noteable: merge_request) }
+ let!(:diff_note_b) { create(:diff_note_on_merge_request, project: merge_request.project, noteable: merge_request) }
+ let(:note_diff_file_a) { diff_note_a.note_diff_file }
+ let(:note_diff_file_b) { diff_note_b.note_diff_file }
+
+ subject { described_class.new([note_diff_file_a, note_diff_file_b]) }
+
+ describe '#load_highlight', :clean_gitlab_redis_shared_state do
+ it 'writes uncached diffs highlight' do
+ file_a_caching_content = diff_note_a.diff_file.highlighted_diff_lines.map(&:to_hash)
+ file_b_caching_content = diff_note_b.diff_file.highlighted_diff_lines.map(&:to_hash)
+
+ expect(Gitlab::DiscussionsDiff::HighlightCache)
+ .to receive(:write_multiple)
+ .with({ note_diff_file_a.id => file_a_caching_content,
+ note_diff_file_b.id => file_b_caching_content })
+ .and_call_original
+
+ subject.load_highlight([note_diff_file_a.id, note_diff_file_b.id])
+ end
+
+ it 'does not write cache for already cached file' do
+ subject.load_highlight([note_diff_file_a.id])
+
+ file_b_caching_content = diff_note_b.diff_file.highlighted_diff_lines.map(&:to_hash)
+
+ expect(Gitlab::DiscussionsDiff::HighlightCache)
+ .to receive(:write_multiple)
+ .with({ note_diff_file_b.id => file_b_caching_content })
+ .and_call_original
+
+ subject.load_highlight([note_diff_file_a.id, note_diff_file_b.id])
+ end
+
+ it 'does not err when given ID does not exist in @collection' do
+ expect { subject.load_highlight([999]) }.not_to raise_error
+ end
+
+ it 'loaded diff files have highlighted lines loaded' do
+ subject.load_highlight([note_diff_file_a.id])
+
+ diff_file = subject.find_by_id(note_diff_file_a.id)
+
+ expect(diff_file.highlight_loaded?).to be(true)
+ end
+
+ it 'not loaded diff files does not have highlighted lines loaded' do
+ subject.load_highlight([note_diff_file_a.id])
+
+ diff_file = subject.find_by_id(note_diff_file_b.id)
+
+ expect(diff_file.highlight_loaded?).to be(false)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb b/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb
new file mode 100644
index 00000000000..fe26ebb8796
--- /dev/null
+++ b/spec/lib/gitlab/discussions_diff/highlight_cache_spec.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::DiscussionsDiff::HighlightCache, :clean_gitlab_redis_cache do
+ describe '#write_multiple' do
+ it 'sets multiple keys serializing content as JSON' do
+ mapping = {
+ 3 => [
+ {
+ text: 'foo',
+ type: 'new',
+ index: 2,
+ old_pos: 10,
+ new_pos: 11,
+ line_code: 'xpto',
+ rich_text: '<blips>blops</blips>'
+ },
+ {
+ text: 'foo',
+ type: 'new',
+ index: 3,
+ old_pos: 11,
+ new_pos: 12,
+ line_code: 'xpto',
+ rich_text: '<blops>blips</blops>'
+ }
+ ]
+ }
+
+ described_class.write_multiple(mapping)
+
+ mapping.each do |key, value|
+ full_key = described_class.cache_key_for(key)
+ found = Gitlab::Redis::Cache.with { |r| r.get(full_key) }
+
+ expect(found).to eq(value.to_json)
+ end
+ end
+ end
+
+ describe '#read_multiple' do
+ it 'reads multiple keys and serializes content into Gitlab::Diff::Line objects' do
+ mapping = {
+ 3 => [
+ {
+ text: 'foo',
+ type: 'new',
+ index: 2,
+ old_pos: 11,
+ new_pos: 12,
+ line_code: 'xpto',
+ rich_text: '<blips>blops</blips>'
+ },
+ {
+ text: 'foo',
+ type: 'new',
+ index: 3,
+ old_pos: 10,
+ new_pos: 11,
+ line_code: 'xpto',
+ rich_text: '<blips>blops</blips>'
+ }
+ ]
+ }
+
+ described_class.write_multiple(mapping)
+
+ found = described_class.read_multiple(mapping.keys)
+
+ expect(found.size).to eq(1)
+ expect(found.first.size).to eq(2)
+ expect(found.first).to all(be_a(Gitlab::Diff::Line))
+ end
+
+ it 'returns nil when cached key is not found' do
+ mapping = {
+ 3 => [
+ {
+ text: 'foo',
+ type: 'new',
+ index: 2,
+ old_pos: 11,
+ new_pos: 12,
+ line_code: 'xpto',
+ rich_text: '<blips>blops</blips>'
+ }
+ ]
+ }
+
+ described_class.write_multiple(mapping)
+
+ found = described_class.read_multiple([2, 3])
+
+ expect(found.size).to eq(2)
+
+ expect(found.first).to eq(nil)
+ expect(found.second.size).to eq(1)
+ expect(found.second).to all(be_a(Gitlab::Diff::Line))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb
index a5bf2f2b3df..429816efec3 100644
--- a/spec/lib/gitlab/encoding_helper_spec.rb
+++ b/spec/lib/gitlab/encoding_helper_spec.rb
@@ -124,7 +124,7 @@ describe Gitlab::EncodingHelper do
end
it 'returns empty string on conversion errors' do
- expect { ext_class.encode_utf8('') }.not_to raise_error(ArgumentError)
+ expect { ext_class.encode_utf8('') }.not_to raise_error
end
context 'with strings that can be forcefully encoded into utf8' do
diff --git a/spec/lib/gitlab/git/object_pool_spec.rb b/spec/lib/gitlab/git/object_pool_spec.rb
new file mode 100644
index 00000000000..0d5069568e1
--- /dev/null
+++ b/spec/lib/gitlab/git/object_pool_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Git::ObjectPool do
+ let(:pool_repository) { create(:pool_repository) }
+ let(:source_repository) { pool_repository.source_project.repository }
+
+ subject { pool_repository.object_pool }
+
+ describe '#storage' do
+ it "equals the pool repository's shard name" do
+ expect(subject.storage).not_to be_nil
+ expect(subject.storage).to eq(pool_repository.shard_name)
+ end
+ end
+
+ describe '#create' do
+ before do
+ subject.create
+ end
+
+ context "when the pool doesn't exist yet" do
+ it 'creates the pool' do
+ expect(subject.exists?).to be(true)
+ end
+ end
+
+ context 'when the pool already exists' do
+ it 'raises an FailedPrecondition' do
+ expect do
+ subject.create
+ end.to raise_error(GRPC::FailedPrecondition)
+ end
+ end
+ end
+
+ describe '#exists?' do
+ context "when the object pool doesn't exist" do
+ it 'returns false' do
+ expect(subject.exists?).to be(false)
+ end
+ end
+
+ context 'when the object pool exists' do
+ let(:pool) { create(:pool_repository, :ready) }
+
+ subject { pool.object_pool }
+
+ it 'returns true' do
+ expect(subject.exists?).to be(true)
+ end
+ end
+ end
+
+ describe '#link' do
+ let!(:pool_repository) { create(:pool_repository, :ready) }
+
+ context 'when linked for the first time' do
+ it 'sets a remote' do
+ expect do
+ subject.link(source_repository)
+ end.not_to raise_error
+ end
+ end
+
+ context 'when the remote is already set' do
+ before do
+ subject.link(source_repository)
+ end
+
+ it "doesn't raise an error" do
+ expect do
+ subject.link(source_repository)
+ end.not_to raise_error
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/repository_cleaner_spec.rb b/spec/lib/gitlab/git/repository_cleaner_spec.rb
new file mode 100644
index 00000000000..7f9cc2bc9ec
--- /dev/null
+++ b/spec/lib/gitlab/git/repository_cleaner_spec.rb
@@ -0,0 +1,62 @@
+require 'spec_helper'
+
+describe Gitlab::Git::RepositoryCleaner do
+ include HttpIOHelpers
+
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+ let(:head_sha) { repository.head_commit.id }
+ let(:object_map_data) { "#{head_sha} #{'0' * 40}" }
+
+ subject(:cleaner) { described_class.new(repository.raw) }
+
+ describe '#apply_bfg_object_map' do
+ let(:clean_refs) { %W[refs/environments/1 refs/merge-requests/1 refs/keep-around/#{head_sha}] }
+ let(:keep_refs) { %w[refs/heads/_keep refs/tags/_keep] }
+
+ before do
+ (clean_refs + keep_refs).each { |ref| repository.create_ref(head_sha, ref) }
+ end
+
+ context 'from StringIO' do
+ let(:object_map) { StringIO.new(object_map_data) }
+
+ it 'removes internal references' do
+ cleaner.apply_bfg_object_map(object_map)
+
+ aggregate_failures do
+ clean_refs.each { |ref| expect(repository.ref_exists?(ref)).to be_falsy }
+ keep_refs.each { |ref| expect(repository.ref_exists?(ref)).to be_truthy }
+ end
+ end
+ end
+
+ context 'from Gitlab::HttpIO' do
+ let(:url) { 'http://example.com/bfg_object_map.txt' }
+ let(:tempfile) { Tempfile.new }
+ let(:object_map) { Gitlab::HttpIO.new(url, object_map_data.size) }
+
+ around do |example|
+ begin
+ tempfile.write(object_map_data)
+ tempfile.close
+
+ example.run
+ ensure
+ tempfile.unlink
+ end
+ end
+
+ it 'removes internal references' do
+ stub_remote_url_200(url, tempfile.path)
+
+ cleaner.apply_bfg_object_map(object_map)
+
+ aggregate_failures do
+ clean_refs.each { |ref| expect(repository.ref_exists?(ref)).to be_falsy }
+ keep_refs.each { |ref| expect(repository.ref_exists?(ref)).to be_truthy }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb b/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb
new file mode 100644
index 00000000000..369deff732a
--- /dev/null
+++ b/spec/lib/gitlab/gitaly_client/cleanup_service_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe Gitlab::GitalyClient::CleanupService do
+ let(:project) { create(:project) }
+ let(:storage_name) { project.repository_storage }
+ let(:relative_path) { project.disk_path + '.git' }
+ let(:client) { described_class.new(project.repository) }
+
+ describe '#apply_bfg_object_map' do
+ it 'sends an apply_bfg_object_map message' do
+ expect_any_instance_of(Gitaly::CleanupService::Stub)
+ .to receive(:apply_bfg_object_map)
+ .with(kind_of(Enumerator), kind_of(Hash))
+ .and_return(double)
+
+ client.apply_bfg_object_map(StringIO.new)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb b/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb
new file mode 100644
index 00000000000..149b7ec5bb0
--- /dev/null
+++ b/spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::GitalyClient::ObjectPoolService do
+ let(:pool_repository) { create(:pool_repository) }
+ let(:project) { create(:project, :repository) }
+ let(:raw_repository) { project.repository.raw }
+ let(:object_pool) { pool_repository.object_pool }
+
+ subject { described_class.new(object_pool) }
+
+ before do
+ subject.create(raw_repository)
+ end
+
+ describe '#create' do
+ it 'exists on disk' do
+ expect(object_pool.repository.exists?).to be(true)
+ end
+
+ context 'when the pool already exists' do
+ it 'returns an error' do
+ expect do
+ subject.create(raw_repository)
+ end.to raise_error(GRPC::FailedPrecondition)
+ end
+ end
+ end
+
+ describe '#delete' do
+ it 'removes the repository from disk' do
+ subject.delete
+
+ expect(object_pool.repository.exists?).to be(false)
+ end
+
+ context 'when called twice' do
+ it "doesn't raise an error" do
+ subject.delete
+
+ expect { object_pool.delete }.not_to raise_error
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index 5eda4d041a8..e41a75c37a7 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -3,6 +3,20 @@ require 'spec_helper'
# We stub Gitaly in `spec/support/gitaly.rb` for other tests. We don't want
# those stubs while testing the GitalyClient itself.
describe Gitlab::GitalyClient do
+ let(:sample_cert) { Rails.root.join('spec/fixtures/clusters/sample_cert.pem').to_s }
+
+ before do
+ allow(described_class)
+ .to receive(:stub_cert_paths)
+ .and_return([sample_cert])
+ end
+
+ def stub_repos_storages(address)
+ allow(Gitlab.config.repositories).to receive(:storages).and_return({
+ 'default' => { 'gitaly_address' => address }
+ })
+ 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)
@@ -15,12 +29,8 @@ describe Gitlab::GitalyClient do
describe '.stub_address' do
it 'returns the same result after being called multiple times' do
- address = 'localhost:9876'
- prefixed_address = "tcp://#{address}"
-
- allow(Gitlab.config.repositories).to receive(:storages).and_return({
- 'default' => { 'gitaly_address' => prefixed_address }
- })
+ address = 'tcp://localhost:9876'
+ stub_repos_storages address
2.times do
expect(described_class.stub_address('default')).to eq('localhost:9876')
@@ -28,6 +38,45 @@ describe Gitlab::GitalyClient do
end
end
+ describe '.stub_certs' do
+ it 'skips certificates if OpenSSLError is raised and report it' do
+ expect(Rails.logger).to receive(:error).at_least(:once)
+ expect(Gitlab::Sentry)
+ .to receive(:track_exception)
+ .with(
+ a_kind_of(OpenSSL::X509::CertificateError),
+ extra: { cert_file: a_kind_of(String) }).at_least(:once)
+
+ expect(OpenSSL::X509::Certificate)
+ .to receive(:new)
+ .and_raise(OpenSSL::X509::CertificateError).at_least(:once)
+
+ expect(described_class.stub_certs).to be_a(String)
+ end
+ end
+ describe '.stub_creds' do
+ it 'returns :this_channel_is_insecure if unix' do
+ address = 'unix:/tmp/gitaly.sock'
+ stub_repos_storages address
+
+ expect(described_class.stub_creds('default')).to eq(:this_channel_is_insecure)
+ end
+
+ it 'returns :this_channel_is_insecure if tcp' do
+ address = 'tcp://localhost:9876'
+ stub_repos_storages address
+
+ expect(described_class.stub_creds('default')).to eq(:this_channel_is_insecure)
+ end
+
+ it 'returns Credentials object if tls' do
+ address = 'tls://localhost:9876'
+ stub_repos_storages address
+
+ expect(described_class.stub_creds('default')).to be_a(GRPC::Core::ChannelCredentials)
+ end
+ end
+
describe '.stub' do
# Notice that this is referring to gRPC "stubs", not rspec stubs
before do
@@ -37,9 +86,19 @@ describe Gitlab::GitalyClient do
context 'when passed a UNIX socket address' do
it 'passes the address as-is to GRPC' do
address = 'unix:/tmp/gitaly.sock'
- allow(Gitlab.config.repositories).to receive(:storages).and_return({
- 'default' => { 'gitaly_address' => address }
- })
+ stub_repos_storages address
+
+ expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args)
+
+ described_class.stub(:commit_service, 'default')
+ end
+ end
+
+ context 'when passed a TLS address' do
+ it 'strips tls:// prefix before passing it to GRPC::Core::Channel initializer' do
+ address = 'localhost:9876'
+ prefixed_address = "tls://#{address}"
+ stub_repos_storages prefixed_address
expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args)
@@ -51,10 +110,7 @@ describe Gitlab::GitalyClient do
it 'strips tcp:// prefix before passing it to GRPC::Core::Channel initializer' do
address = 'localhost:9876'
prefixed_address = "tcp://#{address}"
-
- allow(Gitlab.config.repositories).to receive(:storages).and_return({
- 'default' => { 'gitaly_address' => prefixed_address }
- })
+ stub_repos_storages prefixed_address
expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args)
diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
index efca8564894..25684ea9e2c 100644
--- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
@@ -240,12 +240,7 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
.and_return(user.id)
end
- # TODO: remove rails5-only after removing rails4 tests
- # rails 4 can not handle multiple indexes on the same column set if
- # index was added by 't.index' - t.index is used by default in schema.rb in
- # rails 5. Let's run this test only in rails 5 env:
- # see https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21492#note_113602758
- it 'returns the existing merge request', :rails5 do
+ it 'returns the existing merge request' do
mr1, exists1 = importer.create_merge_request
mr2, exists2 = importer.create_merge_request
diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb
index 8c6d673391b..8229f0eb794 100644
--- a/spec/lib/gitlab/gpg/commit_spec.rb
+++ b/spec/lib/gitlab/gpg/commit_spec.rb
@@ -26,6 +26,28 @@ describe Gitlab::Gpg::Commit do
end
end
+ context 'invalid signature' do
+ let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User1.emails.first }
+
+ let!(:user) { create(:user, email: GpgHelpers::User1.emails.first) }
+
+ before do
+ allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
+ .with(Gitlab::Git::Repository, commit_sha)
+ .and_return(
+ [
+ # Corrupt the key
+ GpgHelpers::User1.signed_commit_signature.tr('=', 'a'),
+ GpgHelpers::User1.signed_commit_base_data
+ ]
+ )
+ end
+
+ it 'returns nil' do
+ expect(described_class.new(commit).signature).to be_nil
+ end
+ end
+
context 'known key' do
context 'user matches the key uid' do
context 'user email matches the email committer' do
diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb
index 39d09c49989..48bbd7f854c 100644
--- a/spec/lib/gitlab/gpg_spec.rb
+++ b/spec/lib/gitlab/gpg_spec.rb
@@ -137,7 +137,7 @@ describe Gitlab::Gpg do
described_class.using_tmp_keychain do
end
end
- end.not_to raise_error(ThreadError)
+ end.not_to raise_error
end
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 7df129da95a..c8c74883640 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -37,6 +37,7 @@ notes:
- events
- system_note_metadata
- note_diff_file
+- suggestions
label_links:
- target
- label
@@ -63,6 +64,7 @@ snippets:
- award_emoji
- user_agent_detail
releases:
+- author
- project
project_members:
- created_by
@@ -287,6 +289,7 @@ project:
- statistics
- container_repositories
- uploads
+- file_uploads
- import_state
- members_and_requesters
- build_trace_section_names
diff --git a/spec/lib/gitlab/import_export/command_line_util_spec.rb b/spec/lib/gitlab/import_export/command_line_util_spec.rb
new file mode 100644
index 00000000000..8e5e0aefac0
--- /dev/null
+++ b/spec/lib/gitlab/import_export/command_line_util_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ImportExport::CommandLineUtil do
+ include ExportFileHelper
+
+ let(:path) { "#{Dir.tmpdir}/symlink_test" }
+ let(:archive) { 'spec/fixtures/symlink_export.tar.gz' }
+ let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
+
+ subject do
+ Class.new do
+ include Gitlab::ImportExport::CommandLineUtil
+
+ def initialize
+ @shared = Gitlab::ImportExport::Shared.new(nil)
+ end
+ end.new
+ end
+
+ before do
+ FileUtils.mkdir_p(path)
+ subject.untar_zxf(archive: archive, dir: path)
+ end
+
+ after do
+ FileUtils.rm_rf(path)
+ end
+
+ it 'has the right mask for project.json' do
+ expect(file_permissions("#{path}/project.json")).to eq(0755) # originally 777
+ end
+
+ it 'has the right mask for uploads' do
+ expect(file_permissions("#{path}/uploads")).to eq(0755) # originally 555
+ end
+end
diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb
index bf34cefe18f..fbc9bcd2df5 100644
--- a/spec/lib/gitlab/import_export/file_importer_spec.rb
+++ b/spec/lib/gitlab/import_export/file_importer_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Gitlab::ImportExport::FileImporter do
+ include ExportFileHelper
+
let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
let(:storage_path) { "#{Dir.tmpdir}/file_importer_spec" }
let(:valid_file) { "#{shared.export_path}/valid.json" }
@@ -8,6 +10,7 @@ describe Gitlab::ImportExport::FileImporter do
let(:hidden_symlink_file) { "#{shared.export_path}/.hidden" }
let(:subfolder_symlink_file) { "#{shared.export_path}/subfolder/invalid.json" }
let(:evil_symlink_file) { "#{shared.export_path}/.\nevil" }
+ let(:custom_mode_symlink_file) { "#{shared.export_path}/symlink.mode" }
before do
stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0)
@@ -45,10 +48,18 @@ describe Gitlab::ImportExport::FileImporter do
expect(File.exist?(subfolder_symlink_file)).to be false
end
+ it 'removes symlinks without any file permissions' do
+ expect(File.exist?(custom_mode_symlink_file)).to be false
+ end
+
it 'does not remove a valid file' do
expect(File.exist?(valid_file)).to be true
end
+ it 'does not change a valid file permissions' do
+ expect(file_permissions(valid_file)).not_to eq(0000)
+ end
+
it 'creates the file in the right subfolder' do
expect(shared.export_path).to include('test/abcd')
end
@@ -84,5 +95,7 @@ describe Gitlab::ImportExport::FileImporter do
FileUtils.ln_s(valid_file, subfolder_symlink_file)
FileUtils.ln_s(valid_file, hidden_symlink_file)
FileUtils.ln_s(valid_file, evil_symlink_file)
+ FileUtils.ln_s(valid_file, custom_mode_symlink_file)
+ FileUtils.chmod_R(0000, custom_mode_symlink_file)
end
end
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index d3bfde181bc..24b1f2d995b 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -112,8 +112,11 @@ ProjectSnippet:
- visibility_level
Release:
- id
+- name
- tag
+- sha
- description
+- author_id
- project_id
- created_at
- updated_at
diff --git a/spec/lib/gitlab/json_cache_spec.rb b/spec/lib/gitlab/json_cache_spec.rb
new file mode 100644
index 00000000000..b52078e8556
--- /dev/null
+++ b/spec/lib/gitlab/json_cache_spec.rb
@@ -0,0 +1,401 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::JsonCache do
+ let(:backend) { double('backend').as_null_object }
+ let(:namespace) { 'geo' }
+ let(:key) { 'foo' }
+ let(:expanded_key) { "#{namespace}:#{key}:#{Rails.version}" }
+ let(:broadcast_message) { create(:broadcast_message) }
+
+ subject(:cache) { described_class.new(namespace: namespace, backend: backend) }
+
+ describe '#active?' do
+ context 'when backend respond to active? method' do
+ it 'delegates to the underlying cache implementation' do
+ backend = double('backend', active?: false)
+
+ cache = described_class.new(namespace: namespace, backend: backend)
+
+ expect(cache.active?).to eq(false)
+ end
+ end
+
+ context 'when backend does not respond to active? method' do
+ it 'returns true' do
+ backend = double('backend')
+
+ cache = described_class.new(namespace: namespace, backend: backend)
+
+ expect(cache.active?).to eq(true)
+ end
+ end
+ end
+
+ describe '#cache_key' do
+ context 'when namespace is not defined' do
+ it 'expands out the key with Rails version' do
+ cache = described_class.new(cache_key_with_version: true)
+
+ cache_key = cache.cache_key(key)
+
+ expect(cache_key).to eq("#{key}:#{Rails.version}")
+ end
+ end
+
+ context 'when cache_key_with_version is true' do
+ it 'expands out the key with namespace and Rails version' do
+ cache = described_class.new(namespace: namespace, cache_key_with_version: true)
+
+ cache_key = cache.cache_key(key)
+
+ expect(cache_key).to eq("#{namespace}:#{key}:#{Rails.version}")
+ end
+ end
+
+ context 'when cache_key_with_version is false' do
+ it 'expands out the key with namespace' do
+ cache = described_class.new(namespace: namespace, cache_key_with_version: false)
+
+ cache_key = cache.cache_key(key)
+
+ expect(cache_key).to eq("#{namespace}:#{key}")
+ end
+ end
+
+ context 'when namespace is nil, and cache_key_with_version is false' do
+ it 'returns the key' do
+ cache = described_class.new(namespace: nil, cache_key_with_version: false)
+
+ cache_key = cache.cache_key(key)
+
+ expect(cache_key).to eq(key)
+ end
+ end
+ end
+
+ describe '#expire' do
+ it 'expires the given key from the cache' do
+ cache.expire(key)
+
+ expect(backend).to have_received(:delete).with(expanded_key)
+ end
+ end
+
+ describe '#read' do
+ it 'reads the given key from the cache' do
+ cache.read(key)
+
+ expect(backend).to have_received(:read).with(expanded_key)
+ end
+
+ it 'returns the cached value when there is data in the cache with the given key' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return("true")
+
+ expect(cache.read(key)).to eq(true)
+ end
+
+ it 'returns nil when there is no data in the cache with the given key' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return(nil)
+
+ expect(cache.read(key)).to be_nil
+ end
+
+ context 'when the cached value is a hash' do
+ it 'parses the cached value' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return(broadcast_message.to_json)
+
+ expect(cache.read(key, BroadcastMessage)).to eq(broadcast_message)
+ end
+
+ it 'returns nil when klass is nil' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return(broadcast_message.to_json)
+
+ expect(cache.read(key)).to be_nil
+ end
+
+ it 'gracefully handles bad cached entry' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return('{')
+
+ expect(cache.read(key, BroadcastMessage)).to be_nil
+ end
+
+ it 'gracefully handles an empty hash' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return('{}')
+
+ expect(cache.read(key, BroadcastMessage)).to be_a(BroadcastMessage)
+ end
+
+ it 'gracefully handles unknown attributes' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return(broadcast_message.attributes.merge(unknown_attribute: 1).to_json)
+
+ expect(cache.read(key, BroadcastMessage)).to be_nil
+ end
+ end
+
+ context 'when the cached value is an array' do
+ it 'parses the cached value' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return([broadcast_message].to_json)
+
+ expect(cache.read(key, BroadcastMessage)).to eq([broadcast_message])
+ end
+
+ it 'returns an empty array when klass is nil' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return([broadcast_message].to_json)
+
+ expect(cache.read(key)).to eq([])
+ end
+
+ it 'gracefully handles bad cached entry' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return('[')
+
+ expect(cache.read(key, BroadcastMessage)).to be_nil
+ end
+
+ it 'gracefully handles an empty array' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return('[]')
+
+ expect(cache.read(key, BroadcastMessage)).to eq([])
+ end
+
+ it 'gracefully handles unknown attributes' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return([{ unknown_attribute: 1 }, broadcast_message.attributes].to_json)
+
+ expect(cache.read(key, BroadcastMessage)).to eq([broadcast_message])
+ end
+ end
+ end
+
+ describe '#write' do
+ it 'writes value to the cache with the given key' do
+ cache.write(key, true)
+
+ expect(backend).to have_received(:write).with(expanded_key, "true", nil)
+ end
+
+ it 'writes a string containing a JSON representation of the value to the cache' do
+ cache.write(key, broadcast_message)
+
+ expect(backend).to have_received(:write)
+ .with(expanded_key, broadcast_message.to_json, nil)
+ end
+
+ it 'passes options the underlying cache implementation' do
+ cache.write(key, true, expires_in: 15.seconds)
+
+ expect(backend).to have_received(:write)
+ .with(expanded_key, "true", expires_in: 15.seconds)
+ end
+
+ it 'passes options the underlying cache implementation when options is empty' do
+ cache.write(key, true, {})
+
+ expect(backend).to have_received(:write)
+ .with(expanded_key, "true", {})
+ end
+
+ it 'passes options the underlying cache implementation when options is nil' do
+ cache.write(key, true, nil)
+
+ expect(backend).to have_received(:write)
+ .with(expanded_key, "true", nil)
+ end
+ end
+
+ describe '#fetch', :use_clean_rails_memory_store_caching do
+ let(:backend) { Rails.cache }
+
+ it 'requires a block' do
+ expect { cache.fetch(key) }.to raise_error(LocalJumpError)
+ end
+
+ it 'passes options the underlying cache implementation' do
+ expect(backend).to receive(:write)
+ .with(expanded_key, "true", expires_in: 15.seconds)
+
+ cache.fetch(key, expires_in: 15.seconds) { true }
+ end
+
+ context 'when the given key does not exist in the cache' do
+ context 'when the result of the block is truthy' do
+ it 'returns the result of the block' do
+ result = cache.fetch(key) { true }
+
+ expect(result).to eq(true)
+ end
+
+ it 'caches the value' do
+ expect(backend).to receive(:write).with(expanded_key, "true", {})
+
+ cache.fetch(key) { true }
+ end
+ end
+
+ context 'when the result of the block is false' do
+ it 'returns the result of the block' do
+ result = cache.fetch(key) { false }
+
+ expect(result).to eq(false)
+ end
+
+ it 'caches the value' do
+ expect(backend).to receive(:write).with(expanded_key, "false", {})
+
+ cache.fetch(key) { false }
+ end
+ end
+
+ context 'when the result of the block is nil' do
+ it 'returns the result of the block' do
+ result = cache.fetch(key) { nil }
+
+ expect(result).to eq(nil)
+ end
+
+ it 'caches the value' do
+ expect(backend).to receive(:write).with(expanded_key, "null", {})
+
+ cache.fetch(key) { nil }
+ end
+ end
+ end
+
+ context 'when the given key exists in the cache' do
+ context 'when the cached value is a hash' do
+ before do
+ backend.write(expanded_key, broadcast_message.to_json)
+ end
+
+ it 'parses the cached value' do
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to eq(broadcast_message)
+ end
+
+ it "returns the result of the block when 'as' option is nil" do
+ result = cache.fetch(key, as: nil) { 'block result' }
+
+ expect(result).to eq('block result')
+ end
+
+ it "returns the result of the block when 'as' option is not informed" do
+ result = cache.fetch(key) { 'block result' }
+
+ expect(result).to eq('block result')
+ end
+ end
+
+ context 'when the cached value is a array' do
+ before do
+ backend.write(expanded_key, [broadcast_message].to_json)
+ end
+
+ it 'parses the cached value' do
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to eq([broadcast_message])
+ end
+
+ it "returns an empty array when 'as' option is nil" do
+ result = cache.fetch(key, as: nil) { 'block result' }
+
+ expect(result).to eq([])
+ end
+
+ it "returns an empty array when 'as' option is not informed" do
+ result = cache.fetch(key) { 'block result' }
+
+ expect(result).to eq([])
+ end
+ end
+
+ context 'when the cached value is true' do
+ before do
+ backend.write(expanded_key, "true")
+ end
+
+ it 'returns the cached value' do
+ result = cache.fetch(key) { 'block result' }
+
+ expect(result).to eq(true)
+ end
+
+ it 'does not execute the block' do
+ expect { |block| cache.fetch(key, &block) }.not_to yield_control
+ end
+
+ it 'does not write to the cache' do
+ expect(backend).not_to receive(:write)
+
+ cache.fetch(key) { 'block result' }
+ end
+ end
+
+ context 'when the cached value is false' do
+ before do
+ backend.write(expanded_key, "false")
+ end
+
+ it 'returns the cached value' do
+ result = cache.fetch(key) { 'block result' }
+
+ expect(result).to eq(false)
+ end
+
+ it 'does not execute the block' do
+ expect { |block| cache.fetch(key, &block) }.not_to yield_control
+ end
+
+ it 'does not write to the cache' do
+ expect(backend).not_to receive(:write)
+
+ cache.fetch(key) { 'block result' }
+ end
+ end
+
+ context 'when the cached value is nil' do
+ before do
+ backend.write(expanded_key, "null")
+ end
+
+ it 'returns the result of the block' do
+ result = cache.fetch(key) { 'block result' }
+
+ expect(result).to eq('block result')
+ end
+
+ it 'writes the result of the block to the cache' do
+ expect(backend).to receive(:write)
+ .with(expanded_key, 'block result'.to_json, {})
+
+ cache.fetch(key) { 'block result' }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/json_logger_spec.rb b/spec/lib/gitlab/json_logger_spec.rb
index 0a62785f880..cff7dd58c8c 100644
--- a/spec/lib/gitlab/json_logger_spec.rb
+++ b/spec/lib/gitlab/json_logger_spec.rb
@@ -7,6 +7,10 @@ describe Gitlab::JsonLogger do
let(:now) { Time.now }
describe '#format_message' do
+ before do
+ allow(Gitlab::CorrelationId).to receive(:current_id).and_return('new-correlation-id')
+ end
+
it 'formats strings' do
output = subject.format_message('INFO', now, 'test', 'Hello world')
data = JSON.parse(output)
@@ -14,6 +18,7 @@ describe Gitlab::JsonLogger do
expect(data['severity']).to eq('INFO')
expect(data['time']).to eq(now.utc.iso8601(3))
expect(data['message']).to eq('Hello world')
+ expect(data['correlation_id']).to eq('new-correlation-id')
end
it 'formats hashes' do
@@ -24,6 +29,7 @@ describe Gitlab::JsonLogger do
expect(data['time']).to eq(now.utc.iso8601(3))
expect(data['hello']).to eq(1)
expect(data['message']).to be_nil
+ expect(data['correlation_id']).to eq('new-correlation-id')
end
end
end
diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb
index 77ee30264bf..1ec1ba19e39 100644
--- a/spec/lib/gitlab/lfs_token_spec.rb
+++ b/spec/lib/gitlab/lfs_token_spec.rb
@@ -1,50 +1,187 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe Gitlab::LfsToken do
+describe Gitlab::LfsToken, :clean_gitlab_redis_shared_state do
describe '#token' do
shared_examples 'an LFS token generator' do
- it 'returns a randomly generated token' do
- token = handler.token
+ it 'returns a computed token' do
+ expect(Settings).to receive(:attr_encrypted_db_key_base).and_return('fbnbv6hdjweo53qka7kza8v8swxc413c05pb51qgtfte0bygh1p2e508468hfsn5ntmjcyiz7h1d92ashpet5pkdyejg7g8or3yryhuso4h8o5c73h429d9c3r6bjnet').twice
+
+ token = lfs_token.token
expect(token).not_to be_nil
expect(token).to be_a String
- expect(token.length).to eq 50
+ expect(described_class.new(actor).token_valid?(token)).to be_truthy
+ end
+ end
+
+ context 'when the actor is a user' do
+ let(:actor) { create(:user, username: 'test_user_lfs_1') }
+ let(:lfs_token) { described_class.new(actor) }
+
+ before do
+ allow(actor).to receive(:encrypted_password).and_return('$2a$04$ETfzVS5spE9Hexn9wh6NUenCHG1LyZ2YdciOYxieV1WLSa8DHqOFO')
end
- it 'returns the correct token based on the key' do
- token = handler.token
+ it_behaves_like 'an LFS token generator'
+
+ it 'returns the correct username' do
+ expect(lfs_token.actor_name).to eq(actor.username)
+ end
- expect(handler.token).to eq(token)
+ it 'returns the correct token type' do
+ expect(lfs_token.type).to eq(:lfs_token)
end
end
- context 'when the actor is a user' do
- let(:actor) { create(:user) }
- let(:handler) { described_class.new(actor) }
+ context 'when the actor is a key' do
+ let(:user) { create(:user, username: 'test_user_lfs_2') }
+ let(:actor) { create(:key, user: user) }
+ let(:lfs_token) { described_class.new(actor) }
+
+ before do
+ allow(user).to receive(:encrypted_password).and_return('$2a$04$C1GAMKsOKouEbhKy2JQoe./3LwOfQAZc.hC8zW2u/wt8xgukvnlV.')
+ end
it_behaves_like 'an LFS token generator'
it 'returns the correct username' do
- expect(handler.actor_name).to eq(actor.username)
+ expect(lfs_token.actor_name).to eq(user.username)
end
it 'returns the correct token type' do
- expect(handler.type).to eq(:lfs_token)
+ expect(lfs_token.type).to eq(:lfs_token)
end
end
context 'when the actor is a deploy key' do
+ let(:actor_id) { 1 }
let(:actor) { create(:deploy_key) }
- let(:handler) { described_class.new(actor) }
+ let(:project) { create(:project) }
+ let(:lfs_token) { described_class.new(actor) }
+
+ before do
+ allow(actor).to receive(:id).and_return(actor_id)
+ end
it_behaves_like 'an LFS token generator'
it 'returns the correct username' do
- expect(handler.actor_name).to eq("lfs+deploy-key-#{actor.id}")
+ expect(lfs_token.actor_name).to eq("lfs+deploy-key-#{actor_id}")
end
it 'returns the correct token type' do
- expect(handler.type).to eq(:lfs_deploy_token)
+ expect(lfs_token.type).to eq(:lfs_deploy_token)
+ end
+ end
+
+ context 'when the actor is invalid' do
+ it 'raises an exception' do
+ expect { described_class.new('invalid') }.to raise_error('Bad Actor')
+ end
+ end
+ end
+
+ describe '#token_valid?' do
+ let(:actor) { create(:user, username: 'test_user_lfs_1') }
+ let(:lfs_token) { described_class.new(actor) }
+
+ before do
+ allow(actor).to receive(:encrypted_password).and_return('$2a$04$ETfzVS5spE9Hexn9wh6NUenCHG1LyZ2YdciOYxieV1WLSa8DHqOFO')
+ end
+
+ context 'for an HMAC token' do
+ before do
+ # We're not interested in testing LegacyRedisDeviseToken here
+ allow(Gitlab::LfsToken::LegacyRedisDeviseToken).to receive_message_chain(:new, :token_valid?).and_return(false)
+ end
+
+ context 'where the token is invalid' do
+ context "because it's junk" do
+ it 'returns false' do
+ expect(lfs_token.token_valid?('junk')).to be_falsey
+ end
+ end
+
+ context "because it's been fiddled with" do
+ it 'returns false' do
+ fiddled_token = lfs_token.token.tap { |token| token[0] = 'E' }
+ expect(lfs_token.token_valid?(fiddled_token)).to be_falsey
+ end
+ end
+
+ context "because it was generated with a different secret" do
+ it 'returns false' do
+ different_actor = create(:user, username: 'test_user_lfs_2')
+ different_secret_token = described_class.new(different_actor).token
+ expect(lfs_token.token_valid?(different_secret_token)).to be_falsey
+ end
+ end
+
+ context "because it's expired" do
+ it 'returns false' do
+ expired_token = lfs_token.token
+ # Needs to be at least 1860 seconds, because the default expiry is
+ # 1800 seconds with an additional 60 second leeway.
+ Timecop.freeze(Time.now + 1865) do
+ expect(lfs_token.token_valid?(expired_token)).to be_falsey
+ end
+ end
+ end
+ end
+
+ context 'where the token is valid' do
+ it 'returns true' do
+ expect(lfs_token.token_valid?(lfs_token.token)).to be_truthy
+ end
+ end
+ end
+
+ context 'for a LegacyRedisDevise token' do
+ before do
+ # We're not interested in testing HMACToken here
+ allow_any_instance_of(Gitlab::LfsToken::HMACToken).to receive(:token_valid?).and_return(false)
+ end
+
+ context 'where the token is invalid' do
+ context "because it's junk" do
+ it 'returns false' do
+ expect(lfs_token.token_valid?('junk')).to be_falsey
+ end
+ end
+
+ context "because it's been fiddled with" do
+ it 'returns false' do
+ generated_token = Gitlab::LfsToken::LegacyRedisDeviseToken.new(actor).store_new_token
+ fiddled_token = generated_token.tap { |token| token[0] = 'E' }
+ expect(lfs_token.token_valid?(fiddled_token)).to be_falsey
+ end
+ end
+
+ context "because it was generated with a different secret" do
+ it 'returns false' do
+ different_actor = create(:user, username: 'test_user_lfs_2')
+ different_secret_token = described_class.new(different_actor).token
+ expect(lfs_token.token_valid?(different_secret_token)).to be_falsey
+ end
+ end
+
+ context "because it's expired" do
+ it 'returns false' do
+ generated_token = Gitlab::LfsToken::LegacyRedisDeviseToken.new(actor).store_new_token(1)
+ # We need a real sleep here because we need to wait for redis to expire the key.
+ sleep(0.01)
+ expect(lfs_token.token_valid?(generated_token)).to be_falsey
+ end
+ end
+ end
+
+ context 'where the token is valid' do
+ it 'returns true' do
+ generated_token = Gitlab::LfsToken::LegacyRedisDeviseToken.new(actor).store_new_token
+ expect(lfs_token.token_valid?(generated_token)).to be_truthy
+ end
end
end
end
diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb
index ac3bc6b2dfe..bdb1f34d2f6 100644
--- a/spec/lib/gitlab/middleware/read_only_spec.rb
+++ b/spec/lib/gitlab/middleware/read_only_spec.rb
@@ -8,7 +8,6 @@ describe Gitlab::Middleware::ReadOnly do
rack = Rack::Builder.new do
use ActionDispatch::Session::CacheStore
use ActionDispatch::Flash
- use ActionDispatch::ParamsParser
end
rack.run(subject)
diff --git a/spec/lib/gitlab/group_hierarchy_spec.rb b/spec/lib/gitlab/object_hierarchy_spec.rb
index f3de7adcec7..4700a7ad2e1 100644
--- a/spec/lib/gitlab/group_hierarchy_spec.rb
+++ b/spec/lib/gitlab/object_hierarchy_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::GroupHierarchy, :postgresql do
+describe Gitlab::ObjectHierarchy, :postgresql do
let!(:parent) { create(:group) }
let!(:child1) { create(:group, parent: parent) }
let!(:child2) { create(:group, parent: child1) }
@@ -105,9 +105,9 @@ describe Gitlab::GroupHierarchy, :postgresql do
end
end
- describe '#all_groups' do
+ describe '#all_objects' do
let(:relation) do
- described_class.new(Group.where(id: child1.id)).all_groups
+ described_class.new(Group.where(id: child1.id)).all_objects
end
it 'includes the base rows' do
@@ -123,13 +123,13 @@ describe Gitlab::GroupHierarchy, :postgresql do
end
it 'uses ancestors_base #initialize argument for ancestors' do
- relation = described_class.new(Group.where(id: child1.id), Group.where(id: Group.maximum(:id).succ)).all_groups
+ relation = described_class.new(Group.where(id: child1.id), Group.where(id: Group.maximum(:id).succ)).all_objects
expect(relation).to include(parent)
end
it 'uses descendants_base #initialize argument for descendants' do
- relation = described_class.new(Group.where(id: Group.maximum(:id).succ), Group.where(id: child1.id)).all_groups
+ relation = described_class.new(Group.where(id: Group.maximum(:id).succ), Group.where(id: child1.id)).all_objects
expect(relation).to include(child2)
end
diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb
index 00c62c7bf96..bd0bc2c9044 100644
--- a/spec/lib/gitlab/project_authorizations_spec.rb
+++ b/spec/lib/gitlab/project_authorizations_spec.rb
@@ -20,7 +20,7 @@ describe Gitlab::ProjectAuthorizations do
end
let(:authorizations) do
- klass = if Group.supports_nested_groups?
+ klass = if Group.supports_nested_objects?
Gitlab::ProjectAuthorizations::WithNestedGroups
else
Gitlab::ProjectAuthorizations::WithoutNestedGroups
@@ -46,7 +46,7 @@ describe Gitlab::ProjectAuthorizations do
expect(mapping[group_project.id]).to eq(Gitlab::Access::DEVELOPER)
end
- if Group.supports_nested_groups?
+ if Group.supports_nested_objects?
context 'with nested groups' do
let!(:nested_group) { create(:group, parent: group) }
let!(:nested_project) { create(:project, namespace: nested_group) }
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index 4a0dc3686ec..6831274d37c 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -54,11 +54,18 @@ describe Gitlab::ProjectSearchResults do
end
it 'finds by name' do
- expect(results.map(&:first)).to include(expected_file_by_name)
+ expect(results.map(&:filename)).to include(expected_file_by_name)
+ end
+
+ it "loads all blobs for filename matches in single batch" do
+ expect(Gitlab::Git::Blob).to receive(:batch).once.and_call_original
+
+ expected = project.repository.search_files_by_name(query, 'master')
+ expect(results.map(&:filename)).to include(*expected)
end
it 'finds by content' do
- blob = results.select { |result| result.first == expected_file_by_content }.flatten.last
+ blob = results.select { |result| result.filename == expected_file_by_content }.flatten.last
expect(blob.filename).to eq(expected_file_by_content)
end
@@ -122,126 +129,6 @@ describe Gitlab::ProjectSearchResults do
let(:blob_type) { 'blobs' }
let(:entity) { project }
end
-
- describe 'parsing results' do
- let(:results) { project.repository.search_files_by_content('feature', 'master') }
- let(:search_result) { results.first }
-
- subject { described_class.parse_search_result(search_result) }
-
- it "returns a valid FoundBlob" do
- is_expected.to be_an Gitlab::SearchResults::FoundBlob
- expect(subject.id).to be_nil
- expect(subject.path).to eq('CHANGELOG')
- expect(subject.filename).to eq('CHANGELOG')
- expect(subject.basename).to eq('CHANGELOG')
- expect(subject.ref).to eq('master')
- expect(subject.startline).to eq(188)
- expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n")
- end
-
- context 'when the matching filename contains a colon' do
- let(:search_result) { "master:testdata/project::function1.yaml\x001\x00---\n" }
-
- it 'returns a valid FoundBlob' do
- expect(subject.filename).to eq('testdata/project::function1.yaml')
- expect(subject.basename).to eq('testdata/project::function1')
- expect(subject.ref).to eq('master')
- expect(subject.startline).to eq(1)
- expect(subject.data).to eq("---\n")
- end
- end
-
- context 'when the matching content contains a number surrounded by colons' do
- let(:search_result) { "master:testdata/foo.txt\x001\x00blah:9:blah" }
-
- it 'returns a valid FoundBlob' do
- expect(subject.filename).to eq('testdata/foo.txt')
- expect(subject.basename).to eq('testdata/foo')
- expect(subject.ref).to eq('master')
- expect(subject.startline).to eq(1)
- expect(subject.data).to eq('blah:9:blah')
- end
- end
-
- context 'when the matching content contains multiple null bytes' do
- let(:search_result) { "master:testdata/foo.txt\x001\x00blah\x001\x00foo" }
-
- it 'returns a valid FoundBlob' do
- expect(subject.filename).to eq('testdata/foo.txt')
- expect(subject.basename).to eq('testdata/foo')
- expect(subject.ref).to eq('master')
- expect(subject.startline).to eq(1)
- expect(subject.data).to eq("blah\x001\x00foo")
- end
- end
-
- context 'when the search result ends with an empty line' do
- let(:results) { project.repository.search_files_by_content('Role models', 'master') }
-
- it 'returns a valid FoundBlob that ends with an empty line' do
- expect(subject.filename).to eq('files/markdown/ruby-style-guide.md')
- expect(subject.basename).to eq('files/markdown/ruby-style-guide')
- expect(subject.ref).to eq('master')
- expect(subject.startline).to eq(1)
- expect(subject.data).to eq("# Prelude\n\n> Role models are important. <br/>\n> -- Officer Alex J. Murphy / RoboCop\n\n")
- end
- end
-
- context 'when the search returns non-ASCII data' do
- context 'with UTF-8' do
- let(:results) { project.repository.search_files_by_content('файл', 'master') }
-
- it 'returns results as UTF-8' do
- expect(subject.filename).to eq('encoding/russian.rb')
- expect(subject.basename).to eq('encoding/russian')
- expect(subject.ref).to eq('master')
- expect(subject.startline).to eq(1)
- expect(subject.data).to eq("Хороший файл\n")
- end
- end
-
- context 'with UTF-8 in the filename' do
- let(:results) { project.repository.search_files_by_content('webhook', 'master') }
-
- it 'returns results as UTF-8' do
- expect(subject.filename).to eq('encoding/テスト.txt')
- expect(subject.basename).to eq('encoding/テスト')
- expect(subject.ref).to eq('master')
- expect(subject.startline).to eq(3)
- expect(subject.data).to include('WebHookの確認')
- end
- end
-
- context 'with ISO-8859-1' do
- let(:search_result) { "master:encoding/iso8859.txt\x001\x00\xC4\xFC\nmaster:encoding/iso8859.txt\x002\x00\nmaster:encoding/iso8859.txt\x003\x00foo\n".force_encoding(Encoding::ASCII_8BIT) }
-
- it 'returns results as UTF-8' do
- expect(subject.filename).to eq('encoding/iso8859.txt')
- expect(subject.basename).to eq('encoding/iso8859')
- expect(subject.ref).to eq('master')
- expect(subject.startline).to eq(1)
- expect(subject.data).to eq("Äü\n\nfoo\n")
- end
- end
- end
-
- context "when filename has extension" do
- let(:search_result) { "master:CONTRIBUTE.md\x005\x00- [Contribute to GitLab](#contribute-to-gitlab)\n" }
-
- it { expect(subject.path).to eq('CONTRIBUTE.md') }
- it { expect(subject.filename).to eq('CONTRIBUTE.md') }
- it { expect(subject.basename).to eq('CONTRIBUTE') }
- end
-
- context "when file under directory" do
- let(:search_result) { "master:a/b/c.md\x005\x00a b c\n" }
-
- it { expect(subject.path).to eq('a/b/c.md') }
- it { expect(subject.filename).to eq('a/b/c.md') }
- it { expect(subject.basename).to eq('a/b/c') }
- end
- end
end
describe 'wiki search' do
diff --git a/spec/lib/gitlab/prometheus/query_variables_spec.rb b/spec/lib/gitlab/prometheus/query_variables_spec.rb
index 78974cadb69..78c74266c61 100644
--- a/spec/lib/gitlab/prometheus/query_variables_spec.rb
+++ b/spec/lib/gitlab/prometheus/query_variables_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
describe Gitlab::Prometheus::QueryVariables do
describe '.call' do
- set(:environment) { create(:environment) }
+ let(:environment) { create(:environment) }
let(:slug) { environment.slug }
subject { described_class.call(environment) }
@@ -20,7 +20,7 @@ describe Gitlab::Prometheus::QueryVariables do
it { is_expected.to include(kube_namespace: '') }
end
- context 'with deplyoment platform' do
+ context 'with deployment platform' do
let(:kube_namespace) { environment.deployment_platform.actual_namespace }
before do
diff --git a/spec/lib/gitlab/safe_request_store_spec.rb b/spec/lib/gitlab/safe_request_store_spec.rb
index c797171dbe2..bae87e43615 100644
--- a/spec/lib/gitlab/safe_request_store_spec.rb
+++ b/spec/lib/gitlab/safe_request_store_spec.rb
@@ -78,6 +78,12 @@ describe Gitlab::SafeRequestStore do
described_class.write('foo', true)
end.to change { described_class.read('foo') }.from(nil).to(true)
end
+
+ it 'does not pass the options hash to the underlying store implementation' do
+ expect(described_class.store).to receive(:write).with('foo', true)
+
+ described_class.write('foo', true, expires_in: 15.seconds)
+ end
end
context 'when RequestStore is NOT active' do
@@ -86,6 +92,12 @@ describe Gitlab::SafeRequestStore do
described_class.write('foo', true)
end.not_to change { described_class.read('foo') }.from(nil)
end
+
+ it 'does not pass the options hash to the underlying store implementation' do
+ expect(described_class.store).to receive(:write).with('foo', true)
+
+ described_class.write('foo', true, expires_in: 15.seconds)
+ end
end
end
diff --git a/spec/lib/gitlab/search/found_blob_spec.rb b/spec/lib/gitlab/search/found_blob_spec.rb
new file mode 100644
index 00000000000..74157e5c67c
--- /dev/null
+++ b/spec/lib/gitlab/search/found_blob_spec.rb
@@ -0,0 +1,138 @@
+# coding: utf-8
+
+require 'spec_helper'
+
+describe Gitlab::Search::FoundBlob do
+ describe 'parsing results' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:results) { project.repository.search_files_by_content('feature', 'master') }
+ let(:search_result) { results.first }
+
+ subject { described_class.new(content_match: search_result, project: project) }
+
+ it "returns a valid FoundBlob" do
+ is_expected.to be_an described_class
+ expect(subject.id).to be_nil
+ expect(subject.path).to eq('CHANGELOG')
+ expect(subject.filename).to eq('CHANGELOG')
+ expect(subject.basename).to eq('CHANGELOG')
+ expect(subject.ref).to eq('master')
+ expect(subject.startline).to eq(188)
+ expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n")
+ end
+
+ it "doesn't parses content if not needed" do
+ expect(subject).not_to receive(:parse_search_result)
+ expect(subject.project_id).to eq(project.id)
+ expect(subject.binary_filename).to eq('CHANGELOG')
+ end
+
+ it "parses content only once when needed" do
+ expect(subject).to receive(:parse_search_result).once.and_call_original
+ expect(subject.filename).to eq('CHANGELOG')
+ expect(subject.startline).to eq(188)
+ end
+
+ context 'when the matching filename contains a colon' do
+ let(:search_result) { "master:testdata/project::function1.yaml\x001\x00---\n" }
+
+ it 'returns a valid FoundBlob' do
+ expect(subject.filename).to eq('testdata/project::function1.yaml')
+ expect(subject.basename).to eq('testdata/project::function1')
+ expect(subject.ref).to eq('master')
+ expect(subject.startline).to eq(1)
+ expect(subject.data).to eq("---\n")
+ end
+ end
+
+ context 'when the matching content contains a number surrounded by colons' do
+ let(:search_result) { "master:testdata/foo.txt\x001\x00blah:9:blah" }
+
+ it 'returns a valid FoundBlob' do
+ expect(subject.filename).to eq('testdata/foo.txt')
+ expect(subject.basename).to eq('testdata/foo')
+ expect(subject.ref).to eq('master')
+ expect(subject.startline).to eq(1)
+ expect(subject.data).to eq('blah:9:blah')
+ end
+ end
+
+ context 'when the matching content contains multiple null bytes' do
+ let(:search_result) { "master:testdata/foo.txt\x001\x00blah\x001\x00foo" }
+
+ it 'returns a valid FoundBlob' do
+ expect(subject.filename).to eq('testdata/foo.txt')
+ expect(subject.basename).to eq('testdata/foo')
+ expect(subject.ref).to eq('master')
+ expect(subject.startline).to eq(1)
+ expect(subject.data).to eq("blah\x001\x00foo")
+ end
+ end
+
+ context 'when the search result ends with an empty line' do
+ let(:results) { project.repository.search_files_by_content('Role models', 'master') }
+
+ it 'returns a valid FoundBlob that ends with an empty line' do
+ expect(subject.filename).to eq('files/markdown/ruby-style-guide.md')
+ expect(subject.basename).to eq('files/markdown/ruby-style-guide')
+ expect(subject.ref).to eq('master')
+ expect(subject.startline).to eq(1)
+ expect(subject.data).to eq("# Prelude\n\n> Role models are important. <br/>\n> -- Officer Alex J. Murphy / RoboCop\n\n")
+ end
+ end
+
+ context 'when the search returns non-ASCII data' do
+ context 'with UTF-8' do
+ let(:results) { project.repository.search_files_by_content('файл', 'master') }
+
+ it 'returns results as UTF-8' do
+ expect(subject.filename).to eq('encoding/russian.rb')
+ expect(subject.basename).to eq('encoding/russian')
+ expect(subject.ref).to eq('master')
+ expect(subject.startline).to eq(1)
+ expect(subject.data).to eq("Хороший файл\n")
+ end
+ end
+
+ context 'with UTF-8 in the filename' do
+ let(:results) { project.repository.search_files_by_content('webhook', 'master') }
+
+ it 'returns results as UTF-8' do
+ expect(subject.filename).to eq('encoding/テスト.txt')
+ expect(subject.basename).to eq('encoding/テスト')
+ expect(subject.ref).to eq('master')
+ expect(subject.startline).to eq(3)
+ expect(subject.data).to include('WebHookの確認')
+ end
+ end
+
+ context 'with ISO-8859-1' do
+ let(:search_result) { "master:encoding/iso8859.txt\x001\x00\xC4\xFC\nmaster:encoding/iso8859.txt\x002\x00\nmaster:encoding/iso8859.txt\x003\x00foo\n".force_encoding(Encoding::ASCII_8BIT) }
+
+ it 'returns results as UTF-8' do
+ expect(subject.filename).to eq('encoding/iso8859.txt')
+ expect(subject.basename).to eq('encoding/iso8859')
+ expect(subject.ref).to eq('master')
+ expect(subject.startline).to eq(1)
+ expect(subject.data).to eq("Äü\n\nfoo\n")
+ end
+ end
+ end
+
+ context "when filename has extension" do
+ let(:search_result) { "master:CONTRIBUTE.md\x005\x00- [Contribute to GitLab](#contribute-to-gitlab)\n" }
+
+ it { expect(subject.path).to eq('CONTRIBUTE.md') }
+ it { expect(subject.filename).to eq('CONTRIBUTE.md') }
+ it { expect(subject.basename).to eq('CONTRIBUTE') }
+ end
+
+ context "when file under directory" do
+ let(:search_result) { "master:a/b/c.md\x005\x00a b c\n" }
+
+ it { expect(subject.path).to eq('a/b/c.md') }
+ it { expect(subject.filename).to eq('a/b/c.md') }
+ it { expect(subject.basename).to eq('a/b/c') }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sentry_spec.rb b/spec/lib/gitlab/sentry_spec.rb
index d3b41b27b80..1128eaf8560 100644
--- a/spec/lib/gitlab/sentry_spec.rb
+++ b/spec/lib/gitlab/sentry_spec.rb
@@ -19,14 +19,15 @@ describe Gitlab::Sentry do
end
it 'raises the exception if it should' do
- expect(described_class).to receive(:should_raise?).and_return(true)
+ expect(described_class).to receive(:should_raise_for_dev?).and_return(true)
expect { described_class.track_exception(exception) }
.to raise_error(RuntimeError)
end
context 'when exceptions should not be raised' do
before do
- allow(described_class).to receive(:should_raise?).and_return(false)
+ allow(described_class).to receive(:should_raise_for_dev?).and_return(false)
+ allow(Gitlab::CorrelationId).to receive(:current_id).and_return('cid')
end
it 'logs the exception with all attributes passed' do
@@ -35,8 +36,14 @@ describe Gitlab::Sentry do
issue_url: 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1'
}
+ expected_tags = {
+ correlation_id: 'cid'
+ }
+
expect(Raven).to receive(:capture_exception)
- .with(exception, extra: a_hash_including(expected_extras))
+ .with(exception,
+ tags: a_hash_including(expected_tags),
+ extra: a_hash_including(expected_extras))
described_class.track_exception(
exception,
@@ -58,6 +65,7 @@ describe Gitlab::Sentry do
before do
allow(described_class).to receive(:enabled?).and_return(true)
+ allow(Gitlab::CorrelationId).to receive(:current_id).and_return('cid')
end
it 'calls Raven.capture_exception' do
@@ -66,8 +74,14 @@ describe Gitlab::Sentry do
issue_url: 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1'
}
+ expected_tags = {
+ correlation_id: 'cid'
+ }
+
expect(Raven).to receive(:capture_exception)
- .with(exception, extra: a_hash_including(expected_extras))
+ .with(exception,
+ tags: a_hash_including(expected_tags),
+ extra: a_hash_including(expected_extras))
described_class.track_acceptable_exception(
exception,
diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
index 2421b1e5a1a..f773f370ee2 100644
--- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -12,7 +12,8 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
"queue_namespace" => "cronjob",
"jid" => "da883554ee4fe414012f5f42",
"created_at" => timestamp.to_f,
- "enqueued_at" => timestamp.to_f
+ "enqueued_at" => timestamp.to_f,
+ "correlation_id" => 'cid'
}
end
let(:logger) { double() }
diff --git a/spec/lib/gitlab/sidekiq_middleware/correlation_injector_spec.rb b/spec/lib/gitlab/sidekiq_middleware/correlation_injector_spec.rb
new file mode 100644
index 00000000000..a138ad7c910
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/correlation_injector_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SidekiqMiddleware::CorrelationInjector do
+ class TestWorker
+ include ApplicationWorker
+ end
+
+ before do |example|
+ Sidekiq.client_middleware do |chain|
+ chain.add described_class
+ end
+ end
+
+ after do |example|
+ Sidekiq.client_middleware do |chain|
+ chain.remove described_class
+ end
+
+ Sidekiq::Queues.clear_all
+ end
+
+ around do |example|
+ Sidekiq::Testing.fake! do
+ example.run
+ end
+ end
+
+ it 'injects into payload the correlation id' do
+ expect_any_instance_of(described_class).to receive(:call).and_call_original
+
+ Gitlab::CorrelationId.use_id('new-correlation-id') do
+ TestWorker.perform_async(1234)
+ end
+
+ expected_job_params = {
+ "class" => "TestWorker",
+ "args" => [1234],
+ "correlation_id" => "new-correlation-id"
+ }
+
+ expect(Sidekiq::Queues.jobs_by_worker).to a_hash_including(
+ "TestWorker" => a_collection_containing_exactly(
+ a_hash_including(expected_job_params)))
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_middleware/correlation_logger_spec.rb b/spec/lib/gitlab/sidekiq_middleware/correlation_logger_spec.rb
new file mode 100644
index 00000000000..94ae4ffa184
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/correlation_logger_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SidekiqMiddleware::CorrelationLogger do
+ class TestWorker
+ include ApplicationWorker
+ end
+
+ before do |example|
+ Sidekiq::Testing.server_middleware do |chain|
+ chain.add described_class
+ end
+ end
+
+ after do |example|
+ Sidekiq::Testing.server_middleware do |chain|
+ chain.remove described_class
+ end
+ end
+
+ it 'injects into payload the correlation id' do
+ expect_any_instance_of(described_class).to receive(:call).and_call_original
+
+ expect_any_instance_of(TestWorker).to receive(:perform).with(1234) do
+ expect(Gitlab::CorrelationId.current_id).to eq('new-correlation-id')
+ end
+
+ Sidekiq::Client.push(
+ 'queue' => 'test',
+ 'class' => TestWorker,
+ 'args' => [1234],
+ 'correlation_id' => 'new-correlation-id')
+ end
+end
diff --git a/spec/lib/gitlab/sql/glob_spec.rb b/spec/lib/gitlab/sql/glob_spec.rb
index 1cf8935bfe3..3147b52dcc5 100644
--- a/spec/lib/gitlab/sql/glob_spec.rb
+++ b/spec/lib/gitlab/sql/glob_spec.rb
@@ -35,9 +35,8 @@ describe Gitlab::SQL::Glob do
value = query("SELECT #{quote(string)} LIKE #{pattern}")
.rows.flatten.first
- check = Gitlab.rails5? ? true : 't'
case value
- when check, 1
+ when true, 1
true
else
false
diff --git a/spec/lib/gitlab/template/finders/global_template_finder_spec.rb b/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
new file mode 100644
index 00000000000..c7f58fbd2a5
--- /dev/null
+++ b/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe Gitlab::Template::Finders::GlobalTemplateFinder do
+ let(:base_dir) { Dir.mktmpdir }
+
+ def create_template!(name_with_category)
+ full_path = File.join(base_dir, name_with_category)
+ FileUtils.mkdir_p(File.dirname(full_path))
+ FileUtils.touch(full_path)
+ end
+
+ after do
+ FileUtils.rm_rf(base_dir)
+ end
+
+ subject(:finder) { described_class.new(base_dir, '', 'Foo' => '', 'Bar' => 'bar') }
+
+ describe '.find' do
+ it 'finds a template in the Foo category' do
+ create_template!('test-template')
+
+ expect(finder.find('test-template')).to be_present
+ end
+
+ it 'finds a template in the Bar category' do
+ create_template!('bar/test-template')
+
+ expect(finder.find('test-template')).to be_present
+ end
+
+ it 'does not permit path traversal requests' do
+ expect { finder.find('../foo') }.to raise_error(/Invalid path/)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
index 2eabccd5dff..e329d55d837 100644
--- a/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
+++ b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
@@ -25,6 +25,10 @@ describe Gitlab::Template::Finders::RepoTemplateFinder do
expect(result).to eq('files/html/500.html')
end
+
+ it 'does not permit path traversal requests' do
+ expect { finder.find('../foo') }.to raise_error(/Invalid path/)
+ end
end
describe '#list_files_for' do
diff --git a/spec/lib/gitlab/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb
deleted file mode 100644
index 6106f13c774..00000000000
--- a/spec/lib/gitlab/upgrader_spec.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Upgrader do
- let(:upgrader) { described_class.new }
- let(:current_version) { Gitlab::VERSION }
-
- describe 'current_version_raw' do
- it { expect(upgrader.current_version_raw).to eq(current_version) }
- end
-
- describe 'latest_version?' do
- it 'is true if newest version' do
- allow(upgrader).to receive(:latest_version_raw).and_return(current_version)
- expect(upgrader.latest_version?).to be_truthy
- end
- end
-
- describe 'latest_version_raw' do
- it 'is the latest version for GitLab 5' do
- allow(upgrader).to receive(:current_version_raw).and_return("5.3.0")
- expect(upgrader.latest_version_raw).to eq("v5.4.2")
- end
-
- it 'gets the latest version from tags' do
- allow(upgrader).to receive(:fetch_git_tags).and_return([
- '6f0733310546402c15d3ae6128a95052f6c8ea96 refs/tags/v7.1.1',
- 'facfec4b242ce151af224e20715d58e628aa5e74 refs/tags/v7.1.1^{}',
- 'f7068d99c79cf79befbd388030c051bb4b5e86d4 refs/tags/v7.10.4',
- '337225a4fcfa9674e2528cb6d41c46556bba9dfa refs/tags/v7.10.4^{}',
- '880e0ba0adbed95d087f61a9a17515e518fc6440 refs/tags/v7.11.1',
- '6584346b604f981f00af8011cd95472b2776d912 refs/tags/v7.11.1^{}',
- '43af3e65a486a9237f29f56d96c3b3da59c24ae0 refs/tags/v7.11.2',
- 'dac18e7728013a77410e926a1e64225703754a2d refs/tags/v7.11.2^{}',
- '0bf21fd4b46c980c26fd8c90a14b86a4d90cc950 refs/tags/v7.9.4',
- 'b10de29edbaff7219547dc506cb1468ee35065c3 refs/tags/v7.9.4^{}'
- ])
- expect(upgrader.latest_version_raw).to eq("v7.11.2")
- end
- end
-end
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index 39e0a17a307..62970bd8cb6 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -249,6 +249,27 @@ describe Gitlab::UrlBlocker do
end
end
end
+
+ context 'when ascii_only is true' do
+ it 'returns true for unicode domain' do
+ expect(described_class.blocked_url?('https://𝕘itⅼαƄ.com/foo/foo.bar', ascii_only: true)).to be true
+ end
+
+ it 'returns true for unicode tld' do
+ expect(described_class.blocked_url?('https://gitlab.ᴄοm/foo/foo.bar', ascii_only: true)).to be true
+ end
+
+ it 'returns true for unicode path' do
+ expect(described_class.blocked_url?('https://gitlab.com/𝒇οο/𝒇οο.Ƅαꮁ', ascii_only: true)).to be true
+ end
+
+ it 'returns true for IDNA deviations' do
+ expect(described_class.blocked_url?('https://mißile.com/foo/foo.bar', ascii_only: true)).to be true
+ expect(described_class.blocked_url?('https://miςςile.com/foo/foo.bar', ascii_only: true)).to be true
+ expect(described_class.blocked_url?('https://git‍lab.com/foo/foo.bar', ascii_only: true)).to be true
+ expect(described_class.blocked_url?('https://git‌lab.com/foo/foo.bar', ascii_only: true)).to be true
+ end
+ end
end
describe '#validate_hostname!' do
diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb
index b41a81a8167..6e98a999766 100644
--- a/spec/lib/gitlab/url_sanitizer_spec.rb
+++ b/spec/lib/gitlab/url_sanitizer_spec.rb
@@ -41,6 +41,7 @@ describe Gitlab::UrlSanitizer do
false | '123://invalid:url'
false | 'valid@project:url.git'
false | 'valid:pass@project:url.git'
+ false | %w(test array)
true | 'ssh://example.com'
true | 'ssh://:@example.com'
true | 'ssh://foo@example.com'
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index e2de612ff46..2a09f581f68 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -117,6 +117,7 @@ describe Gitlab::UsageData do
releases
remote_mirrors
snippets
+ suggestions
todos
uploads
web_hooks
@@ -213,4 +214,29 @@ describe Gitlab::UsageData do
expect(described_class.count(relation, fallback: 15)).to eq(15)
end
end
+
+ describe '#approximate_counts' do
+ it 'gets approximate counts for selected models' do
+ create(:label)
+
+ expect(Gitlab::Database::Count).to receive(:approximate_counts)
+ .with(described_class::APPROXIMATE_COUNT_MODELS).once.and_call_original
+
+ counts = described_class.approximate_counts.values
+
+ expect(counts.count).to eq(described_class::APPROXIMATE_COUNT_MODELS.count)
+ expect(counts.any? { |count| count < 0 }).to be_falsey
+ end
+
+ it 'returns default values if counts can not be retrieved' do
+ described_class::APPROXIMATE_COUNT_MODELS.map do |model|
+ model.name.underscore.pluralize.to_sym
+ end
+
+ expect(Gitlab::Database::Count).to receive(:approximate_counts)
+ .and_return({})
+
+ expect(described_class.approximate_counts.values.uniq).to eq([-1])
+ end
+ end
end
diff --git a/spec/lib/gitlab/utils/override_spec.rb b/spec/lib/gitlab/utils/override_spec.rb
index fc08ebcfc6d..9e7c97f8095 100644
--- a/spec/lib/gitlab/utils/override_spec.rb
+++ b/spec/lib/gitlab/utils/override_spec.rb
@@ -25,11 +25,21 @@ describe Gitlab::Utils::Override do
let(:klass) { subject }
- def good(mod)
+ def good(mod, bad_arity: false, negative_arity: false)
mod.module_eval do
override :good
- def good
- super.succ
+
+ if bad_arity
+ def good(num)
+ end
+ elsif negative_arity
+ def good(*args)
+ super.succ
+ end
+ else
+ def good
+ super.succ
+ end
end
end
@@ -56,6 +66,14 @@ describe Gitlab::Utils::Override do
described_class.verify!
end
+ it 'checks ok for overriding method using negative arity' do
+ good(subject, negative_arity: true)
+ result = instance.good
+
+ expect(result).to eq(1)
+ described_class.verify!
+ end
+
it 'raises NotImplementedError when it is not overriding anything' do
expect do
bad(subject)
@@ -63,6 +81,14 @@ describe Gitlab::Utils::Override do
described_class.verify!
end.to raise_error(NotImplementedError)
end
+
+ it 'raises NotImplementedError when overriding a method with different arity' do
+ expect do
+ good(subject, bad_arity: true)
+ instance.good(1)
+ described_class.verify!
+ end.to raise_error(NotImplementedError)
+ end
end
shared_examples 'checking as intended, nothing was overridden' do
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 3579ed9a759..f5a4b7e2ebf 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -2,7 +2,33 @@ require 'spec_helper'
describe Gitlab::Utils do
delegate :to_boolean, :boolean_to_yes_no, :slugify, :random_string, :which, :ensure_array_from_string,
- :bytes_to_megabytes, :append_path, to: :described_class
+ :bytes_to_megabytes, :append_path, :check_path_traversal!, to: :described_class
+
+ describe '.check_path_traversal!' do
+ it 'detects path traversal at the start of the string' do
+ expect { check_path_traversal!('../foo') }.to raise_error(/Invalid path/)
+ end
+
+ it 'detects path traversal at the start of the string, even to just the subdirectory' do
+ expect { check_path_traversal!('../') }.to raise_error(/Invalid path/)
+ end
+
+ it 'detects path traversal in the middle of the string' do
+ expect { check_path_traversal!('foo/../../bar') }.to raise_error(/Invalid path/)
+ end
+
+ it 'detects path traversal at the end of the string when slash-terminates' do
+ expect { check_path_traversal!('foo/../') }.to raise_error(/Invalid path/)
+ end
+
+ it 'detects path traversal at the end of the string' do
+ expect { check_path_traversal!('foo/..') }.to raise_error(/Invalid path/)
+ end
+
+ it 'does nothing for a safe string' do
+ expect(check_path_traversal!('./foo')).to eq('./foo')
+ end
+ end
describe '.slugify' do
{
@@ -18,6 +44,12 @@ describe Gitlab::Utils do
end
end
+ describe '.nlbr' do
+ it 'replaces new lines with <br>' do
+ expect(described_class.nlbr("<b>hello</b>\n<i>world</i>".freeze)).to eq("hello<br>world")
+ end
+ end
+
describe '.remove_line_breaks' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index b3f55a2e1bd..7213eee5675 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -246,7 +246,6 @@ describe Gitlab::Workhorse do
GL_ID: "user-#{user.id}",
GL_USERNAME: user.username,
GL_REPOSITORY: "project-#{project.id}",
- RepoPath: repo_path,
ShowAllRefs: false
}
end
@@ -261,7 +260,6 @@ describe Gitlab::Workhorse do
GL_ID: "user-#{user.id}",
GL_USERNAME: user.username,
GL_REPOSITORY: "wiki-#{project.id}",
- RepoPath: repo_path,
ShowAllRefs: false
}
end
diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb
index f18f97a9c6a..77fea5b2d24 100644
--- a/spec/lib/mattermost/session_spec.rb
+++ b/spec/lib/mattermost/session_spec.rb
@@ -67,11 +67,13 @@ describe Mattermost::Session, type: :request do
.with(query: hash_including({ 'state' => state }))
.to_return do |request|
post "/oauth/token",
- client_id: doorkeeper.uid,
- client_secret: doorkeeper.secret,
- redirect_uri: params[:redirect_uri],
- grant_type: 'authorization_code',
- code: request.uri.query_values['code']
+ params: {
+ client_id: doorkeeper.uid,
+ client_secret: doorkeeper.secret,
+ redirect_uri: params[:redirect_uri],
+ grant_type: 'authorization_code',
+ code: request.uri.query_values['code']
+ }
if response.status == 200
{ headers: { 'token' => 'thisworksnow' }, status: 202 }
diff --git a/spec/lib/omni_auth/strategies/jwt_spec.rb b/spec/lib/omni_auth/strategies/jwt_spec.rb
index 88d6d0b559a..c2e2db27362 100644
--- a/spec/lib/omni_auth/strategies/jwt_spec.rb
+++ b/spec/lib/omni_auth/strategies/jwt_spec.rb
@@ -4,12 +4,10 @@ describe OmniAuth::Strategies::Jwt do
include Rack::Test::Methods
include DeviseHelpers
- context '.decoded' do
- let(:strategy) { described_class.new({}) }
+ context '#decoded' do
+ subject { described_class.new({}) }
let(:timestamp) { Time.now.to_i }
let(:jwt_config) { Devise.omniauth_configs[:jwt] }
- let(:key) { JWT.encode(claims, jwt_config.strategy.secret) }
-
let(:claims) do
{
id: 123,
@@ -18,19 +16,55 @@ describe OmniAuth::Strategies::Jwt do
iat: timestamp
}
end
+ let(:algorithm) { 'HS256' }
+ let(:secret) { jwt_config.strategy.secret }
+ let(:private_key) { secret }
+ let(:payload) { JWT.encode(claims, private_key, algorithm) }
before do
- allow_any_instance_of(OmniAuth::Strategy).to receive(:options).and_return(jwt_config.strategy)
- allow_any_instance_of(Rack::Request).to receive(:params).and_return({ 'jwt' => key })
+ subject.options[:secret] = secret
+ subject.options[:algorithm] = algorithm
+
+ expect_next_instance_of(Rack::Request) do |rack_request|
+ expect(rack_request).to receive(:params).and_return('jwt' => payload)
+ end
end
- it 'decodes the user information' do
- result = strategy.decoded
+ ECDSA_NAMED_CURVES = {
+ 'ES256' => 'prime256v1',
+ 'ES384' => 'secp384r1',
+ 'ES512' => 'secp521r1'
+ }.freeze
- expect(result["id"]).to eq(123)
- expect(result["name"]).to eq("user_example")
- expect(result["email"]).to eq("user@example.com")
- expect(result["iat"]).to eq(timestamp)
+ {
+ OpenSSL::PKey::RSA => %w[RS256 RS384 RS512],
+ OpenSSL::PKey::EC => %w[ES256 ES384 ES512],
+ String => %w[HS256 HS384 HS512]
+ }.each do |private_key_class, algorithms|
+ algorithms.each do |algorithm|
+ context "when the #{algorithm} algorithm is used" do
+ let(:algorithm) { algorithm }
+ let(:secret) do
+ if private_key_class == OpenSSL::PKey::RSA
+ private_key_class.generate(2048)
+ .to_pem
+ elsif private_key_class == OpenSSL::PKey::EC
+ private_key_class.new(ECDSA_NAMED_CURVES[algorithm])
+ .tap { |key| key.generate_key! }
+ .to_pem
+ else
+ private_key_class.new(jwt_config.strategy.secret)
+ end
+ end
+ let(:private_key) { private_key_class ? private_key_class.new(secret) : secret }
+
+ it 'decodes the user information' do
+ result = subject.decoded
+
+ expect(result).to eq(claims.stringify_keys)
+ end
+ end
+ end
end
context 'required claims is missing' do
@@ -43,7 +77,7 @@ describe OmniAuth::Strategies::Jwt do
end
it 'raises error' do
- expect { strategy.decoded }.to raise_error(OmniAuth::Strategies::Jwt::ClaimInvalid)
+ expect { subject.decoded }.to raise_error(OmniAuth::Strategies::Jwt::ClaimInvalid)
end
end
@@ -57,11 +91,12 @@ describe OmniAuth::Strategies::Jwt do
end
before do
- jwt_config.strategy.valid_within = Time.now.to_i
+ # Omniauth config values are always strings!
+ subject.options[:valid_within] = 2.days.to_s
end
it 'raises error' do
- expect { strategy.decoded }.to raise_error(OmniAuth::Strategies::Jwt::ClaimInvalid)
+ expect { subject.decoded }.to raise_error(OmniAuth::Strategies::Jwt::ClaimInvalid)
end
end
@@ -76,11 +111,12 @@ describe OmniAuth::Strategies::Jwt do
end
before do
- jwt_config.strategy.valid_within = 2.seconds
+ # Omniauth config values are always strings!
+ subject.options[:valid_within] = 2.seconds.to_s
end
it 'raises error' do
- expect { strategy.decoded }.to raise_error(OmniAuth::Strategies::Jwt::ClaimInvalid)
+ expect { subject.decoded }.to raise_error(OmniAuth::Strategies::Jwt::ClaimInvalid)
end
end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 150c00e4bfe..f2d99872401 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -4,6 +4,7 @@ require 'email_spec'
describe Notify do
include EmailSpec::Helpers
include EmailSpec::Matchers
+ include EmailHelpers
include RepoHelpers
include_context 'gitlab email notification'
@@ -27,15 +28,6 @@ describe Notify do
description: 'My awesome description!')
end
- def have_referable_subject(referable, reply: false)
- prefix = referable.project ? "#{referable.project.name} | " : ''
- prefix.prepend('Re: ') if reply
-
- suffix = "#{referable.title} (#{referable.to_reference})"
-
- have_subject [prefix, suffix].compact.join
- end
-
context 'for a project' do
shared_examples 'an assignee email' do
it 'is sent to the assignee as the author' do
@@ -898,22 +890,14 @@ describe Notify do
shared_examples 'an email for a note on a diff discussion' do |model|
let(:note) { create(model, author: note_author) }
- context 'when note is on image' do
+ context 'when note is not on text' do
before do
- allow_any_instance_of(DiffDiscussion).to receive(:on_image?).and_return(true)
+ allow_any_instance_of(DiffDiscussion).to receive(:on_text?).and_return(false)
end
it 'does not include diffs with character-level highlighting' do
is_expected.not_to have_body_text '<span class="p">}</span></span>'
end
-
- it 'ends the intro with a dot' do
- is_expected.to have_body_text "#{note.diff_file.file_path}</a>."
- end
- end
-
- it 'ends the intro with a colon' do
- is_expected.to have_body_text "#{note.diff_file.file_path}</a>:"
end
it 'includes diffs with character-level highlighting' do
diff --git a/spec/migrations/backfill_releases_name_with_tag_name_spec.rb b/spec/migrations/backfill_releases_name_with_tag_name_spec.rb
new file mode 100644
index 00000000000..6f436de84b7
--- /dev/null
+++ b/spec/migrations/backfill_releases_name_with_tag_name_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20181212104941_backfill_releases_name_with_tag_name.rb')
+
+describe BackfillReleasesNameWithTagName, :migration do
+ let(:releases) { table(:releases) }
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+
+ let(:namespace) { namespaces.create(name: 'foo', path: 'foo') }
+ let(:project) { projects.create!(namespace_id: namespace.id) }
+ let(:release) { releases.create!(project_id: project.id, tag: 'v1.0.0') }
+
+ it 'defaults name to tag value' do
+ expect(release.tag).to be_present
+
+ migrate!
+
+ release.reload
+ expect(release.name).to eq(release.tag)
+ end
+end
diff --git a/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb b/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb
new file mode 100644
index 00000000000..b2d8f476bb2
--- /dev/null
+++ b/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb
@@ -0,0 +1,68 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20181219145520_migrate_cluster_configure_worker_sidekiq_queue.rb')
+
+describe MigrateClusterConfigureWorkerSidekiqQueue, :sidekiq, :redis do
+ include Gitlab::Database::MigrationHelpers
+
+ context 'when there are jobs in the queue' do
+ it 'correctly migrates queue when migrating up' do
+ Sidekiq::Testing.disable! do
+ stubbed_worker(queue: 'gcp_cluster:cluster_platform_configure').perform_async('Something', [1])
+ stubbed_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1])
+
+ described_class.new.up
+
+ expect(sidekiq_queue_length('gcp_cluster:cluster_platform_configure')).to eq 0
+ expect(sidekiq_queue_length('gcp_cluster:cluster_configure')).to eq 2
+ end
+ end
+
+ it 'does not affect other queues under the same namespace' do
+ Sidekiq::Testing.disable! do
+ stubbed_worker(queue: 'gcp_cluster:cluster_install_app').perform_async('Something', [1])
+ stubbed_worker(queue: 'gcp_cluster:cluster_provision').perform_async('Something', [1])
+ stubbed_worker(queue: 'gcp_cluster:cluster_wait_for_app_installation').perform_async('Something', [1])
+ stubbed_worker(queue: 'gcp_cluster:wait_for_cluster_creation').perform_async('Something', [1])
+ stubbed_worker(queue: 'gcp_cluster:cluster_wait_for_ingress_ip_address').perform_async('Something', [1])
+ stubbed_worker(queue: 'gcp_cluster:cluster_project_configure').perform_async('Something', [1])
+
+ described_class.new.up
+
+ expect(sidekiq_queue_length('gcp_cluster:cluster_install_app')).to eq 1
+ expect(sidekiq_queue_length('gcp_cluster:cluster_provision')).to eq 1
+ expect(sidekiq_queue_length('gcp_cluster:cluster_wait_for_app_installation')).to eq 1
+ expect(sidekiq_queue_length('gcp_cluster:wait_for_cluster_creation')).to eq 1
+ expect(sidekiq_queue_length('gcp_cluster:cluster_wait_for_ingress_ip_address')).to eq 1
+ expect(sidekiq_queue_length('gcp_cluster:cluster_project_configure')).to eq 1
+ end
+ end
+
+ it 'correctly migrates queue when migrating down' do
+ Sidekiq::Testing.disable! do
+ stubbed_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1])
+
+ described_class.new.down
+
+ expect(sidekiq_queue_length('gcp_cluster:cluster_platform_configure')).to eq 1
+ expect(sidekiq_queue_length('gcp_cluster:cluster_configure')).to eq 0
+ end
+ end
+ end
+
+ context 'when there are no jobs in the queues' do
+ it 'does not raise error when migrating up' do
+ expect { described_class.new.up }.not_to raise_error
+ end
+
+ it 'does not raise error when migrating down' do
+ expect { described_class.new.down }.not_to raise_error
+ end
+ end
+
+ def stubbed_worker(queue:)
+ Class.new do
+ include Sidekiq::Worker
+ sidekiq_options queue: queue
+ end
+ end
+end
diff --git a/spec/migrations/populate_mr_metrics_with_events_data_spec.rb b/spec/migrations/populate_mr_metrics_with_events_data_spec.rb
new file mode 100644
index 00000000000..291a52b904d
--- /dev/null
+++ b/spec/migrations/populate_mr_metrics_with_events_data_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20181204154019_populate_mr_metrics_with_events_data.rb')
+
+describe PopulateMrMetricsWithEventsData, :migration, :sidekiq do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') }
+ let(:merge_requests) { table(:merge_requests) }
+
+ def create_merge_request(id)
+ params = {
+ id: id,
+ target_project_id: project.id,
+ target_branch: 'master',
+ source_project_id: project.id,
+ source_branch: 'mr name',
+ title: "mr name#{id}"
+ }
+
+ merge_requests.create!(params)
+ end
+
+ it 'correctly schedules background migrations' do
+ create_merge_request(1)
+ create_merge_request(2)
+ create_merge_request(3)
+
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(8.minutes, 1, 2)
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(16.minutes, 3, 3)
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb
index 77b07cf1ac9..ec2e7d672f0 100644
--- a/spec/models/appearance_spec.rb
+++ b/spec/models/appearance_spec.rb
@@ -20,10 +20,40 @@ describe Appearance do
end
context 'with uploads' do
- it_behaves_like 'model with mounted uploader', false do
+ it_behaves_like 'model with uploads', false do
let(:model_object) { create(:appearance, :with_logo) }
let(:upload_attribute) { :logo }
let(:uploader_class) { AttachmentUploader }
end
end
+
+ shared_examples 'logo paths' do |logo_type|
+ let(:appearance) { create(:appearance, "with_#{logo_type}".to_sym) }
+ let(:filename) { 'dk.png' }
+ let(:expected_path) { "/uploads/-/system/appearance/#{logo_type}/#{appearance.id}/#{filename}" }
+
+ it 'returns nil when there is no upload' do
+ expect(subject.send("#{logo_type}_path")).to be_nil
+ end
+
+ it 'returns a local path using the system route' do
+ expect(appearance.send("#{logo_type}_path")).to eq(expected_path)
+ end
+
+ describe 'with asset host configured' do
+ let(:asset_host) { 'https://gitlab-assets.example.com' }
+
+ before do
+ allow(ActionController::Base).to receive(:asset_host) { asset_host }
+ end
+
+ it 'returns a full URL with the system path' do
+ expect(appearance.send("#{logo_type}_path")).to eq("#{asset_host}#{expected_path}")
+ end
+ end
+ end
+
+ %i(logo header_logo favicon).each do |logo_type|
+ it_behaves_like 'logo paths', logo_type
+ end
end
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index 5326f9cb8c0..89839709131 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -49,7 +49,7 @@ describe BroadcastMessage do
it 'caches the output of the query' do
create(:broadcast_message)
- expect(described_class).to receive(:where).and_call_original.once
+ expect(described_class).to receive(:current_and_future_messages).and_call_original.once
described_class.current
@@ -58,6 +58,12 @@ describe BroadcastMessage do
end
end
+ it 'does not create new records' do
+ create(:broadcast_message)
+
+ expect { described_class.current }.not_to change { described_class.count }
+ end
+
it 'includes messages that need to be displayed in the future' do
create(:broadcast_message)
@@ -77,7 +83,14 @@ describe BroadcastMessage do
it 'does not clear the cache if only a future message should be displayed' do
create(:broadcast_message, :future)
- expect(Rails.cache).not_to receive(:delete)
+ expect(Rails.cache).not_to receive(:delete).with(described_class::CACHE_KEY)
+ expect(described_class.current.length).to eq(0)
+ end
+
+ it 'clears the legacy cache key' do
+ create(:broadcast_message, :future)
+
+ expect(Rails.cache).to receive(:delete).with(described_class::LEGACY_CACHE_KEY)
expect(described_class.current.length).to eq(0)
end
end
@@ -143,6 +156,7 @@ describe BroadcastMessage do
message = create(:broadcast_message)
expect(Rails.cache).to receive(:delete).with(described_class::CACHE_KEY)
+ expect(Rails.cache).to receive(:delete).with(described_class::LEGACY_CACHE_KEY)
message.flush_redis_cache
end
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb
new file mode 100644
index 00000000000..741cdfef1a5
--- /dev/null
+++ b/spec/models/ci/bridge_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe Ci::Bridge do
+ set(:project) { create(:project) }
+ set(:pipeline) { create(:ci_pipeline, project: project) }
+
+ let(:bridge) do
+ create(:ci_bridge, pipeline: pipeline)
+ end
+
+ describe '#tags' do
+ it 'only has a bridge tag' do
+ expect(bridge.tags).to eq [:bridge]
+ end
+ end
+
+ describe '#detailed_status' do
+ let(:user) { create(:user) }
+ let(:status) { bridge.detailed_status(user) }
+
+ it 'returns detailed status object' do
+ expect(status).to be_a Gitlab::Ci::Status::Success
+ end
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 4cdcae5f670..fe7f5f8e1e3 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1925,7 +1925,7 @@ describe Ci::Build do
context 'when token is empty' do
before do
- build.token = nil
+ build.update_columns(token: nil, token_encrypted: nil)
end
it { is_expected.to be_nil}
@@ -2114,6 +2114,7 @@ describe Ci::Build do
{ key: 'CI_JOB_NAME', value: 'test', public: true },
{ key: 'CI_JOB_STAGE', value: 'test', public: true },
{ key: 'CI_COMMIT_SHA', value: build.sha, public: true },
+ { key: 'CI_COMMIT_SHORT_SHA', value: build.short_sha, public: true },
{ key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true },
{ key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true },
{ key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true },
@@ -2141,7 +2142,7 @@ describe Ci::Build do
end
before do
- build.token = 'my-token'
+ build.set_token('my-token')
build.yaml_variables = []
end
@@ -2725,6 +2726,7 @@ describe Ci::Build do
it 'returns static predefined variables' do
keys = %w[CI_JOB_NAME
CI_COMMIT_SHA
+ CI_COMMIT_SHORT_SHA
CI_COMMIT_REF_NAME
CI_COMMIT_REF_SLUG
CI_JOB_STAGE]
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index ba9540c84d4..b67c6a4cffa 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -350,6 +350,50 @@ describe Ci::Pipeline, :mailer do
CI_COMMIT_TITLE
CI_COMMIT_DESCRIPTION]
end
+
+ context 'when source is merge request' do
+ let(:pipeline) do
+ create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ end
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: 'feature',
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ it 'exposes merge request pipeline variables' do
+ expect(subject.to_hash)
+ .to include(
+ 'CI_MERGE_REQUEST_ID' => merge_request.id.to_s,
+ 'CI_MERGE_REQUEST_IID' => merge_request.iid.to_s,
+ 'CI_MERGE_REQUEST_REF_PATH' => merge_request.ref_path.to_s,
+ 'CI_MERGE_REQUEST_PROJECT_ID' => merge_request.project.id.to_s,
+ 'CI_MERGE_REQUEST_PROJECT_PATH' => merge_request.project.full_path,
+ 'CI_MERGE_REQUEST_PROJECT_URL' => merge_request.project.web_url,
+ 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME' => merge_request.target_branch.to_s,
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID' => merge_request.source_project.id.to_s,
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH' => merge_request.source_project.full_path,
+ 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL' => merge_request.source_project.web_url,
+ 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s)
+ end
+
+ context 'when source project does not exist' do
+ before do
+ merge_request.update_column(:source_project_id, nil)
+ end
+
+ it 'does not expose source project related variables' do
+ expect(subject.to_hash.keys).not_to include(
+ %w[CI_MERGE_REQUEST_SOURCE_PROJECT_ID
+ CI_MERGE_REQUEST_SOURCE_PROJECT_PATH
+ CI_MERGE_REQUEST_SOURCE_PROJECT_URL
+ CI_MERGE_REQUEST_SOURCE_BRANCH_NAME])
+ end
+ end
+ end
end
describe '#protected_ref?' do
diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb
index d43d88c2924..809880f5969 100644
--- a/spec/models/clusters/applications/knative_spec.rb
+++ b/spec/models/clusters/applications/knative_spec.rb
@@ -1,6 +1,9 @@
require 'rails_helper'
describe Clusters::Applications::Knative do
+ include KubernetesHelpers
+ include ReactiveCachingHelpers
+
let(:knative) { create(:clusters_applications_knative) }
include_examples 'cluster application core specs', :clusters_applications_knative
@@ -30,10 +33,10 @@ describe Clusters::Applications::Knative do
end
context 'application install previously errored with older version' do
- let(:application) { create(:clusters_applications_knative, :scheduled, version: '0.1.3') }
+ let(:application) { create(:clusters_applications_knative, :scheduled, version: '0.2.2') }
it 'updates the application version' do
- expect(application.reload.version).to eq('0.1.3')
+ expect(application.reload.version).to eq('0.2.2')
end
end
end
@@ -102,7 +105,7 @@ describe Clusters::Applications::Knative do
it 'should be initialized with knative arguments' do
expect(subject.name).to eq('knative')
expect(subject.chart).to eq('knative/knative')
- expect(subject.version).to eq('0.1.3')
+ expect(subject.version).to eq('0.2.2')
expect(subject.files).to eq(knative.files)
end
end
@@ -121,4 +124,43 @@ describe Clusters::Applications::Knative do
describe 'validations' do
it { is_expected.to validate_presence_of(:hostname) }
end
+
+ describe '#services' do
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:service) { cluster.platform_kubernetes }
+ let(:knative) { create(:clusters_applications_knative, cluster: cluster) }
+
+ let(:namespace) do
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster,
+ cluster_project: cluster.cluster_project,
+ project: cluster.cluster_project.project)
+ end
+
+ subject { knative.services }
+
+ before do
+ stub_kubeclient_discover(service.api_url)
+ stub_kubeclient_knative_services
+ end
+
+ it 'should have an unintialized cache' do
+ is_expected.to be_nil
+ end
+
+ context 'when using synchronous reactive cache' do
+ before do
+ stub_reactive_cache(knative, services: kube_response(kube_knative_services_body))
+ synchronous_reactive_cache(knative)
+ end
+
+ it 'should have cached services' do
+ is_expected.not_to be_nil
+ end
+
+ it 'should match our namespace' do
+ expect(knative.services_for(ns: namespace)).not_to be_nil
+ end
+ end
+ end
end
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index 97e50809647..47daa79873e 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -18,7 +18,7 @@ describe Clusters::Applications::Runner do
let(:application) { create(:clusters_applications_runner, :scheduled, version: '0.1.30') }
it 'updates the application version' do
- expect(application.reload.version).to eq('0.1.38')
+ expect(application.reload.version).to eq('0.1.39')
end
end
end
@@ -46,7 +46,7 @@ describe Clusters::Applications::Runner do
it 'should be initialized with 4 arguments' do
expect(subject.name).to eq('runner')
expect(subject.chart).to eq('runner/gitlab-runner')
- expect(subject.version).to eq('0.1.38')
+ expect(subject.version).to eq('0.1.39')
expect(subject).not_to be_rbac
expect(subject.repository).to eq('https://charts.gitlab.io')
expect(subject.files).to eq(gitlab_runner.files)
@@ -64,7 +64,7 @@ describe Clusters::Applications::Runner do
let(:gitlab_runner) { create(:clusters_applications_runner, :errored, runner: ci_runner, version: '0.1.13') }
it 'should be initialized with the locked version' do
- expect(subject.version).to eq('0.1.38')
+ expect(subject.version).to eq('0.1.39')
end
end
end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index 062d2fd0768..e6b076adc76 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -325,12 +325,13 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
context 'with valid pods' do
let(:pod) { kube_pod(app: environment.slug) }
+ let(:pod_with_no_terminal) { kube_pod(app: environment.slug, status: "Pending") }
let(:terminals) { kube_terminals(service, pod) }
before do
stub_reactive_cache(
service,
- pods: [pod, pod, kube_pod(app: "should-be-filtered-out")]
+ pods: [pod, pod, pod_with_no_terminal, kube_pod(app: "should-be-filtered-out")]
)
end
@@ -394,7 +395,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
context 'when namespace is updated' do
it 'should call ConfigureWorker' do
- expect(ClusterPlatformConfigureWorker).to receive(:perform_async).with(cluster.id).once
+ expect(ClusterConfigureWorker).to receive(:perform_async).with(cluster.id).once
platform.namespace = 'new-namespace'
platform.save
@@ -403,7 +404,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
context 'when namespace is not updated' do
it 'should not call ConfigureWorker' do
- expect(ClusterPlatformConfigureWorker).not_to receive(:perform_async)
+ expect(ClusterConfigureWorker).not_to receive(:perform_async)
platform.username = "new-username"
platform.save
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 2a0039a0635..a2d2d77746d 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -204,7 +204,7 @@ describe Commit do
message = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sodales id felis id blandit. Vivamus egestas lacinia lacus, sed rutrum mauris.'
allow(commit).to receive(:safe_message).and_return(message)
- expect(commit.title).to eq('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sodales id felis…')
+ expect(commit.title).to eq('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sodales id...')
end
it "truncates a message with a newline before 80 characters at the newline" do
diff --git a/spec/models/concerns/avatarable_spec.rb b/spec/models/concerns/avatarable_spec.rb
index 7d617cb7b19..1ea7f2b9985 100644
--- a/spec/models/concerns/avatarable_spec.rb
+++ b/spec/models/concerns/avatarable_spec.rb
@@ -33,6 +33,43 @@ describe Avatarable do
end
describe '#avatar_path' do
+ context 'with caching enabled', :request_store do
+ let!(:avatar_path) { [relative_url_root, project.avatar.local_url].join }
+ let!(:avatar_url) { [gitlab_host, relative_url_root, project.avatar.local_url].join }
+
+ it 'only calls local_url once' do
+ expect(project.avatar).to receive(:local_url).once.and_call_original
+
+ 2.times do
+ expect(project.avatar_path).to eq(avatar_path)
+ end
+ end
+
+ it 'calls local_url twice for path and URLs' do
+ expect(project.avatar).to receive(:local_url).exactly(2).times.and_call_original
+
+ expect(project.avatar_path(only_path: true)).to eq(avatar_path)
+ expect(project.avatar_path(only_path: false)).to eq(avatar_url)
+ end
+
+ it 'calls local_url twice for different sizes' do
+ expect(project.avatar).to receive(:local_url).exactly(2).times.and_call_original
+
+ expect(project.avatar_path).to eq(avatar_path)
+ expect(project.avatar_path(size: 40)).to eq(avatar_path + "?width=40")
+ end
+
+ it 'handles unpersisted objects' do
+ new_project = build(:project, :with_avatar)
+ path = [relative_url_root, new_project.avatar.local_url].join
+ expect(new_project.avatar).to receive(:local_url).exactly(2).times.and_call_original
+
+ 2.times do
+ expect(new_project.avatar_path).to eq(path)
+ end
+ end
+ end
+
using RSpec::Parameterized::TableSyntax
where(:has_asset_host, :visibility_level, :only_path, :avatar_path_prefix) do
diff --git a/spec/models/concerns/cacheable_attributes_spec.rb b/spec/models/concerns/cacheable_attributes_spec.rb
index 827fbc9d7d5..689e7d3058f 100644
--- a/spec/models/concerns/cacheable_attributes_spec.rb
+++ b/spec/models/concerns/cacheable_attributes_spec.rb
@@ -20,6 +20,10 @@ describe CacheableAttributes do
@_last ||= new('foo' => 'a', 'bar' => 'b')
end
+ def self.column_names
+ %w[foo bar baz]
+ end
+
attr_accessor :attributes
def initialize(attrs = {}, *)
@@ -75,13 +79,13 @@ describe CacheableAttributes do
context 'without any attributes given' do
it 'intializes a new object with the defaults' do
- expect(minimal_test_class.build_from_defaults.attributes).to eq(minimal_test_class.defaults)
+ expect(minimal_test_class.build_from_defaults.attributes).to eq(minimal_test_class.defaults.stringify_keys)
end
end
context 'with attributes given' do
it 'intializes a new object with the given attributes merged into the defaults' do
- expect(minimal_test_class.build_from_defaults(foo: 'd').attributes[:foo]).to eq('d')
+ expect(minimal_test_class.build_from_defaults(foo: 'd').attributes['foo']).to eq('d')
end
end
diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb
index b14b773b653..51221e07ca3 100644
--- a/spec/models/concerns/chronic_duration_attribute_spec.rb
+++ b/spec/models/concerns/chronic_duration_attribute_spec.rb
@@ -43,7 +43,7 @@ shared_examples 'ChronicDurationAttribute writer' do
end
it "doesn't raise exception" do
- expect { subject.send("#{virtual_field}=", '-10m') }.not_to raise_error(ChronicDuration::DurationParseError)
+ expect { subject.send("#{virtual_field}=", '-10m') }.not_to raise_error
end
it "doesn't change value" do
@@ -87,7 +87,7 @@ shared_examples 'ChronicDurationAttribute writer' do
end
it "doesn't raise exception" do
- expect { subject.send("#{virtual_field}=", nil) }.not_to raise_error(NoMethodError)
+ expect { subject.send("#{virtual_field}=", nil) }.not_to raise_error
end
end
end
diff --git a/spec/models/concerns/discussion_on_diff_spec.rb b/spec/models/concerns/discussion_on_diff_spec.rb
index 8cd129dc851..4b16e6e3902 100644
--- a/spec/models/concerns/discussion_on_diff_spec.rb
+++ b/spec/models/concerns/discussion_on_diff_spec.rb
@@ -12,6 +12,34 @@ describe DiscussionOnDiff do
expect(truncated_lines.count).to be <= DiffDiscussion::NUMBER_OF_TRUNCATED_DIFF_LINES
end
+
+ context 'with truncated diff lines diff limit set' do
+ let(:truncated_lines) do
+ subject.truncated_diff_lines(
+ diff_limit: diff_limit
+ )
+ end
+
+ context 'when diff limit is higher than default' do
+ let(:diff_limit) { DiffDiscussion::NUMBER_OF_TRUNCATED_DIFF_LINES + 1 }
+
+ it 'returns fewer lines than the default' do
+ expect(subject.diff_lines.count).to be > diff_limit
+
+ expect(truncated_lines.count).to be <= DiffDiscussion::NUMBER_OF_TRUNCATED_DIFF_LINES
+ end
+ end
+
+ context 'when diff_limit is lower than default' do
+ let(:diff_limit) { 3 }
+
+ it 'returns fewer lines than the default' do
+ expect(subject.diff_lines.count).to be > DiffDiscussion::NUMBER_OF_TRUNCATED_DIFF_LINES
+
+ expect(truncated_lines.count).to be <= diff_limit
+ end
+ end
+ end
end
context "when some diff lines are meta" do
@@ -22,11 +50,17 @@ describe DiscussionOnDiff do
end
context "when the diff line does not exist on a legacy diff note" do
+ subject { create(:legacy_diff_note_on_merge_request).to_discussion }
+
it "returns an empty array" do
- legacy_note = LegacyDiffNote.new
+ expect(truncated_lines).to eq([])
+ end
+ end
- allow(subject).to receive(:first_note).and_return(legacy_note)
+ context 'when the discussion is on an image' do
+ subject { create(:image_diff_note_on_merge_request).to_discussion }
+ it 'returns an empty array' do
expect(truncated_lines).to eq([])
end
end
diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb
index 0cdf430e9ab..55d83bc3a6b 100644
--- a/spec/models/concerns/token_authenticatable_spec.rb
+++ b/spec/models/concerns/token_authenticatable_spec.rb
@@ -351,3 +351,89 @@ describe PersonalAccessToken, 'TokenAuthenticatable' do
end
end
end
+
+describe Ci::Build, 'TokenAuthenticatable' do
+ let(:token_field) { :token }
+ let(:build) { FactoryBot.build(:ci_build) }
+
+ it_behaves_like 'TokenAuthenticatable'
+
+ describe 'generating new token' do
+ context 'token is not generated yet' do
+ describe 'token field accessor' do
+ it 'makes it possible to access token' do
+ expect(build.token).to be_nil
+
+ build.save!
+
+ expect(build.token).to be_present
+ end
+ end
+
+ describe "ensure_token" do
+ subject { build.ensure_token }
+
+ it { is_expected.to be_a String }
+ it { is_expected.not_to be_blank }
+
+ it 'does not persist token' do
+ expect(build).not_to be_persisted
+ end
+ end
+
+ describe 'ensure_token!' do
+ it 'persists a new token' do
+ expect(build.ensure_token!).to eq build.reload.token
+ expect(build).to be_persisted
+ end
+
+ it 'persists new token as an encrypted string' do
+ build.ensure_token!
+
+ encrypted = Gitlab::CryptoHelper.aes256_gcm_encrypt(build.token)
+
+ expect(build.read_attribute('token_encrypted')).to eq encrypted
+ end
+
+ it 'does not persist a token in a clear text' do
+ build.ensure_token!
+
+ expect(build.read_attribute('token')).to be_nil
+ end
+ end
+ end
+
+ describe '#reset_token!' do
+ it 'persists a new token' do
+ build.save!
+
+ build.token.yield_self do |previous_token|
+ build.reset_token!
+
+ expect(build.token).not_to eq previous_token
+ expect(build.token).to be_a String
+ end
+ end
+ end
+ end
+
+ describe 'setting a new token' do
+ subject { build.set_token('0123456789') }
+
+ it 'returns the token' do
+ expect(subject).to eq '0123456789'
+ end
+
+ it 'writes a new encrypted token' do
+ expect(build.read_attribute('token_encrypted')).to be_nil
+ expect(subject).to eq '0123456789'
+ expect(build.read_attribute('token_encrypted')).to be_present
+ end
+
+ it 'does not write a new cleartext token' do
+ expect(build.read_attribute('token')).to be_nil
+ expect(subject).to eq '0123456789'
+ expect(build.read_attribute('token')).to be_nil
+ end
+ end
+end
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index 8624f0daa4d..fda00a693f0 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -318,25 +318,28 @@ describe DiffNote do
end
end
- describe "image diff notes" do
- let(:path) { "files/images/any_image.png" }
+ describe '#supports_suggestion?' do
+ context 'when noteable does not support suggestions' do
+ it 'returns false' do
+ allow(subject.noteable).to receive(:supports_suggestion?) { false }
- let!(:position) do
- Gitlab::Diff::Position.new(
- old_path: path,
- new_path: path,
- width: 10,
- height: 10,
- x: 1,
- y: 1,
- diff_refs: merge_request.diff_refs,
- position_type: "image"
- )
+ expect(subject.supports_suggestion?).to be(false)
+ end
end
- describe "validations" do
- subject { build(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) }
+ context 'when line is not suggestible' do
+ it 'returns false' do
+ allow_any_instance_of(Gitlab::Diff::Line).to receive(:suggestible?) { false }
+
+ expect(subject.supports_suggestion?).to be(false)
+ end
+ end
+ end
+ describe "image diff notes" do
+ subject { build(:image_diff_note_on_merge_request, project: project, noteable: merge_request) }
+
+ describe "validations" do
it { is_expected.not_to validate_presence_of(:line_code) }
it "does not validate diff line" do
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 0c3a49cd0f2..e63881242f6 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -76,7 +76,7 @@ describe Group do
before do
group.add_developer(user)
- sub_group.add_developer(user)
+ sub_group.add_maintainer(user)
end
it 'also gets notification settings from parent groups' do
@@ -498,7 +498,7 @@ describe Group do
it 'returns member users on every nest level without duplication' do
group.add_developer(user_a)
nested_group.add_developer(user_b)
- deep_nested_group.add_developer(user_a)
+ deep_nested_group.add_maintainer(user_a)
expect(group.users_with_descendants).to contain_exactly(user_a, user_b)
expect(nested_group.users_with_descendants).to contain_exactly(user_a, user_b)
@@ -739,7 +739,7 @@ describe Group do
end
context 'with uploads' do
- it_behaves_like 'model with mounted uploader', true do
+ it_behaves_like 'model with uploads', true do
let(:model_object) { create(:group, :with_avatar) }
let(:upload_attribute) { :avatar }
let(:uploader_class) { AttachmentUploader }
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index fca1b1f90d9..188beac1582 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -53,6 +53,29 @@ describe Member do
expect(member).to be_valid
end
end
+
+ context "when a child member inherits its access level" do
+ let(:user) { create(:user) }
+ let(:member) { create(:group_member, :developer, user: user) }
+ let(:child_group) { create(:group, parent: member.group) }
+ let(:child_member) { build(:group_member, group: child_group, user: user) }
+
+ it "requires a higher level" do
+ child_member.access_level = GroupMember::REPORTER
+
+ child_member.validate
+
+ expect(child_member).not_to be_valid
+ end
+
+ it "is valid with a higher level" do
+ child_member.access_level = GroupMember::MAINTAINER
+
+ child_member.validate
+
+ expect(child_member).to be_valid
+ end
+ end
end
describe 'Scopes & finders' do
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index 97959ed4304..a3451c67bd8 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -50,4 +50,26 @@ describe GroupMember do
group_member.destroy
end
end
+
+ context 'access levels', :nested_groups do
+ context 'with parent group' do
+ it_behaves_like 'inherited access level as a member of entity' do
+ let(:entity) { create(:group, parent: parent_entity) }
+ end
+ end
+
+ context 'with parent group and a sub subgroup' do
+ it_behaves_like 'inherited access level as a member of entity' do
+ let(:subgroup) { create(:group, parent: parent_entity) }
+ let(:entity) { create(:group, parent: subgroup) }
+ end
+
+ context 'when only the subgroup has the member' do
+ it_behaves_like 'inherited access level as a member of entity' do
+ let(:parent_entity) { create(:group, parent: create(:group)) }
+ let(:entity) { create(:group, parent: parent_entity) }
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 334d4f95f53..99d3ab41b97 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -11,10 +11,6 @@ describe ProjectMember do
it { is_expected.to validate_inclusion_of(:access_level).in_array(Gitlab::Access.values) }
end
- describe 'modules' do
- it { is_expected.to include_module(Gitlab::ShellAdapter) }
- end
-
describe '.access_level_roles' do
it 'returns Gitlab::Access.options' do
expect(described_class.access_level_roles).to eq(Gitlab::Access.options)
@@ -124,4 +120,19 @@ describe ProjectMember do
end
it_behaves_like 'members notifications', :project
+
+ context 'access levels' do
+ context 'with parent group' do
+ it_behaves_like 'inherited access level as a member of entity' do
+ let(:entity) { create(:project, group: parent_entity) }
+ end
+ end
+
+ context 'with parent group and a subgroup', :nested_groups do
+ it_behaves_like 'inherited access level as a member of entity' do
+ let(:subgroup) { create(:group, parent: parent_entity) }
+ let(:entity) { create(:project, group: subgroup) }
+ end
+ end
+ end
end
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index cbe60b3a4a5..33e984dc399 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -105,7 +105,7 @@ describe MergeRequestDiff do
context 'when the raw diffs are empty' do
before do
- MergeRequestDiffFile.delete_all(merge_request_diff_id: diff_with_commits.id)
+ MergeRequestDiffFile.where(merge_request_diff_id: diff_with_commits.id).delete_all
end
it 'returns an empty DiffCollection' do
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 9b60054e14a..4cc3a6a3644 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -559,6 +559,57 @@ describe MergeRequest do
end
end
+ describe '#preload_discussions_diff_highlight' do
+ let(:merge_request) { create(:merge_request) }
+
+ context 'with commit diff note' do
+ let(:other_merge_request) { create(:merge_request) }
+
+ let!(:diff_note) do
+ create(:diff_note_on_commit, project: merge_request.project)
+ end
+
+ let!(:other_mr_diff_note) do
+ create(:diff_note_on_commit, project: other_merge_request.project)
+ end
+
+ it 'preloads diff highlighting' do
+ expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection|
+ note_diff_file = diff_note.note_diff_file
+
+ expect(collection)
+ .to receive(:load_highlight)
+ .with([note_diff_file.id]).and_call_original
+ end
+
+ merge_request.preload_discussions_diff_highlight
+ end
+ end
+
+ context 'with merge request diff note' do
+ let!(:unresolved_diff_note) do
+ create(:diff_note_on_merge_request, project: merge_request.project, noteable: merge_request)
+ end
+
+ let!(:resolved_diff_note) do
+ create(:diff_note_on_merge_request, :resolved, project: merge_request.project, noteable: merge_request)
+ end
+
+ it 'preloads diff highlighting' do
+ expect_next_instance_of(Gitlab::DiscussionsDiff::FileCollection) do |collection|
+ note_diff_file = unresolved_diff_note.note_diff_file
+
+ expect(collection)
+ .to receive(:load_highlight)
+ .with([note_diff_file.id])
+ .and_call_original
+ end
+
+ merge_request.preload_discussions_diff_highlight
+ end
+ end
+ end
+
describe '#diff_size' do
let(:merge_request) do
build(:merge_request, source_branch: 'expand-collapse-files', target_branch: 'master')
@@ -1339,6 +1390,30 @@ describe MergeRequest do
end
end
+ describe '#calculate_reactive_cache' do
+ let(:project) { create(:project, :repository) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ subject { merge_request.calculate_reactive_cache(service_class_name) }
+
+ context 'when given an unknown service class name' do
+ let(:service_class_name) { 'Integer' }
+
+ it 'raises a NameError exception' do
+ expect { subject }.to raise_error(NameError, service_class_name)
+ end
+ end
+
+ context 'when given a known service class name' do
+ let(:service_class_name) { 'Ci::CompareTestReportsService' }
+
+ it 'does not raises a NameError exception' do
+ allow_any_instance_of(service_class_name.constantize).to receive(:execute).and_return(nil)
+
+ expect { subject }.not_to raise_error
+ end
+ end
+ end
+
describe '#compare_test_reports' do
subject { merge_request.compare_test_reports }
@@ -1885,7 +1960,7 @@ describe MergeRequest do
allow(subject).to receive(:head_pipeline) { nil }
end
- it { expect(subject.mergeable_ci_state?).to be_falsey }
+ it { expect(subject.mergeable_ci_state?).to be_truthy }
end
end
@@ -2068,7 +2143,7 @@ describe MergeRequest do
head_commit_sha: commit.sha
)
- subject.merge_request_diff(true)
+ subject.reload_merge_request_diff
end
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index d11eb46159e..b3d31e65c85 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -316,6 +316,15 @@ describe Milestone do
end
end
+ describe '#reference_link_text' do
+ let(:project) { build_stubbed(:project, name: 'sample-project') }
+ let(:milestone) { build_stubbed(:milestone, iid: 1, project: project, name: 'milestone') }
+
+ it 'returns the title with the reference prefix' do
+ expect(milestone.reference_link_text).to eq '%milestone'
+ end
+ end
+
describe '#participants' do
let(:project) { build(:project, name: 'sample-project') }
let(:milestone) { build(:milestone, iid: 1, project: project) }
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 6ee19c0ddf4..475fbe56e4d 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -249,7 +249,7 @@ describe Namespace do
move_dir_result
end
- expect(Gitlab::Sentry).to receive(:should_raise?).and_return(false) # like prod
+ expect(Gitlab::Sentry).to receive(:should_raise_for_dev?).and_return(false) # like prod
namespace.update(path: namespace.full_path + '_new')
end
@@ -337,32 +337,40 @@ describe Namespace do
end
end
- it 'updates project full path in .git/config for each project inside namespace' do
- parent = create(:group, name: 'mygroup', path: 'mygroup')
- subgroup = create(:group, name: 'mysubgroup', path: 'mysubgroup', parent: parent)
- project_in_parent_group = create(:project, :legacy_storage, :repository, namespace: parent, name: 'foo1')
- hashed_project_in_subgroup = create(:project, :repository, namespace: subgroup, name: 'foo2')
- legacy_project_in_subgroup = create(:project, :legacy_storage, :repository, namespace: subgroup, name: 'foo3')
+ context 'for each project inside the namespace' do
+ let!(:parent) { create(:group, name: 'mygroup', path: 'mygroup') }
+ let!(:subgroup) { create(:group, name: 'mysubgroup', path: 'mysubgroup', parent: parent) }
+ let!(:project_in_parent_group) { create(:project, :legacy_storage, :repository, namespace: parent, name: 'foo1') }
+ let!(:hashed_project_in_subgroup) { create(:project, :repository, namespace: subgroup, name: 'foo2') }
+ let!(:legacy_project_in_subgroup) { create(:project, :legacy_storage, :repository, namespace: subgroup, name: 'foo3') }
- parent.update(path: 'mygroup_new')
+ it 'updates project full path in .git/config' do
+ parent.update(path: 'mygroup_new')
- # Routes are loaded when creating the projects, so we need to manually
- # reload them for the below code to be aware of the above UPDATE.
- [
- project_in_parent_group,
- hashed_project_in_subgroup,
- legacy_project_in_subgroup
- ].each do |project|
- project.route.reload
+ expect(project_rugged(project_in_parent_group).config['gitlab.fullpath']).to eq "mygroup_new/#{project_in_parent_group.path}"
+ expect(project_rugged(hashed_project_in_subgroup).config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{hashed_project_in_subgroup.path}"
+ expect(project_rugged(legacy_project_in_subgroup).config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{legacy_project_in_subgroup.path}"
end
- expect(project_rugged(project_in_parent_group).config['gitlab.fullpath']).to eq "mygroup_new/#{project_in_parent_group.path}"
- expect(project_rugged(hashed_project_in_subgroup).config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{hashed_project_in_subgroup.path}"
- expect(project_rugged(legacy_project_in_subgroup).config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{legacy_project_in_subgroup.path}"
- end
+ it 'updates the project storage location' do
+ repository_project_in_parent_group = create(:project_repository, project: project_in_parent_group)
+ repository_hashed_project_in_subgroup = create(:project_repository, project: hashed_project_in_subgroup)
+ repository_legacy_project_in_subgroup = create(:project_repository, project: legacy_project_in_subgroup)
+
+ parent.update(path: 'mygroup_moved')
+
+ expect(repository_project_in_parent_group.reload.disk_path).to eq "mygroup_moved/#{project_in_parent_group.path}"
+ expect(repository_hashed_project_in_subgroup.reload.disk_path).to eq hashed_project_in_subgroup.disk_path
+ expect(repository_legacy_project_in_subgroup.reload.disk_path).to eq "mygroup_moved/mysubgroup/#{legacy_project_in_subgroup.path}"
+ end
+
+ def project_rugged(project)
+ # Routes are loaded when creating the projects, so we need to manually
+ # reload them for the below code to be aware of the above UPDATE.
+ project.route.reload
- def project_rugged(project)
- rugged_repo(project.repository)
+ rugged_repo(project.repository)
+ end
end
end
@@ -538,7 +546,7 @@ describe Namespace do
it 'returns member users on every nest level without duplication' do
group.add_developer(user_a)
nested_group.add_developer(user_b)
- deep_nested_group.add_developer(user_a)
+ deep_nested_group.add_maintainer(user_a)
expect(group.users_with_descendants).to contain_exactly(user_a, user_b)
expect(nested_group.users_with_descendants).to contain_exactly(user_a, user_b)
diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb
index 771d834c4bc..c8ab564e3bc 100644
--- a/spec/models/notification_setting_spec.rb
+++ b/spec/models/notification_setting_spec.rb
@@ -42,12 +42,7 @@ RSpec.describe NotificationSetting do
expect(notification_setting.new_issue).to eq(true)
expect(notification_setting.close_issue).to eq(true)
expect(notification_setting.merge_merge_request).to eq(true)
-
- # In Rails 5 assigning a value which is not explicitly `true` or `false` ("nil" in this case)
- # to a boolean column transforms it to `true`.
- # In Rails 4 it transforms the value to `false` with deprecation warning.
- # Replace `eq(Gitlab.rails5?)` with `eq(true)` when removing rails5? code.
- expect(notification_setting.close_merge_request).to eq(Gitlab.rails5?)
+ expect(notification_setting.close_merge_request).to eq(true)
expect(notification_setting.reopen_merge_request).to eq(false)
end
end
diff --git a/spec/models/pool_repository_spec.rb b/spec/models/pool_repository_spec.rb
index 541e78507e5..112d4ab56fc 100644
--- a/spec/models/pool_repository_spec.rb
+++ b/spec/models/pool_repository_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
describe PoolRepository do
describe 'associations' do
it { is_expected.to belong_to(:shard) }
+ it { is_expected.to have_one(:source_project) }
it { is_expected.to have_many(:member_projects) }
end
@@ -12,15 +13,35 @@ describe PoolRepository do
let!(:pool_repository) { create(:pool_repository) }
it { is_expected.to validate_presence_of(:shard) }
+ it { is_expected.to validate_presence_of(:source_project) }
end
describe '#disk_path' do
it 'sets the hashed disk_path' do
pool = create(:pool_repository)
- elements = File.split(pool.disk_path)
+ expect(pool.disk_path).to match(%r{\A@pools/\h{2}/\h{2}/\h{64}})
+ end
+ end
+
+ describe '#unlink_repository' do
+ let(:pool) { create(:pool_repository, :ready) }
+
+ context 'when the last member leaves' do
+ it 'schedules pool removal' do
+ expect(::ObjectPool::DestroyWorker).to receive(:perform_async).with(pool.id).and_call_original
+
+ pool.unlink_repository(pool.source_project.repository)
+ end
+ end
+
+ context 'when the second member leaves' do
+ it 'does not schedule pool removal' do
+ create(:project, :repository, pool_repository: pool)
+ expect(::ObjectPool::DestroyWorker).not_to receive(:perform_async).with(pool.id)
- expect(elements).to all( match(/\d{2,}/) )
+ pool.unlink_repository(pool.source_project.repository)
+ end
end
end
end
diff --git a/spec/models/project_import_data_spec.rb b/spec/models/project_import_data_spec.rb
new file mode 100644
index 00000000000..e9910c0a5d1
--- /dev/null
+++ b/spec/models/project_import_data_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ProjectImportData do
+ describe '#merge_data' do
+ it 'writes the Hash to the attribute if it is nil' do
+ row = described_class.new
+
+ row.merge_data('number' => 10)
+
+ expect(row.data).to eq({ 'number' => 10 })
+ end
+
+ it 'merges the Hash into an existing Hash if one was present' do
+ row = described_class.new(data: { 'number' => 10 })
+
+ row.merge_data('foo' => 'bar')
+
+ expect(row.data).to eq({ 'number' => 10, 'foo' => 'bar' })
+ end
+ end
+
+ describe '#merge_credentials' do
+ it 'writes the Hash to the attribute if it is nil' do
+ row = described_class.new
+
+ row.merge_credentials('number' => 10)
+
+ expect(row.credentials).to eq({ 'number' => 10 })
+ end
+
+ it 'merges the Hash into an existing Hash if one was present' do
+ row = described_class.new
+
+ row.credentials = { 'number' => 10 }
+ row.merge_credentials('foo' => 'bar')
+
+ expect(row.credentials).to eq({ 'number' => 10, 'foo' => 'bar' })
+ end
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 50920d9d1fc..a01f76a5bab 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -145,25 +145,10 @@ describe Project do
end
describe 'ci_pipelines association' do
- context 'when feature flag pipeline_ci_sources_only is enabled' do
- it 'returns only pipelines from ci_sources' do
- stub_feature_flags(pipeline_ci_sources_only: true)
+ it 'returns only pipelines from ci_sources' do
+ expect(Ci::Pipeline).to receive(:ci_sources).and_call_original
- expect(Ci::Pipeline).to receive(:ci_sources).and_call_original
-
- subject.ci_pipelines
- end
- end
-
- context 'when feature flag pipeline_ci_sources_only is disabled' do
- it 'returns all pipelines' do
- stub_feature_flags(pipeline_ci_sources_only: false)
-
- expect(Ci::Pipeline).not_to receive(:ci_sources).and_call_original
- expect(Ci::Pipeline).to receive(:all).and_call_original.at_least(:once)
-
- subject.ci_pipelines
- end
+ subject.ci_pipelines
end
end
end
@@ -1666,26 +1651,54 @@ describe Project do
end
describe '#track_project_repository' do
- let(:project) { create(:project, :repository) }
+ shared_examples 'tracks storage location' do
+ context 'when a project repository entry does not exist' do
+ it 'creates a new entry' do
+ expect { project.track_project_repository }.to change(project, :project_repository)
+ end
+
+ it 'tracks the project storage location' do
+ project.track_project_repository
+
+ expect(project.project_repository).to have_attributes(
+ disk_path: project.disk_path,
+ shard_name: project.repository_storage
+ )
+ end
+ end
+
+ context 'when a tracking entry exists' do
+ let!(:project_repository) { create(:project_repository, project: project) }
+ let!(:shard) { create(:shard, name: 'foo') }
+
+ it 'does not create a new entry in the database' do
+ expect { project.track_project_repository }.not_to change(project, :project_repository)
+ end
+
+ it 'updates the project storage location' do
+ allow(project).to receive(:disk_path).and_return('fancy/new/path')
+ allow(project).to receive(:repository_storage).and_return('foo')
- it 'creates a project_repository' do
- project.track_project_repository
+ project.track_project_repository
- expect(project.reload.project_repository).to be_present
- expect(project.project_repository.disk_path).to eq(project.disk_path)
- expect(project.project_repository.shard_name).to eq(project.repository_storage)
+ expect(project.project_repository).to have_attributes(
+ disk_path: 'fancy/new/path',
+ shard_name: 'foo'
+ )
+ end
+ end
end
- it 'updates the project_repository' do
- project.track_project_repository
+ context 'with projects on legacy storage' do
+ let(:project) { create(:project, :repository, :legacy_storage) }
- allow(project).to receive(:disk_path).and_return('@fancy/new/path')
+ it_behaves_like 'tracks storage location'
+ end
- expect do
- project.track_project_repository
- end.not_to change(ProjectRepository, :count)
+ context 'with projects on hashed storage' do
+ let(:project) { create(:project, :repository) }
- expect(project.reload.project_repository.disk_path).to eq(project.disk_path)
+ it_behaves_like 'tracks storage location'
end
end
@@ -1897,7 +1910,7 @@ describe Project do
end
end
- describe '#latest_successful_builds_for' do
+ describe '#latest_successful_builds_for and #latest_successful_build_for' do
def create_pipeline(status = 'success')
create(:ci_pipeline, project: project,
sha: project.commit.sha,
@@ -1919,14 +1932,16 @@ describe Project do
it 'gives the latest builds from latest pipeline' do
pipeline1 = create_pipeline
pipeline2 = create_pipeline
- build1_p2 = create_build(pipeline2, 'test')
create_build(pipeline1, 'test')
create_build(pipeline1, 'test2')
+ build1_p2 = create_build(pipeline2, 'test')
build2_p2 = create_build(pipeline2, 'test2')
latest_builds = project.latest_successful_builds_for
+ single_build = project.latest_successful_build_for(build1_p2.name)
expect(latest_builds).to contain_exactly(build2_p2, build1_p2)
+ expect(single_build).to eq(build1_p2)
end
end
@@ -1936,16 +1951,22 @@ describe Project do
context 'standalone pipeline' do
it 'returns builds for ref for default_branch' do
builds = project.latest_successful_builds_for
+ single_build = project.latest_successful_build_for(build.name)
expect(builds).to contain_exactly(build)
+ expect(single_build).to eq(build)
end
- it 'returns empty relation if the build cannot be found' do
+ it 'returns empty relation if the build cannot be found for #latest_successful_builds_for' do
builds = project.latest_successful_builds_for('TAIL')
expect(builds).to be_kind_of(ActiveRecord::Relation)
expect(builds).to be_empty
end
+
+ it 'returns exception if the build cannot be found for #latest_successful_build_for' do
+ expect { project.latest_successful_build_for(build.name, 'TAIL') }.to raise_error(ActiveRecord::RecordNotFound)
+ end
end
context 'with some pending pipeline' do
@@ -1954,9 +1975,11 @@ describe Project do
end
it 'gives the latest build from latest pipeline' do
- latest_build = project.latest_successful_builds_for
+ latest_builds = project.latest_successful_builds_for
+ last_single_build = project.latest_successful_build_for(build.name)
- expect(latest_build).to contain_exactly(build)
+ expect(latest_builds).to contain_exactly(build)
+ expect(last_single_build).to eq(build)
end
end
end
@@ -3680,7 +3703,7 @@ describe Project do
expect(project.badges.count).to eq 3
end
- if Group.supports_nested_groups?
+ if Group.supports_nested_objects?
context 'with nested_groups' do
let(:parent_group) { create(:group) }
@@ -3898,7 +3921,7 @@ describe Project do
end
context 'with uploads' do
- it_behaves_like 'model with mounted uploader', true do
+ it_behaves_like 'model with uploads', true do
let(:model_object) { create(:project, :with_avatar) }
let(:upload_attribute) { :avatar }
let(:uploader_class) { AttachmentUploader }
@@ -4092,6 +4115,67 @@ describe Project do
end
end
+ describe '#object_pool_params' do
+ let(:project) { create(:project, :repository, :public) }
+
+ subject { project.object_pool_params }
+
+ before do
+ stub_application_setting(hashed_storage_enabled: true)
+ end
+
+ context 'when the objects cannot be pooled' do
+ let(:project) { create(:project, :repository, :private) }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'when a pool is created' do
+ it 'returns that pool repository' do
+ expect(subject).not_to be_empty
+ expect(subject[:pool_repository]).to be_persisted
+ end
+ end
+ end
+
+ 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) }
+
+ 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) }
+
+ 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
+
+ context 'when objects are poolable' do
+ let(:project) { create(:project, :repository, :public) }
+
+ before do
+ stub_application_setting(hashed_storage_enabled: true)
+ end
+
+ it { is_expected.to be_git_objects_poolable }
+ end
+ end
+ end
+
def rugged_config
rugged_repo(project.repository).config
end
diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb
index 3f86347c3ae..51725eeacac 100644
--- a/spec/models/release_spec.rb
+++ b/spec/models/release_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Release do
describe 'associations' do
it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:author).class_name('User') }
end
describe 'validation' do
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index b12ca79847c..5d3c25062d5 100644
--- a/spec/models/remote_mirror_spec.rb
+++ b/spec/models/remote_mirror_spec.rb
@@ -1,6 +1,6 @@
require 'rails_helper'
-describe RemoteMirror do
+describe RemoteMirror, :mailer do
include GitHelpers
describe 'URL validation' do
@@ -137,6 +137,43 @@ describe RemoteMirror do
end
end
+ describe '#mark_as_failed' do
+ let(:remote_mirror) { create(:remote_mirror) }
+ let(:error_message) { 'http://user:pass@test.com/root/repoC.git/' }
+ let(:sanitized_error_message) { 'http://*****:*****@test.com/root/repoC.git/' }
+
+ subject do
+ remote_mirror.update_start
+ remote_mirror.mark_as_failed(error_message)
+ end
+
+ it 'sets the update_status to failed' do
+ subject
+
+ expect(remote_mirror.reload.update_status).to eq('failed')
+ end
+
+ it 'saves the sanitized error' do
+ subject
+
+ expect(remote_mirror.last_error).to eq(sanitized_error_message)
+ end
+
+ context 'notifications' do
+ let(:user) { create(:user) }
+
+ before do
+ remote_mirror.project.add_maintainer(user)
+ end
+
+ it 'notifies the project maintainers' do
+ perform_enqueued_jobs { subject }
+
+ should_email(user)
+ end
+ end
+ end
+
context 'when remote mirror gets destroyed' do
it 'removes remote' do
mirror = create_mirror(url: 'http://foo:bar@test.com')
diff --git a/spec/models/suggestion_spec.rb b/spec/models/suggestion_spec.rb
new file mode 100644
index 00000000000..cafc725dddb
--- /dev/null
+++ b/spec/models/suggestion_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Suggestion do
+ let(:suggestion) { create(:suggestion) }
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:note) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:note) }
+
+ context 'when suggestion is applied' do
+ before do
+ allow(subject).to receive(:applied?).and_return(true)
+ end
+
+ it { is_expected.to validate_presence_of(:commit_id) }
+ end
+ end
+
+ describe '#appliable?' do
+ context 'when note does not support suggestions' do
+ it 'returns false' do
+ expect_next_instance_of(DiffNote) do |note|
+ allow(note).to receive(:supports_suggestion?) { false }
+ end
+
+ expect(suggestion).not_to be_appliable
+ end
+ end
+
+ context 'when patch is already applied' do
+ let(:suggestion) { create(:suggestion, :applied) }
+
+ it 'returns false' do
+ expect(suggestion).not_to be_appliable
+ end
+ end
+
+ context 'when merge request is not opened' do
+ let(:merge_request) { create(:merge_request, :merged) }
+ let(:note) do
+ create(:diff_note_on_merge_request, project: merge_request.project,
+ noteable: merge_request)
+ end
+
+ let(:suggestion) { create(:suggestion, note: note) }
+
+ it 'returns false' do
+ expect(suggestion).not_to be_appliable
+ end
+ end
+ end
+end
diff --git a/spec/models/uploads/fog_spec.rb b/spec/models/uploads/fog_spec.rb
new file mode 100644
index 00000000000..4a44cf5ab0f
--- /dev/null
+++ b/spec/models/uploads/fog_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Uploads::Fog do
+ let(:data_store) { described_class.new }
+
+ before do
+ stub_uploads_object_storage(FileUploader)
+ end
+
+ describe '#available?' do
+ subject { data_store.available? }
+
+ context 'when object storage is enabled' do
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when object storage is disabled' do
+ before do
+ stub_uploads_object_storage(FileUploader, enabled: false)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ context 'model with uploads' do
+ let(:project) { create(:project) }
+ let(:relation) { project.uploads }
+
+ describe '#keys' do
+ let!(:uploads) { create_list(:upload, 2, :object_storage, uploader: FileUploader, model: project) }
+ subject { data_store.keys(relation) }
+
+ it 'returns keys' do
+ is_expected.to match_array(relation.pluck(:path))
+ end
+ end
+
+ describe '#delete_keys' do
+ let(:keys) { data_store.keys(relation) }
+ let!(:uploads) { create_list(:upload, 2, :with_file, :issuable_upload, model: project) }
+ subject { data_store.delete_keys(keys) }
+
+ before do
+ uploads.each { |upload| upload.build_uploader.migrate!(2) }
+ end
+
+ it 'deletes multiple data' do
+ paths = relation.pluck(:path)
+
+ ::Fog::Storage.new(FileUploader.object_store_credentials).tap do |connection|
+ paths.each do |path|
+ expect(connection.get_object('uploads', path)[:body]).not_to be_nil
+ end
+ end
+
+ subject
+
+ ::Fog::Storage.new(FileUploader.object_store_credentials).tap do |connection|
+ paths.each do |path|
+ expect { connection.get_object('uploads', path)[:body] }.to raise_error(Excon::Error::NotFound)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/uploads/local_spec.rb b/spec/models/uploads/local_spec.rb
new file mode 100644
index 00000000000..3468399f370
--- /dev/null
+++ b/spec/models/uploads/local_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Uploads::Local do
+ let(:data_store) { described_class.new }
+
+ before do
+ stub_uploads_object_storage(FileUploader)
+ end
+
+ context 'model with uploads' do
+ let(:project) { create(:project) }
+ let(:relation) { project.uploads }
+
+ describe '#keys' do
+ let!(:uploads) { create_list(:upload, 2, uploader: FileUploader, model: project) }
+ subject { data_store.keys(relation) }
+
+ it 'returns keys' do
+ is_expected.to match_array(relation.map(&:absolute_path))
+ end
+ end
+
+ describe '#delete_keys' do
+ let(:keys) { data_store.keys(relation) }
+ let!(:uploads) { create_list(:upload, 2, :with_file, :issuable_upload, model: project) }
+ subject { data_store.delete_keys(keys) }
+
+ it 'deletes multiple data' do
+ paths = relation.map(&:absolute_path)
+
+ paths.each do |path|
+ expect(File.exist?(path)).to be_truthy
+ end
+
+ subject
+
+ paths.each do |path|
+ expect(File.exist?(path)).to be_falsey
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index e5490e0a156..33842e74b92 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -45,6 +45,7 @@ describe User do
it { is_expected.to have_many(:uploads) }
it { is_expected.to have_many(:reported_abuse_reports).dependent(:destroy).class_name('AbuseReport') }
it { is_expected.to have_many(:custom_attributes).class_name('UserCustomAttribute') }
+ it { is_expected.to have_many(:releases).dependent(:nullify) }
describe "#abuse_report" do
let(:current_user) { create(:user) }
@@ -1965,7 +1966,7 @@ describe User do
subject { user.membership_groups }
- if Group.supports_nested_groups?
+ if Group.supports_nested_objects?
it { is_expected.to contain_exactly parent_group, child_group }
else
it { is_expected.to contain_exactly parent_group }
@@ -2325,11 +2326,11 @@ describe User do
context 'user is member of all groups' do
before do
- group.add_owner(user)
- nested_group_1.add_owner(user)
- nested_group_1_1.add_owner(user)
- nested_group_2.add_owner(user)
- nested_group_2_1.add_owner(user)
+ group.add_reporter(user)
+ nested_group_1.add_developer(user)
+ nested_group_1_1.add_maintainer(user)
+ nested_group_2.add_developer(user)
+ nested_group_2_1.add_maintainer(user)
end
it 'returns all groups' do
@@ -2346,7 +2347,7 @@ describe User do
group.add_owner(user)
end
- if Group.supports_nested_groups?
+ if Group.supports_nested_objects?
it 'returns all groups' do
is_expected.to match_array [
group,
@@ -3231,7 +3232,7 @@ describe User do
end
context 'with uploads' do
- it_behaves_like 'model with mounted uploader', false do
+ it_behaves_like 'model with uploads', false do
let(:model_object) { create(:user, :with_avatar) }
let(:upload_attribute) { :avatar }
let(:uploader_class) { AttachmentUploader }
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index 9d0093e8159..baf21efa75c 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -147,7 +147,7 @@ describe GroupPolicy do
let(:current_user) { owner }
it do
- allow(Group).to receive(:supports_nested_groups?).and_return(true)
+ allow(Group).to receive(:supports_nested_objects?).and_return(true)
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
@@ -161,7 +161,7 @@ describe GroupPolicy do
let(:current_user) { admin }
it do
- allow(Group).to receive(:supports_nested_groups?).and_return(true)
+ allow(Group).to receive(:supports_nested_objects?).and_return(true)
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
@@ -173,7 +173,7 @@ describe GroupPolicy do
describe 'when nested group support feature is disabled' do
before do
- allow(Group).to receive(:supports_nested_groups?).and_return(false)
+ allow(Group).to receive(:supports_nested_objects?).and_return(false)
end
context 'admin' do
@@ -282,7 +282,7 @@ describe GroupPolicy do
let(:current_user) { owner }
it do
- allow(Group).to receive(:supports_nested_groups?).and_return(true)
+ allow(Group).to receive(:supports_nested_objects?).and_return(true)
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb
index 72c5eac3ede..754ba0a594c 100644
--- a/spec/presenters/clusters/cluster_presenter_spec.rb
+++ b/spec/presenters/clusters/cluster_presenter_spec.rb
@@ -4,9 +4,10 @@ describe Clusters::ClusterPresenter do
include Gitlab::Routing.url_helpers
let(:cluster) { create(:cluster, :provided_by_gcp, :project) }
+ let(:user) { create(:user) }
subject(:presenter) do
- described_class.new(cluster)
+ described_class.new(cluster, current_user: user)
end
it 'inherits from Gitlab::View::Presenter::Delegated' do
@@ -27,6 +28,129 @@ describe Clusters::ClusterPresenter do
end
end
+ describe '#item_link' do
+ let(:clusterable_presenter) { double('ClusterablePresenter', subject: clusterable) }
+
+ subject { presenter.item_link(clusterable_presenter) }
+
+ context 'for a group cluster' do
+ let(:cluster) { create(:cluster, cluster_type: :group_type, groups: [group]) }
+ let(:group) { create(:group, name: 'Foo') }
+ let(:cluster_link) { "<a href=\"#{group_cluster_path(cluster.group, cluster)}\">#{cluster.name}</a>" }
+
+ before do
+ group.add_maintainer(user)
+ end
+
+ shared_examples 'ancestor clusters' do
+ context 'ancestor clusters', :nested_groups do
+ let(:root_group) { create(:group, name: 'Root Group') }
+ let(:parent) { create(:group, name: 'parent', parent: root_group) }
+ let(:child) { create(:group, name: 'child', parent: parent) }
+ let(:group) { create(:group, name: 'group', parent: child) }
+
+ before do
+ root_group.add_maintainer(user)
+ end
+
+ context 'top level group cluster' do
+ let(:cluster) { create(:cluster, cluster_type: :group_type, groups: [root_group]) }
+
+ it 'returns full group names and link for cluster' do
+ expect(subject).to eq("Root Group / #{cluster_link}")
+ end
+
+ it 'is html safe' do
+ expect(presenter).to receive(:sanitize).with('Root Group').and_call_original
+
+ expect(subject).to be_html_safe
+ end
+ end
+
+ context 'first level group cluster' do
+ let(:cluster) { create(:cluster, cluster_type: :group_type, groups: [parent]) }
+
+ it 'returns full group names and link for cluster' do
+ expect(subject).to eq("Root Group / parent / #{cluster_link}")
+ end
+
+ it 'is html safe' do
+ expect(presenter).to receive(:sanitize).with('Root Group / parent').and_call_original
+
+ expect(subject).to be_html_safe
+ end
+ end
+
+ context 'second level group cluster' do
+ let(:cluster) { create(:cluster, cluster_type: :group_type, groups: [child]) }
+
+ let(:ellipsis_h) do
+ /.*ellipsis_h.*/
+ end
+
+ it 'returns clipped group names and link for cluster' do
+ expect(subject).to match("Root Group / #{ellipsis_h} / child / #{cluster_link}")
+ end
+
+ it 'is html safe' do
+ expect(presenter).to receive(:sanitize).with('Root Group / parent / child').and_call_original
+
+ expect(subject).to be_html_safe
+ end
+ end
+ end
+ end
+
+ context 'for a project clusterable' do
+ let(:clusterable) { project }
+ let(:project) { create(:project, group: group) }
+
+ it 'returns the group name and the link for cluster' do
+ expect(subject).to eq("Foo / #{cluster_link}")
+ end
+
+ it 'is html safe' do
+ expect(presenter).to receive(:sanitize).with('Foo').and_call_original
+
+ expect(subject).to be_html_safe
+ end
+
+ include_examples 'ancestor clusters'
+ end
+
+ context 'for the group clusterable for the cluster' do
+ let(:clusterable) { group }
+
+ it 'returns link for cluster' do
+ expect(subject).to eq(cluster_link)
+ end
+
+ include_examples 'ancestor clusters'
+
+ it 'is html safe' do
+ expect(subject).to be_html_safe
+ end
+ end
+ end
+
+ context 'for a project cluster' do
+ let(:cluster) { create(:cluster, :project) }
+ let(:cluster_link) { "<a href=\"#{project_cluster_path(cluster.project, cluster)}\">#{cluster.name}</a>" }
+
+ before do
+ cluster.project.add_maintainer(user)
+ end
+
+ context 'for the project clusterable' do
+ let(:clusterable) { cluster.project }
+
+ it 'returns link for cluster' do
+ expect(subject).to eq(cluster_link)
+ end
+ end
+ end
+ end
+
describe '#gke_cluster_url' do
subject { described_class.new(cluster).gke_cluster_url }
@@ -74,6 +198,20 @@ describe Clusters::ClusterPresenter do
end
end
+ describe '#cluster_type_description' do
+ subject { described_class.new(cluster).cluster_type_description }
+
+ context 'project_type cluster' do
+ it { is_expected.to eq('Project cluster') }
+ end
+
+ context 'group_type cluster' do
+ let(:cluster) { create(:cluster, :provided_by_gcp, :group) }
+
+ it { is_expected.to eq('Group cluster') }
+ end
+ end
+
describe '#show_path' do
subject { described_class.new(cluster).show_path }
diff --git a/spec/presenters/group_member_presenter_spec.rb b/spec/presenters/group_member_presenter_spec.rb
index c00e41725d9..bb66523a83d 100644
--- a/spec/presenters/group_member_presenter_spec.rb
+++ b/spec/presenters/group_member_presenter_spec.rb
@@ -135,4 +135,12 @@ describe GroupMemberPresenter do
end
end
end
+
+ it_behaves_like '#valid_level_roles', :group do
+ let(:expected_roles) { { 'Developer' => 30, 'Maintainer' => 40, 'Owner' => 50, 'Reporter' => 20 } }
+
+ before do
+ entity.parent = group
+ end
+ end
end
diff --git a/spec/presenters/project_member_presenter_spec.rb b/spec/presenters/project_member_presenter_spec.rb
index 83db5c56cdf..73ef113a1c5 100644
--- a/spec/presenters/project_member_presenter_spec.rb
+++ b/spec/presenters/project_member_presenter_spec.rb
@@ -135,4 +135,10 @@ describe ProjectMemberPresenter do
end
end
end
+
+ it_behaves_like '#valid_level_roles', :project do
+ before do
+ entity.group = group
+ end
+ end
end
diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb
index 7b0192fa9c8..456de5f1b9a 100644
--- a/spec/presenters/project_presenter_spec.rb
+++ b/spec/presenters/project_presenter_spec.rb
@@ -165,32 +165,32 @@ describe ProjectPresenter do
describe '#files_anchor_data' do
it 'returns files data' do
- expect(presenter.files_anchor_data).to have_attributes(enabled: true,
- label: 'Files (0 Bytes)',
+ expect(presenter.files_anchor_data).to have_attributes(is_link: true,
+ label: a_string_including('0 Bytes'),
link: nil)
end
end
describe '#commits_anchor_data' do
it 'returns commits data' do
- expect(presenter.commits_anchor_data).to have_attributes(enabled: true,
- label: 'Commits (0)',
+ expect(presenter.commits_anchor_data).to have_attributes(is_link: true,
+ label: a_string_including('0'),
link: nil)
end
end
describe '#branches_anchor_data' do
it 'returns branches data' do
- expect(presenter.branches_anchor_data).to have_attributes(enabled: true,
- label: "Branches (0)",
+ expect(presenter.branches_anchor_data).to have_attributes(is_link: true,
+ label: a_string_including('0'),
link: nil)
end
end
describe '#tags_anchor_data' do
it 'returns tags data' do
- expect(presenter.tags_anchor_data).to have_attributes(enabled: true,
- label: "Tags (0)",
+ expect(presenter.tags_anchor_data).to have_attributes(is_link: true,
+ label: a_string_including('0'),
link: nil)
end
end
@@ -202,32 +202,32 @@ describe ProjectPresenter do
describe '#files_anchor_data' do
it 'returns files data' do
- expect(presenter.files_anchor_data).to have_attributes(enabled: true,
- label: 'Files (0 Bytes)',
+ expect(presenter.files_anchor_data).to have_attributes(is_link: true,
+ label: a_string_including('0 Bytes'),
link: presenter.project_tree_path(project))
end
end
describe '#commits_anchor_data' do
it 'returns commits data' do
- expect(presenter.commits_anchor_data).to have_attributes(enabled: true,
- label: 'Commits (0)',
+ expect(presenter.commits_anchor_data).to have_attributes(is_link: true,
+ label: a_string_including('0'),
link: presenter.project_commits_path(project, project.repository.root_ref))
end
end
describe '#branches_anchor_data' do
it 'returns branches data' do
- expect(presenter.branches_anchor_data).to have_attributes(enabled: true,
- label: "Branches (#{project.repository.branches.size})",
+ expect(presenter.branches_anchor_data).to have_attributes(is_link: true,
+ label: a_string_including("#{project.repository.branches.size}"),
link: presenter.project_branches_path(project))
end
end
describe '#tags_anchor_data' do
it 'returns tags data' do
- expect(presenter.tags_anchor_data).to have_attributes(enabled: true,
- label: "Tags (#{project.repository.tags.size})",
+ expect(presenter.tags_anchor_data).to have_attributes(is_link: true,
+ label: a_string_including("#{project.repository.tags.size}"),
link: presenter.project_tags_path(project))
end
end
@@ -236,8 +236,8 @@ describe ProjectPresenter do
it 'returns new file data if user can push' do
project.add_developer(user)
- expect(presenter.new_file_anchor_data).to have_attributes(enabled: false,
- label: "New file",
+ expect(presenter.new_file_anchor_data).to have_attributes(is_link: false,
+ label: a_string_including("New file"),
link: presenter.project_new_blob_path(project, 'master'),
class_modifier: 'success')
end
@@ -264,8 +264,8 @@ describe ProjectPresenter do
project.add_developer(user)
allow(project.repository).to receive(:readme).and_return(nil)
- expect(presenter.readme_anchor_data).to have_attributes(enabled: false,
- label: 'Add Readme',
+ expect(presenter.readme_anchor_data).to have_attributes(is_link: false,
+ label: a_string_including('Add README'),
link: presenter.add_readme_path)
end
end
@@ -274,21 +274,21 @@ describe ProjectPresenter do
it 'returns anchor data' do
allow(project.repository).to receive(:readme).and_return(double(name: 'readme'))
- expect(presenter.readme_anchor_data).to have_attributes(enabled: true,
- label: 'Readme',
+ expect(presenter.readme_anchor_data).to have_attributes(is_link: false,
+ label: a_string_including('README'),
link: presenter.readme_path)
end
end
end
describe '#changelog_anchor_data' do
- context 'when user can push and CHANGELOG does not exists' do
+ context 'when user can push and CHANGELOG does not exist' do
it 'returns anchor data' do
project.add_developer(user)
allow(project.repository).to receive(:changelog).and_return(nil)
- expect(presenter.changelog_anchor_data).to have_attributes(enabled: false,
- label: 'Add Changelog',
+ expect(presenter.changelog_anchor_data).to have_attributes(is_link: false,
+ label: a_string_including('Add CHANGELOG'),
link: presenter.add_changelog_path)
end
end
@@ -297,21 +297,21 @@ describe ProjectPresenter do
it 'returns anchor data' do
allow(project.repository).to receive(:changelog).and_return(double(name: 'foo'))
- expect(presenter.changelog_anchor_data).to have_attributes(enabled: true,
- label: 'Changelog',
+ expect(presenter.changelog_anchor_data).to have_attributes(is_link: false,
+ label: a_string_including('CHANGELOG'),
link: presenter.changelog_path)
end
end
end
describe '#license_anchor_data' do
- context 'when user can push and LICENSE does not exists' do
+ context 'when user can push and LICENSE does not exist' do
it 'returns anchor data' do
project.add_developer(user)
allow(project.repository).to receive(:license_blob).and_return(nil)
- expect(presenter.license_anchor_data).to have_attributes(enabled: false,
- label: 'Add license',
+ expect(presenter.license_anchor_data).to have_attributes(is_link: true,
+ label: a_string_including('Add license'),
link: presenter.add_license_path)
end
end
@@ -320,21 +320,21 @@ describe ProjectPresenter do
it 'returns anchor data' do
allow(project.repository).to receive(:license_blob).and_return(double(name: 'foo'))
- expect(presenter.license_anchor_data).to have_attributes(enabled: true,
- label: presenter.license_short_name,
+ expect(presenter.license_anchor_data).to have_attributes(is_link: true,
+ label: a_string_including(presenter.license_short_name),
link: presenter.license_path)
end
end
end
describe '#contribution_guide_anchor_data' do
- context 'when user can push and CONTRIBUTING does not exists' do
+ context 'when user can push and CONTRIBUTING does not exist' do
it 'returns anchor data' do
project.add_developer(user)
allow(project.repository).to receive(:contribution_guide).and_return(nil)
- expect(presenter.contribution_guide_anchor_data).to have_attributes(enabled: false,
- label: 'Add Contribution guide',
+ expect(presenter.contribution_guide_anchor_data).to have_attributes(is_link: false,
+ label: a_string_including('Add CONTRIBUTING'),
link: presenter.add_contribution_guide_path)
end
end
@@ -343,8 +343,8 @@ describe ProjectPresenter do
it 'returns anchor data' do
allow(project.repository).to receive(:contribution_guide).and_return(double(name: 'foo'))
- expect(presenter.contribution_guide_anchor_data).to have_attributes(enabled: true,
- label: 'Contribution guide',
+ expect(presenter.contribution_guide_anchor_data).to have_attributes(is_link: false,
+ label: a_string_including('CONTRIBUTING'),
link: presenter.contribution_guide_path)
end
end
@@ -355,20 +355,20 @@ describe ProjectPresenter do
it 'returns anchor data' do
allow(project).to receive(:auto_devops_enabled?).and_return(true)
- expect(presenter.autodevops_anchor_data).to have_attributes(enabled: true,
- label: 'Auto DevOps enabled',
+ expect(presenter.autodevops_anchor_data).to have_attributes(is_link: false,
+ label: a_string_including('Auto DevOps enabled'),
link: nil)
end
end
- context 'when user can admin pipeline and CI yml does not exists' do
+ context 'when user can admin pipeline and CI yml does not exist' do
it 'returns anchor data' do
project.add_maintainer(user)
allow(project).to receive(:auto_devops_enabled?).and_return(false)
allow(project.repository).to receive(:gitlab_ci_yml).and_return(nil)
- expect(presenter.autodevops_anchor_data).to have_attributes(enabled: false,
- label: 'Enable Auto DevOps',
+ expect(presenter.autodevops_anchor_data).to have_attributes(is_link: false,
+ label: a_string_including('Enable Auto DevOps'),
link: presenter.project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
end
end
@@ -380,8 +380,8 @@ describe ProjectPresenter do
project.add_maintainer(user)
cluster = create(:cluster, projects: [project])
- expect(presenter.kubernetes_cluster_anchor_data).to have_attributes(enabled: true,
- label: 'Kubernetes configured',
+ expect(presenter.kubernetes_cluster_anchor_data).to have_attributes(is_link: false,
+ label: a_string_including('Kubernetes configured'),
link: presenter.project_cluster_path(project, cluster))
end
@@ -390,16 +390,16 @@ describe ProjectPresenter do
create(:cluster, :production_environment, projects: [project])
create(:cluster, projects: [project])
- expect(presenter.kubernetes_cluster_anchor_data).to have_attributes(enabled: true,
- label: 'Kubernetes configured',
+ expect(presenter.kubernetes_cluster_anchor_data).to have_attributes(is_link: false,
+ label: a_string_including('Kubernetes configured'),
link: presenter.project_clusters_path(project))
end
it 'returns link to create a cluster if no cluster exists' do
project.add_maintainer(user)
- expect(presenter.kubernetes_cluster_anchor_data).to have_attributes(enabled: false,
- label: 'Add Kubernetes cluster',
+ expect(presenter.kubernetes_cluster_anchor_data).to have_attributes(is_link: false,
+ label: a_string_including('Add Kubernetes cluster'),
link: presenter.new_project_cluster_path(project))
end
end
diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb
index e13129967b2..1af6602ea9e 100644
--- a/spec/requests/api/access_requests_spec.rb
+++ b/spec/requests/api/access_requests_spec.rb
@@ -145,7 +145,7 @@ describe API::AccessRequests do
it 'returns 201' do
expect do
put api("/#{source_type.pluralize}/#{source.id}/access_requests/#{access_requester.id}/approve", maintainer),
- access_level: Member::MAINTAINER
+ params: { access_level: Member::MAINTAINER }
expect(response).to have_gitlab_http_status(201)
end.to change { source.members.count }.by(1)
diff --git a/spec/requests/api/applications_spec.rb b/spec/requests/api/applications_spec.rb
index 6154be5c425..e47166544d9 100644
--- a/spec/requests/api/applications_spec.rb
+++ b/spec/requests/api/applications_spec.rb
@@ -11,7 +11,7 @@ describe API::Applications, :api do
context 'authenticated and authorized user' do
it 'creates and returns an OAuth application' do
expect do
- post api('/applications', admin_user), name: 'application_name', redirect_uri: 'http://application.url', scopes: ''
+ post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: '' }
end.to change { Doorkeeper::Application.count }.by 1
application = Doorkeeper::Application.find_by(name: 'application_name', redirect_uri: 'http://application.url')
@@ -25,7 +25,7 @@ describe API::Applications, :api do
it 'does not allow creating an application with the wrong redirect_uri format' do
expect do
- post api('/applications', admin_user), name: 'application_name', redirect_uri: 'http://', scopes: ''
+ post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'http://', scopes: '' }
end.not_to change { Doorkeeper::Application.count }
expect(response).to have_gitlab_http_status(400)
@@ -35,7 +35,7 @@ describe API::Applications, :api do
it 'does not allow creating an application with a forbidden URI format' do
expect do
- post api('/applications', admin_user), name: 'application_name', redirect_uri: 'javascript://alert()', scopes: ''
+ post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'javascript://alert()', scopes: '' }
end.not_to change { Doorkeeper::Application.count }
expect(response).to have_gitlab_http_status(400)
@@ -45,7 +45,7 @@ describe API::Applications, :api do
it 'does not allow creating an application without a name' do
expect do
- post api('/applications', admin_user), redirect_uri: 'http://application.url', scopes: ''
+ post api('/applications', admin_user), params: { redirect_uri: 'http://application.url', scopes: '' }
end.not_to change { Doorkeeper::Application.count }
expect(response).to have_gitlab_http_status(400)
@@ -55,7 +55,7 @@ describe API::Applications, :api do
it 'does not allow creating an application without a redirect_uri' do
expect do
- post api('/applications', admin_user), name: 'application_name', scopes: ''
+ post api('/applications', admin_user), params: { name: 'application_name', scopes: '' }
end.not_to change { Doorkeeper::Application.count }
expect(response).to have_gitlab_http_status(400)
@@ -65,7 +65,7 @@ describe API::Applications, :api do
it 'does not allow creating an application without scopes' do
expect do
- post api('/applications', admin_user), name: 'application_name', redirect_uri: 'http://application.url'
+ post api('/applications', admin_user), params: { name: 'application_name', redirect_uri: 'http://application.url' }
end.not_to change { Doorkeeper::Application.count }
expect(response).to have_gitlab_http_status(400)
@@ -77,7 +77,7 @@ describe API::Applications, :api do
context 'authorized user without authorization' do
it 'does not create application' do
expect do
- post api('/applications', user), name: 'application_name', redirect_uri: 'http://application.url', scopes: ''
+ post api('/applications', user), params: { name: 'application_name', redirect_uri: 'http://application.url', scopes: '' }
end.not_to change { Doorkeeper::Application.count }
expect(response).to have_gitlab_http_status(403)
@@ -87,7 +87,7 @@ describe API::Applications, :api do
context 'non-authenticated user' do
it 'does not create application' do
expect do
- post api('/applications'), name: 'application_name', redirect_uri: 'http://application.url'
+ post api('/applications'), params: { name: 'application_name', redirect_uri: 'http://application.url' }
end.not_to change { Doorkeeper::Application.count }
expect(response).to have_gitlab_http_status(401)
diff --git a/spec/requests/api/avatar_spec.rb b/spec/requests/api/avatar_spec.rb
index 26e0435a6d5..17e66725dc9 100644
--- a/spec/requests/api/avatar_spec.rb
+++ b/spec/requests/api/avatar_spec.rb
@@ -13,7 +13,7 @@ describe API::Avatar do
end
it 'returns the avatar url' do
- get api('/avatar'), { email: 'public@example.com' }
+ get api('/avatar'), params: { email: 'public@example.com' }
expect(response.status).to eq 200
expect(json_response['avatar_url']).to eql("#{::Settings.gitlab.base_url}#{user.avatar.local_url}")
@@ -30,7 +30,7 @@ describe API::Avatar do
end
it 'returns the avatar url from Gravatar' do
- get api('/avatar'), { email: 'private@example.com' }
+ get api('/avatar'), params: { email: 'private@example.com' }
expect(response.status).to eq 200
expect(json_response['avatar_url']).to eq('https://gravatar')
@@ -53,7 +53,7 @@ describe API::Avatar do
end
it 'returns the avatar url from Gravatar' do
- get api('/avatar'), { email: 'public@example.com' }
+ get api('/avatar'), params: { email: 'public@example.com' }
expect(response.status).to eq 200
expect(json_response['avatar_url']).to eq('https://gravatar')
@@ -70,7 +70,7 @@ describe API::Avatar do
end
it 'returns the avatar url from Gravatar' do
- get api('/avatar'), { email: 'private@example.com' }
+ get api('/avatar'), params: { email: 'private@example.com' }
expect(response.status).to eq 200
expect(json_response['avatar_url']).to eq('https://gravatar')
@@ -88,7 +88,7 @@ describe API::Avatar do
context 'when authenticated' do
it 'returns the avatar url' do
- get api('/avatar', user), { email: 'public@example.com' }
+ get api('/avatar', user), params: { email: 'public@example.com' }
expect(response.status).to eq 200
expect(json_response['avatar_url']).to eql("#{::Settings.gitlab.base_url}#{user.avatar.local_url}")
@@ -97,7 +97,7 @@ describe API::Avatar do
context 'when unauthenticated' do
it_behaves_like '403 response' do
- let(:request) { get api('/avatar'), { email: 'public@example.com' } }
+ let(:request) { get api('/avatar'), params: { email: 'public@example.com' } }
end
end
end
diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb
index 7f3f3ab0977..22f6fcdc922 100644
--- a/spec/requests/api/award_emoji_spec.rb
+++ b/spec/requests/api/award_emoji_spec.rb
@@ -148,7 +148,7 @@ describe API::AwardEmoji do
context "on an issue" do
it "creates a new award emoji" do
- post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: 'blowfish'
+ post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), params: { name: 'blowfish' }
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq('blowfish')
@@ -162,21 +162,21 @@ describe API::AwardEmoji do
end
it "returns a 401 unauthorized error if the user is not authenticated" do
- post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji"), name: 'thumbsup'
+ post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji"), params: { name: 'thumbsup' }
expect(response).to have_gitlab_http_status(401)
end
it "normalizes +1 as thumbsup award" do
- post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: '+1'
+ post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), params: { name: '+1' }
expect(issue.award_emoji.last.name).to eq("thumbsup")
end
context 'when the emoji already has been awarded' do
it 'returns a 404 status code' do
- post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: 'thumbsup'
- post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), name: 'thumbsup'
+ post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), params: { name: 'thumbsup' }
+ post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), params: { name: 'thumbsup' }
expect(response).to have_gitlab_http_status(404)
expect(json_response["message"]).to match("has already been taken")
@@ -188,7 +188,7 @@ describe API::AwardEmoji do
it 'creates a new award emoji' do
snippet = create(:project_snippet, :public, project: project)
- post api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji", user), name: 'blowfish'
+ post api("/projects/#{project.id}/snippets/#{snippet.id}/award_emoji", user), params: { name: 'blowfish' }
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq('blowfish')
@@ -202,7 +202,7 @@ describe API::AwardEmoji do
it 'creates a new award emoji' do
expect do
- post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: 'rocket'
+ post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), params: { name: 'rocket' }
end.to change { note.award_emoji.count }.from(0).to(1)
expect(response).to have_gitlab_http_status(201)
@@ -210,15 +210,15 @@ describe API::AwardEmoji do
end
it "normalizes +1 as thumbsup award" do
- post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: '+1'
+ post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), params: { name: '+1' }
expect(note.award_emoji.last.name).to eq("thumbsup")
end
context 'when the emoji already has been awarded' do
it 'returns a 404 status code' do
- post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: 'rocket'
- post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), name: 'rocket'
+ post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), params: { name: 'rocket' }
+ post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), params: { name: 'rocket' }
expect(response).to have_gitlab_http_status(404)
expect(json_response["message"]).to match("has already been taken")
diff --git a/spec/requests/api/badges_spec.rb b/spec/requests/api/badges_spec.rb
index e232e2e04ee..1271324a2ba 100644
--- a/spec/requests/api/badges_spec.rb
+++ b/spec/requests/api/badges_spec.rb
@@ -103,7 +103,7 @@ describe API::Badges do
it_behaves_like 'a 404 response when source is private' do
let(:route) do
post api("/#{source_type.pluralize}/#{source.id}/badges", stranger),
- link_url: example_url, image_url: example_url2
+ params: { link_url: example_url, image_url: example_url2 }
end
end
@@ -114,7 +114,7 @@ describe API::Badges do
user = public_send(type)
post api("/#{source_type.pluralize}/#{source.id}/badges", user),
- link_url: example_url, image_url: example_url2
+ params: { link_url: example_url, image_url: example_url2 }
expect(response).to have_gitlab_http_status(403)
end
@@ -126,7 +126,7 @@ describe API::Badges do
it 'creates a new badge' do
expect do
post api("/#{source_type.pluralize}/#{source.id}/badges", maintainer),
- link_url: example_url, image_url: example_url2
+ params: { link_url: example_url, image_url: example_url2 }
expect(response).to have_gitlab_http_status(201)
end.to change { source.badges.count }.by(1)
@@ -139,21 +139,21 @@ describe API::Badges do
it 'returns 400 when link_url is not given' do
post api("/#{source_type.pluralize}/#{source.id}/badges", maintainer),
- link_url: example_url
+ params: { link_url: example_url }
expect(response).to have_gitlab_http_status(400)
end
it 'returns 400 when image_url is not given' do
post api("/#{source_type.pluralize}/#{source.id}/badges", maintainer),
- image_url: example_url2
+ params: { image_url: example_url2 }
expect(response).to have_gitlab_http_status(400)
end
it 'returns 400 when link_url or image_url is not valid' do
post api("/#{source_type.pluralize}/#{source.id}/badges", maintainer),
- link_url: 'whatever', image_url: 'whatever'
+ params: { link_url: 'whatever', image_url: 'whatever' }
expect(response).to have_gitlab_http_status(400)
end
@@ -173,7 +173,7 @@ describe API::Badges do
it_behaves_like 'a 404 response when source is private' do
let(:route) do
put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", stranger),
- link_url: example_url
+ params: { link_url: example_url }
end
end
@@ -184,7 +184,7 @@ describe API::Badges do
user = public_send(type)
put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", user),
- link_url: example_url
+ params: { link_url: example_url }
expect(response).to have_gitlab_http_status(403)
end
@@ -195,7 +195,7 @@ describe API::Badges do
context 'when authenticated as a maintainer/owner' do
it 'updates the member' do
put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", maintainer),
- link_url: example_url, image_url: example_url2
+ params: { link_url: example_url, image_url: example_url2 }
expect(response).to have_gitlab_http_status(200)
expect(json_response['link_url']).to eq(example_url)
@@ -206,7 +206,7 @@ describe API::Badges do
it 'returns 400 when link_url or image_url is not valid' do
put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", maintainer),
- link_url: 'whatever', image_url: 'whatever'
+ params: { link_url: 'whatever', image_url: 'whatever' }
expect(response).to have_gitlab_http_status(400)
end
diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb
index 7710f19ce4e..ab4f42cad47 100644
--- a/spec/requests/api/boards_spec.rb
+++ b/spec/requests/api/boards_spec.rb
@@ -41,7 +41,7 @@ describe API::Boards do
group_label = create(:group_label, group: group)
board_parent.update(group: group)
- post api(url, user), label_id: group_label.id
+ post api(url, user), params: { label_id: group_label.id }
expect(response).to have_gitlab_http_status(201)
expect(json_response['label']['name']).to eq(group_label.title)
@@ -56,7 +56,7 @@ describe API::Boards do
group.add_developer(user)
sub_group.add_developer(user)
- post api(url, user), label_id: group_label.id
+ post api(url, user), params: { label_id: group_label.id }
expect(response).to have_gitlab_http_status(201)
expect(json_response['label']['name']).to eq(group_label.title)
@@ -73,7 +73,7 @@ describe API::Boards do
group.add_developer(user)
group_label = create(:group_label, group: group)
- post api(url, user), label_id: group_label.id
+ post api(url, user), params: { label_id: group_label.id }
expect(response).to have_gitlab_http_status(201)
expect(json_response['label']['name']).to eq(group_label.title)
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 7fff0a6cce6..93c411476bb 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -21,7 +21,7 @@ describe API::Branches do
shared_examples_for 'repository branches' do
it 'returns the repository branches' do
- get api(route, current_user), per_page: 100
+ get api(route, current_user), params: { per_page: 100 }
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/branches')
@@ -42,7 +42,7 @@ describe API::Branches do
context 'when search parameter is passed' do
context 'and branch exists' do
it 'returns correct branches' do
- get api(route, user), per_page: 100, search: branch_name
+ get api(route, user), params: { per_page: 100, search: branch_name }
searched_branch_names = json_response.map { |branch| branch['name'] }
project_branch_names = project.repository.branch_names.grep(/#{branch_name}/)
@@ -53,7 +53,7 @@ describe API::Branches do
context 'and branch does not exist' do
it 'returns an empty array' do
- get api(route, user), per_page: 100, search: 'no_such_branch_name_entropy_of_jabadabadu'
+ get api(route, user), params: { per_page: 100, search: 'no_such_branch_name_entropy_of_jabadabadu' }
expect(json_response).to eq []
end
@@ -252,7 +252,7 @@ describe API::Branches do
end
it 'protects a single branch and developers can push' do
- put api(route, current_user), developers_can_push: true
+ put api(route, current_user), params: { developers_can_push: true }
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/branch')
@@ -263,7 +263,7 @@ describe API::Branches do
end
it 'protects a single branch and developers can merge' do
- put api(route, current_user), developers_can_merge: true
+ put api(route, current_user), params: { developers_can_merge: true }
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/branch')
@@ -274,7 +274,7 @@ describe API::Branches do
end
it 'protects a single branch and developers can push and merge' do
- put api(route, current_user), developers_can_push: true, developers_can_merge: true
+ put api(route, current_user), params: { developers_can_push: true, developers_can_merge: true }
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/branch')
@@ -373,7 +373,7 @@ describe API::Branches do
it 'updates that a developer cannot push or merge' do
put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user),
- developers_can_push: false, developers_can_merge: false
+ params: { developers_can_push: false, developers_can_merge: false }
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/branch')
@@ -391,7 +391,7 @@ describe API::Branches do
it 'updates that a developer can push and merge' do
put api("/projects/#{project.id}/repository/branches/#{protected_branch.name}/protect", user),
- developers_can_push: true, developers_can_merge: true
+ params: { developers_can_push: true, developers_can_merge: true }
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/branch')
@@ -504,7 +504,7 @@ describe API::Branches do
shared_examples_for 'repository new branch' do
it 'creates a new branch' do
- post api(route, current_user), branch: 'feature1', ref: branch_sha
+ post api(route, current_user), params: { branch: 'feature1', ref: branch_sha }
expect(response).to have_gitlab_http_status(201)
expect(response).to match_response_schema('public_api/v4/branch')
@@ -549,25 +549,25 @@ describe API::Branches do
end
it 'returns 400 if branch name is invalid' do
- post api(route, user), branch: 'new design', ref: branch_sha
+ post api(route, user), params: { branch: 'new design', ref: branch_sha }
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq('Branch name is invalid')
end
it 'returns 400 if branch already exists' do
- post api(route, user), branch: 'new_design1', ref: branch_sha
+ post api(route, user), params: { branch: 'new_design1', ref: branch_sha }
expect(response).to have_gitlab_http_status(201)
- post api(route, user), branch: 'new_design1', ref: branch_sha
+ post api(route, user), params: { branch: 'new_design1', ref: branch_sha }
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq('Branch already exists')
end
it 'returns 400 if ref name is invalid' do
- post api(route, user), branch: 'new_design3', ref: 'foo'
+ post api(route, user), params: { branch: 'new_design3', ref: 'foo' }
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq('Invalid reference name')
diff --git a/spec/requests/api/broadcast_messages_spec.rb b/spec/requests/api/broadcast_messages_spec.rb
index fe8a14fae9e..0b48b79219c 100644
--- a/spec/requests/api/broadcast_messages_spec.rb
+++ b/spec/requests/api/broadcast_messages_spec.rb
@@ -56,13 +56,13 @@ describe API::BroadcastMessages do
describe 'POST /broadcast_messages' do
it 'returns a 401 for anonymous users' do
- post api('/broadcast_messages'), attributes_for(:broadcast_message)
+ post api('/broadcast_messages'), params: attributes_for(:broadcast_message)
expect(response).to have_gitlab_http_status(401)
end
it 'returns a 403 for users' do
- post api('/broadcast_messages', user), attributes_for(:broadcast_message)
+ post api('/broadcast_messages', user), params: attributes_for(:broadcast_message)
expect(response).to have_gitlab_http_status(403)
end
@@ -72,7 +72,7 @@ describe API::BroadcastMessages do
attrs = attributes_for(:broadcast_message)
attrs.delete(:message)
- post api('/broadcast_messages', admin), attrs
+ post api('/broadcast_messages', admin), params: attrs
expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq 'message is missing'
@@ -81,7 +81,7 @@ describe API::BroadcastMessages do
it 'defines sane default start and end times' do
time = Time.zone.parse('2016-07-02 10:11:12')
travel_to(time) do
- post api('/broadcast_messages', admin), message: 'Test message'
+ post api('/broadcast_messages', admin), params: { message: 'Test message' }
expect(response).to have_gitlab_http_status(201)
expect(json_response['starts_at']).to eq '2016-07-02T10:11:12.000Z'
@@ -92,7 +92,7 @@ describe API::BroadcastMessages do
it 'accepts a custom background and foreground color' do
attrs = attributes_for(:broadcast_message, color: '#000000', font: '#cecece')
- post api('/broadcast_messages', admin), attrs
+ post api('/broadcast_messages', admin), params: attrs
expect(response).to have_gitlab_http_status(201)
expect(json_response['color']).to eq attrs[:color]
@@ -104,14 +104,14 @@ describe API::BroadcastMessages do
describe 'PUT /broadcast_messages/:id' do
it 'returns a 401 for anonymous users' do
put api("/broadcast_messages/#{message.id}"),
- attributes_for(:broadcast_message)
+ params: attributes_for(:broadcast_message)
expect(response).to have_gitlab_http_status(401)
end
it 'returns a 403 for users' do
put api("/broadcast_messages/#{message.id}", user),
- attributes_for(:broadcast_message)
+ params: attributes_for(:broadcast_message)
expect(response).to have_gitlab_http_status(403)
end
@@ -120,7 +120,7 @@ describe API::BroadcastMessages do
it 'accepts new background and foreground colors' do
attrs = { color: '#000000', font: '#cecece' }
- put api("/broadcast_messages/#{message.id}", admin), attrs
+ put api("/broadcast_messages/#{message.id}", admin), params: attrs
expect(response).to have_gitlab_http_status(200)
expect(json_response['color']).to eq attrs[:color]
@@ -132,7 +132,7 @@ describe API::BroadcastMessages do
travel_to(time) do
attrs = { starts_at: Time.zone.now, ends_at: 3.hours.from_now }
- put api("/broadcast_messages/#{message.id}", admin), attrs
+ put api("/broadcast_messages/#{message.id}", admin), params: attrs
expect(response).to have_gitlab_http_status(200)
expect(json_response['starts_at']).to eq '2016-07-02T10:11:12.000Z'
@@ -143,7 +143,7 @@ describe API::BroadcastMessages do
it 'accepts a new message' do
attrs = { message: 'new message' }
- put api("/broadcast_messages/#{message.id}", admin), attrs
+ put api("/broadcast_messages/#{message.id}", admin), params: attrs
expect(response).to have_gitlab_http_status(200)
expect { message.reload }.to change { message.message }.to('new message')
@@ -154,14 +154,14 @@ describe API::BroadcastMessages do
describe 'DELETE /broadcast_messages/:id' do
it 'returns a 401 for anonymous users' do
delete api("/broadcast_messages/#{message.id}"),
- attributes_for(:broadcast_message)
+ params: attributes_for(:broadcast_message)
expect(response).to have_gitlab_http_status(401)
end
it 'returns a 403 for users' do
delete api("/broadcast_messages/#{message.id}", user),
- attributes_for(:broadcast_message)
+ params: attributes_for(:broadcast_message)
expect(response).to have_gitlab_http_status(403)
end
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index a43304c9b83..9388343c392 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -51,7 +51,7 @@ describe API::CommitStatuses do
context 'all commit statuses' do
before do
- get api(get_url, reporter), all: 1
+ get api(get_url, reporter), params: { all: 1 }
end
it 'returns all commit statuses' do
@@ -66,7 +66,7 @@ describe API::CommitStatuses do
context 'latest commit statuses for specific ref' do
before do
- get api(get_url, reporter), ref: 'develop'
+ get api(get_url, reporter), params: { ref: 'develop' }
end
it 'returns latest commit statuses for specific ref' do
@@ -79,7 +79,7 @@ describe API::CommitStatuses do
context 'latest commit statues for specific name' do
before do
- get api(get_url, reporter), name: 'coverage'
+ get api(get_url, reporter), params: { name: 'coverage' }
end
it 'return latest commit statuses for specific name' do
@@ -133,7 +133,7 @@ describe API::CommitStatuses do
context "for #{status}" do
context 'uses only required parameters' do
it 'creates commit status' do
- post api(post_url, developer), state: status
+ post api(post_url, developer), params: { state: status }
expect(response).to have_gitlab_http_status(201)
expect(json_response['sha']).to eq(commit.id)
@@ -153,12 +153,12 @@ describe API::CommitStatuses do
context 'transitions status from pending' do
before do
- post api(post_url, developer), state: 'pending'
+ post api(post_url, developer), params: { state: 'pending' }
end
%w[running success failed canceled].each do |status|
it "to #{status}" do
- expect { post api(post_url, developer), state: status }.not_to change { CommitStatus.count }
+ expect { post api(post_url, developer), params: { state: status } }.not_to change { CommitStatus.count }
expect(response).to have_gitlab_http_status(201)
expect(json_response['status']).to eq(status)
@@ -169,7 +169,7 @@ describe API::CommitStatuses do
context 'with all optional parameters' do
context 'when creating a commit status' do
subject do
- post api(post_url, developer), {
+ post api(post_url, developer), params: {
state: 'success',
context: 'coverage',
ref: 'master',
@@ -206,7 +206,7 @@ describe API::CommitStatuses do
context 'when updatig a commit status' do
before do
- post api(post_url, developer), {
+ post api(post_url, developer), params: {
state: 'running',
context: 'coverage',
ref: 'master',
@@ -215,7 +215,7 @@ describe API::CommitStatuses do
target_url: 'http://gitlab.com/status'
}
- post api(post_url, developer), {
+ post api(post_url, developer), params: {
state: 'success',
name: 'coverage',
ref: 'master',
@@ -244,10 +244,10 @@ describe API::CommitStatuses do
context 'when retrying a commit status' do
before do
post api(post_url, developer),
- { state: 'failed', name: 'test', ref: 'master' }
+ params: { state: 'failed', name: 'test', ref: 'master' }
post api(post_url, developer),
- { state: 'success', name: 'test', ref: 'master' }
+ params: { state: 'success', name: 'test', ref: 'master' }
end
it 'correctly posts a new commit status' do
@@ -265,7 +265,7 @@ describe API::CommitStatuses do
context 'when status is invalid' do
before do
- post api(post_url, developer), state: 'invalid'
+ post api(post_url, developer), params: { state: 'invalid' }
end
it 'does not create commit status' do
@@ -287,7 +287,7 @@ describe API::CommitStatuses do
let(:sha) { 'invalid_sha' }
before do
- post api(post_url, developer), state: 'running'
+ post api(post_url, developer), params: { state: 'running' }
end
it 'returns not found error' do
@@ -297,8 +297,10 @@ describe API::CommitStatuses do
context 'when target URL is an invalid address' do
before do
- post api(post_url, developer), state: 'pending',
- target_url: 'invalid url'
+ post api(post_url, developer), params: {
+ state: 'pending',
+ target_url: 'invalid url'
+ }
end
it 'responds with bad request status and validation errors' do
@@ -311,7 +313,7 @@ describe API::CommitStatuses do
context 'reporter user' do
before do
- post api(post_url, reporter), state: 'running'
+ post api(post_url, reporter), params: { state: 'running' }
end
it 'does not create commit status' do
@@ -321,7 +323,7 @@ describe API::CommitStatuses do
context 'guest user' do
before do
- post api(post_url, guest), state: 'running'
+ post api(post_url, guest), params: { state: 'running' }
end
it 'does not create commit status' do
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 9e599c2175f..6b9bc6eda6a 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -281,11 +281,11 @@ describe API::Commits do
it 'does not increment the usage counters using access token authentication' do
expect(::Gitlab::WebIdeCommitsCounter).not_to receive(:increment)
- post api(url, user), valid_c_params
+ post api(url, user), params: valid_c_params
end
it 'a new file in project repo' do
- post api(url, user), valid_c_params
+ post api(url, user), params: valid_c_params
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq(message)
@@ -294,7 +294,7 @@ describe API::Commits do
end
it 'a new file with utf8 chars in project repo' do
- post api(url, user), valid_utf8_c_params
+ post api(url, user), params: valid_utf8_c_params
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq(message)
@@ -303,7 +303,7 @@ describe API::Commits do
end
it 'returns a 400 bad request if file exists' do
- post api(url, user), invalid_c_params
+ post api(url, user), params: invalid_c_params
expect(response).to have_gitlab_http_status(400)
end
@@ -312,7 +312,7 @@ describe API::Commits do
let(:url) { "/projects/#{CGI.escape(project.full_path)}/repository/commits" }
it 'a new file in project repo' do
- post api(url, user), valid_c_params
+ post api(url, user), params: valid_c_params
expect(response).to have_gitlab_http_status(201)
end
@@ -347,14 +347,14 @@ describe API::Commits do
end
it 'an existing file in project repo' do
- post api(url, user), valid_d_params
+ post api(url, user), params: valid_d_params
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq(message)
end
it 'returns a 400 bad request if file does not exist' do
- post api(url, user), invalid_d_params
+ post api(url, user), params: invalid_d_params
expect(response).to have_gitlab_http_status(400)
end
@@ -392,14 +392,14 @@ describe API::Commits do
end
it 'an existing file in project repo' do
- post api(url, user), valid_m_params
+ post api(url, user), params: valid_m_params
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq(message)
end
it 'returns a 400 bad request if file does not exist' do
- post api(url, user), invalid_m_params
+ post api(url, user), params: invalid_m_params
expect(response).to have_gitlab_http_status(400)
end
@@ -435,14 +435,14 @@ describe API::Commits do
end
it 'an existing file in project repo' do
- post api(url, user), valid_u_params
+ post api(url, user), params: valid_u_params
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq(message)
end
it 'returns a 400 bad request if file does not exist' do
- post api(url, user), invalid_u_params
+ post api(url, user), params: invalid_u_params
expect(response).to have_gitlab_http_status(400)
end
@@ -467,7 +467,7 @@ describe API::Commits do
end
it 'responds with success' do
- post api(url, user), params
+ post api(url, user), params: params
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq(message)
@@ -477,7 +477,7 @@ describe API::Commits do
let(:execute_filemode) { false }
it 'responds with success' do
- post api(url, user), params
+ post api(url, user), params: params
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq(message)
@@ -488,7 +488,7 @@ describe API::Commits do
let(:file_path) { 'foo/bar.baz' }
it "responds with 400" do
- post api(url, user), params
+ post api(url, user), params: params
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq("A file with this name doesn't exist")
@@ -566,28 +566,28 @@ describe API::Commits do
end
it 'are committed as one in project repo' do
- post api(url, user), valid_mo_params
+ post api(url, user), params: valid_mo_params
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq(message)
end
it 'includes the commit stats' do
- post api(url, user), valid_mo_params
+ post api(url, user), params: valid_mo_params
expect(response).to have_gitlab_http_status(201)
expect(json_response).to include 'stats'
end
it "doesn't include the commit stats when stats is false" do
- post api(url, user), valid_mo_params.merge(stats: false)
+ post api(url, user), params: valid_mo_params.merge(stats: false)
expect(response).to have_gitlab_http_status(201)
expect(json_response).not_to include 'stats'
end
it 'return a 400 bad request if there are any issues' do
- post api(url, user), invalid_mo_params
+ post api(url, user), params: invalid_mo_params
expect(response).to have_gitlab_http_status(400)
end
@@ -613,13 +613,13 @@ describe API::Commits do
end
it 'allows pushing to the source branch of the merge request' do
- post api(url, user), push_params('feature')
+ post api(url, user), params: push_params('feature')
expect(response).to have_gitlab_http_status(:created)
end
it 'denies pushing to another branch' do
- post api(url, user), push_params('other-branch')
+ post api(url, user), params: push_params('other-branch')
expect(response).to have_gitlab_http_status(:forbidden)
end
@@ -651,7 +651,7 @@ describe API::Commits do
context 'for a valid commit' do
it 'returns all refs with no scope' do
- get api(route, current_user), per_page: 100
+ get api(route, current_user), params: { per_page: 100 }
refs = project.repository.branch_names_contains(commit_id).map {|name| ['branch', name]}
refs.concat(project.repository.tag_names_contains(commit_id).map {|name| ['tag', name]})
@@ -663,7 +663,7 @@ describe API::Commits do
end
it 'returns all refs' do
- get api(route, current_user), type: 'all', per_page: 100
+ get api(route, current_user), params: { type: 'all', per_page: 100 }
refs = project.repository.branch_names_contains(commit_id).map {|name| ['branch', name]}
refs.concat(project.repository.tag_names_contains(commit_id).map {|name| ['tag', name]})
@@ -673,7 +673,7 @@ describe API::Commits do
end
it 'returns the branch refs' do
- get api(route, current_user), type: 'branch', per_page: 100
+ get api(route, current_user), params: { type: 'branch', per_page: 100 }
refs = project.repository.branch_names_contains(commit_id).map {|name| ['branch', name]}
@@ -682,7 +682,7 @@ describe API::Commits do
end
it 'returns the tag refs' do
- get api(route, current_user), type: 'tag', per_page: 100
+ get api(route, current_user), params: { type: 'tag', per_page: 100 }
refs = project.repository.tag_names_contains(commit_id).map {|name| ['tag', name]}
@@ -750,14 +750,14 @@ describe API::Commits do
end
it "is false it does not include stats" do
- get api(route, user), stats: false
+ get api(route, user), params: { stats: false }
expect(response).to have_gitlab_http_status(200)
expect(json_response).not_to include 'stats'
end
it "is true it includes stats" do
- get api(route, user), stats: true
+ get api(route, user), params: { stats: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response).to include 'stats'
@@ -1063,7 +1063,7 @@ describe API::Commits do
shared_examples_for 'ref cherry-pick' do
context 'when ref exists' do
it 'cherry-picks the ref commit' do
- post api(route, current_user), branch: branch
+ post api(route, current_user), params: { branch: branch }
expect(response).to have_gitlab_http_status(201)
expect(response).to match_response_schema('public_api/v4/commit/basic')
@@ -1078,7 +1078,7 @@ describe API::Commits do
include_context 'disabled repository'
it_behaves_like '403 response' do
- let(:request) { post api(route, current_user), branch: 'master' }
+ let(:request) { post api(route, current_user), params: { branch: 'master' } }
end
end
end
@@ -1087,13 +1087,13 @@ describe API::Commits do
let(:project) { create(:project, :public, :repository) }
it_behaves_like '403 response' do
- let(:request) { post api(route), branch: 'master' }
+ let(:request) { post api(route), params: { branch: 'master' } }
end
end
context 'when unauthenticated', 'and project is private' do
it_behaves_like '404 response' do
- let(:request) { post api(route), branch: 'master' }
+ let(:request) { post api(route), params: { branch: 'master' } }
let(:message) { '404 Project Not Found' }
end
end
@@ -1107,7 +1107,7 @@ describe API::Commits do
let(:commit_id) { 'unknown' }
it_behaves_like '404 response' do
- let(:request) { post api(route, current_user), branch: 'master' }
+ let(:request) { post api(route, current_user), params: { branch: 'master' } }
let(:message) { '404 Commit Not Found' }
end
end
@@ -1121,21 +1121,21 @@ describe API::Commits do
context 'when branch is empty' do
['', ' '].each do |branch|
it_behaves_like '400 response' do
- let(:request) { post api(route, current_user), branch: branch }
+ let(:request) { post api(route, current_user), params: { branch: branch } }
end
end
end
context 'when branch does not exist' do
it_behaves_like '404 response' do
- let(:request) { post api(route, current_user), branch: 'foo' }
+ let(:request) { post api(route, current_user), params: { branch: 'foo' } }
let(:message) { '404 Branch Not Found' }
end
end
context 'when commit is already included in the target branch' do
it_behaves_like '400 response' do
- let(:request) { post api(route, current_user), branch: 'markdown' }
+ let(:request) { post api(route, current_user), params: { branch: 'markdown' } }
end
end
@@ -1150,7 +1150,7 @@ describe API::Commits do
let(:commit_id) { branch_with_slash.name }
it_behaves_like '404 response' do
- let(:request) { post api(route, current_user), branch: 'master' }
+ let(:request) { post api(route, current_user), params: { branch: 'master' } }
end
end
@@ -1181,7 +1181,7 @@ describe API::Commits do
end
it 'returns 400 if you are not allowed to push to the target branch' do
- post api(route, current_user), branch: 'feature'
+ post api(route, current_user), params: { branch: 'feature' }
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to match(/You are not allowed to push into this branch/)
@@ -1195,13 +1195,13 @@ describe API::Commits do
let(:project_id) { forked_project.id }
it 'allows access from a maintainer that to the source branch' do
- post api(route, user), branch: 'feature'
+ post api(route, user), params: { branch: 'feature' }
expect(response).to have_gitlab_http_status(:created)
end
it 'denies cherry picking to another branch' do
- post api(route, user), branch: 'master'
+ post api(route, user), params: { branch: 'master' }
expect(response).to have_gitlab_http_status(:forbidden)
end
@@ -1217,7 +1217,7 @@ describe API::Commits do
shared_examples_for 'ref revert' do
context 'when ref exists' do
it 'reverts the ref commit' do
- post api(route, current_user), branch: branch
+ post api(route, current_user), params: { branch: branch }
expect(response).to have_gitlab_http_status(201)
expect(response).to match_response_schema('public_api/v4/commit/basic')
@@ -1233,7 +1233,7 @@ describe API::Commits do
include_context 'disabled repository'
it_behaves_like '403 response' do
- let(:request) { post api(route, current_user), branch: branch }
+ let(:request) { post api(route, current_user), params: { branch: branch } }
end
end
end
@@ -1242,13 +1242,13 @@ describe API::Commits do
let(:project) { create(:project, :public, :repository) }
it_behaves_like '403 response' do
- let(:request) { post api(route), branch: branch }
+ let(:request) { post api(route), params: { branch: branch } }
end
end
context 'when unauthenticated', 'and project is private' do
it_behaves_like '404 response' do
- let(:request) { post api(route), branch: branch }
+ let(:request) { post api(route), params: { branch: branch } }
let(:message) { '404 Project Not Found' }
end
end
@@ -1262,7 +1262,7 @@ describe API::Commits do
let(:commit_id) { 'unknown' }
it_behaves_like '404 response' do
- let(:request) { post api(route, current_user), branch: branch }
+ let(:request) { post api(route, current_user), params: { branch: branch } }
let(:message) { '404 Commit Not Found' }
end
end
@@ -1276,14 +1276,14 @@ describe API::Commits do
context 'when branch is empty' do
['', ' '].each do |branch|
it_behaves_like '400 response' do
- let(:request) { post api(route, current_user), branch: branch }
+ let(:request) { post api(route, current_user), params: { branch: branch } }
end
end
end
context 'when branch does not exist' do
it_behaves_like '404 response' do
- let(:request) { post api(route, current_user), branch: 'foo' }
+ let(:request) { post api(route, current_user), params: { branch: 'foo' } }
let(:message) { '404 Branch Not Found' }
end
end
@@ -1311,7 +1311,7 @@ describe API::Commits do
end
it 'returns 400 if you are not allowed to push to the target branch' do
- post api(route, current_user), branch: 'feature'
+ post api(route, current_user), params: { branch: 'feature' }
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to match(/You are not allowed to push into this branch/)
@@ -1329,7 +1329,7 @@ describe API::Commits do
shared_examples_for 'ref new comment' do
context 'when ref exists' do
it 'creates the comment' do
- post api(route, current_user), note: note
+ post api(route, current_user), params: { note: note }
expect(response).to have_gitlab_http_status(201)
expect(response).to match_response_schema('public_api/v4/commit_note')
@@ -1344,7 +1344,7 @@ describe API::Commits do
include_context 'disabled repository'
it_behaves_like '403 response' do
- let(:request) { post api(route, current_user), note: 'My comment' }
+ let(:request) { post api(route, current_user), params: { note: 'My comment' } }
end
end
end
@@ -1353,13 +1353,13 @@ describe API::Commits do
let(:project) { create(:project, :public, :repository) }
it_behaves_like '400 response' do
- let(:request) { post api(route), note: 'My comment' }
+ let(:request) { post api(route), params: { note: 'My comment' } }
end
end
context 'when unauthenticated', 'and project is private' do
it_behaves_like '404 response' do
- let(:request) { post api(route), note: 'My comment' }
+ let(:request) { post api(route), params: { note: 'My comment' } }
let(:message) { '404 Project Not Found' }
end
end
@@ -1370,7 +1370,7 @@ describe API::Commits do
it_behaves_like 'ref new comment'
it 'returns the inline comment' do
- post api(route, current_user), note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 1, line_type: 'new'
+ post api(route, current_user), params: { note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 1, line_type: 'new' }
expect(response).to have_gitlab_http_status(201)
expect(response).to match_response_schema('public_api/v4/commit_note')
@@ -1384,7 +1384,7 @@ describe API::Commits do
let(:commit_id) { 'unknown' }
it_behaves_like '404 response' do
- let(:request) { post api(route, current_user), note: 'My comment' }
+ let(:request) { post api(route, current_user), params: { note: 'My comment' } }
let(:message) { '404 Commit Not Found' }
end
end
@@ -1405,7 +1405,7 @@ describe API::Commits do
let(:commit_id) { branch_with_slash.name }
it_behaves_like '404 response' do
- let(:request) { post api(route, current_user), note: 'My comment' }
+ let(:request) { post api(route, current_user), params: { note: 'My comment' } }
end
end
diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb
index 32fc704a79b..b93ee148736 100644
--- a/spec/requests/api/deploy_keys_spec.rb
+++ b/spec/requests/api/deploy_keys_spec.rb
@@ -72,14 +72,14 @@ describe API::DeployKeys do
describe 'POST /projects/:id/deploy_keys' do
it 'does not create an invalid ssh key' do
- post api("/projects/#{project.id}/deploy_keys", admin), { title: 'invalid key' }
+ post api("/projects/#{project.id}/deploy_keys", admin), params: { title: 'invalid key' }
expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq('key is missing')
end
it 'does not create a key without title' do
- post api("/projects/#{project.id}/deploy_keys", admin), key: 'some key'
+ post api("/projects/#{project.id}/deploy_keys", admin), params: { key: 'some key' }
expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq('title is missing')
@@ -89,7 +89,7 @@ describe API::DeployKeys do
key_attrs = attributes_for :another_key
expect do
- post api("/projects/#{project.id}/deploy_keys", admin), key_attrs
+ post api("/projects/#{project.id}/deploy_keys", admin), params: key_attrs
end.to change { project.deploy_keys.count }.by(1)
new_key = project.deploy_keys.last
@@ -99,7 +99,7 @@ describe API::DeployKeys do
it 'returns an existing ssh key when attempting to add a duplicate' do
expect do
- post api("/projects/#{project.id}/deploy_keys", admin), { key: deploy_key.key, title: deploy_key.title }
+ post api("/projects/#{project.id}/deploy_keys", admin), params: { key: deploy_key.key, title: deploy_key.title }
end.not_to change { project.deploy_keys.count }
expect(response).to have_gitlab_http_status(201)
@@ -107,7 +107,7 @@ describe API::DeployKeys do
it 'joins an existing ssh key to a new project' do
expect do
- post api("/projects/#{project2.id}/deploy_keys", admin), { key: deploy_key.key, title: deploy_key.title }
+ post api("/projects/#{project2.id}/deploy_keys", admin), params: { key: deploy_key.key, title: deploy_key.title }
end.to change { project2.deploy_keys.count }.by(1)
expect(response).to have_gitlab_http_status(201)
@@ -116,7 +116,7 @@ describe API::DeployKeys do
it 'accepts can_push parameter' do
key_attrs = attributes_for(:another_key).merge(can_push: true)
- post api("/projects/#{project.id}/deploy_keys", admin), key_attrs
+ post api("/projects/#{project.id}/deploy_keys", admin), params: key_attrs
expect(response).to have_gitlab_http_status(201)
expect(json_response['can_push']).to eq(true)
@@ -131,7 +131,7 @@ describe API::DeployKeys do
it 'updates a public deploy key as admin' do
expect do
- put api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin), { title: 'new title' }
+ put api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin), params: { title: 'new title' }
end.not_to change(deploy_key, :title)
expect(response).to have_gitlab_http_status(200)
@@ -139,7 +139,7 @@ describe API::DeployKeys do
it 'does not update a public deploy key as non admin' do
expect do
- put api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", user), { title: 'new title' }
+ put api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", user), params: { title: 'new title' }
end.not_to change(deploy_key, :title)
expect(response).to have_gitlab_http_status(404)
@@ -149,7 +149,7 @@ describe API::DeployKeys do
project_private_deploy_key
expect do
- put api("/projects/#{project.id}/deploy_keys/#{private_deploy_key.id}", admin), { title: '' }
+ put api("/projects/#{project.id}/deploy_keys/#{private_deploy_key.id}", admin), params: { title: '' }
end.not_to change(deploy_key, :title)
expect(response).to have_gitlab_http_status(400)
@@ -158,7 +158,7 @@ describe API::DeployKeys do
it 'updates a private ssh key with correct attributes' do
project_private_deploy_key
- put api("/projects/#{project.id}/deploy_keys/#{private_deploy_key.id}", admin), { title: 'new title', can_push: true }
+ put api("/projects/#{project.id}/deploy_keys/#{private_deploy_key.id}", admin), params: { title: 'new title', can_push: true }
expect(json_response['id']).to eq(private_deploy_key.id)
expect(json_response['title']).to eq('new title')
diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb
index 308134eba72..d74484c8d29 100644
--- a/spec/requests/api/doorkeeper_access_spec.rb
+++ b/spec/requests/api/doorkeeper_access_spec.rb
@@ -7,20 +7,20 @@ describe 'doorkeeper access' do
describe "unauthenticated" do
it "returns authentication success" do
- get api("/user"), access_token: token.token
+ get api("/user"), params: { access_token: token.token }
expect(response).to have_gitlab_http_status(200)
end
include_examples 'user login request with unique ip limit' do
def request
- get api('/user'), access_token: token.token
+ get api('/user'), params: { access_token: token.token }
end
end
end
describe "when token invalid" do
it "returns authentication error" do
- get api("/user"), access_token: "123a"
+ get api("/user"), params: { access_token: "123a" }
expect(response).to have_gitlab_http_status(401)
end
end
@@ -41,7 +41,7 @@ describe 'doorkeeper access' do
describe "when user is blocked" do
it "returns authorization error" do
user.block
- get api("/user"), access_token: token.token
+ get api("/user"), params: { access_token: token.token }
expect(response).to have_gitlab_http_status(403)
end
@@ -50,7 +50,7 @@ describe 'doorkeeper access' do
describe "when user is ldap_blocked" do
it "returns authorization error" do
user.ldap_block
- get api("/user"), access_token: token.token
+ get api("/user"), params: { access_token: token.token }
expect(response).to have_gitlab_http_status(403)
end
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
index f3db0c122a0..493d3642255 100644
--- a/spec/requests/api/environments_spec.rb
+++ b/spec/requests/api/environments_spec.rb
@@ -47,7 +47,7 @@ describe API::Environments do
describe 'POST /projects/:id/environments' do
context 'as a member' do
it 'creates a environment with valid params' do
- post api("/projects/#{project.id}/environments", user), name: "mepmep"
+ post api("/projects/#{project.id}/environments", user), params: { name: "mepmep" }
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq('mepmep')
@@ -56,19 +56,19 @@ describe API::Environments do
end
it 'requires name to be passed' do
- post api("/projects/#{project.id}/environments", user), external_url: 'test.gitlab.com'
+ post api("/projects/#{project.id}/environments", user), params: { external_url: 'test.gitlab.com' }
expect(response).to have_gitlab_http_status(400)
end
it 'returns a 400 if environment already exists' do
- post api("/projects/#{project.id}/environments", user), name: environment.name
+ post api("/projects/#{project.id}/environments", user), params: { name: environment.name }
expect(response).to have_gitlab_http_status(400)
end
it 'returns a 400 if slug is specified' do
- post api("/projects/#{project.id}/environments", user), name: "foo", slug: "foo"
+ post api("/projects/#{project.id}/environments", user), params: { name: "foo", slug: "foo" }
expect(response).to have_gitlab_http_status(400)
expect(json_response["error"]).to eq("slug is automatically generated and cannot be changed")
@@ -77,13 +77,13 @@ describe API::Environments do
context 'a non member' do
it 'rejects the request' do
- post api("/projects/#{project.id}/environments", non_member), name: 'gitlab.com'
+ post api("/projects/#{project.id}/environments", non_member), params: { name: 'gitlab.com' }
expect(response).to have_gitlab_http_status(404)
end
it 'returns a 400 when the required params are missing' do
- post api("/projects/12345/environments", non_member), external_url: 'http://env.git.com'
+ post api("/projects/12345/environments", non_member), params: { external_url: 'http://env.git.com' }
end
end
end
@@ -92,7 +92,7 @@ describe API::Environments do
it 'returns a 200 if name and external_url are changed' do
url = 'https://mepmep.whatever.ninja'
put api("/projects/#{project.id}/environments/#{environment.id}", user),
- name: 'Mepmep', external_url: url
+ params: { name: 'Mepmep', external_url: url }
expect(response).to have_gitlab_http_status(200)
expect(json_response['name']).to eq('Mepmep')
@@ -102,7 +102,7 @@ describe API::Environments do
it "won't allow slug to be changed" do
slug = environment.slug
api_url = api("/projects/#{project.id}/environments/#{environment.id}", user)
- put api_url, slug: slug + "-foo"
+ put api_url, params: { slug: slug + "-foo" }
expect(response).to have_gitlab_http_status(400)
expect(json_response["error"]).to eq("slug is automatically generated and cannot be changed")
@@ -111,7 +111,7 @@ describe API::Environments do
it "won't update the external_url if only the name is passed" do
url = environment.external_url
put api("/projects/#{project.id}/environments/#{environment.id}", user),
- name: 'Mepmep'
+ params: { name: 'Mepmep' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['name']).to eq('Mepmep')
diff --git a/spec/requests/api/events_spec.rb b/spec/requests/api/events_spec.rb
index 573eebe314d..0ac23505de7 100644
--- a/spec/requests/api/events_spec.rb
+++ b/spec/requests/api/events_spec.rb
@@ -182,6 +182,68 @@ describe API::Events do
end
end
+ context 'with inaccessible events' do
+ let(:public_project) { create(:project, :public, creator_id: user.id, namespace: user.namespace) }
+ let(:confidential_issue) { create(:closed_issue, confidential: true, project: public_project, author: user) }
+ let!(:confidential_event) { create(:event, project: public_project, author: user, target: confidential_issue, action: Event::CLOSED) }
+ let(:public_issue) { create(:closed_issue, project: public_project, author: user) }
+ let!(:public_event) { create(:event, project: public_project, author: user, target: public_issue, action: Event::CLOSED) }
+
+ it 'returns only accessible events' do
+ get api("/projects/#{public_project.id}/events", non_member)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.size).to eq(1)
+ end
+
+ it 'returns all events when the user has access' do
+ get api("/projects/#{public_project.id}/events", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.size).to eq(2)
+ end
+ end
+
+ context 'pagination' do
+ let(:public_project) { create(:project, :public) }
+
+ before do
+ create(:event,
+ project: public_project,
+ target: create(:issue, project: public_project, title: 'Issue 1'),
+ action: Event::CLOSED,
+ created_at: Date.parse('2018-12-10'))
+ create(:event,
+ project: public_project,
+ target: create(:issue, confidential: true, project: public_project, title: 'Confidential event'),
+ action: Event::CLOSED,
+ created_at: Date.parse('2018-12-11'))
+ create(:event,
+ project: public_project,
+ target: create(:issue, project: public_project, title: 'Issue 2'),
+ action: Event::CLOSED,
+ created_at: Date.parse('2018-12-12'))
+ end
+
+ it 'correctly returns the second page without inaccessible events' do
+ get api("/projects/#{public_project.id}/events", user), params: { per_page: 2, page: 2 }
+
+ titles = json_response.map { |event| event['target_title'] }
+
+ expect(titles.first).to eq('Issue 1')
+ expect(titles).not_to include('Confidential event')
+ end
+
+ it 'correctly returns the first page without inaccessible events' do
+ get api("/projects/#{public_project.id}/events", user), params: { per_page: 2, page: 1 }
+
+ titles = json_response.map { |event| event['target_title'] }
+
+ expect(titles.first).to eq('Issue 2')
+ expect(titles).not_to include('Confidential event')
+ end
+ end
+
context 'when not permitted to read' do
it 'returns 404' do
get api("/projects/#{private_project.id}/events", non_member)
@@ -217,13 +279,13 @@ describe API::Events do
it 'avoids N+1 queries' do
control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
- get api("/projects/#{private_project.id}/events", user), target_type: :merge_request
+ get api("/projects/#{private_project.id}/events", user), params: { target_type: :merge_request }
end.count
create_event(merge_request2)
expect do
- get api("/projects/#{private_project.id}/events", user), target_type: :merge_request
+ get api("/projects/#{private_project.id}/events", user), params: { target_type: :merge_request }
end.not_to exceed_all_query_limit(control_count)
expect(response).to have_gitlab_http_status(200)
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
index c5354c2d639..7d3eff7d32d 100644
--- a/spec/requests/api/features_spec.rb
+++ b/spec/requests/api/features_spec.rb
@@ -79,7 +79,7 @@ describe API::Features do
context 'when passed value=true' do
it 'creates an enabled feature' do
- post api("/features/#{feature_name}", admin), value: 'true'
+ post api("/features/#{feature_name}", admin), params: { value: 'true' }
expect(response).to have_gitlab_http_status(201)
expect(json_response).to eq(
@@ -89,7 +89,7 @@ describe API::Features do
end
it 'creates an enabled feature for the given Flipper group when passed feature_group=perf_team' do
- post api("/features/#{feature_name}", admin), value: 'true', feature_group: 'perf_team'
+ post api("/features/#{feature_name}", admin), params: { value: 'true', feature_group: 'perf_team' }
expect(response).to have_gitlab_http_status(201)
expect(json_response).to eq(
@@ -102,7 +102,7 @@ describe API::Features do
end
it 'creates an enabled feature for the given user when passed user=username' do
- post api("/features/#{feature_name}", admin), value: 'true', user: user.username
+ post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username }
expect(response).to have_gitlab_http_status(201)
expect(json_response).to eq(
@@ -115,7 +115,7 @@ describe API::Features do
end
it 'creates an enabled feature for the given user and feature group when passed user=username and feature_group=perf_team' do
- post api("/features/#{feature_name}", admin), value: 'true', user: user.username, feature_group: 'perf_team'
+ post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username, feature_group: 'perf_team' }
expect(response).to have_gitlab_http_status(201)
expect(json_response).to eq(
@@ -130,7 +130,7 @@ describe API::Features do
end
it 'creates a feature with the given percentage if passed an integer' do
- post api("/features/#{feature_name}", admin), value: '50'
+ post api("/features/#{feature_name}", admin), params: { value: '50' }
expect(response).to have_gitlab_http_status(201)
expect(json_response).to eq(
@@ -152,7 +152,7 @@ describe API::Features do
context 'when passed value=true' do
it 'enables the feature' do
- post api("/features/#{feature_name}", admin), value: 'true'
+ post api("/features/#{feature_name}", admin), params: { value: 'true' }
expect(response).to have_gitlab_http_status(201)
expect(json_response).to eq(
@@ -162,7 +162,7 @@ describe API::Features do
end
it 'enables the feature for the given Flipper group when passed feature_group=perf_team' do
- post api("/features/#{feature_name}", admin), value: 'true', feature_group: 'perf_team'
+ post api("/features/#{feature_name}", admin), params: { value: 'true', feature_group: 'perf_team' }
expect(response).to have_gitlab_http_status(201)
expect(json_response).to eq(
@@ -175,7 +175,7 @@ describe API::Features do
end
it 'enables the feature for the given user when passed user=username' do
- post api("/features/#{feature_name}", admin), value: 'true', user: user.username
+ post api("/features/#{feature_name}", admin), params: { value: 'true', user: user.username }
expect(response).to have_gitlab_http_status(201)
expect(json_response).to eq(
@@ -193,7 +193,7 @@ describe API::Features do
feature.enable
expect(feature).to be_enabled
- post api("/features/#{feature_name}", admin), value: 'false'
+ post api("/features/#{feature_name}", admin), params: { value: 'false' }
expect(response).to have_gitlab_http_status(201)
expect(json_response).to eq(
@@ -206,7 +206,7 @@ describe API::Features do
feature.enable(Feature.group(:perf_team))
expect(Feature.get(feature_name).enabled?(admin)).to be_truthy
- post api("/features/#{feature_name}", admin), value: 'false', feature_group: 'perf_team'
+ post api("/features/#{feature_name}", admin), params: { value: 'false', feature_group: 'perf_team' }
expect(response).to have_gitlab_http_status(201)
expect(json_response).to eq(
@@ -219,7 +219,7 @@ describe API::Features do
feature.enable(user)
expect(Feature.get(feature_name).enabled?(user)).to be_truthy
- post api("/features/#{feature_name}", admin), value: 'false', user: user.username
+ post api("/features/#{feature_name}", admin), params: { value: 'false', user: user.username }
expect(response).to have_gitlab_http_status(201)
expect(json_response).to eq(
@@ -235,7 +235,7 @@ describe API::Features do
end
it 'updates the percentage of time if passed an integer' do
- post api("/features/#{feature_name}", admin), value: '30'
+ post api("/features/#{feature_name}", admin), params: { value: '30' }
expect(response).to have_gitlab_http_status(201)
expect(json_response).to eq(
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 620f9f5e1d6..0ba1f2d7a2b 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -54,7 +54,7 @@ describe API::Files do
describe "HEAD /projects/:id/repository/files/:file_path" do
shared_examples_for 'repository files' do
it 'returns file attributes in headers' do
- head api(route(file_path), current_user), params
+ head api(route(file_path), current_user), params: params
expect(response).to have_gitlab_http_status(200)
expect(response.headers['X-Gitlab-File-Path']).to eq(CGI.unescape(file_path))
@@ -68,7 +68,7 @@ describe API::Files do
file_path = "files%2Fjs%2Fcommit%2Ejs%2Ecoffee"
params[:ref] = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9"
- head api(route(file_path), current_user), params
+ head api(route(file_path), current_user), params: params
expect(response).to have_gitlab_http_status(200)
expect(response.headers['X-Gitlab-File-Name']).to eq('commit.js.coffee')
@@ -87,7 +87,7 @@ describe API::Files do
it "responds with a 404 status" do
params[:ref] = 'master'
- head api(route('app%2Fmodels%2Fapplication%2Erb'), current_user), params
+ head api(route('app%2Fmodels%2Fapplication%2Erb'), current_user), params: params
expect(response).to have_gitlab_http_status(404)
end
@@ -97,7 +97,7 @@ describe API::Files do
include_context 'disabled repository'
it "responds with a 403 status" do
- head api(route(file_path), current_user), params
+ head api(route(file_path), current_user), params: params
expect(response).to have_gitlab_http_status(403)
end
@@ -115,7 +115,7 @@ describe API::Files do
it "responds with a 404 status" do
current_user = nil
- head api(route(file_path), current_user), params
+ head api(route(file_path), current_user), params: params
expect(response).to have_gitlab_http_status(404)
end
@@ -136,7 +136,7 @@ describe API::Files do
context 'when authenticated', 'as a guest' do
it_behaves_like '403 response' do
- let(:request) { head api(route(file_path), guest), params }
+ let(:request) { head api(route(file_path), guest), params: params }
end
end
end
@@ -144,7 +144,7 @@ describe API::Files do
describe "GET /projects/:id/repository/files/:file_path" do
shared_examples_for 'repository files' do
it 'returns file attributes as json' do
- get api(route(file_path), current_user), params
+ get api(route(file_path), current_user), params: params
expect(response).to have_gitlab_http_status(200)
expect(json_response['file_path']).to eq(CGI.unescape(file_path))
@@ -157,7 +157,7 @@ describe API::Files do
it 'returns json when file has txt extension' do
file_path = "bar%2Fbranch-test.txt"
- get api(route(file_path), current_user), params
+ get api(route(file_path), current_user), params: params
expect(response).to have_gitlab_http_status(200)
expect(response.content_type).to eq('application/json')
@@ -168,7 +168,7 @@ describe API::Files do
file_path = "files%2Fjs%2Fcommit%2Ejs%2Ecoffee"
params[:ref] = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9"
- get api(route(file_path), current_user), params
+ get api(route(file_path), current_user), params: params
expect(response).to have_gitlab_http_status(200)
expect(json_response['file_name']).to eq('commit.js.coffee')
@@ -180,7 +180,7 @@ describe API::Files do
url = route(file_path) + "/raw"
expect(Gitlab::Workhorse).to receive(:send_git_blob)
- get api(url, current_user), params
+ get api(url, current_user), params: params
expect(response).to have_gitlab_http_status(200)
end
@@ -188,7 +188,7 @@ describe API::Files do
it 'forces attachment content disposition' do
url = route(file_path) + "/raw"
- get api(url, current_user), params
+ get api(url, current_user), params: params
expect(headers['Content-Disposition']).to match(/^attachment/)
end
@@ -203,7 +203,7 @@ describe API::Files do
let(:params) { { ref: 'master' } }
it_behaves_like '404 response' do
- let(:request) { get api(route('app%2Fmodels%2Fapplication%2Erb'), current_user), params }
+ let(:request) { get api(route('app%2Fmodels%2Fapplication%2Erb'), current_user), params: params }
let(:message) { '404 File Not Found' }
end
end
@@ -212,7 +212,7 @@ describe API::Files do
include_context 'disabled repository'
it_behaves_like '403 response' do
- let(:request) { get api(route(file_path), current_user), params }
+ let(:request) { get api(route(file_path), current_user), params: params }
end
end
end
@@ -233,7 +233,7 @@ describe API::Files do
context 'when unauthenticated', 'and project is private' do
it_behaves_like '404 response' do
- let(:request) { get api(route(file_path)), params }
+ let(:request) { get api(route(file_path)), params: params }
let(:message) { '404 Project Not Found' }
end
end
@@ -246,7 +246,7 @@ describe API::Files do
context 'when authenticated', 'as a guest' do
it_behaves_like '403 response' do
- let(:request) { get api(route(file_path), guest), params }
+ let(:request) { get api(route(file_path), guest), params: params }
end
end
end
@@ -257,7 +257,7 @@ describe API::Files do
url = route(file_path) + "/raw"
expect(Gitlab::Workhorse).to receive(:send_git_blob)
- get api(url, current_user), params
+ get api(url, current_user), params: params
expect(response).to have_gitlab_http_status(200)
end
@@ -266,7 +266,7 @@ describe API::Files do
url = route('.gitignore') + "/raw"
expect(Gitlab::Workhorse).to receive(:send_git_blob)
- get api(url, current_user), params
+ get api(url, current_user), params: params
expect(response).to have_gitlab_http_status(200)
end
@@ -277,7 +277,7 @@ describe API::Files do
params[:ref] = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9"
expect(Gitlab::Workhorse).to receive(:send_git_blob)
- get api(route(file_path) + "/raw", current_user), params
+ get api(route(file_path) + "/raw", current_user), params: params
expect(response).to have_gitlab_http_status(200)
end
@@ -292,7 +292,7 @@ describe API::Files do
let(:params) { { ref: 'master' } }
it_behaves_like '404 response' do
- let(:request) { get api(route('app%2Fmodels%2Fapplication%2Erb'), current_user), params }
+ let(:request) { get api(route('app%2Fmodels%2Fapplication%2Erb'), current_user), params: params }
let(:message) { '404 File Not Found' }
end
end
@@ -301,7 +301,7 @@ describe API::Files do
include_context 'disabled repository'
it_behaves_like '403 response' do
- let(:request) { get api(route(file_path), current_user), params }
+ let(:request) { get api(route(file_path), current_user), params: params }
end
end
end
@@ -315,7 +315,7 @@ describe API::Files do
context 'when unauthenticated', 'and project is private' do
it_behaves_like '404 response' do
- let(:request) { get api(route(file_path)), params }
+ let(:request) { get api(route(file_path)), params: params }
let(:message) { '404 Project Not Found' }
end
end
@@ -328,7 +328,7 @@ describe API::Files do
context 'when authenticated', 'as a guest' do
it_behaves_like '403 response' do
- let(:request) { get api(route(file_path), guest), params }
+ let(:request) { get api(route(file_path), guest), params: params }
end
end
@@ -341,7 +341,7 @@ describe API::Files do
params[:ref] = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9"
expect(Gitlab::Workhorse).to receive(:send_git_blob)
- get api(route(file_path) + "/raw", personal_access_token: token), params
+ get api(route(file_path) + "/raw", personal_access_token: token), params: params
expect(response).to have_gitlab_http_status(200)
end
@@ -359,7 +359,7 @@ describe API::Files do
end
it "creates a new file in project repo" do
- post api(route(file_path), user), params
+ post api(route(file_path), user), params: params
expect(response).to have_gitlab_http_status(201)
expect(json_response["file_path"]).to eq(CGI.unescape(file_path))
@@ -377,7 +377,7 @@ describe API::Files do
it 'returns a 400 bad request if the commit message is empty' do
params[:commit_message] = ''
- post api(route(file_path), user), params
+ post api(route(file_path), user), params: params
expect(response).to have_gitlab_http_status(400)
end
@@ -386,7 +386,7 @@ describe API::Files do
allow_any_instance_of(Repository).to receive(:create_file)
.and_raise(Gitlab::Git::CommitError, 'Cannot create file')
- post api(route("any%2Etxt"), user), params
+ post api(route("any%2Etxt"), user), params: params
expect(response).to have_gitlab_http_status(400)
end
@@ -395,7 +395,7 @@ describe API::Files do
it 'returns 403 with `read_repository` scope' do
token = create(:personal_access_token, scopes: ['read_repository'], user: user)
- post api(route(file_path), personal_access_token: token), params
+ post api(route(file_path), personal_access_token: token), params: params
expect(response).to have_gitlab_http_status(403)
end
@@ -403,7 +403,7 @@ describe API::Files do
it 'returns 201 with `api` scope' do
token = create(:personal_access_token, scopes: ['api'], user: user)
- post api(route(file_path), personal_access_token: token), params
+ post api(route(file_path), personal_access_token: token), params: params
expect(response).to have_gitlab_http_status(201)
end
@@ -413,7 +413,7 @@ describe API::Files do
it "creates a new file with the specified author" do
params.merge!(author_email: author_email, author_name: author_name)
- post api(route("new_file_with_author%2Etxt"), user), params
+ post api(route("new_file_with_author%2Etxt"), user), params: params
expect(response).to have_gitlab_http_status(201)
expect(response.content_type).to eq('application/json')
@@ -427,7 +427,7 @@ describe API::Files do
let!(:project) { create(:project_empty_repo, namespace: user.namespace ) }
it "creates a new file in project repo" do
- post api(route("newfile%2Erb"), user), params
+ post api(route("newfile%2Erb"), user), params: params
expect(response).to have_gitlab_http_status(201)
expect(json_response['file_path']).to eq('newfile.rb')
@@ -448,7 +448,7 @@ describe API::Files do
end
it "updates existing file in project repo" do
- put api(route(file_path), user), params
+ put api(route(file_path), user), params: params
expect(response).to have_gitlab_http_status(200)
expect(json_response['file_path']).to eq(CGI.unescape(file_path))
@@ -460,7 +460,7 @@ describe API::Files do
it 'returns a 400 bad request if the commit message is empty' do
params[:commit_message] = ''
- put api(route(file_path), user), params
+ put api(route(file_path), user), params: params
expect(response).to have_gitlab_http_status(400)
end
@@ -468,7 +468,7 @@ describe API::Files do
it "returns a 400 bad request if update existing file with stale last commit id" do
params_with_stale_id = params.merge(last_commit_id: 'stale')
- put api(route(file_path), user), params_with_stale_id
+ put api(route(file_path), user), params: params_with_stale_id
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq('You are attempting to update a file that has changed since you started editing it.')
@@ -479,7 +479,7 @@ describe API::Files do
.last_for_path(project.repository, 'master', URI.unescape(file_path))
params_with_correct_id = params.merge(last_commit_id: last_commit.id)
- put api(route(file_path), user), params_with_correct_id
+ put api(route(file_path), user), params: params_with_correct_id
expect(response).to have_gitlab_http_status(200)
end
@@ -494,7 +494,7 @@ describe API::Files do
it "updates a file with the specified author" do
params.merge!(author_email: author_email, author_name: author_name, content: "New content")
- put api(route(file_path), user), params
+ put api(route(file_path), user), params: params
expect(response).to have_gitlab_http_status(200)
last_commit = project.repository.commit.raw
@@ -513,7 +513,7 @@ describe API::Files do
end
it "deletes existing file in project repo" do
- delete api(route(file_path), user), params
+ delete api(route(file_path), user), params: params
expect(response).to have_gitlab_http_status(204)
end
@@ -527,7 +527,7 @@ describe API::Files do
it 'returns a 400 bad request if the commit message is empty' do
params[:commit_message] = ''
- delete api(route(file_path), user), params
+ delete api(route(file_path), user), params: params
expect(response).to have_gitlab_http_status(400)
end
@@ -535,7 +535,7 @@ describe API::Files do
it "returns a 400 if fails to delete file" do
allow_any_instance_of(Repository).to receive(:delete_file).and_raise(Gitlab::Git::CommitError, 'Cannot delete file')
- delete api(route(file_path), user), params
+ delete api(route(file_path), user), params: params
expect(response).to have_gitlab_http_status(400)
end
@@ -544,7 +544,7 @@ describe API::Files do
it "removes a file with the specified author" do
params.merge!(author_email: author_email, author_name: author_name)
- delete api(route(file_path), user), params
+ delete api(route(file_path), user), params: params
expect(response).to have_gitlab_http_status(204)
end
@@ -568,11 +568,11 @@ describe API::Files do
end
before do
- post api(route(file_path), user), put_params
+ post api(route(file_path), user), params: put_params
end
it "remains unchanged" do
- get api(route(file_path), user), get_params
+ get api(route(file_path), user), params: get_params
expect(response).to have_gitlab_http_status(200)
expect(json_response['file_path']).to eq(CGI.unescape(file_path))
diff --git a/spec/requests/api/group_boards_spec.rb b/spec/requests/api/group_boards_spec.rb
index 894c94688ba..b400a7f55ef 100644
--- a/spec/requests/api/group_boards_spec.rb
+++ b/spec/requests/api/group_boards_spec.rb
@@ -46,7 +46,7 @@ describe API::GroupBoards do
it 'does not create lists for child project labels' do
project_label = create(:label, project: project)
- post api(url, user), label_id: project_label.id
+ post api(url, user), params: { label_id: project_label.id }
expect(response).to have_gitlab_http_status(400)
end
diff --git a/spec/requests/api/group_variables_spec.rb b/spec/requests/api/group_variables_spec.rb
index f87e035c89d..e52f4c70407 100644
--- a/spec/requests/api/group_variables_spec.rb
+++ b/spec/requests/api/group_variables_spec.rb
@@ -87,7 +87,7 @@ describe API::GroupVariables do
it 'creates variable' do
expect do
- post api("/groups/#{group.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true
+ post api("/groups/#{group.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true }
end.to change {group.variables.count}.by(1)
expect(response).to have_gitlab_http_status(201)
@@ -98,7 +98,7 @@ describe API::GroupVariables do
it 'creates variable with optional attributes' do
expect do
- post api("/groups/#{group.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2'
+ post api("/groups/#{group.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2' }
end.to change {group.variables.count}.by(1)
expect(response).to have_gitlab_http_status(201)
@@ -109,7 +109,7 @@ describe API::GroupVariables do
it 'does not allow to duplicate variable key' do
expect do
- post api("/groups/#{group.id}/variables", user), key: variable.key, value: 'VALUE_2'
+ post api("/groups/#{group.id}/variables", user), params: { key: variable.key, value: 'VALUE_2' }
end.to change {group.variables.count}.by(0)
expect(response).to have_gitlab_http_status(400)
@@ -145,7 +145,7 @@ describe API::GroupVariables do
initial_variable = group.variables.reload.first
value_before = initial_variable.value
- put api("/groups/#{group.id}/variables/#{variable.key}", user), value: 'VALUE_1_UP', protected: true
+ put api("/groups/#{group.id}/variables/#{variable.key}", user), params: { value: 'VALUE_1_UP', protected: true }
updated_variable = group.variables.reload.first
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 688d91113ad..c9dfc5c4a7e 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -60,7 +60,7 @@ describe API::Groups do
end
it "does not include statistics" do
- get api("/groups", user1), statistics: true
+ get api("/groups", user1), params: { statistics: true }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -100,7 +100,7 @@ describe API::Groups do
project1.statistics.update!(attributes)
- get api("/groups", admin), statistics: true
+ get api("/groups", admin), params: { statistics: true }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -112,7 +112,7 @@ describe API::Groups do
context "when using skip_groups in request" do
it "returns all groups excluding skipped groups" do
- get api("/groups", admin), skip_groups: [group2.id]
+ get api("/groups", admin), params: { skip_groups: [group2.id] }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -127,7 +127,7 @@ describe API::Groups do
it "returns all groups you have access to" do
public_group = create :group, :public
- get api("/groups", user1), all_available: true
+ get api("/groups", user1), params: { all_available: true }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -159,7 +159,7 @@ describe API::Groups do
end
it "sorts in descending order when passed" do
- get api("/groups", user1), sort: "desc"
+ get api("/groups", user1), params: { sort: "desc" }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -168,7 +168,7 @@ describe API::Groups do
end
it "sorts by path in order_by param" do
- get api("/groups", user1), order_by: "path"
+ get api("/groups", user1), params: { order_by: "path" }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -177,7 +177,7 @@ describe API::Groups do
end
it "sorts by id in the order_by param" do
- get api("/groups", user1), order_by: "id"
+ get api("/groups", user1), params: { order_by: "id" }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -186,7 +186,7 @@ describe API::Groups do
end
it "sorts also by descending id with pagination fix" do
- get api("/groups", user1), order_by: "id", sort: "desc"
+ get api("/groups", user1), params: { order_by: "id", sort: "desc" }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -195,7 +195,7 @@ describe API::Groups do
end
it "sorts identical keys by id for good pagination" do
- get api("/groups", user1), search: "same-name", order_by: "name"
+ get api("/groups", user1), params: { search: "same-name", order_by: "name" }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -204,7 +204,7 @@ describe API::Groups do
end
it "sorts descending identical keys by id for good pagination" do
- get api("/groups", user1), search: "same-name", order_by: "name", sort: "desc"
+ get api("/groups", user1), params: { search: "same-name", order_by: "name", sort: "desc" }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -221,7 +221,7 @@ describe API::Groups do
it 'returns an array of groups the user owns' do
group1.add_maintainer(user2)
- get api('/groups', user2), owned: true
+ get api('/groups', user2), params: { owned: true }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -241,7 +241,7 @@ describe API::Groups do
end
it 'returns an array of groups the user has at least master access' do
- get api('/groups', user2), min_access_level: 40
+ get api('/groups', user2), params: { min_access_level: 40 }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -344,7 +344,7 @@ describe API::Groups do
project = create(:project, namespace: group2, path: 'Foo')
create(:project_group_link, project: project, group: group1)
- get api("/groups/#{group1.id}", user1), with_projects: false
+ get api("/groups/#{group1.id}", user1), params: { with_projects: false }
expect(response).to have_gitlab_http_status(200)
expect(json_response['projects']).to be_nil
@@ -426,7 +426,7 @@ describe API::Groups do
context 'when authenticated as the group owner' do
it 'updates the group' do
- put api("/groups/#{group1.id}", user1), name: new_group_name, request_access_enabled: true
+ put api("/groups/#{group1.id}", user1), params: { name: new_group_name, request_access_enabled: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response['name']).to eq(new_group_name)
@@ -434,7 +434,7 @@ describe API::Groups do
end
it 'returns 404 for a non existing group' do
- put api('/groups/1328', user1), name: new_group_name
+ put api('/groups/1328', user1), params: { name: new_group_name }
expect(response).to have_gitlab_http_status(404)
end
@@ -442,7 +442,7 @@ describe API::Groups do
context 'when authenticated as the admin' do
it 'updates the group' do
- put api("/groups/#{group1.id}", admin), name: new_group_name
+ put api("/groups/#{group1.id}", admin), params: { name: new_group_name }
expect(response).to have_gitlab_http_status(200)
expect(json_response['name']).to eq(new_group_name)
@@ -451,7 +451,7 @@ describe API::Groups do
context 'when authenticated as an user that can see the group' do
it 'does not updates the group' do
- put api("/groups/#{group1.id}", user2), name: new_group_name
+ put api("/groups/#{group1.id}", user2), params: { name: new_group_name }
expect(response).to have_gitlab_http_status(403)
end
@@ -459,7 +459,7 @@ describe API::Groups do
context 'when authenticated as an user that cannot see the group' do
it 'returns 404 when trying to update the group' do
- put api("/groups/#{group2.id}", user1), name: new_group_name
+ put api("/groups/#{group2.id}", user1), params: { name: new_group_name }
expect(response).to have_gitlab_http_status(404)
end
@@ -480,7 +480,7 @@ describe API::Groups do
end
it "returns the group's projects with simple representation" do
- get api("/groups/#{group1.id}/projects", user1), simple: true
+ get api("/groups/#{group1.id}/projects", user1), params: { simple: true }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -493,7 +493,7 @@ describe API::Groups do
it "filters the groups projects" do
public_project = create(:project, :public, path: 'test1', group: group1)
- get api("/groups/#{group1.id}/projects", user1), visibility: 'public'
+ get api("/groups/#{group1.id}/projects", user1), params: { visibility: 'public' }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -507,7 +507,7 @@ describe API::Groups do
create(:project_group_link, project: create(:project), group: group1)
create(:project_group_link, project: create(:project), group: group1)
- get api("/groups/#{group1.id}/projects", user1), with_shared: false
+ get api("/groups/#{group1.id}/projects", user1), params: { with_shared: false }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -520,7 +520,7 @@ describe API::Groups do
create(:project, group: subgroup)
create(:project, group: subgroup)
- get api("/groups/#{group1.id}/projects", user1), include_subgroups: true
+ get api("/groups/#{group1.id}/projects", user1), params: { include_subgroups: true }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -554,7 +554,7 @@ describe API::Groups do
it 'only returns the projects owned by user' do
project2.group.add_owner(user3)
- get api("/groups/#{project2.group.id}/projects", user3), owned: true
+ get api("/groups/#{project2.group.id}/projects", user3), params: { owned: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response.length).to eq(1)
@@ -564,7 +564,7 @@ describe API::Groups do
it 'only returns the projects starred by user' do
user1.starred_projects = [project1]
- get api("/groups/#{group1.id}/projects", user1), starred: true
+ get api("/groups/#{group1.id}/projects", user1), params: { starred: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response.length).to eq(1)
@@ -663,7 +663,7 @@ describe API::Groups do
context 'when using all_available in request' do
it 'returns public subgroups' do
- get api("/groups/#{group1.id}/subgroups", user2), all_available: true
+ get api("/groups/#{group1.id}/subgroups", user2), params: { all_available: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
@@ -702,7 +702,7 @@ describe API::Groups do
context 'when using statistics in request' do
it 'does not include statistics' do
- get api("/groups/#{group1.id}/subgroups", user2), statistics: true
+ get api("/groups/#{group1.id}/subgroups", user2), params: { statistics: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
@@ -754,7 +754,7 @@ describe API::Groups do
end
it 'includes statistics if requested' do
- get api("/groups/#{group1.id}/subgroups", admin), statistics: true
+ get api("/groups/#{group1.id}/subgroups", admin), params: { statistics: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
@@ -766,7 +766,7 @@ describe API::Groups do
describe "POST /groups" do
context "when authenticated as user without group permissions" do
it "does not create group" do
- post api("/groups", user1), attributes_for(:group)
+ post api("/groups", user1), params: attributes_for(:group)
expect(response).to have_gitlab_http_status(403)
end
@@ -777,7 +777,7 @@ describe API::Groups do
end
it 'can create subgroups' do
- post api("/groups", user1), parent_id: group2.id, name: 'foo', path: 'foo'
+ post api("/groups", user1), params: { parent_id: group2.id, name: 'foo', path: 'foo' }
expect(response).to have_gitlab_http_status(201)
end
@@ -789,7 +789,7 @@ describe API::Groups do
end
it 'cannot create subgroups' do
- post api("/groups", user1), parent_id: group2.id, name: 'foo', path: 'foo'
+ post api("/groups", user1), params: { parent_id: group2.id, name: 'foo', path: 'foo' }
expect(response).to have_gitlab_http_status(403)
end
@@ -800,7 +800,7 @@ describe API::Groups do
it "creates group" do
group = attributes_for(:group, { request_access_enabled: false })
- post api("/groups", user3), group
+ post api("/groups", user3), params: group
expect(response).to have_gitlab_http_status(201)
@@ -815,7 +815,7 @@ describe API::Groups do
parent.add_owner(user3)
group = attributes_for(:group, { parent_id: parent.id })
- post api("/groups", user3), group
+ post api("/groups", user3), params: group
expect(response).to have_gitlab_http_status(201)
@@ -824,20 +824,20 @@ describe API::Groups do
end
it "does not create group, duplicate" do
- post api("/groups", user3), { name: 'Duplicate Test', path: group2.path }
+ post api("/groups", user3), params: { name: 'Duplicate Test', path: group2.path }
expect(response).to have_gitlab_http_status(400)
expect(response.message).to eq("Bad Request")
end
it "returns 400 bad request error if name not given" do
- post api("/groups", user3), { path: group2.path }
+ post api("/groups", user3), params: { path: group2.path }
expect(response).to have_gitlab_http_status(400)
end
it "returns 400 bad request error if path not given" do
- post api("/groups", user3), { name: 'test' }
+ post api("/groups", user3), params: { name: 'test' }
expect(response).to have_gitlab_http_status(400)
end
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index 2c40e266f5f..a0c64d295c0 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -5,7 +5,6 @@ require_relative '../../../config/initializers/sentry'
describe API::Helpers do
include API::APIGuard::HelperMethods
include described_class
- include SentryHelper
include TermsHelper
let(:user) { create(:user) }
@@ -224,8 +223,15 @@ describe API::Helpers do
describe '.handle_api_exception' do
before do
- allow_any_instance_of(self.class).to receive(:sentry_enabled?).and_return(true)
allow_any_instance_of(self.class).to receive(:rack_response)
+ allow(Gitlab::Sentry).to receive(:enabled?).and_return(true)
+
+ stub_application_setting(
+ sentry_enabled: true,
+ sentry_dsn: "dummy://12345:67890@sentry.localdomain/sentry/42"
+ )
+ configure_sentry
+ Raven.client.configuration.encoding = 'json'
end
it 'does not report a MethodNotAllowed exception to Sentry' do
@@ -241,10 +247,13 @@ describe API::Helpers do
exception = RuntimeError.new('test error')
allow(exception).to receive(:backtrace).and_return(caller)
- expect_any_instance_of(self.class).to receive(:sentry_context)
- expect(Raven).to receive(:capture_exception).with(exception, extra: {})
+ expect(Raven).to receive(:capture_exception).with(exception, tags: {
+ correlation_id: 'new-correlation-id'
+ }, extra: {})
- handle_api_exception(exception)
+ Gitlab::CorrelationId.use_id('new-correlation-id') do
+ handle_api_exception(exception)
+ end
end
context 'with a personal access token given' do
@@ -255,7 +264,6 @@ describe API::Helpers do
# We need to stub at a lower level than #sentry_enabled? otherwise
# Sentry is not enabled when the request below is made, and the test
# would pass even without the fix
- expect(Gitlab::Sentry).to receive(:enabled?).twice.and_return(true)
expect(ProjectsFinder).to receive(:new).and_raise('Runtime Error!')
get api('/projects', personal_access_token: token)
@@ -272,20 +280,10 @@ describe API::Helpers do
# Sentry events are an array of the form [auth_header, data, options]
let(:event_data) { Raven.client.transport.events.first[1] }
- before do
- stub_application_setting(
- sentry_enabled: true,
- sentry_dsn: "dummy://12345:67890@sentry.localdomain/sentry/42"
- )
- configure_sentry
- Raven.client.configuration.encoding = 'json'
- end
-
it 'sends the params, excluding confidential values' do
- expect(Gitlab::Sentry).to receive(:enabled?).twice.and_return(true)
expect(ProjectsFinder).to receive(:new).and_raise('Runtime Error!')
- get api('/projects', user), password: 'dont_send_this', other_param: 'send_this'
+ get api('/projects', user), params: { password: 'dont_send_this', other_param: 'send_this' }
expect(event_data).to include('other_param=send_this')
expect(event_data).to include('password=********')
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 2ebcb787d06..589816b5d8f 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -12,7 +12,7 @@ describe API::Internal do
it do
expect_any_instance_of(Redis).to receive(:ping).and_return('PONG')
- get api("/internal/check"), secret_token: secret_token
+ get api("/internal/check"), params: { secret_token: secret_token }
expect(response).to have_gitlab_http_status(200)
expect(json_response['api_version']).to eq(API::API.version)
@@ -22,7 +22,7 @@ describe API::Internal do
it 'returns false for field `redis` when redis is unavailable' do
expect_any_instance_of(Redis).to receive(:ping).and_raise(Errno::ENOENT)
- get api("/internal/check"), secret_token: secret_token
+ get api("/internal/check"), params: { secret_token: secret_token }
expect(json_response['redis']).to be(false)
end
@@ -33,7 +33,7 @@ describe API::Internal do
let!(:broadcast_message) { create(:broadcast_message, starts_at: 1.day.ago, ends_at: 1.day.from_now ) }
it 'returns one broadcast message' do
- get api('/internal/broadcast_message'), secret_token: secret_token
+ get api('/internal/broadcast_message'), params: { secret_token: secret_token }
expect(response).to have_gitlab_http_status(200)
expect(json_response['message']).to eq(broadcast_message.message)
@@ -42,7 +42,7 @@ describe API::Internal do
context 'broadcast message does not exist' do
it 'returns nothing' do
- get api('/internal/broadcast_message'), secret_token: secret_token
+ get api('/internal/broadcast_message'), params: { secret_token: secret_token }
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_empty
@@ -53,7 +53,7 @@ describe API::Internal do
it 'returns nothing' do
allow(BroadcastMessage).to receive(:current).and_return(nil)
- get api('/internal/broadcast_message'), secret_token: secret_token
+ get api('/internal/broadcast_message'), params: { secret_token: secret_token }
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_empty
@@ -66,7 +66,7 @@ describe API::Internal do
let!(:broadcast_message) { create(:broadcast_message, starts_at: 1.day.ago, ends_at: 1.day.from_now ) }
it 'returns active broadcast message(s)' do
- get api('/internal/broadcast_messages'), secret_token: secret_token
+ get api('/internal/broadcast_messages'), params: { secret_token: secret_token }
expect(response).to have_gitlab_http_status(200)
expect(json_response[0]['message']).to eq(broadcast_message.message)
@@ -75,7 +75,7 @@ describe API::Internal do
context 'broadcast message does not exist' do
it 'returns nothing' do
- get api('/internal/broadcast_messages'), secret_token: secret_token
+ get api('/internal/broadcast_messages'), params: { secret_token: secret_token }
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_empty
@@ -86,8 +86,10 @@ describe API::Internal do
describe 'GET /internal/two_factor_recovery_codes' do
it 'returns an error message when the key does not exist' do
post api('/internal/two_factor_recovery_codes'),
- secret_token: secret_token,
- key_id: 12345
+ params: {
+ secret_token: secret_token,
+ key_id: 12345
+ }
expect(json_response['success']).to be_falsey
expect(json_response['message']).to eq('Could not find the given key')
@@ -97,8 +99,10 @@ describe API::Internal do
deploy_key = create(:deploy_key)
post api('/internal/two_factor_recovery_codes'),
- secret_token: secret_token,
- key_id: deploy_key.id
+ params: {
+ secret_token: secret_token,
+ key_id: deploy_key.id
+ }
expect(json_response['success']).to be_falsey
expect(json_response['message']).to eq('Deploy keys cannot be used to retrieve recovery codes')
@@ -108,8 +112,10 @@ describe API::Internal do
key_without_user = create(:key, user: nil)
post api('/internal/two_factor_recovery_codes'),
- secret_token: secret_token,
- key_id: key_without_user.id
+ params: {
+ secret_token: secret_token,
+ key_id: key_without_user.id
+ }
expect(json_response['success']).to be_falsey
expect(json_response['message']).to eq('Could not find a user for the given key')
@@ -123,8 +129,10 @@ describe API::Internal do
.to receive(:generate_otp_backup_codes!).and_return(%w(119135e5a3ebce8e 34bd7b74adbc8861))
post api('/internal/two_factor_recovery_codes'),
- secret_token: secret_token,
- key_id: key.id
+ params: {
+ secret_token: secret_token,
+ key_id: key.id
+ }
expect(json_response['success']).to be_truthy
expect(json_response['recovery_codes']).to match_array(%w(119135e5a3ebce8e 34bd7b74adbc8861))
@@ -136,8 +144,10 @@ describe API::Internal do
allow_any_instance_of(User).to receive(:two_factor_enabled?).and_return(false)
post api('/internal/two_factor_recovery_codes'),
- secret_token: secret_token,
- key_id: key.id
+ params: {
+ secret_token: secret_token,
+ key_id: key.id
+ }
expect(json_response['success']).to be_falsey
expect(json_response['recovery_codes']).to be_nil
@@ -156,9 +166,8 @@ describe API::Internal do
expect(response).to have_gitlab_http_status(200)
expect(json_response['username']).to eq(user.username)
- expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).token)
-
expect(json_response['repository_http_path']).to eq(project.http_url_to_repo)
+ expect(Gitlab::LfsToken.new(key).token_valid?(json_response['lfs_token'])).to be_truthy
end
it 'returns the correct information about the user' do
@@ -166,9 +175,8 @@ describe API::Internal do
expect(response).to have_gitlab_http_status(200)
expect(json_response['username']).to eq(user.username)
- expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(user).token)
-
expect(json_response['repository_http_path']).to eq(project.http_url_to_repo)
+ expect(Gitlab::LfsToken.new(user).token_valid?(json_response['lfs_token'])).to be_truthy
end
it 'returns a 404 when no key or user is provided' do
@@ -198,15 +206,15 @@ describe API::Internal do
expect(response).to have_gitlab_http_status(200)
expect(json_response['username']).to eq("lfs+deploy-key-#{key.id}")
- expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).token)
expect(json_response['repository_http_path']).to eq(project.http_url_to_repo)
+ expect(Gitlab::LfsToken.new(key).token_valid?(json_response['lfs_token'])).to be_truthy
end
end
end
describe "GET /internal/discover" do
it "finds a user by key id" do
- get(api("/internal/discover"), key_id: key.id, secret_token: secret_token)
+ get(api("/internal/discover"), params: { key_id: key.id, secret_token: secret_token })
expect(response).to have_gitlab_http_status(200)
@@ -214,7 +222,7 @@ describe API::Internal do
end
it "finds a user by user id" do
- get(api("/internal/discover"), user_id: user.id, secret_token: secret_token)
+ get(api("/internal/discover"), params: { user_id: user.id, secret_token: secret_token })
expect(response).to have_gitlab_http_status(200)
@@ -222,7 +230,7 @@ describe API::Internal do
end
it "finds a user by username" do
- get(api("/internal/discover"), username: user.username, secret_token: secret_token)
+ get(api("/internal/discover"), params: { username: user.username, secret_token: secret_token })
expect(response).to have_gitlab_http_status(200)
@@ -233,7 +241,7 @@ describe API::Internal do
describe "GET /internal/authorized_keys" do
context "using an existing key's fingerprint" do
it "finds the key" do
- get(api('/internal/authorized_keys'), fingerprint: key.fingerprint, secret_token: secret_token)
+ get(api('/internal/authorized_keys'), params: { fingerprint: key.fingerprint, secret_token: secret_token })
expect(response.status).to eq(200)
expect(json_response["key"]).to eq(key.key)
@@ -242,7 +250,7 @@ describe API::Internal do
context "non existing key's fingerprint" do
it "returns 404" do
- get(api('/internal/authorized_keys'), fingerprint: "no:t-:va:li:d0", secret_token: secret_token)
+ get(api('/internal/authorized_keys'), params: { fingerprint: "no:t-:va:li:d0", secret_token: secret_token })
expect(response.status).to eq(404)
end
@@ -250,7 +258,7 @@ describe API::Internal do
context "using a partial fingerprint" do
it "returns 404" do
- get(api('/internal/authorized_keys'), fingerprint: "#{key.fingerprint[0..5]}%", secret_token: secret_token)
+ get(api('/internal/authorized_keys'), params: { fingerprint: "#{key.fingerprint[0..5]}%", secret_token: secret_token })
expect(response.status).to eq(404)
end
@@ -258,20 +266,20 @@ describe API::Internal do
context "sending the key" do
it "finds the key" do
- get(api('/internal/authorized_keys'), key: key.key.split[1], secret_token: secret_token)
+ get(api('/internal/authorized_keys'), params: { key: key.key.split[1], secret_token: secret_token })
expect(response.status).to eq(200)
expect(json_response["key"]).to eq(key.key)
end
it "returns 404 with a partial key" do
- get(api('/internal/authorized_keys'), key: key.key.split[1][0...-3], secret_token: secret_token)
+ get(api('/internal/authorized_keys'), params: { key: key.key.split[1][0...-3], secret_token: secret_token })
expect(response.status).to eq(404)
end
it "returns 404 with an not valid base64 string" do
- get(api('/internal/authorized_keys'), key: "whatever!", secret_token: secret_token)
+ get(api('/internal/authorized_keys'), params: { key: "whatever!", secret_token: secret_token })
expect(response.status).to eq(404)
end
@@ -679,7 +687,7 @@ describe API::Internal do
end
it 'returns link to create new merge request' do
- get api("/internal/merge_request_urls?project=#{repo_name}&changes=#{changes}"), secret_token: secret_token
+ get api("/internal/merge_request_urls?project=#{repo_name}&changes=#{changes}"), params: { secret_token: secret_token }
expect(json_response).to match [{
"branch_name" => "new_branch",
@@ -691,7 +699,7 @@ describe API::Internal do
it 'returns empty array if printing_merge_request_link_enabled is false' do
project.update!(printing_merge_request_link_enabled: false)
- get api("/internal/merge_request_urls?project=#{repo_name}&changes=#{changes}"), secret_token: secret_token
+ get api("/internal/merge_request_urls?project=#{repo_name}&changes=#{changes}"), params: { secret_token: secret_token }
expect(json_response).to eq([])
end
@@ -700,7 +708,7 @@ describe API::Internal do
let(:gl_repository) { "project-#{project.id}" }
it 'returns link to create new merge request' do
- get api("/internal/merge_request_urls?gl_repository=#{gl_repository}&changes=#{changes}"), secret_token: secret_token
+ get api("/internal/merge_request_urls?gl_repository=#{gl_repository}&changes=#{changes}"), params: { secret_token: secret_token }
expect(json_response).to match [{
"branch_name" => "new_branch",
@@ -819,7 +827,7 @@ describe API::Internal do
expect(PostReceive).to receive(:perform_async)
.with(gl_repository, identifier, changes)
- post api("/internal/post_receive"), valid_params
+ post api("/internal/post_receive"), params: valid_params
end
it 'decreases the reference counter and returns the result' do
@@ -827,13 +835,13 @@ describe API::Internal do
.and_return(reference_counter)
expect(reference_counter).to receive(:decrease).and_return(true)
- post api("/internal/post_receive"), valid_params
+ post api("/internal/post_receive"), params: valid_params
expect(json_response['reference_counter_decreased']).to be(true)
end
it 'returns link to create new merge request' do
- post api("/internal/post_receive"), valid_params
+ post api("/internal/post_receive"), params: valid_params
expect(json_response['merge_request_urls']).to match [{
"branch_name" => "new_branch",
@@ -845,7 +853,7 @@ describe API::Internal do
it 'returns empty array if printing_merge_request_link_enabled is false' do
project.update!(printing_merge_request_link_enabled: false)
- post api("/internal/post_receive"), valid_params
+ post api("/internal/post_receive"), params: valid_params
expect(json_response['merge_request_urls']).to eq([])
end
@@ -854,7 +862,7 @@ describe API::Internal do
let!(:broadcast_message) { create(:broadcast_message, starts_at: 1.day.ago, ends_at: 1.day.from_now ) }
it 'returns one broadcast message' do
- post api("/internal/post_receive"), valid_params
+ 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)
@@ -863,7 +871,7 @@ describe API::Internal do
context 'broadcast message does not exist' do
it 'returns empty string' do
- post api("/internal/post_receive"), valid_params
+ post api("/internal/post_receive"), params: valid_params
expect(response).to have_gitlab_http_status(200)
expect(json_response['broadcast_message']).to eq(nil)
@@ -874,7 +882,7 @@ describe API::Internal do
it 'returns empty string' do
allow(BroadcastMessage).to receive(:current).and_return(nil)
- post api("/internal/post_receive"), valid_params
+ post api("/internal/post_receive"), params: valid_params
expect(response).to have_gitlab_http_status(200)
expect(json_response['broadcast_message']).to eq(nil)
@@ -886,7 +894,7 @@ describe API::Internal do
project_moved = Gitlab::Checks::ProjectMoved.new(project, user, 'http', 'foo/baz')
project_moved.add_message
- post api("/internal/post_receive"), valid_params
+ post api("/internal/post_receive"), params: valid_params
expect(response).to have_gitlab_http_status(200)
expect(json_response["redirected_message"]).to be_present
@@ -899,7 +907,7 @@ describe API::Internal do
project_created = Gitlab::Checks::ProjectCreated.new(project, user, 'http')
project_created.add_message
- post api("/internal/post_receive"), valid_params
+ 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
@@ -911,7 +919,7 @@ describe API::Internal do
it 'does not try to notify that project moved' do
allow_any_instance_of(Gitlab::Identifier).to receive(:identify).and_return(nil)
- post api("/internal/post_receive"), valid_params
+ post api("/internal/post_receive"), params: valid_params
expect(response).to have_gitlab_http_status(200)
end
@@ -928,7 +936,7 @@ describe API::Internal do
.and_return(reference_counter)
expect(reference_counter).to receive(:increase).and_return(true)
- post api("/internal/pre_receive"), valid_params
+ post api("/internal/pre_receive"), params: valid_params
expect(json_response['reference_counter_increased']).to be(true)
end
@@ -948,12 +956,14 @@ describe API::Internal do
def pull(key, project, protocol = 'ssh')
post(
api("/internal/allowed"),
- key_id: key.id,
- project: project.full_path,
- gl_repository: gl_repository_for(project),
- action: 'git-upload-pack',
- secret_token: secret_token,
- protocol: protocol
+ params: {
+ key_id: key.id,
+ project: project.full_path,
+ gl_repository: gl_repository_for(project),
+ action: 'git-upload-pack',
+ secret_token: secret_token,
+ protocol: protocol
+ }
)
end
@@ -969,55 +979,56 @@ describe API::Internal do
env: env
}
- if Gitlab.rails5?
- post(
- api("/internal/allowed"),
- params: params
- )
- else
- post(
- api("/internal/allowed"),
- params
- )
- end
+ post(
+ api("/internal/allowed"),
+ params: params
+ )
end
def archive(key, project)
post(
api("/internal/allowed"),
- ref: 'master',
- key_id: key.id,
- project: project.full_path,
- gl_repository: gl_repository_for(project),
- action: 'git-upload-archive',
- secret_token: secret_token,
- protocol: 'ssh'
+ params: {
+ ref: 'master',
+ key_id: key.id,
+ project: project.full_path,
+ gl_repository: gl_repository_for(project),
+ action: 'git-upload-archive',
+ secret_token: secret_token,
+ protocol: 'ssh'
+ }
)
end
def lfs_auth_project(project)
post(
api("/internal/lfs_authenticate"),
- secret_token: secret_token,
- project: project.full_path
+ params: {
+ secret_token: secret_token,
+ project: project.full_path
+ }
)
end
def lfs_auth_key(key_id, project)
post(
api("/internal/lfs_authenticate"),
- key_id: key_id,
- secret_token: secret_token,
- project: project.full_path
+ params: {
+ key_id: key_id,
+ secret_token: secret_token,
+ project: project.full_path
+ }
)
end
def lfs_auth_user(user_id, project)
post(
api("/internal/lfs_authenticate"),
- user_id: user_id,
- secret_token: secret_token,
- project: project.full_path
+ params: {
+ user_id: user_id,
+ secret_token: secret_token,
+ project: project.full_path
+ }
)
end
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 1827da61e2d..0fd465da4f8 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -66,7 +66,7 @@ describe API::Issues do
describe "GET /issues" do
context "when unauthenticated" do
it "returns an array of all issues" do
- get api("/issues"), scope: 'all'
+ get api("/issues"), params: { scope: 'all' }
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
@@ -79,13 +79,13 @@ describe API::Issues do
end
it "returns authentication error when scope is assigned-to-me" do
- get api("/issues"), scope: 'assigned-to-me'
+ get api("/issues"), params: { scope: 'assigned-to-me' }
expect(response).to have_http_status(401)
end
it "returns authentication error when scope is created-by-me" do
- get api("/issues"), scope: 'created-by-me'
+ get api("/issues"), params: { scope: 'created-by-me' }
expect(response).to have_http_status(401)
end
@@ -103,21 +103,21 @@ describe API::Issues do
end
it 'returns an array of closed issues' do
- get api('/issues', user), state: :closed
+ get api('/issues', user), params: { state: :closed }
expect_paginated_array_response(size: 1)
expect(first_issue['id']).to eq(closed_issue.id)
end
it 'returns an array of opened issues' do
- get api('/issues', user), state: :opened
+ get api('/issues', user), params: { state: :opened }
expect_paginated_array_response(size: 1)
expect(first_issue['id']).to eq(issue.id)
end
it 'returns an array of all issues' do
- get api('/issues', user), state: :all
+ get api('/issues', user), params: { state: :all }
expect_paginated_array_response(size: 2)
expect(first_issue['id']).to eq(issue.id)
@@ -127,7 +127,7 @@ describe API::Issues do
it 'returns issues assigned to me' do
issue2 = create(:issue, assignees: [user2], project: project)
- get api('/issues', user2), scope: 'assigned_to_me'
+ get api('/issues', user2), params: { scope: 'assigned_to_me' }
expect_paginated_array_response(size: 1)
expect(first_issue['id']).to eq(issue2.id)
@@ -136,7 +136,7 @@ describe API::Issues do
it 'returns issues assigned to me (kebab-case)' do
issue2 = create(:issue, assignees: [user2], project: project)
- get api('/issues', user2), scope: 'assigned-to-me'
+ get api('/issues', user2), params: { scope: 'assigned-to-me' }
expect_paginated_array_response(size: 1)
expect(first_issue['id']).to eq(issue2.id)
@@ -145,7 +145,7 @@ describe API::Issues do
it 'returns issues authored by the given author id' do
issue2 = create(:issue, author: user2, project: project)
- get api('/issues', user), author_id: user2.id, scope: 'all'
+ get api('/issues', user), params: { author_id: user2.id, scope: 'all' }
expect_paginated_array_response(size: 1)
expect(first_issue['id']).to eq(issue2.id)
@@ -154,7 +154,7 @@ describe API::Issues do
it 'returns issues assigned to the given assignee id' do
issue2 = create(:issue, assignees: [user2], project: project)
- get api('/issues', user), assignee_id: user2.id, scope: 'all'
+ get api('/issues', user), params: { assignee_id: user2.id, scope: 'all' }
expect_paginated_array_response(size: 1)
expect(first_issue['id']).to eq(issue2.id)
@@ -163,7 +163,7 @@ describe API::Issues do
it 'returns issues authored by the given author id and assigned to the given assignee id' do
issue2 = create(:issue, author: user2, assignees: [user2], project: project)
- get api('/issues', user), author_id: user2.id, assignee_id: user2.id, scope: 'all'
+ get api('/issues', user), params: { author_id: user2.id, assignee_id: user2.id, scope: 'all' }
expect_paginated_array_response(size: 1)
expect(first_issue['id']).to eq(issue2.id)
@@ -172,7 +172,7 @@ describe API::Issues do
it 'returns issues with no assignee' do
issue2 = create(:issue, author: user2, project: project)
- get api('/issues', user), assignee_id: 0, scope: 'all'
+ get api('/issues', user), params: { assignee_id: 0, scope: 'all' }
expect_paginated_array_response(size: 1)
expect(first_issue['id']).to eq(issue2.id)
@@ -181,7 +181,7 @@ describe API::Issues do
it 'returns issues with no assignee' do
issue2 = create(:issue, author: user2, project: project)
- get api('/issues', user), assignee_id: 'None', scope: 'all'
+ get api('/issues', user), params: { assignee_id: 'None', scope: 'all' }
expect_paginated_array_response(size: 1)
expect(first_issue['id']).to eq(issue2.id)
@@ -191,7 +191,7 @@ describe API::Issues do
# This issue without assignee should not be returned
create(:issue, author: user2, project: project)
- get api('/issues', user), assignee_id: 'Any', scope: 'all'
+ get api('/issues', user), params: { assignee_id: 'Any', scope: 'all' }
expect_paginated_array_response(size: 3)
end
@@ -202,7 +202,7 @@ describe API::Issues do
create(:award_emoji, awardable: issue, user: user2, name: 'thumbsup')
- get api('/issues', user2), my_reaction_emoji: 'Any', scope: 'all'
+ get api('/issues', user2), params: { my_reaction_emoji: 'Any', scope: 'all' }
expect_paginated_array_response(size: 2)
end
@@ -211,20 +211,20 @@ describe API::Issues do
issue2 = create(:issue, project: project, author: user, assignees: [user])
create(:award_emoji, awardable: issue2, user: user2, name: 'star')
- get api('/issues', user2), my_reaction_emoji: 'None', scope: 'all'
+ get api('/issues', user2), params: { my_reaction_emoji: 'None', scope: 'all' }
expect_paginated_array_response(size: 2)
end
it 'returns issues matching given search string for title' do
- get api("/issues", user), search: issue.title
+ get api("/issues", user), params: { search: issue.title }
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(issue.id)
end
it 'returns issues matching given search string for description' do
- get api("/issues", user), search: issue.description
+ get api("/issues", user), params: { search: issue.description }
expect_paginated_array_response(size: 1)
expect(first_issue['id']).to eq(issue.id)
@@ -267,7 +267,7 @@ describe API::Issues do
end
it 'returns an array of labeled issues' do
- get api("/issues", user), labels: label.title
+ get api("/issues", user), params: { labels: label.title }
expect_paginated_array_response(size: 1)
expect(first_issue['labels']).to eq([label.title])
@@ -280,20 +280,20 @@ describe API::Issues do
create(:label_link, label: label_b, target: issue)
create(:label_link, label: label_c, target: issue)
- get api("/issues", user), labels: "#{label.title},#{label_b.title},#{label_c.title}"
+ get api("/issues", user), params: { labels: "#{label.title},#{label_b.title},#{label_c.title}" }
expect_paginated_array_response(size: 1)
expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title])
end
it 'returns an empty array if no issue matches labels' do
- get api('/issues', user), labels: 'foo,bar'
+ get api('/issues', user), params: { labels: 'foo,bar' }
expect_paginated_array_response(size: 0)
end
it 'returns an array of labeled issues matching given state' do
- get api("/issues", user), labels: label.title, state: :opened
+ get api("/issues", user), params: { labels: label.title, state: :opened }
expect_paginated_array_response(size: 1)
expect(json_response.first['labels']).to eq([label.title])
@@ -301,27 +301,27 @@ describe API::Issues do
end
it 'returns an empty array if no issue matches labels and state filters' do
- get api("/issues", user), labels: label.title, state: :closed
+ get api("/issues", user), params: { labels: label.title, state: :closed }
expect_paginated_array_response(size: 0)
end
it 'returns an array of issues with any label' do
- get api("/issues", user), labels: IssuesFinder::FILTER_ANY
+ get api("/issues", user), params: { labels: IssuesFinder::FILTER_ANY }
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(issue.id)
end
it 'returns an array of issues with no label' do
- get api("/issues", user), labels: IssuesFinder::FILTER_NONE
+ get api("/issues", user), params: { labels: IssuesFinder::FILTER_NONE }
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(closed_issue.id)
end
it 'returns an array of issues with no label when using the legacy No+Label filter' do
- get api("/issues", user), labels: "No Label"
+ get api("/issues", user), params: { labels: "No Label" }
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(closed_issue.id)
@@ -363,14 +363,14 @@ describe API::Issues do
end
it 'returns an array of issues found by iids' do
- get api('/issues', user), iids: [closed_issue.iid]
+ get api('/issues', user), params: { iids: [closed_issue.iid] }
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(closed_issue.id)
end
it 'returns an empty array if iid does not exist' do
- get api("/issues", user), iids: [99999]
+ get api("/issues", user), params: { iids: [99999] }
expect_paginated_array_response(size: 0)
end
@@ -506,58 +506,58 @@ describe API::Issues do
end
it 'returns group issues without confidential issues for non project members' do
- get api(base_url, non_member), state: :opened
+ get api(base_url, non_member), params: { state: :opened }
expect_paginated_array_response(size: 1)
expect(json_response.first['title']).to eq(group_issue.title)
end
it 'returns group confidential issues for author' do
- get api(base_url, author), state: :opened
+ get api(base_url, author), params: { state: :opened }
expect_paginated_array_response(size: 2)
end
it 'returns group confidential issues for assignee' do
- get api(base_url, assignee), state: :opened
+ get api(base_url, assignee), params: { state: :opened }
expect_paginated_array_response(size: 2)
end
it 'returns group issues with confidential issues for project members' do
- get api(base_url, user), state: :opened
+ get api(base_url, user), params: { state: :opened }
expect_paginated_array_response(size: 2)
end
it 'returns group confidential issues for admin' do
- get api(base_url, admin), state: :opened
+ get api(base_url, admin), params: { state: :opened }
expect_paginated_array_response(size: 2)
end
it 'returns an array of labeled group issues' do
- get api(base_url, user), labels: group_label.title
+ get api(base_url, user), params: { labels: group_label.title }
expect_paginated_array_response(size: 1)
expect(json_response.first['labels']).to eq([group_label.title])
end
it 'returns an array of labeled group issues where all labels match' do
- get api(base_url, user), labels: "#{group_label.title},foo,bar"
+ get api(base_url, user), params: { labels: "#{group_label.title},foo,bar" }
expect_paginated_array_response(size: 0)
end
it 'returns issues matching given search string for title' do
- get api(base_url, user), search: group_issue.title
+ get api(base_url, user), params: { search: group_issue.title }
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(group_issue.id)
end
it 'returns issues matching given search string for description' do
- get api(base_url, user), search: group_issue.description
+ get api(base_url, user), params: { search: group_issue.description }
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(group_issue.id)
@@ -570,40 +570,40 @@ describe API::Issues do
create(:label_link, label: label_b, target: group_issue)
create(:label_link, label: label_c, target: group_issue)
- get api(base_url, user), labels: "#{group_label.title},#{label_b.title},#{label_c.title}"
+ get api(base_url, user), params: { labels: "#{group_label.title},#{label_b.title},#{label_c.title}" }
expect_paginated_array_response(size: 1)
expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title])
end
it 'returns an array of issues found by iids' do
- get api(base_url, user), iids: [group_issue.iid]
+ get api(base_url, user), params: { iids: [group_issue.iid] }
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(group_issue.id)
end
it 'returns an empty array if iid does not exist' do
- get api(base_url, user), iids: [99999]
+ get api(base_url, user), params: { iids: [99999] }
expect_paginated_array_response(size: 0)
end
it 'returns an empty array if no group issue matches labels' do
- get api(base_url, user), labels: 'foo,bar'
+ get api(base_url, user), params: { labels: 'foo,bar' }
expect_paginated_array_response(size: 0)
end
it 'returns an array of group issues with any label' do
- get api(base_url, user), labels: IssuesFinder::FILTER_ANY
+ get api(base_url, user), params: { labels: IssuesFinder::FILTER_ANY }
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(group_issue.id)
end
it 'returns an array of group issues with no label' do
- get api(base_url, user), labels: IssuesFinder::FILTER_NONE
+ get api(base_url, user), params: { labels: IssuesFinder::FILTER_NONE }
response_ids = json_response.map { |issue| issue['id'] }
@@ -612,33 +612,33 @@ describe API::Issues do
end
it 'returns an empty array if no issue matches milestone' do
- get api(base_url, user), milestone: group_empty_milestone.title
+ get api(base_url, user), params: { milestone: group_empty_milestone.title }
expect_paginated_array_response(size: 0)
end
it 'returns an empty array if milestone does not exist' do
- get api(base_url, user), milestone: 'foo'
+ get api(base_url, user), params: { milestone: 'foo' }
expect_paginated_array_response(size: 0)
end
it 'returns an array of issues in given milestone' do
- get api(base_url, user), state: :opened, milestone: group_milestone.title
+ get api(base_url, user), params: { state: :opened, milestone: group_milestone.title }
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(group_issue.id)
end
it 'returns an array of issues matching state in milestone' do
- get api(base_url, user), milestone: group_milestone.title, state: :closed
+ get api(base_url, user), params: { milestone: group_milestone.title, state: :closed }
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(group_closed_issue.id)
end
it 'returns an array of issues with no milestone' do
- get api(base_url, user), milestone: no_milestone_title
+ get api(base_url, user), params: { milestone: no_milestone_title }
expect(response).to have_gitlab_http_status(200)
@@ -674,7 +674,7 @@ describe API::Issues do
end
it 'sorts by updated_at ascending when requested' do
- get api(base_url, user), order_by: :updated_at, sort: :asc
+ get api(base_url, user), params: { order_by: :updated_at, sort: :asc }
response_dates = json_response.map { |issue| issue['updated_at'] }
@@ -777,7 +777,7 @@ describe API::Issues do
end
it 'returns an array of labeled project issues' do
- get api("#{base_url}/issues", user), labels: label.title
+ get api("#{base_url}/issues", user), params: { labels: label.title }
expect_paginated_array_response(size: 1)
expect(json_response.first['labels']).to eq([label.title])
@@ -790,7 +790,7 @@ describe API::Issues do
create(:label_link, label: label_b, target: issue)
create(:label_link, label: label_c, target: issue)
- get api("#{base_url}/issues", user), labels: "#{label.title},#{label_b.title},#{label_c.title}"
+ get api("#{base_url}/issues", user), params: { labels: "#{label.title},#{label_b.title},#{label_c.title}" }
expect_paginated_array_response(size: 1)
expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title])
@@ -811,14 +811,14 @@ describe API::Issues do
end
it 'returns an array of issues found by iids' do
- get api("#{base_url}/issues", user), iids: [issue.iid]
+ get api("#{base_url}/issues", user), params: { iids: [issue.iid] }
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(issue.id)
end
it 'returns an empty array if iid does not exist' do
- get api("#{base_url}/issues", user), iids: [99999]
+ get api("#{base_url}/issues", user), params: { iids: [99999] }
expect_paginated_array_response(size: 0)
end
@@ -830,14 +830,14 @@ describe API::Issues do
end
it 'returns an array of project issues with any label' do
- get api("#{base_url}/issues", user), labels: IssuesFinder::FILTER_ANY
+ get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_ANY }
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(issue.id)
end
it 'returns an array of project issues with no label' do
- get api("#{base_url}/issues", user), labels: IssuesFinder::FILTER_NONE
+ get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_NONE }
response_ids = json_response.map { |issue| issue['id'] }
@@ -846,25 +846,25 @@ describe API::Issues do
end
it 'returns an empty array if no project issue matches labels' do
- get api("#{base_url}/issues", user), labels: 'foo,bar'
+ get api("#{base_url}/issues", user), params: { labels: 'foo,bar' }
expect_paginated_array_response(size: 0)
end
it 'returns an empty array if no issue matches milestone' do
- get api("#{base_url}/issues", user), milestone: empty_milestone.title
+ get api("#{base_url}/issues", user), params: { milestone: empty_milestone.title }
expect_paginated_array_response(size: 0)
end
it 'returns an empty array if milestone does not exist' do
- get api("#{base_url}/issues", user), milestone: :foo
+ get api("#{base_url}/issues", user), params: { milestone: :foo }
expect_paginated_array_response(size: 0)
end
it 'returns an array of issues in given milestone' do
- get api("#{base_url}/issues", user), milestone: milestone.title
+ get api("#{base_url}/issues", user), params: { milestone: milestone.title }
expect_paginated_array_response(size: 2)
expect(json_response.first['id']).to eq(issue.id)
@@ -872,21 +872,21 @@ describe API::Issues do
end
it 'returns an array of issues matching state in milestone' do
- get api("#{base_url}/issues", user), milestone: milestone.title, state: :closed
+ get api("#{base_url}/issues", user), params: { milestone: milestone.title, state: :closed }
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(closed_issue.id)
end
it 'returns an array of issues with no milestone' do
- get api("#{base_url}/issues", user), milestone: no_milestone_title
+ get api("#{base_url}/issues", user), params: { milestone: no_milestone_title }
expect_paginated_array_response(size: 1)
expect(json_response.first['id']).to eq(confidential_issue.id)
end
it 'returns an array of issues with any milestone' do
- get api("#{base_url}/issues", user), milestone: any_milestone_title
+ get api("#{base_url}/issues", user), params: { milestone: any_milestone_title }
response_ids = json_response.map { |issue| issue['id'] }
@@ -904,7 +904,7 @@ describe API::Issues do
end
it 'sorts ascending when requested' do
- get api("#{base_url}/issues", user), sort: :asc
+ get api("#{base_url}/issues", user), params: { sort: :asc }
response_dates = json_response.map { |issue| issue['created_at'] }
@@ -913,7 +913,7 @@ describe API::Issues do
end
it 'sorts by updated_at descending when requested' do
- get api("#{base_url}/issues", user), order_by: :updated_at
+ get api("#{base_url}/issues", user), params: { order_by: :updated_at }
response_dates = json_response.map { |issue| issue['updated_at'] }
@@ -922,7 +922,7 @@ describe API::Issues do
end
it 'sorts by updated_at ascending when requested' do
- get api("#{base_url}/issues", user), order_by: :updated_at, sort: :asc
+ get api("#{base_url}/issues", user), params: { order_by: :updated_at, sort: :asc }
response_dates = json_response.map { |issue| issue['updated_at'] }
@@ -1051,7 +1051,7 @@ describe API::Issues do
context 'support for deprecated assignee_id' do
it 'creates a new project issue' do
post api("/projects/#{project.id}/issues", user),
- title: 'new issue', assignee_id: user2.id
+ params: { title: 'new issue', assignee_id: user2.id }
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('new issue')
@@ -1061,7 +1061,7 @@ describe API::Issues do
it 'creates a new project issue when assignee_id is empty' do
post api("/projects/#{project.id}/issues", user),
- title: 'new issue', assignee_id: ''
+ params: { title: 'new issue', assignee_id: '' }
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('new issue')
@@ -1072,7 +1072,7 @@ describe API::Issues do
context 'single assignee restrictions' do
it 'creates a new project issue with no more than one assignee' do
post api("/projects/#{project.id}/issues", user),
- title: 'new issue', assignee_ids: [user2.id, guest.id]
+ params: { title: 'new issue', assignee_ids: [user2.id, guest.id] }
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('new issue')
@@ -1088,7 +1088,7 @@ describe API::Issues do
end
it 'renders 403' do
- post api("/projects/#{project.id}/issues", not_member), title: 'new issue'
+ post api("/projects/#{project.id}/issues", not_member), params: { title: 'new issue' }
expect(response).to have_gitlab_http_status(403)
end
@@ -1098,7 +1098,7 @@ describe API::Issues do
context 'by an admin' do
it 'sets the internal ID on the new issue' do
post api("/projects/#{project.id}/issues", admin),
- title: 'new issue', iid: 9001
+ params: { title: 'new issue', iid: 9001 }
expect(response).to have_gitlab_http_status(201)
expect(json_response['iid']).to eq 9001
@@ -1108,7 +1108,7 @@ describe API::Issues do
context 'by an owner' do
it 'sets the internal ID on the new issue' do
post api("/projects/#{project.id}/issues", user),
- title: 'new issue', iid: 9001
+ params: { title: 'new issue', iid: 9001 }
expect(response).to have_gitlab_http_status(201)
expect(json_response['iid']).to eq 9001
@@ -1122,7 +1122,7 @@ describe API::Issues do
it 'sets the internal ID on the new issue' do
group.add_owner(user2)
post api("/projects/#{group_project.id}/issues", user2),
- title: 'new issue', iid: 9001
+ params: { title: 'new issue', iid: 9001 }
expect(response).to have_gitlab_http_status(201)
expect(json_response['iid']).to eq 9001
@@ -1132,7 +1132,7 @@ describe API::Issues do
context 'by another user' do
it 'ignores the given internal ID' do
post api("/projects/#{project.id}/issues", user2),
- title: 'new issue', iid: 9001
+ params: { title: 'new issue', iid: 9001 }
expect(response).to have_gitlab_http_status(201)
expect(json_response['iid']).not_to eq 9001
@@ -1142,8 +1142,7 @@ describe API::Issues do
it 'creates a new project issue' do
post api("/projects/#{project.id}/issues", user),
- title: 'new issue', labels: 'label, label2', weight: 3,
- assignee_ids: [user2.id]
+ params: { title: 'new issue', labels: 'label, label2', weight: 3, assignee_ids: [user2.id] }
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('new issue')
@@ -1156,7 +1155,7 @@ describe API::Issues do
it 'creates a new confidential project issue' do
post api("/projects/#{project.id}/issues", user),
- title: 'new issue', confidential: true
+ params: { title: 'new issue', confidential: true }
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('new issue')
@@ -1165,7 +1164,7 @@ describe API::Issues do
it 'creates a new confidential project issue with a different param' do
post api("/projects/#{project.id}/issues", user),
- title: 'new issue', confidential: 'y'
+ params: { title: 'new issue', confidential: 'y' }
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('new issue')
@@ -1174,7 +1173,7 @@ describe API::Issues do
it 'creates a public issue when confidential param is false' do
post api("/projects/#{project.id}/issues", user),
- title: 'new issue', confidential: false
+ params: { title: 'new issue', confidential: false }
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('new issue')
@@ -1183,21 +1182,23 @@ describe API::Issues do
it 'creates a public issue when confidential param is invalid' do
post api("/projects/#{project.id}/issues", user),
- title: 'new issue', confidential: 'foo'
+ params: { title: 'new issue', confidential: 'foo' }
expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq('confidential is invalid')
end
it "returns a 400 bad request if title not given" do
- post api("/projects/#{project.id}/issues", user), labels: 'label, label2'
+ post api("/projects/#{project.id}/issues", user), params: { labels: 'label, label2' }
expect(response).to have_gitlab_http_status(400)
end
it 'allows special label names' do
post api("/projects/#{project.id}/issues", user),
- title: 'new issue',
- labels: 'label, label?, label&foo, ?, &'
+ params: {
+ title: 'new issue',
+ labels: 'label, label?, label&foo, ?, &'
+ }
expect(response.status).to eq(201)
expect(json_response['labels']).to include 'label'
expect(json_response['labels']).to include 'label?'
@@ -1208,7 +1209,7 @@ describe API::Issues do
it 'returns 400 if title is too long' do
post api("/projects/#{project.id}/issues", user),
- title: 'g' * 256
+ params: { title: 'g' * 256 }
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']['title']).to eq([
'is too long (maximum is 255 characters)'
@@ -1227,8 +1228,10 @@ describe API::Issues do
context 'resolving all discussions in a merge request' do
before do
post api("/projects/#{project.id}/issues", user),
- title: 'New Issue',
- merge_request_to_resolve_discussions_of: merge_request.iid
+ params: {
+ title: 'New Issue',
+ merge_request_to_resolve_discussions_of: merge_request.iid
+ }
end
it_behaves_like 'creating an issue resolving discussions through the API'
@@ -1237,9 +1240,11 @@ describe API::Issues do
context 'resolving a single discussion' do
before do
post api("/projects/#{project.id}/issues", user),
- title: 'New Issue',
- merge_request_to_resolve_discussions_of: merge_request.iid,
- discussion_to_resolve: discussion.id
+ params: {
+ title: 'New Issue',
+ merge_request_to_resolve_discussions_of: merge_request.iid,
+ discussion_to_resolve: discussion.id
+ }
end
it_behaves_like 'creating an issue resolving discussions through the API'
@@ -1251,7 +1256,7 @@ describe API::Issues do
due_date = 2.weeks.from_now.strftime('%Y-%m-%d')
post api("/projects/#{project.id}/issues", user),
- title: 'new issue', due_date: due_date
+ params: { title: 'new issue', due_date: due_date }
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('new issue')
@@ -1266,7 +1271,7 @@ describe API::Issues do
context 'by an admin' do
it 'sets the creation time on the new issue' do
- post api("/projects/#{project.id}/issues", admin), params
+ post api("/projects/#{project.id}/issues", admin), params: params
expect(response).to have_gitlab_http_status(201)
expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
@@ -1275,7 +1280,7 @@ describe API::Issues do
context 'by a project owner' do
it 'sets the creation time on the new issue' do
- post api("/projects/#{project.id}/issues", user), params
+ post api("/projects/#{project.id}/issues", user), params: params
expect(response).to have_gitlab_http_status(201)
expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
@@ -1287,7 +1292,7 @@ describe API::Issues do
group = create(:group)
group_project = create(:project, :public, namespace: group)
group.add_owner(user2)
- post api("/projects/#{group_project.id}/issues", user2), params
+ post api("/projects/#{group_project.id}/issues", user2), params: params
expect(response).to have_gitlab_http_status(201)
expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
@@ -1296,7 +1301,7 @@ describe API::Issues do
context 'by another user' do
it 'ignores the given creation time' do
- post api("/projects/#{project.id}/issues", user2), params
+ post api("/projects/#{project.id}/issues", user2), params: params
expect(response).to have_gitlab_http_status(201)
expect(Time.parse(json_response['created_at'])).not_to be_like_time(creation_time)
@@ -1307,7 +1312,7 @@ describe API::Issues do
context 'the user can only read the issue' do
it 'cannot create new labels' do
expect do
- post api("/projects/#{project.id}/issues", non_member), title: 'new issue', labels: 'label, label2'
+ post api("/projects/#{project.id}/issues", non_member), params: { title: 'new issue', labels: 'label, label2' }
end.not_to change { project.labels.count }
end
end
@@ -1328,7 +1333,7 @@ describe API::Issues do
end
it "does not create a new project issue" do
- expect { post api("/projects/#{project.id}/issues", user), params }.not_to change(Issue, :count)
+ expect { post api("/projects/#{project.id}/issues", user), params: params }.not_to change(Issue, :count)
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq({ "error" => "Spam detected" })
@@ -1344,7 +1349,7 @@ describe API::Issues do
describe "PUT /projects/:id/issues/:issue_iid to update only title" do
it "updates a project issue" do
put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- title: 'updated title'
+ params: { title: 'updated title' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq('updated title')
@@ -1352,20 +1357,22 @@ describe API::Issues do
it "returns 404 error if issue iid not found" do
put api("/projects/#{project.id}/issues/44444", user),
- title: 'updated title'
+ params: { title: 'updated title' }
expect(response).to have_gitlab_http_status(404)
end
it "returns 404 error if issue id is used instead of the iid" do
put api("/projects/#{project.id}/issues/#{issue.id}", user),
- title: 'updated title'
+ params: { title: 'updated title' }
expect(response).to have_gitlab_http_status(404)
end
it 'allows special label names' do
put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- title: 'updated title',
- labels: 'label, label?, label&foo, ?, &'
+ params: {
+ title: 'updated title',
+ labels: 'label, label?, label&foo, ?, &'
+ }
expect(response.status).to eq(200)
expect(json_response['labels']).to include 'label'
@@ -1378,40 +1385,40 @@ describe API::Issues do
context 'confidential issues' do
it "returns 403 for non project members" do
put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", non_member),
- title: 'updated title'
+ params: { title: 'updated title' }
expect(response).to have_gitlab_http_status(403)
end
it "returns 403 for project members with guest role" do
put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", guest),
- title: 'updated title'
+ params: { title: 'updated title' }
expect(response).to have_gitlab_http_status(403)
end
it "updates a confidential issue for project members" do
put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
- title: 'updated title'
+ params: { title: 'updated title' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq('updated title')
end
it "updates a confidential issue for author" do
put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author),
- title: 'updated title'
+ params: { title: 'updated title' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq('updated title')
end
it "updates a confidential issue for admin" do
put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin),
- title: 'updated title'
+ params: { title: 'updated title' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq('updated title')
end
it 'sets an issue to confidential' do
put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- confidential: true
+ params: { confidential: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response['confidential']).to be_truthy
@@ -1419,7 +1426,7 @@ describe API::Issues do
it 'makes a confidential issue public' do
put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
- confidential: false
+ params: { confidential: false }
expect(response).to have_gitlab_http_status(200)
expect(json_response['confidential']).to be_falsy
@@ -1427,7 +1434,7 @@ describe API::Issues do
it 'does not update a confidential issue with wrong confidential flag' do
put api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user),
- confidential: 'foo'
+ params: { confidential: 'foo' }
expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq('confidential is invalid')
@@ -1448,7 +1455,7 @@ describe API::Issues do
allow_any_instance_of(SpamService).to receive_messages(check_for_spam?: true)
allow_any_instance_of(AkismetService).to receive_messages(spam?: true)
- put api("/projects/#{project.id}/issues/#{issue.iid}", user), params
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: params
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq({ "error" => "Spam detected" })
@@ -1466,7 +1473,7 @@ describe API::Issues do
context 'support for deprecated assignee_id' do
it 'removes assignee' do
put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- assignee_id: 0
+ params: { assignee_id: 0 }
expect(response).to have_gitlab_http_status(200)
@@ -1475,7 +1482,7 @@ describe API::Issues do
it 'updates an issue with new assignee' do
put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- assignee_id: user2.id
+ params: { assignee_id: user2.id }
expect(response).to have_gitlab_http_status(200)
@@ -1485,7 +1492,7 @@ describe API::Issues do
it 'removes assignee' do
put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- assignee_ids: [0]
+ params: { assignee_ids: [0] }
expect(response).to have_gitlab_http_status(200)
@@ -1494,7 +1501,7 @@ describe API::Issues do
it 'updates an issue with new assignee' do
put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- assignee_ids: [user2.id]
+ params: { assignee_ids: [user2.id] }
expect(response).to have_gitlab_http_status(200)
@@ -1504,7 +1511,7 @@ describe API::Issues do
context 'single assignee restrictions' do
it 'updates an issue with several assignees but only one has been applied' do
put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- assignee_ids: [user2.id, guest.id]
+ params: { assignee_ids: [user2.id, guest.id] }
expect(response).to have_gitlab_http_status(200)
@@ -1519,14 +1526,14 @@ describe API::Issues do
it 'does not update labels if not present' do
put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- title: 'updated title'
+ params: { title: 'updated title' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['labels']).to eq([label.title])
end
it 'removes all labels and touches the record' do
Timecop.travel(1.minute.from_now) do
- put api("/projects/#{project.id}/issues/#{issue.iid}", user), labels: ''
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { labels: '' }
end
expect(response).to have_gitlab_http_status(200)
@@ -1537,7 +1544,7 @@ describe API::Issues do
it 'updates labels and touches the record' do
Timecop.travel(1.minute.from_now) do
put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- labels: 'foo,bar'
+ params: { labels: 'foo,bar' }
end
expect(response).to have_gitlab_http_status(200)
expect(json_response['labels']).to include 'foo'
@@ -1547,7 +1554,7 @@ describe API::Issues do
it 'allows special label names' do
put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&'
+ params: { labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&' }
expect(response.status).to eq(200)
expect(json_response['labels']).to include 'label:foo'
expect(json_response['labels']).to include 'label-bar'
@@ -1561,7 +1568,7 @@ describe API::Issues do
it 'returns 400 if title is too long' do
put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- title: 'g' * 256
+ params: { title: 'g' * 256 }
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']['title']).to eq([
'is too long (maximum is 255 characters)'
@@ -1572,7 +1579,7 @@ describe API::Issues do
describe "PUT /projects/:id/issues/:issue_iid to update state and label" do
it "updates a project issue" do
put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- labels: 'label2', state_event: "close"
+ params: { labels: 'label2', state_event: "close" }
expect(response).to have_gitlab_http_status(200)
expect(json_response['labels']).to include 'label2'
@@ -1580,7 +1587,7 @@ describe API::Issues do
end
it 'reopens a project isssue' do
- put api("/projects/#{project.id}/issues/#{closed_issue.iid}", user), state_event: 'reopen'
+ put api("/projects/#{project.id}/issues/#{closed_issue.iid}", user), params: { state_event: 'reopen' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['state']).to eq 'opened'
@@ -1590,7 +1597,7 @@ describe API::Issues do
it 'accepts the update date to be set' do
update_time = 2.weeks.ago
put api("/projects/#{project.id}/issues/#{issue.iid}", user),
- labels: 'label3', state_event: 'close', updated_at: update_time
+ params: { labels: 'label3', state_event: 'close', updated_at: update_time }
expect(response).to have_gitlab_http_status(200)
expect(json_response['labels']).to include 'label3'
@@ -1603,7 +1610,7 @@ describe API::Issues do
it 'creates a new project issue' do
due_date = 2.weeks.from_now.strftime('%Y-%m-%d')
- put api("/projects/#{project.id}/issues/#{issue.iid}", user), due_date: due_date
+ put api("/projects/#{project.id}/issues/#{issue.iid}", user), params: { due_date: due_date }
expect(response).to have_gitlab_http_status(200)
expect(json_response['due_date']).to eq(due_date)
@@ -1657,7 +1664,7 @@ describe API::Issues do
it 'moves an issue' do
post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
- to_project_id: target_project.id
+ params: { to_project_id: target_project.id }
expect(response).to have_gitlab_http_status(201)
expect(json_response['project_id']).to eq(target_project.id)
@@ -1666,7 +1673,7 @@ describe API::Issues do
context 'when source and target projects are the same' do
it 'returns 400 when trying to move an issue' do
post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
- to_project_id: project.id
+ params: { to_project_id: project.id }
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq('Cannot move issue to project it originates from!')
@@ -1676,7 +1683,7 @@ describe API::Issues do
context 'when the user does not have the permission to move issues' do
it 'returns 400 when trying to move an issue' do
post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
- to_project_id: target_project2.id
+ params: { to_project_id: target_project2.id }
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq('Cannot move issue due to insufficient permissions!')
@@ -1685,7 +1692,7 @@ describe API::Issues do
it 'moves the issue to another namespace if I am admin' do
post api("/projects/#{project.id}/issues/#{issue.iid}/move", admin),
- to_project_id: target_project2.id
+ params: { to_project_id: target_project2.id }
expect(response).to have_gitlab_http_status(201)
expect(json_response['project_id']).to eq(target_project2.id)
@@ -1694,7 +1701,7 @@ describe API::Issues do
context 'when using the issue ID instead of iid' do
it 'returns 404 when trying to move an issue' do
post api("/projects/#{project.id}/issues/#{issue.id}/move", user),
- to_project_id: target_project.id
+ params: { to_project_id: target_project.id }
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Issue Not Found')
@@ -1704,7 +1711,7 @@ describe API::Issues do
context 'when issue does not exist' do
it 'returns 404 when trying to move an issue' do
post api("/projects/#{project.id}/issues/123/move", user),
- to_project_id: target_project.id
+ params: { to_project_id: target_project.id }
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Issue Not Found')
@@ -1714,7 +1721,7 @@ describe API::Issues do
context 'when source project does not exist' do
it 'returns 404 when trying to move an issue' do
post api("/projects/0/issues/#{issue.iid}/move", user),
- to_project_id: target_project.id
+ params: { to_project_id: target_project.id }
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Project Not Found')
@@ -1724,7 +1731,7 @@ describe API::Issues do
context 'when target project does not exist' do
it 'returns 404 when trying to move an issue' do
post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
- to_project_id: 0
+ params: { to_project_id: 0 }
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
index 8770365c893..73131dba542 100644
--- a/spec/requests/api/jobs_spec.rb
+++ b/spec/requests/api/jobs_spec.rb
@@ -58,7 +58,7 @@ describe API::Jobs do
before do |example|
unless example.metadata[:skip_before_request]
- get api("/projects/#{project.id}/jobs", api_user), query
+ get api("/projects/#{project.id}/jobs", api_user), params: query
end
end
@@ -150,7 +150,7 @@ describe API::Jobs do
end
def go
- get api("/projects/#{project.id}/jobs", api_user), query
+ get api("/projects/#{project.id}/jobs", api_user), params: query
end
end
@@ -160,7 +160,7 @@ describe API::Jobs do
before do |example|
unless example.metadata[:skip_before_request]
job
- get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query
+ get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), params: query
end
end
@@ -229,13 +229,13 @@ describe API::Jobs do
it 'avoids N+1 queries' do
control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
- get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query
+ get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), params: query
end.count
3.times { create(:ci_build, :trace_artifact, :artifacts, :test_reports, pipeline: pipeline) }
expect do
- get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), query
+ get api("/projects/#{project.id}/pipelines/#{pipeline.id}/jobs", api_user), params: query
end.not_to exceed_all_query_limit(control_count)
end
end
@@ -479,7 +479,7 @@ describe API::Jobs do
end
def get_for_ref(ref = pipeline.ref, job_name = job.name)
- get api("/projects/#{project.id}/jobs/artifacts/#{ref}/download", api_user), job: job_name
+ get api("/projects/#{project.id}/jobs/artifacts/#{ref}/download", api_user), params: { job: job_name }
end
context 'when not logged in' do
@@ -586,6 +586,136 @@ describe API::Jobs do
end
end
+ describe 'GET id/jobs/artifacts/:ref_name/raw/*artifact_path?job=name' do
+ context 'when job has artifacts' do
+ let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user) }
+ let(:artifact) { 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif' }
+ let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
+ let(:public_builds) { true }
+
+ before do
+ stub_artifacts_object_storage
+ job.success
+
+ project.update(visibility_level: visibility_level,
+ public_builds: public_builds)
+
+ get_artifact_file(artifact)
+ end
+
+ context 'when user is anonymous' do
+ let(:api_user) { nil }
+
+ context 'when project is public' do
+ let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
+ let(:public_builds) { true }
+
+ it 'allows to access artifacts' do
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.headers.to_h)
+ .to include('Content-Type' => 'application/json',
+ 'Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
+ end
+ end
+
+ context 'when project is public with builds access disabled' do
+ let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
+ let(:public_builds) { false }
+
+ it 'rejects access to artifacts' do
+ expect(response).to have_gitlab_http_status(403)
+ expect(json_response).to have_key('message')
+ expect(response.headers.to_h)
+ .not_to include('Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
+ end
+ end
+
+ context 'when project is private' do
+ let(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE }
+ let(:public_builds) { true }
+
+ it 'rejects access and hides existence of artifacts' do
+ expect(response).to have_gitlab_http_status(404)
+ expect(json_response).to have_key('message')
+ expect(response.headers.to_h)
+ .not_to include('Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
+ end
+ end
+ end
+
+ context 'when user is authorized' do
+ let(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE }
+ let(:public_builds) { true }
+
+ it 'returns a specific artifact file for a valid path' do
+ expect(Gitlab::Workhorse)
+ .to receive(:send_artifacts_entry)
+ .and_call_original
+
+ get_artifact_file(artifact)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.headers.to_h)
+ .to include('Content-Type' => 'application/json',
+ 'Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
+ end
+ end
+
+ context 'with branch name containing slash' do
+ before do
+ pipeline.reload
+ pipeline.update(ref: 'improve/awesome',
+ sha: project.commit('improve/awesome').sha)
+ end
+
+ it 'returns a specific artifact file for a valid path' do
+ get_artifact_file(artifact, 'improve/awesome')
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.headers.to_h)
+ .to include('Content-Type' => 'application/json',
+ 'Gitlab-Workhorse-Send-Data' => /artifacts-entry/)
+ end
+ end
+
+ context 'non-existing job' do
+ shared_examples 'not found' do
+ it { expect(response).to have_gitlab_http_status(:not_found) }
+ end
+
+ context 'has no such ref' do
+ before do
+ get_artifact_file('some/artifact', 'wrong-ref')
+ end
+
+ it_behaves_like 'not found'
+ end
+
+ context 'has no such job' do
+ before do
+ get_artifact_file('some/artifact', pipeline.ref, 'wrong-job-name')
+ end
+
+ it_behaves_like 'not found'
+ end
+ end
+ end
+
+ context 'when job does not have artifacts' do
+ let(:job) { create(:ci_build, pipeline: pipeline, user: api_user) }
+
+ it 'does not return job artifact file' do
+ get_artifact_file('some/artifact')
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ def get_artifact_file(artifact_path, ref = pipeline.ref, job_name = job.name)
+ get api("/projects/#{project.id}/jobs/artifacts/#{ref}/raw/#{artifact_path}", api_user), params: { job: job_name }
+ end
+ end
+
describe 'GET /projects/:id/jobs/:job_id/trace' do
before do
get api("/projects/#{project.id}/jobs/#{job.id}/trace", api_user)
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index b8a4a04a7e4..49eea2e362b 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -70,10 +70,12 @@ describe API::Labels do
describe 'POST /projects/:id/labels' do
it 'returns created label when all params' do
post api("/projects/#{project.id}/labels", user),
- name: 'Foo',
- color: '#FFAABB',
- description: 'test',
- priority: 2
+ params: {
+ name: 'Foo',
+ color: '#FFAABB',
+ description: 'test',
+ priority: 2
+ }
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq('Foo')
@@ -84,8 +86,10 @@ describe API::Labels do
it 'returns created label when only required params' do
post api("/projects/#{project.id}/labels", user),
- name: 'Foo & Bar',
- color: '#FFAABB'
+ params: {
+ name: 'Foo & Bar',
+ color: '#FFAABB'
+ }
expect(response.status).to eq(201)
expect(json_response['name']).to eq('Foo & Bar')
@@ -96,9 +100,11 @@ describe API::Labels do
it 'creates a prioritized label' do
post api("/projects/#{project.id}/labels", user),
- name: 'Foo & Bar',
- color: '#FFAABB',
- priority: 3
+ params: {
+ name: 'Foo & Bar',
+ color: '#FFAABB',
+ priority: 3
+ }
expect(response.status).to eq(201)
expect(json_response['name']).to eq('Foo & Bar')
@@ -108,35 +114,41 @@ describe API::Labels do
end
it 'returns a 400 bad request if name not given' do
- post api("/projects/#{project.id}/labels", user), color: '#FFAABB'
+ post api("/projects/#{project.id}/labels", user), params: { color: '#FFAABB' }
expect(response).to have_gitlab_http_status(400)
end
it 'returns a 400 bad request if color not given' do
- post api("/projects/#{project.id}/labels", user), name: 'Foobar'
+ post api("/projects/#{project.id}/labels", user), params: { name: 'Foobar' }
expect(response).to have_gitlab_http_status(400)
end
it 'returns 400 for invalid color' do
post api("/projects/#{project.id}/labels", user),
- name: 'Foo',
- color: '#FFAA'
+ params: {
+ name: 'Foo',
+ color: '#FFAA'
+ }
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
post api("/projects/#{project.id}/labels", user),
- name: 'Foo',
- color: '#FFAAFFFF'
+ params: {
+ name: 'Foo',
+ color: '#FFAAFFFF'
+ }
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 name' do
post api("/projects/#{project.id}/labels", user),
- name: ',',
- color: '#FFAABB'
+ params: {
+ name: ',',
+ color: '#FFAABB'
+ }
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']['title']).to eq(['is invalid'])
end
@@ -147,8 +159,10 @@ describe API::Labels do
project.update(group: group)
post api("/projects/#{project.id}/labels", user),
- name: group_label.name,
- color: '#FFAABB'
+ params: {
+ name: group_label.name,
+ color: '#FFAABB'
+ }
expect(response).to have_gitlab_http_status(409)
expect(json_response['message']).to eq('Label already exists')
@@ -156,17 +170,21 @@ describe API::Labels do
it 'returns 400 for invalid priority' do
post api("/projects/#{project.id}/labels", user),
- name: 'Foo',
- color: '#FFAAFFFF',
- priority: 'foo'
+ params: {
+ name: 'Foo',
+ color: '#FFAAFFFF',
+ priority: 'foo'
+ }
expect(response).to have_gitlab_http_status(400)
end
it 'returns 409 if label already exists in project' do
post api("/projects/#{project.id}/labels", user),
- name: 'label1',
- color: '#FFAABB'
+ params: {
+ name: 'label1',
+ color: '#FFAABB'
+ }
expect(response).to have_gitlab_http_status(409)
expect(json_response['message']).to eq('Label already exists')
end
@@ -174,13 +192,13 @@ describe API::Labels do
describe 'DELETE /projects/:id/labels' do
it 'returns 204 for existing label' do
- delete api("/projects/#{project.id}/labels", user), name: 'label1'
+ delete api("/projects/#{project.id}/labels", user), params: { name: 'label1' }
expect(response).to have_gitlab_http_status(204)
end
it 'returns 404 for non existing label' do
- delete api("/projects/#{project.id}/labels", user), name: 'label2'
+ 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
@@ -199,10 +217,12 @@ describe API::Labels do
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),
- name: 'label1',
- new_name: 'New Label',
- color: '#FFFFFF',
- description: 'test'
+ 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')
@@ -211,8 +231,10 @@ describe API::Labels do
it 'returns 200 if name is changed' do
put api("/projects/#{project.id}/labels", user),
- name: 'label1',
- new_name: 'New Label'
+ 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)
@@ -220,8 +242,10 @@ describe API::Labels do
it 'returns 200 if colors is changed' do
put api("/projects/#{project.id}/labels", user),
- name: 'label1',
- color: '#FFFFFF'
+ params: {
+ name: 'label1',
+ color: '#FFFFFF'
+ }
expect(response).to have_gitlab_http_status(200)
expect(json_response['name']).to eq(label1.name)
expect(json_response['color']).to eq('#FFFFFF')
@@ -229,8 +253,10 @@ describe API::Labels do
it 'returns 200 if description is changed' do
put api("/projects/#{project.id}/labels", user),
- name: 'bug',
- description: 'test'
+ params: {
+ name: 'bug',
+ description: 'test'
+ }
expect(response).to have_gitlab_http_status(200)
expect(json_response['name']).to eq(priority_label.name)
@@ -240,8 +266,10 @@ describe API::Labels do
it 'returns 200 if priority is changed' do
put api("/projects/#{project.id}/labels", user),
- name: 'bug',
- priority: 10
+ params: {
+ name: 'bug',
+ priority: 10
+ }
expect(response.status).to eq(200)
expect(json_response['name']).to eq(priority_label.name)
@@ -250,8 +278,10 @@ describe API::Labels do
it 'returns 200 if a priority is added' do
put api("/projects/#{project.id}/labels", user),
- name: 'label1',
- priority: 3
+ params: {
+ name: 'label1',
+ priority: 3
+ }
expect(response.status).to eq(200)
expect(json_response['name']).to eq(label1.name)
@@ -260,8 +290,10 @@ describe API::Labels do
it 'returns 200 if the priority is removed' do
put api("/projects/#{project.id}/labels", user),
- name: priority_label.name,
- priority: nil
+ params: {
+ name: priority_label.name,
+ priority: nil
+ }
expect(response.status).to eq(200)
expect(json_response['name']).to eq(priority_label.name)
@@ -270,19 +302,21 @@ describe API::Labels do
it 'returns 404 if label does not exist' do
put api("/projects/#{project.id}/labels", user),
- name: 'label2',
- new_name: 'label3'
+ params: {
+ name: 'label2',
+ new_name: 'label3'
+ }
expect(response).to have_gitlab_http_status(404)
end
it 'returns 400 if no label name given' do
- put api("/projects/#{project.id}/labels", user), new_name: 'label2'
+ 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')
end
it 'returns 400 if no new parameters given' do
- put api("/projects/#{project.id}/labels", user), name: 'label1'
+ 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')
@@ -290,33 +324,41 @@ describe API::Labels do
it 'returns 400 for invalid name' do
put api("/projects/#{project.id}/labels", user),
- name: 'label1',
- new_name: ',',
- color: '#FFFFFF'
+ params: {
+ name: 'label1',
+ new_name: ',',
+ color: '#FFFFFF'
+ }
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']['title']).to eq(['is invalid'])
end
it 'returns 400 when color code is too short' do
put api("/projects/#{project.id}/labels", user),
- name: 'label1',
- color: '#FF'
+ 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'])
end
it 'returns 400 for too long color code' do
put api("/projects/#{project.id}/labels", user),
- name: 'label1',
- color: '#FFAAFFFF'
+ 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'])
end
it 'returns 400 for invalid priority' do
put api("/projects/#{project.id}/labels", user),
- name: 'label1',
- priority: 'foo'
+ params: {
+ name: 'label1',
+ priority: 'foo'
+ }
expect(response).to have_gitlab_http_status(400)
end
diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb
index e3065840e6f..f52cdf1c459 100644
--- a/spec/requests/api/lint_spec.rb
+++ b/spec/requests/api/lint_spec.rb
@@ -8,7 +8,7 @@ describe API::Lint do
end
it 'passes validation' do
- post api('/ci/lint'), { content: yaml_content }
+ post api('/ci/lint'), params: { content: yaml_content }
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Hash
@@ -19,7 +19,7 @@ describe API::Lint do
context 'with an invalid .gitlab_ci.yml' do
it 'responds with errors about invalid syntax' do
- post api('/ci/lint'), { content: 'invalid content' }
+ post api('/ci/lint'), params: { content: 'invalid content' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['status']).to eq('invalid')
@@ -27,7 +27,7 @@ describe API::Lint do
end
it "responds with errors about invalid configuration" do
- post api('/ci/lint'), { content: '{ image: "ruby:2.1", services: ["postgres"] }' }
+ post api('/ci/lint'), params: { content: '{ image: "ruby:2.1", services: ["postgres"] }' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['status']).to eq('invalid')
diff --git a/spec/requests/api/markdown_spec.rb b/spec/requests/api/markdown_spec.rb
index e369c1435f0..e82ef002d32 100644
--- a/spec/requests/api/markdown_spec.rb
+++ b/spec/requests/api/markdown_spec.rb
@@ -7,7 +7,7 @@ describe API::Markdown do
let(:user) {} # No-op. It gets overwritten in the contexts below.
before do
- post api("/markdown", user), params
+ post api("/markdown", user), params: params
end
shared_examples "rendered markdown text without GFM" do
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 93e1c3a2294..79edbb301f2 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -78,7 +78,7 @@ describe API::Members do
end
it 'finds members with query string' do
- get api(members_url, developer), query: maintainer.username
+ get api(members_url, developer), params: { query: maintainer.username }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -88,7 +88,7 @@ describe API::Members do
end
it 'finds all members with no query specified' do
- get api(members_url, developer), query: ''
+ get api(members_url, developer), params: { query: '' }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -178,7 +178,7 @@ describe API::Members do
it_behaves_like 'a 404 response when source is private' do
let(:route) do
post api("/#{source_type.pluralize}/#{source.id}/members", stranger),
- user_id: access_requester.id, access_level: Member::MAINTAINER
+ params: { user_id: access_requester.id, access_level: Member::MAINTAINER }
end
end
@@ -188,7 +188,7 @@ describe API::Members do
it 'returns 403' do
user = public_send(type)
post api("/#{source_type.pluralize}/#{source.id}/members", user),
- user_id: access_requester.id, access_level: Member::MAINTAINER
+ params: { user_id: access_requester.id, access_level: Member::MAINTAINER }
expect(response).to have_gitlab_http_status(403)
end
@@ -201,7 +201,7 @@ describe API::Members do
it 'transforms the requester into a proper member' do
expect do
post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
- user_id: access_requester.id, access_level: Member::MAINTAINER
+ params: { user_id: access_requester.id, access_level: Member::MAINTAINER }
expect(response).to have_gitlab_http_status(201)
end.to change { source.members.count }.by(1)
@@ -214,7 +214,7 @@ describe API::Members do
it 'creates a new member' do
expect do
post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
- user_id: stranger.id, access_level: Member::DEVELOPER, expires_at: '2016-08-05'
+ params: { user_id: stranger.id, access_level: Member::DEVELOPER, expires_at: '2016-08-05' }
expect(response).to have_gitlab_http_status(201)
end.to change { source.members.count }.by(1)
@@ -224,16 +224,47 @@ describe API::Members do
end
end
+ context 'access levels' do
+ it 'does not create the member if group level is higher', :nested_groups do
+ parent = create(:group)
+
+ group.update(parent: parent)
+ project.update(group: group)
+ parent.add_developer(stranger)
+
+ post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
+ params: { user_id: stranger.id, access_level: Member::REPORTER }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']['access_level']).to eq(["should be higher than Developer inherited membership from group #{parent.name}"])
+ end
+
+ it 'creates the member if group level is lower', :nested_groups do
+ parent = create(:group)
+
+ group.update(parent: parent)
+ project.update(group: group)
+ parent.add_developer(stranger)
+
+ post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
+ params: { user_id: stranger.id, access_level: Member::MAINTAINER }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['id']).to eq(stranger.id)
+ expect(json_response['access_level']).to eq(Member::MAINTAINER)
+ end
+ end
+
it "returns 409 if member already exists" do
post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
- user_id: maintainer.id, access_level: Member::MAINTAINER
+ params: { user_id: maintainer.id, access_level: Member::MAINTAINER }
expect(response).to have_gitlab_http_status(409)
end
it 'returns 404 when the user_id is not valid' do
post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
- user_id: 0, access_level: Member::MAINTAINER
+ params: { user_id: 0, access_level: Member::MAINTAINER }
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
@@ -241,21 +272,21 @@ describe API::Members do
it 'returns 400 when user_id is not given' do
post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
- access_level: Member::MAINTAINER
+ params: { access_level: Member::MAINTAINER }
expect(response).to have_gitlab_http_status(400)
end
it 'returns 400 when access_level is not given' do
post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
- user_id: stranger.id
+ params: { user_id: stranger.id }
expect(response).to have_gitlab_http_status(400)
end
it 'returns 400 when access_level is not valid' do
post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
- user_id: stranger.id, access_level: 1234
+ params: { user_id: stranger.id, access_level: 1234 }
expect(response).to have_gitlab_http_status(400)
end
@@ -267,7 +298,7 @@ describe API::Members do
it_behaves_like 'a 404 response when source is private' do
let(:route) do
put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger),
- access_level: Member::MAINTAINER
+ params: { access_level: Member::MAINTAINER }
end
end
@@ -277,7 +308,7 @@ describe API::Members do
it 'returns 403' do
user = public_send(type)
put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user),
- access_level: Member::MAINTAINER
+ params: { access_level: Member::MAINTAINER }
expect(response).to have_gitlab_http_status(403)
end
@@ -288,7 +319,7 @@ describe API::Members do
context 'when authenticated as a maintainer/owner' do
it 'updates the member' do
put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", maintainer),
- access_level: Member::MAINTAINER, expires_at: '2016-08-05'
+ params: { access_level: Member::MAINTAINER, expires_at: '2016-08-05' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['id']).to eq(developer.id)
@@ -299,7 +330,7 @@ describe API::Members do
it 'returns 409 if member does not exist' do
put api("/#{source_type.pluralize}/#{source.id}/members/123", maintainer),
- access_level: Member::MAINTAINER
+ params: { access_level: Member::MAINTAINER }
expect(response).to have_gitlab_http_status(404)
end
@@ -312,7 +343,7 @@ describe API::Members do
it 'returns 400 when access level is not valid' do
put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", maintainer),
- access_level: 1234
+ params: { access_level: 1234 }
expect(response).to have_gitlab_http_status(400)
end
@@ -426,7 +457,7 @@ describe API::Members do
it 'returns 403' do
expect do
post api("/projects/#{project.id}/members", maintainer),
- user_id: stranger.id, access_level: Member::OWNER
+ params: { user_id: stranger.id, access_level: Member::OWNER }
expect(response).to have_gitlab_http_status(400)
end.to change { project.members.count }.by(0)
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 27bcde77860..dd40f3d1561 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -45,7 +45,7 @@ describe API::MergeRequests do
describe 'GET /merge_requests' do
context 'when unauthenticated' do
it 'returns an array of all merge requests' do
- get api('/merge_requests', user), scope: 'all'
+ get api('/merge_requests', user), params: { scope: 'all' }
expect_paginated_array_response
end
@@ -57,19 +57,19 @@ describe API::MergeRequests do
end
it "returns authentication error when scope is assigned-to-me" do
- get api("/merge_requests"), scope: 'assigned-to-me'
+ get api("/merge_requests"), params: { scope: 'assigned-to-me' }
expect(response).to have_gitlab_http_status(401)
end
it "returns authentication error when scope is assigned_to_me" do
- get api("/merge_requests"), scope: 'assigned_to_me'
+ get api("/merge_requests"), params: { scope: 'assigned_to_me' }
expect(response).to have_gitlab_http_status(401)
end
it "returns authentication error when scope is created-by-me" do
- get api("/merge_requests"), scope: 'created-by-me'
+ get api("/merge_requests"), params: { scope: 'created-by-me' }
expect(response).to have_gitlab_http_status(401)
end
@@ -81,7 +81,7 @@ describe API::MergeRequests do
let(:user2) { create(:user) }
it 'returns an array of all merge requests except unauthorized ones' do
- get api('/merge_requests', user), scope: :all
+ get api('/merge_requests', user), params: { scope: :all }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -91,7 +91,7 @@ describe API::MergeRequests do
end
it "returns an array of no merge_requests when wip=yes" do
- get api("/merge_requests", user), wip: 'yes'
+ get api("/merge_requests", user), params: { wip: 'yes' }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -100,7 +100,7 @@ describe API::MergeRequests do
end
it "returns an array of no merge_requests when wip=no" do
- get api("/merge_requests", user), wip: 'no'
+ get api("/merge_requests", user), params: { wip: 'no' }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -113,7 +113,7 @@ describe API::MergeRequests do
private_project = create(:project, :private)
merge_request3 = create(:merge_request, :simple, source_project: private_project, target_project: private_project, source_branch: 'other-branch')
- get api('/merge_requests', user), scope: :all
+ get api('/merge_requests', user), params: { scope: :all }
expect_response_contain_exactly(merge_request2, merge_request_merged, merge_request_closed, merge_request, merge_request_locked)
expect(json_response.map { |mr| mr['id'] }).not_to include(merge_request3.id)
@@ -130,7 +130,7 @@ describe API::MergeRequests do
it 'returns an array of merge requests authored by the given user' do
merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch')
- get api('/merge_requests', user), author_id: user2.id, scope: :all
+ get api('/merge_requests', user), params: { author_id: user2.id, scope: :all }
expect_response_ordered_exactly(merge_request3)
end
@@ -138,7 +138,7 @@ describe API::MergeRequests do
it 'returns an array of merge requests assigned to the given user' do
merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch')
- get api('/merge_requests', user), assignee_id: user2.id, scope: :all
+ get api('/merge_requests', user), params: { assignee_id: user2.id, scope: :all }
expect_response_ordered_exactly(merge_request3)
end
@@ -146,7 +146,7 @@ describe API::MergeRequests do
it 'returns an array of merge requests with no assignee' do
merge_request3 = create(:merge_request, :simple, author: user, source_project: project2, target_project: project2, source_branch: 'other-branch')
- get api('/merge_requests', user), assignee_id: 'None', scope: :all
+ get api('/merge_requests', user), params: { assignee_id: 'None', scope: :all }
expect_response_ordered_exactly(merge_request3)
end
@@ -155,7 +155,7 @@ describe API::MergeRequests do
# This MR with no assignee should not be returned
create(:merge_request, :simple, author: user, source_project: project2, target_project: project2, source_branch: 'other-branch')
- get api('/merge_requests', user), assignee_id: 'Any', scope: :all
+ get api('/merge_requests', user), params: { assignee_id: 'Any', scope: :all }
expect_response_contain_exactly(merge_request, merge_request2, merge_request_closed, merge_request_merged, merge_request_locked)
end
@@ -163,7 +163,7 @@ describe API::MergeRequests do
it 'returns an array of merge requests assigned to me' do
merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch')
- get api('/merge_requests', user2), scope: 'assigned_to_me'
+ get api('/merge_requests', user2), params: { scope: 'assigned_to_me' }
expect_response_ordered_exactly(merge_request3)
end
@@ -171,7 +171,7 @@ describe API::MergeRequests do
it 'returns an array of merge requests assigned to me (kebab-case)' do
merge_request3 = create(:merge_request, :simple, author: user, assignee: user2, source_project: project2, target_project: project2, source_branch: 'other-branch')
- get api('/merge_requests', user2), scope: 'assigned-to-me'
+ get api('/merge_requests', user2), params: { scope: 'assigned-to-me' }
expect_response_ordered_exactly(merge_request3)
end
@@ -179,7 +179,7 @@ describe API::MergeRequests do
it 'returns an array of merge requests created by me' do
merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch')
- get api('/merge_requests', user2), scope: 'created_by_me'
+ get api('/merge_requests', user2), params: { scope: 'created_by_me' }
expect_response_ordered_exactly(merge_request3)
end
@@ -187,7 +187,7 @@ describe API::MergeRequests do
it 'returns an array of merge requests created by me (kebab-case)' do
merge_request3 = create(:merge_request, :simple, author: user2, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch')
- get api('/merge_requests', user2), scope: 'created-by-me'
+ get api('/merge_requests', user2), params: { scope: 'created-by-me' }
expect_response_ordered_exactly(merge_request3)
end
@@ -196,14 +196,14 @@ describe API::MergeRequests do
merge_request3 = create(:merge_request, :simple, author: user, assignee: user, source_project: project2, target_project: project2, source_branch: 'other-branch')
award_emoji = create(:award_emoji, awardable: merge_request3, user: user2, name: 'star')
- get api('/merge_requests', user2), my_reaction_emoji: award_emoji.name, scope: 'all'
+ get api('/merge_requests', user2), params: { my_reaction_emoji: award_emoji.name, scope: 'all' }
expect_response_ordered_exactly(merge_request3)
end
context 'source_branch param' do
it 'returns merge requests with the given source branch' do
- get api('/merge_requests', user), source_branch: merge_request_closed.source_branch, state: 'all'
+ get api('/merge_requests', user), params: { source_branch: merge_request_closed.source_branch, state: 'all' }
expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked)
end
@@ -211,7 +211,7 @@ describe API::MergeRequests do
context 'target_branch param' do
it 'returns merge requests with the given target branch' do
- get api('/merge_requests', user), target_branch: merge_request_closed.target_branch, state: 'all'
+ get api('/merge_requests', user), params: { target_branch: merge_request_closed.target_branch, state: 'all' }
expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked)
end
@@ -255,13 +255,13 @@ describe API::MergeRequests do
end
it 'returns merge requests matching given search string for title' do
- get api("/merge_requests", user), search: merge_request.title
+ get api("/merge_requests", user), params: { search: merge_request.title }
expect_response_ordered_exactly(merge_request)
end
it 'returns merge requests for project matching given search string for description' do
- get api("/merge_requests", user), project_id: project.id, search: merge_request.description
+ get api("/merge_requests", user), params: { project_id: project.id, search: merge_request.description }
expect_response_ordered_exactly(merge_request)
end
@@ -269,7 +269,7 @@ describe API::MergeRequests do
context 'state param' do
it 'returns merge requests with the given state' do
- get api('/merge_requests', user), state: 'locked'
+ get api('/merge_requests', user), params: { state: 'locked' }
expect_response_contain_exactly(merge_request_locked)
end
@@ -291,7 +291,7 @@ describe API::MergeRequests do
end
it "returns an array of no merge_requests when wip=yes" do
- get api("/projects/#{project.id}/merge_requests", user), wip: 'yes'
+ get api("/projects/#{project.id}/merge_requests", user), params: { wip: 'yes' }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -300,7 +300,7 @@ describe API::MergeRequests do
end
it 'returns merge_request by "iids" array' do
- get api(endpoint_path, user), iids: [merge_request.iid, merge_request_closed.iid]
+ get api(endpoint_path, user), params: { iids: [merge_request.iid, merge_request_closed.iid] }
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
@@ -364,7 +364,7 @@ describe API::MergeRequests do
end
it 'exposes description and title html when render_html is true' do
- get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), render_html: true
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { render_html: true }
expect(response).to have_gitlab_http_status(200)
@@ -372,7 +372,7 @@ describe API::MergeRequests do
end
it 'exposes rebase_in_progress when include_rebase_in_progress is true' do
- get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), include_rebase_in_progress: true
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { include_rebase_in_progress: true }
expect(response).to have_gitlab_http_status(200)
@@ -421,7 +421,7 @@ describe API::MergeRequests do
it 'returns the commits behind the target branch when include_diverged_commits_count is present' do
allow_any_instance_of(merge_request.class).to receive(:diverged_commits_count).and_return(1)
- get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), include_diverged_commits_count: true
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { include_diverged_commits_count: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response['diverged_commits_count']).to eq(1)
@@ -587,13 +587,15 @@ describe API::MergeRequests do
context 'between branches projects' do
it "returns merge_request" do
post api("/projects/#{project.id}/merge_requests", user),
- title: 'Test merge_request',
- source_branch: 'feature_conflict',
- target_branch: 'master',
- author: user,
- labels: 'label, label2',
- milestone_id: milestone.id,
- squash: true
+ params: {
+ title: 'Test merge_request',
+ source_branch: 'feature_conflict',
+ target_branch: 'master',
+ author: user,
+ labels: 'label, label2',
+ milestone_id: milestone.id,
+ squash: true
+ }
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('Test merge_request')
@@ -605,35 +607,37 @@ describe API::MergeRequests do
it "returns 422 when source_branch equals target_branch" do
post api("/projects/#{project.id}/merge_requests", user),
- title: "Test merge_request", source_branch: "master", target_branch: "master", author: user
+ params: { title: "Test merge_request", source_branch: "master", target_branch: "master", author: user }
expect(response).to have_gitlab_http_status(422)
end
it "returns 400 when source_branch is missing" do
post api("/projects/#{project.id}/merge_requests", user),
- title: "Test merge_request", target_branch: "master", author: user
+ params: { title: "Test merge_request", target_branch: "master", author: user }
expect(response).to have_gitlab_http_status(400)
end
it "returns 400 when target_branch is missing" do
post api("/projects/#{project.id}/merge_requests", user),
- title: "Test merge_request", source_branch: "markdown", author: user
+ params: { title: "Test merge_request", source_branch: "markdown", author: user }
expect(response).to have_gitlab_http_status(400)
end
it "returns 400 when title is missing" do
post api("/projects/#{project.id}/merge_requests", user),
- target_branch: 'master', source_branch: 'markdown'
+ params: { target_branch: 'master', source_branch: 'markdown' }
expect(response).to have_gitlab_http_status(400)
end
it 'allows special label names' do
post api("/projects/#{project.id}/merge_requests", user),
- title: 'Test merge_request',
- source_branch: 'markdown',
- target_branch: 'master',
- author: user,
- labels: 'label, label?, label&foo, ?, &'
+ params: {
+ title: 'Test merge_request',
+ source_branch: 'markdown',
+ target_branch: 'master',
+ author: user,
+ labels: 'label, label?, label&foo, ?, &'
+ }
expect(response).to have_gitlab_http_status(201)
expect(json_response['labels']).to include 'label'
expect(json_response['labels']).to include 'label?'
@@ -645,20 +649,24 @@ describe API::MergeRequests do
context 'with existing MR' do
before do
post api("/projects/#{project.id}/merge_requests", user),
- title: 'Test merge_request',
- source_branch: 'feature_conflict',
- target_branch: 'master',
- author: user
+ params: {
+ title: 'Test merge_request',
+ source_branch: 'feature_conflict',
+ target_branch: 'master',
+ author: user
+ }
@mr = MergeRequest.all.last
end
it 'returns 409 when MR already exists for source/target' do
expect do
post api("/projects/#{project.id}/merge_requests", user),
- title: 'New test merge_request',
- source_branch: 'feature_conflict',
- target_branch: 'master',
- author: user
+ params: {
+ title: 'New test merge_request',
+ source_branch: 'feature_conflict',
+ target_branch: 'master',
+ author: user
+ }
end.to change { MergeRequest.count }.by(0)
expect(response).to have_gitlab_http_status(409)
end
@@ -673,13 +681,13 @@ describe API::MergeRequests do
end
it 'sets force_remove_source_branch to false' do
- post api("/projects/#{project.id}/merge_requests", user), params.merge(remove_source_branch: false)
+ post api("/projects/#{project.id}/merge_requests", user), params: params.merge(remove_source_branch: false)
expect(json_response['force_remove_source_branch']).to be_falsy
end
it 'sets force_remove_source_branch to true' do
- post api("/projects/#{project.id}/merge_requests", user), params.merge(remove_source_branch: true)
+ post api("/projects/#{project.id}/merge_requests", user), params: params.merge(remove_source_branch: true)
expect(json_response['force_remove_source_branch']).to be_truthy
end
@@ -698,8 +706,7 @@ describe API::MergeRequests do
it "returns merge_request" do
post api("/projects/#{forked_project.id}/merge_requests", user2),
- title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master",
- author: user2, target_project_id: project.id, description: 'Test description for Test merge_request'
+ params: { title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master", author: user2, target_project_id: project.id, description: 'Test description for Test merge_request' }
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('Test merge_request')
expect(json_response['description']).to eq('Test description for Test merge_request')
@@ -710,7 +717,7 @@ describe API::MergeRequests do
expect(forked_project.forked?).to be_truthy
expect(forked_project.forked_from_project).to eq(project)
post api("/projects/#{forked_project.id}/merge_requests", user2),
- title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id
+ params: { title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id }
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('Test merge_request')
end
@@ -719,37 +726,38 @@ describe API::MergeRequests do
project.project_feature.update(merge_requests_access_level: 0)
post api("/projects/#{forked_project.id}/merge_requests", user2),
- title: 'Test',
- target_branch: 'master',
- source_branch: 'markdown',
- author: user2,
- target_project_id: project.id
+ params: {
+ title: 'Test',
+ target_branch: 'master',
+ source_branch: 'markdown',
+ author: user2,
+ target_project_id: project.id
+ }
expect(response).to have_gitlab_http_status(403)
end
it "returns 400 when source_branch is missing" do
post api("/projects/#{forked_project.id}/merge_requests", user2),
- title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
+ params: { title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id }
expect(response).to have_gitlab_http_status(400)
end
it "returns 400 when target_branch is missing" do
post api("/projects/#{forked_project.id}/merge_requests", user2),
- title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id
+ params: { title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id }
expect(response).to have_gitlab_http_status(400)
end
it "returns 400 when title is missing" do
post api("/projects/#{forked_project.id}/merge_requests", user2),
- target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id
+ params: { target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: project.id }
expect(response).to have_gitlab_http_status(400)
end
it 'allows setting `allow_collaboration`' do
post api("/projects/#{forked_project.id}/merge_requests", user2),
- title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master",
- author: user2, target_project_id: project.id, allow_collaboration: true
+ params: { title: 'Test merge_request', source_branch: "feature_conflict", target_branch: "master", author: user2, target_project_id: project.id, allow_collaboration: true }
expect(response).to have_gitlab_http_status(201)
expect(json_response['allow_collaboration']).to be_truthy
expect(json_response['allow_maintainer_to_push']).to be_truthy
@@ -767,13 +775,13 @@ describe API::MergeRequests do
it 'returns 422 if targeting a different fork' do
unrelated_project.add_developer(user2)
- post api("/projects/#{forked_project.id}/merge_requests", user2), params
+ post api("/projects/#{forked_project.id}/merge_requests", user2), params: params
expect(response).to have_gitlab_http_status(422)
end
it 'returns 403 if targeting a different fork which user can not access' do
- post api("/projects/#{forked_project.id}/merge_requests", user2), params
+ post api("/projects/#{forked_project.id}/merge_requests", user2), params: params
expect(response).to have_gitlab_http_status(403)
end
@@ -781,7 +789,7 @@ describe API::MergeRequests do
it "returns 201 when target_branch is specified and for the same project" do
post api("/projects/#{forked_project.id}/merge_requests", user2),
- title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: forked_project.id
+ params: { title: 'Test merge_request', target_branch: 'master', source_branch: 'markdown', author: user2, target_project_id: forked_project.id }
expect(response).to have_gitlab_http_status(201)
end
end
@@ -877,21 +885,21 @@ describe API::MergeRequests do
end
it "returns 409 if the SHA parameter doesn't match" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), sha: merge_request.diff_head_sha.reverse
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { sha: merge_request.diff_head_sha.reverse }
expect(response).to have_gitlab_http_status(409)
expect(json_response['message']).to start_with('SHA does not match HEAD of source branch')
end
it "succeeds if the SHA parameter matches" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), sha: merge_request.diff_head_sha
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { sha: merge_request.diff_head_sha }
expect(response).to have_gitlab_http_status(200)
end
it "updates the MR's squash attribute" do
expect do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), squash: true
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { squash: true }
end.to change { merge_request.reload.squash }
expect(response).to have_gitlab_http_status(200)
@@ -901,7 +909,7 @@ describe API::MergeRequests do
allow_any_instance_of(MergeRequest).to receive(:head_pipeline).and_return(pipeline)
allow(pipeline).to receive(:active?).and_return(true)
- put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), merge_when_pipeline_succeeds: true
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { merge_when_pipeline_succeeds: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq('Test')
@@ -913,7 +921,7 @@ describe API::MergeRequests do
allow(pipeline).to receive(:active?).and_return(true)
project.update_attribute(:only_allow_merge_if_pipeline_succeeds, true)
- put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), merge_when_pipeline_succeeds: true
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { merge_when_pipeline_succeeds: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq('Test')
@@ -936,7 +944,7 @@ describe API::MergeRequests do
describe "PUT /projects/:id/merge_requests/:merge_request_iid" do
context "to close a MR" do
it "returns merge_request" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: "close"
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { state_event: "close" }
expect(response).to have_gitlab_http_status(200)
expect(json_response['state']).to eq('closed')
@@ -944,38 +952,38 @@ describe API::MergeRequests do
end
it "updates title and returns merge_request" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), title: "New title"
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { title: "New title" }
expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq('New title')
end
it "updates description and returns merge_request" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), description: "New description"
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { description: "New description" }
expect(response).to have_gitlab_http_status(200)
expect(json_response['description']).to eq('New description')
end
it "updates milestone_id and returns merge_request" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), milestone_id: milestone.id
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { milestone_id: milestone.id }
expect(response).to have_gitlab_http_status(200)
expect(json_response['milestone']['id']).to eq(milestone.id)
end
it "updates squash and returns merge_request" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), squash: true
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { squash: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response['squash']).to be_truthy
end
it "returns merge_request with renamed target_branch" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), target_branch: "wiki"
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { target_branch: "wiki" }
expect(response).to have_gitlab_http_status(200)
expect(json_response['target_branch']).to eq('wiki')
end
it "returns merge_request that removes the source branch" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), remove_source_branch: true
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { remove_source_branch: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response['force_remove_source_branch']).to be_truthy
@@ -983,8 +991,10 @@ describe API::MergeRequests do
it 'allows special label names' do
put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user),
- title: 'new issue',
- labels: 'label, label?, label&foo, ?, &'
+ params: {
+ title: 'new issue',
+ labels: 'label, label?, label&foo, ?, &'
+ }
expect(response.status).to eq(200)
expect(json_response['labels']).to include 'label'
@@ -995,7 +1005,7 @@ describe API::MergeRequests do
end
it 'does not update state when title is empty' do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: 'close', title: nil
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { state_event: 'close', title: nil }
merge_request.reload
expect(response).to have_gitlab_http_status(400)
@@ -1003,7 +1013,7 @@ describe API::MergeRequests do
end
it 'does not update state when target_branch is empty' do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), state_event: 'close', target_branch: nil
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user), params: { state_event: 'close', target_branch: nil }
merge_request.reload
expect(response).to have_gitlab_http_status(400)
@@ -1011,13 +1021,13 @@ describe API::MergeRequests do
end
it "returns 404 for an invalid merge request IID" do
- put api("/projects/#{project.id}/merge_requests/12345", user), state_event: "close"
+ put api("/projects/#{project.id}/merge_requests/12345", user), params: { state_event: "close" }
expect(response).to have_gitlab_http_status(404)
end
it "returns 404 if the merge request id is used instead of iid" do
- put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: "close"
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), params: { state_event: "close" }
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 3fb45449c74..9bf753fe049 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -28,7 +28,7 @@ describe API::Notes do
#
before do
post api("/projects/#{private_issue.project.id}/issues/#{private_issue.iid}/notes", user),
- body: 'Hi!'
+ params: { body: 'Hi!' }
end
it 'responds with resource not found error' do
@@ -154,7 +154,7 @@ describe API::Notes do
end
context 'when a user is a team member' do
- subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes", user), body: 'Hi!' }
+ subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes", user), params: { body: 'Hi!' } }
it 'returns 200 status' do
subject
@@ -168,7 +168,7 @@ describe API::Notes do
end
context 'when a user is not a team member' do
- subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes", private_user), body: 'Hi!' }
+ subject { post api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes", private_user), params: { body: 'Hi!' } }
it 'returns 403 status' do
subject
diff --git a/spec/requests/api/notification_settings_spec.rb b/spec/requests/api/notification_settings_spec.rb
index 3273cd26690..4ed667ad0dc 100644
--- a/spec/requests/api/notification_settings_spec.rb
+++ b/spec/requests/api/notification_settings_spec.rb
@@ -20,7 +20,7 @@ describe API::NotificationSettings do
let(:email) { create(:email, user: user) }
it "updates global notification settings for the current user" do
- put api("/notification_settings", user), { level: 'watch', notification_email: email.email }
+ put api("/notification_settings", user), params: { level: 'watch', notification_email: email.email }
expect(response).to have_gitlab_http_status(200)
expect(json_response['notification_email']).to eq(email.email)
@@ -31,7 +31,7 @@ describe API::NotificationSettings do
describe "PUT /notification_settings" do
it "fails on non-user email address" do
- put api("/notification_settings", user), { notification_email: 'invalid@example.com' }
+ put api("/notification_settings", user), params: { notification_email: 'invalid@example.com' }
expect(response).to have_gitlab_http_status(400)
end
@@ -49,7 +49,7 @@ describe API::NotificationSettings do
describe "PUT /groups/:id/notification_settings" do
it "updates group level notification settings for the current user" do
- put api("/groups/#{group.id}/notification_settings", user), { level: 'watch' }
+ put api("/groups/#{group.id}/notification_settings", user), params: { level: 'watch' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['level']).to eq(user.reload.notification_settings_for(group).level)
@@ -68,7 +68,7 @@ describe API::NotificationSettings do
describe "PUT /projects/:id/notification_settings" do
it "updates project level notification settings for the current user" do
- put api("/projects/#{project.id}/notification_settings", user), { level: 'custom', new_note: true }
+ put api("/projects/#{project.id}/notification_settings", user), params: { level: 'custom', new_note: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response['level']).to eq(user.reload.notification_settings_for(project).level)
@@ -79,7 +79,7 @@ describe API::NotificationSettings do
describe "PUT /projects/:id/notification_settings" do
it "fails on invalid level" do
- put api("/projects/#{project.id}/notification_settings", user), { level: 'invalid' }
+ put api("/projects/#{project.id}/notification_settings", user), params: { level: 'invalid' }
expect(response).to have_gitlab_http_status(400)
end
diff --git a/spec/requests/api/oauth_tokens_spec.rb b/spec/requests/api/oauth_tokens_spec.rb
index bdda80cc229..3811ec751de 100644
--- a/spec/requests/api/oauth_tokens_spec.rb
+++ b/spec/requests/api/oauth_tokens_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe 'OAuth tokens' do
context 'Resource Owner Password Credentials' do
def request_oauth_token(user)
- post '/oauth/token', username: user.username, password: user.password, grant_type: 'password'
+ post '/oauth/token', params: { username: user.username, password: user.password, grant_type: 'password' }
end
context 'when user has 2FA enabled' do
diff --git a/spec/requests/api/pages_domains_spec.rb b/spec/requests/api/pages_domains_spec.rb
index 35b6ed8d5c0..3eb68a6abb6 100644
--- a/spec/requests/api/pages_domains_spec.rb
+++ b/spec/requests/api/pages_domains_spec.rb
@@ -236,7 +236,7 @@ describe API::PagesDomains do
shared_examples_for 'post pages domains' do
it 'creates a new pages domain' do
- post api(route, user), params
+ post api(route, user), params: params
pages_domain = PagesDomain.find_by(domain: json_response['domain'])
expect(response).to have_gitlab_http_status(201)
@@ -247,7 +247,7 @@ describe API::PagesDomains do
end
it 'creates a new secure pages domain' do
- post api(route, user), params_secure
+ post api(route, user), params: params_secure
pages_domain = PagesDomain.find_by(domain: json_response['domain'])
expect(response).to have_gitlab_http_status(201)
@@ -258,13 +258,13 @@ describe API::PagesDomains do
end
it 'fails to create pages domain without key' do
- post api(route, user), pages_domain_secure_params.slice(:domain, :certificate)
+ post api(route, user), params: pages_domain_secure_params.slice(:domain, :certificate)
expect(response).to have_gitlab_http_status(400)
end
it 'fails to create pages domain with key missmatch' do
- post api(route, user), pages_domain_secure_key_missmatch_params.slice(:domain, :certificate, :key)
+ post api(route, user), params: pages_domain_secure_key_missmatch_params.slice(:domain, :certificate, :key)
expect(response).to have_gitlab_http_status(400)
end
@@ -284,7 +284,7 @@ describe API::PagesDomains do
end
it_behaves_like '403 response' do
- let(:request) { post api(route, user), params }
+ let(:request) { post api(route, user), params: params }
end
end
@@ -294,7 +294,7 @@ describe API::PagesDomains do
end
it_behaves_like '403 response' do
- let(:request) { post api(route, user), params }
+ let(:request) { post api(route, user), params: params }
end
end
@@ -304,13 +304,13 @@ describe API::PagesDomains do
end
it_behaves_like '403 response' do
- let(:request) { post api(route, user), params }
+ let(:request) { post api(route, user), params: params }
end
end
context 'when user is not a member' do
it_behaves_like '404 response' do
- let(:request) { post api(route, user), params }
+ let(:request) { post api(route, user), params: params }
end
end
end
@@ -331,7 +331,7 @@ describe API::PagesDomains do
end
it 'updates pages domain adding certificate' do
- put api(route_domain, user), params_secure
+ put api(route_domain, user), params: params_secure
pages_domain.reload
expect(response).to have_gitlab_http_status(200)
@@ -341,7 +341,7 @@ describe API::PagesDomains do
end
it 'updates pages domain with expired certificate' do
- put api(route_expired_domain, user), params_secure
+ put api(route_expired_domain, user), params: params_secure
pages_domain_expired.reload
expect(response).to have_gitlab_http_status(200)
@@ -351,7 +351,7 @@ describe API::PagesDomains do
end
it 'updates pages domain with expired certificate not updating key' do
- put api(route_secure_domain, user), params_secure_nokey
+ put api(route_secure_domain, user), params: params_secure_nokey
pages_domain_secure.reload
expect(response).to have_gitlab_http_status(200)
@@ -360,19 +360,19 @@ describe API::PagesDomains do
end
it 'fails to update pages domain adding certificate without key' do
- put api(route_domain, user), params_secure_nokey
+ put api(route_domain, user), params: params_secure_nokey
expect(response).to have_gitlab_http_status(400)
end
it 'fails to update pages domain adding certificate with missing chain' do
- put api(route_domain, user), pages_domain_secure_missing_chain_params.slice(:certificate)
+ put api(route_domain, user), params: pages_domain_secure_missing_chain_params.slice(:certificate)
expect(response).to have_gitlab_http_status(400)
end
it 'fails to update pages domain with key missmatch' do
- put api(route_secure_domain, user), pages_domain_secure_key_missmatch_params.slice(:certificate, :key)
+ put api(route_secure_domain, user), params: pages_domain_secure_key_missmatch_params.slice(:certificate, :key)
expect(response).to have_gitlab_http_status(400)
end
diff --git a/spec/requests/api/pipeline_schedules_spec.rb b/spec/requests/api/pipeline_schedules_spec.rb
index 997d413eb4f..870ef34437f 100644
--- a/spec/requests/api/pipeline_schedules_spec.rb
+++ b/spec/requests/api/pipeline_schedules_spec.rb
@@ -58,7 +58,7 @@ describe API::PipelineSchedules do
end
it 'returns matched pipeline schedules' do
- get api("/projects/#{project.id}/pipeline_schedules", developer), scope: target
+ get api("/projects/#{project.id}/pipeline_schedules", developer), params: { scope: target }
expect(json_response.map { |r| r['active'] }).to all(eq(active?(target)))
end
@@ -146,7 +146,7 @@ describe API::PipelineSchedules do
it 'creates pipeline_schedule' do
expect do
post api("/projects/#{project.id}/pipeline_schedules", developer),
- params
+ params: params
end.to change { project.pipeline_schedules.count }.by(1)
expect(response).to have_gitlab_http_status(:created)
@@ -170,7 +170,7 @@ describe API::PipelineSchedules do
context 'when cron has validation error' do
it 'does not create pipeline_schedule' do
post api("/projects/#{project.id}/pipeline_schedules", developer),
- params.merge('cron' => 'invalid-cron')
+ params: params.merge('cron' => 'invalid-cron')
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to have_key('cron')
@@ -180,7 +180,7 @@ describe API::PipelineSchedules do
context 'authenticated user with invalid permissions' do
it 'does not create pipeline_schedule' do
- post api("/projects/#{project.id}/pipeline_schedules", user), params
+ post api("/projects/#{project.id}/pipeline_schedules", user), params: params
expect(response).to have_gitlab_http_status(:not_found)
end
@@ -188,7 +188,7 @@ describe API::PipelineSchedules do
context 'unauthenticated user' do
it 'does not create pipeline_schedule' do
- post api("/projects/#{project.id}/pipeline_schedules"), params
+ post api("/projects/#{project.id}/pipeline_schedules"), params: params
expect(response).to have_gitlab_http_status(:unauthorized)
end
@@ -203,7 +203,7 @@ describe API::PipelineSchedules do
context 'authenticated user with valid permissions' do
it 'updates cron' do
put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer),
- cron: '1 2 3 4 *'
+ params: { cron: '1 2 3 4 *' }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('pipeline_schedule')
@@ -213,7 +213,7 @@ describe API::PipelineSchedules do
context 'when cron has validation error' do
it 'does not update pipeline_schedule' do
put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer),
- cron: 'invalid-cron'
+ params: { cron: 'invalid-cron' }
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to have_key('cron')
@@ -331,7 +331,7 @@ describe API::PipelineSchedules do
it 'creates pipeline_schedule_variable' do
expect do
post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer),
- params
+ params: params
end.to change { pipeline_schedule.variables.count }.by(1)
expect(response).to have_gitlab_http_status(:created)
@@ -352,7 +352,7 @@ describe API::PipelineSchedules do
context 'when key has validation error' do
it 'does not create pipeline_schedule_variable' do
post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", developer),
- params.merge('key' => '!?!?')
+ params: params.merge('key' => '!?!?')
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to have_key('key')
@@ -362,7 +362,7 @@ describe API::PipelineSchedules do
context 'authenticated user with invalid permissions' do
it 'does not create pipeline_schedule_variable' do
- post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", user), params
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables", user), params: params
expect(response).to have_gitlab_http_status(:not_found)
end
@@ -370,7 +370,7 @@ describe API::PipelineSchedules do
context 'unauthenticated user' do
it 'does not create pipeline_schedule_variable' do
- post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables"), params
+ post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables"), params: params
expect(response).to have_gitlab_http_status(:unauthorized)
end
@@ -389,7 +389,7 @@ describe API::PipelineSchedules do
context 'authenticated user with valid permissions' do
it 'updates pipeline_schedule_variable' do
put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/variables/#{pipeline_schedule_variable.key}", developer),
- value: 'updated_value'
+ params: { value: 'updated_value' }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('pipeline_schedule_variable')
diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb
index 2e4fa0f9e16..eb002de62a2 100644
--- a/spec/requests/api/pipelines_spec.rb
+++ b/spec/requests/api/pipelines_spec.rb
@@ -36,7 +36,7 @@ describe API::Pipelines do
end
it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), scope: target
+ get api("/projects/#{project.id}/pipelines", user), params: { scope: target }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -54,7 +54,7 @@ describe API::Pipelines do
end
it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), scope: 'finished'
+ get api("/projects/#{project.id}/pipelines", user), params: { scope: 'finished' }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -69,7 +69,7 @@ describe API::Pipelines do
context 'when scope is branches' do
it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), scope: 'branches'
+ get api("/projects/#{project.id}/pipelines", user), params: { scope: 'branches' }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -80,7 +80,7 @@ describe API::Pipelines do
context 'when scope is tags' do
it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), scope: 'tags'
+ get api("/projects/#{project.id}/pipelines", user), params: { scope: 'tags' }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -92,7 +92,7 @@ describe API::Pipelines do
context 'when scope is invalid' do
it 'returns bad_request' do
- get api("/projects/#{project.id}/pipelines", user), scope: 'invalid-scope'
+ get api("/projects/#{project.id}/pipelines", user), params: { scope: 'invalid-scope' }
expect(response).to have_gitlab_http_status(:bad_request)
end
@@ -107,7 +107,7 @@ describe API::Pipelines do
end
it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), status: target
+ get api("/projects/#{project.id}/pipelines", user), params: { status: target }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -119,7 +119,7 @@ describe API::Pipelines do
context 'when status is invalid' do
it 'returns bad_request' do
- get api("/projects/#{project.id}/pipelines", user), status: 'invalid-status'
+ get api("/projects/#{project.id}/pipelines", user), params: { status: 'invalid-status' }
expect(response).to have_gitlab_http_status(:bad_request)
end
@@ -132,7 +132,7 @@ describe API::Pipelines do
context 'when ref exists' do
it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), ref: 'master'
+ get api("/projects/#{project.id}/pipelines", user), params: { ref: 'master' }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -143,7 +143,7 @@ describe API::Pipelines do
context 'when ref does not exist' do
it 'returns empty' do
- get api("/projects/#{project.id}/pipelines", user), ref: 'invalid-ref'
+ get api("/projects/#{project.id}/pipelines", user), params: { ref: 'invalid-ref' }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -157,7 +157,7 @@ describe API::Pipelines do
context 'when name exists' do
it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), name: user.name
+ get api("/projects/#{project.id}/pipelines", user), params: { name: user.name }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -167,7 +167,7 @@ describe API::Pipelines do
context 'when name does not exist' do
it 'returns empty' do
- get api("/projects/#{project.id}/pipelines", user), name: 'invalid-name'
+ get api("/projects/#{project.id}/pipelines", user), params: { name: 'invalid-name' }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -181,7 +181,7 @@ describe API::Pipelines do
context 'when username exists' do
it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), username: user.username
+ get api("/projects/#{project.id}/pipelines", user), params: { username: user.username }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -191,7 +191,7 @@ describe API::Pipelines do
context 'when username does not exist' do
it 'returns empty' do
- get api("/projects/#{project.id}/pipelines", user), username: 'invalid-username'
+ get api("/projects/#{project.id}/pipelines", user), params: { username: 'invalid-username' }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -206,7 +206,7 @@ describe API::Pipelines do
context 'when yaml_errors is true' do
it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), yaml_errors: true
+ get api("/projects/#{project.id}/pipelines", user), params: { yaml_errors: true }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -216,7 +216,7 @@ describe API::Pipelines do
context 'when yaml_errors is false' do
it 'returns matched pipelines' do
- get api("/projects/#{project.id}/pipelines", user), yaml_errors: false
+ get api("/projects/#{project.id}/pipelines", user), params: { yaml_errors: false }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -226,7 +226,7 @@ describe API::Pipelines do
context 'when yaml_errors is invalid' do
it 'returns bad_request' do
- get api("/projects/#{project.id}/pipelines", user), yaml_errors: 'invalid-yaml_errors'
+ get api("/projects/#{project.id}/pipelines", user), params: { yaml_errors: 'invalid-yaml_errors' }
expect(response).to have_gitlab_http_status(:bad_request)
end
@@ -243,7 +243,7 @@ describe API::Pipelines do
context 'when sort parameter is valid' do
it 'sorts as user_id: :desc' do
- get api("/projects/#{project.id}/pipelines", user), order_by: 'user_id', sort: 'desc'
+ get api("/projects/#{project.id}/pipelines", user), params: { order_by: 'user_id', sort: 'desc' }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
@@ -256,7 +256,7 @@ describe API::Pipelines do
context 'when sort parameter is invalid' do
it 'returns bad_request' do
- get api("/projects/#{project.id}/pipelines", user), order_by: 'user_id', sort: 'invalid_sort'
+ get api("/projects/#{project.id}/pipelines", user), params: { order_by: 'user_id', sort: 'invalid_sort' }
expect(response).to have_gitlab_http_status(:bad_request)
end
@@ -265,7 +265,7 @@ describe API::Pipelines do
context 'when order_by is invalid' do
it 'returns bad_request' do
- get api("/projects/#{project.id}/pipelines", user), order_by: 'lock_version', sort: 'asc'
+ get api("/projects/#{project.id}/pipelines", user), params: { order_by: 'lock_version', sort: 'asc' }
expect(response).to have_gitlab_http_status(:bad_request)
end
@@ -303,7 +303,7 @@ describe API::Pipelines do
it 'creates and returns a new pipeline' do
expect do
- post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch
+ post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch }
end.to change { project.ci_pipelines.count }.by(1)
expect(response).to have_gitlab_http_status(201)
@@ -316,7 +316,7 @@ describe API::Pipelines do
it 'creates and returns a new pipeline using the given variables' do
expect do
- post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch, variables: variables
+ post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch, variables: variables }
end.to change { project.ci_pipelines.count }.by(1)
expect_variables(project.ci_pipelines.last.variables, variables)
@@ -337,7 +337,7 @@ describe API::Pipelines do
it 'creates and returns a new pipeline using the given variables' do
expect do
- post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch, variables: variables
+ post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch, variables: variables }
end.to change { project.ci_pipelines.count }.by(1)
expect_variables(project.ci_pipelines.last.variables, variables)
@@ -352,7 +352,7 @@ describe API::Pipelines do
it "doesn't create a job" do
expect do
- post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch
+ post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch }
end.not_to change { project.ci_pipelines.count }
expect(response).to have_gitlab_http_status(400)
@@ -361,7 +361,7 @@ describe API::Pipelines do
end
it 'fails when using an invalid ref' do
- post api("/projects/#{project.id}/pipeline", user), ref: 'invalid_ref'
+ post api("/projects/#{project.id}/pipeline", user), params: { ref: 'invalid_ref' }
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']['base'].first).to eq 'Reference not found'
@@ -376,7 +376,7 @@ describe API::Pipelines do
end
it 'fails to create pipeline' do
- post api("/projects/#{project.id}/pipeline", user), ref: project.default_branch
+ post api("/projects/#{project.id}/pipeline", user), params: { ref: project.default_branch }
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']['base'].first).to eq 'Missing .gitlab-ci.yml file'
@@ -388,7 +388,7 @@ describe API::Pipelines do
context 'unauthorized user' do
it 'does not create pipeline' do
- post api("/projects/#{project.id}/pipeline", non_member), ref: project.default_branch
+ post api("/projects/#{project.id}/pipeline", non_member), params: { ref: project.default_branch }
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq '404 Project Not Found'
diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb
index 0586025956f..1d2f81a397d 100644
--- a/spec/requests/api/project_export_spec.rb
+++ b/spec/requests/api/project_export_spec.rb
@@ -294,14 +294,14 @@ describe API::ProjectExport do
context 'with upload strategy' do
context 'when params invalid' do
it_behaves_like '400 response' do
- let(:request) { post(api(path, user), 'upload[url]' => 'whatever') }
+ let(:request) { post(api(path, user), params: { 'upload[url]' => 'whatever' }) }
end
end
it 'starts' do
allow_any_instance_of(Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy).to receive(:send_file)
- post(api(path, user), 'upload[url]' => 'http://gitlab.com')
+ post(api(path, user), params: { 'upload[url]' => 'http://gitlab.com' })
expect(response).to have_gitlab_http_status(202)
end
@@ -374,7 +374,7 @@ describe API::ProjectExport do
params = { description: "Foo" }
expect_any_instance_of(Projects::ImportExport::ExportService).to receive(:execute)
- post api(path, project.owner), params
+ post api(path, project.owner), params: params
expect(response).to have_gitlab_http_status(202)
end
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index 87997a48dc9..b88a8b95201 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -91,8 +91,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
it "adds hook to project" do
expect do
post api("/projects/#{project.id}/hooks", user),
- url: "http://example.com", issues_events: true, confidential_issues_events: true, wiki_page_events: true,
- job_events: true, push_events_branch_filter: 'some-feature-branch'
+ params: { url: "http://example.com", issues_events: true, confidential_issues_events: true, wiki_page_events: true, job_events: true, push_events_branch_filter: 'some-feature-branch' }
end.to change {project.hooks.count}.by(1)
expect(response).to have_gitlab_http_status(201)
@@ -116,7 +115,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
token = "secret token"
expect do
- post api("/projects/#{project.id}/hooks", user), url: "http://example.com", token: token
+ post api("/projects/#{project.id}/hooks", user), params: { url: "http://example.com", token: token }
end.to change {project.hooks.count}.by(1)
expect(response).to have_gitlab_http_status(201)
@@ -135,12 +134,12 @@ describe API::ProjectHooks, 'ProjectHooks' do
end
it "returns a 422 error if url not valid" do
- post api("/projects/#{project.id}/hooks", user), url: "ftp://example.com"
+ post api("/projects/#{project.id}/hooks", user), params: { url: "ftp://example.com" }
expect(response).to have_gitlab_http_status(422)
end
it "returns a 422 error if branch filter is not valid" do
- post api("/projects/#{project.id}/hooks", user), url: "http://example.com", push_events_branch_filter: '~badbranchname/'
+ post api("/projects/#{project.id}/hooks", user), params: { url: "http://example.com", push_events_branch_filter: '~badbranchname/' }
expect(response).to have_gitlab_http_status(422)
end
end
@@ -148,7 +147,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
describe "PUT /projects/:id/hooks/:hook_id" do
it "updates an existing project hook" do
put api("/projects/#{project.id}/hooks/#{hook.id}", user),
- url: 'http://example.org', push_events: false, job_events: true
+ params: { url: 'http://example.org', push_events: false, job_events: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response['url']).to eq('http://example.org')
@@ -168,7 +167,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
it "adds the token without including it in the response" do
token = "secret token"
- put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: "http://example.org", token: token
+ put api("/projects/#{project.id}/hooks/#{hook.id}", user), params: { url: "http://example.org", token: token }
expect(response).to have_gitlab_http_status(200)
expect(json_response["url"]).to eq("http://example.org")
@@ -179,7 +178,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
end
it "returns 404 error if hook id not found" do
- put api("/projects/#{project.id}/hooks/1234", user), url: 'http://example.org'
+ put api("/projects/#{project.id}/hooks/1234", user), params: { url: 'http://example.org' }
expect(response).to have_gitlab_http_status(404)
end
@@ -189,7 +188,7 @@ describe API::ProjectHooks, 'ProjectHooks' do
end
it "returns a 422 error if url is not valid" do
- put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'ftp://example.com'
+ put api("/projects/#{project.id}/hooks/#{hook.id}", user), params: { url: 'ftp://example.com' }
expect(response).to have_gitlab_http_status(422)
end
end
diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb
index 204702b8a5a..594b42bb6c0 100644
--- a/spec/requests/api/project_import_spec.rb
+++ b/spec/requests/api/project_import_spec.rb
@@ -20,7 +20,7 @@ describe API::ProjectImport do
it 'schedules an import using a namespace' do
stub_import(namespace)
- post api('/projects/import', user), path: 'test-import', file: fixture_file_upload(file), namespace: namespace.id
+ post api('/projects/import', user), params: { path: 'test-import', file: fixture_file_upload(file), namespace: namespace.id }
expect(response).to have_gitlab_http_status(201)
end
@@ -28,7 +28,7 @@ describe API::ProjectImport do
it 'schedules an import using the namespace path' do
stub_import(namespace)
- post api('/projects/import', user), path: 'test-import', file: fixture_file_upload(file), namespace: namespace.full_path
+ post api('/projects/import', user), params: { path: 'test-import', file: fixture_file_upload(file), namespace: namespace.full_path }
expect(response).to have_gitlab_http_status(201)
end
@@ -36,7 +36,7 @@ describe API::ProjectImport do
it 'schedules an import at the user namespace level' do
stub_import(user.namespace)
- post api('/projects/import', user), path: 'test-import2', file: fixture_file_upload(file)
+ post api('/projects/import', user), params: { path: 'test-import2', file: fixture_file_upload(file) }
expect(response).to have_gitlab_http_status(201)
end
@@ -45,7 +45,7 @@ describe API::ProjectImport do
expect_any_instance_of(ProjectImportState).not_to receive(:schedule)
expect(::Projects::CreateService).not_to receive(:new)
- post api('/projects/import', user), namespace: 'nonexistent', path: 'test-import2', file: fixture_file_upload(file)
+ post api('/projects/import', user), params: { namespace: 'nonexistent', path: 'test-import2', file: fixture_file_upload(file) }
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Namespace Not Found')
@@ -55,9 +55,11 @@ describe API::ProjectImport do
expect_any_instance_of(ProjectImportState).not_to receive(:schedule)
post(api('/projects/import', create(:user)),
- path: 'test-import3',
- file: fixture_file_upload(file),
- namespace: namespace.full_path)
+ params: {
+ path: 'test-import3',
+ file: fixture_file_upload(file),
+ namespace: namespace.full_path
+ })
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Namespace Not Found')
@@ -66,7 +68,7 @@ describe API::ProjectImport do
it 'does not schedule an import if the user uploads no valid file' do
expect_any_instance_of(ProjectImportState).not_to receive(:schedule)
- post api('/projects/import', user), path: 'test-import3', file: './random/test'
+ post api('/projects/import', user), params: { path: 'test-import3', file: './random/test' }
expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq('file is invalid')
@@ -77,10 +79,12 @@ describe API::ProjectImport do
override_params = { 'description' => 'Hello world' }
post api('/projects/import', user),
- path: 'test-import',
- file: fixture_file_upload(file),
- namespace: namespace.id,
- override_params: override_params
+ params: {
+ path: 'test-import',
+ file: fixture_file_upload(file),
+ namespace: namespace.id,
+ override_params: override_params
+ }
import_project = Project.find(json_response['id'])
expect(import_project.import_data.data['override_params']).to eq(override_params)
@@ -91,10 +95,12 @@ describe API::ProjectImport do
override_params = { 'not_allowed' => 'Hello world' }
post api('/projects/import', user),
- path: 'test-import',
- file: fixture_file_upload(file),
- namespace: namespace.id,
- override_params: override_params
+ params: {
+ path: 'test-import',
+ file: fixture_file_upload(file),
+ namespace: namespace.id,
+ override_params: override_params
+ }
import_project = Project.find(json_response['id'])
expect(import_project.import_data.data['override_params']).to be_empty
@@ -105,10 +111,12 @@ describe API::ProjectImport do
perform_enqueued_jobs do
post api('/projects/import', user),
- path: 'test-import',
- file: fixture_file_upload(file),
- namespace: namespace.id,
- override_params: override_params
+ params: {
+ path: 'test-import',
+ file: fixture_file_upload(file),
+ namespace: namespace.id,
+ override_params: override_params
+ }
end
import_project = Project.find(json_response['id'])
@@ -121,7 +129,7 @@ describe API::ProjectImport do
it 'does not schedule an import' do
expect_any_instance_of(ProjectImportState).not_to receive(:schedule)
- post api('/projects/import', user), path: existing_project.path, file: fixture_file_upload(file)
+ post api('/projects/import', user), params: { path: existing_project.path, file: fixture_file_upload(file) }
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq('Name has already been taken')
@@ -131,7 +139,7 @@ describe API::ProjectImport do
it 'schedules an import' do
stub_import(user.namespace)
- post api('/projects/import', user), path: existing_project.path, file: fixture_file_upload(file), overwrite: true
+ post api('/projects/import', user), params: { path: existing_project.path, file: fixture_file_upload(file), overwrite: true }
expect(response).to have_gitlab_http_status(201)
end
diff --git a/spec/requests/api/project_milestones_spec.rb b/spec/requests/api/project_milestones_spec.rb
index 62613aa5938..0fa13dd71e2 100644
--- a/spec/requests/api/project_milestones_spec.rb
+++ b/spec/requests/api/project_milestones_spec.rb
@@ -46,7 +46,7 @@ describe API::ProjectMilestones do
expect(Event).to receive(:create!)
put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
- state_event: 'close'
+ params: { state_event: 'close' }
end
end
end
diff --git a/spec/requests/api/project_snapshots_spec.rb b/spec/requests/api/project_snapshots_spec.rb
index 07a920f8d28..44b5ee1f130 100644
--- a/spec/requests/api/project_snapshots_spec.rb
+++ b/spec/requests/api/project_snapshots_spec.rb
@@ -35,14 +35,14 @@ describe API::ProjectSnapshots do
end
it 'requests project repository raw archive as administrator' do
- get api("/projects/#{project.id}/snapshot", admin), wiki: '0'
+ get api("/projects/#{project.id}/snapshot", admin), params: { wiki: '0' }
expect(response).to have_gitlab_http_status(200)
expect_snapshot_response_for(project.repository)
end
it 'requests wiki repository raw archive as administrator' do
- get api("/projects/#{project.id}/snapshot", admin), wiki: '1'
+ get api("/projects/#{project.id}/snapshot", admin), params: { wiki: '1' }
expect(response).to have_gitlab_http_status(200)
expect_snapshot_response_for(project.wiki.repository)
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 5dec0bc778c..29f69b6ce20 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -97,7 +97,7 @@ describe API::ProjectSnippets do
end
it 'creates a new snippet' do
- post api("/projects/#{project.id}/snippets/", admin), params
+ post api("/projects/#{project.id}/snippets/", admin), params: params
expect(response).to have_gitlab_http_status(201)
snippet = ProjectSnippet.find(json_response['id'])
@@ -111,7 +111,7 @@ describe API::ProjectSnippets do
it 'returns 400 for missing parameters' do
params.delete(:title)
- post api("/projects/#{project.id}/snippets/", admin), params
+ post api("/projects/#{project.id}/snippets/", admin), params: params
expect(response).to have_gitlab_http_status(400)
end
@@ -119,7 +119,7 @@ describe API::ProjectSnippets do
it 'returns 400 for empty code field' do
params[:code] = ''
- post api("/projects/#{project.id}/snippets/", admin), params
+ post api("/projects/#{project.id}/snippets/", admin), params: params
expect(response).to have_gitlab_http_status(400)
end
@@ -128,7 +128,7 @@ describe API::ProjectSnippets do
def create_snippet(project, snippet_params = {})
project.add_developer(user)
- post api("/projects/#{project.id}/snippets", user), params.merge(snippet_params)
+ post api("/projects/#{project.id}/snippets", user), params: params.merge(snippet_params)
end
before do
@@ -167,7 +167,7 @@ describe API::ProjectSnippets do
new_content = 'New content'
new_description = 'New description'
- put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content, description: new_description
+ put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), params: { code: new_content, description: new_description }
expect(response).to have_gitlab_http_status(200)
snippet.reload
@@ -176,7 +176,7 @@ describe API::ProjectSnippets do
end
it 'returns 404 for invalid snippet id' do
- put api("/projects/#{snippet.project.id}/snippets/1234", admin), title: 'foo'
+ put api("/projects/#{snippet.project.id}/snippets/1234", admin), params: { title: 'foo' }
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Snippet Not Found')
@@ -191,14 +191,14 @@ describe API::ProjectSnippets do
it 'returns 400 for empty code field' do
new_content = ''
- put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content
+ put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), params: { code: new_content }
expect(response).to have_gitlab_http_status(400)
end
context 'when the snippet is spam' do
def update_snippet(snippet_params = {})
- put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}", admin), snippet_params
+ put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}", admin), params: snippet_params
end
before do
diff --git a/spec/requests/api/project_templates_spec.rb b/spec/requests/api/project_templates_spec.rb
index 86e33f23951..ab5d4de7ff7 100644
--- a/spec/requests/api/project_templates_spec.rb
+++ b/spec/requests/api/project_templates_spec.rb
@@ -130,8 +130,10 @@ describe API::ProjectTemplates do
describe 'GET /projects/:id/templates/licenses/:key' do
it 'fills placeholders in the license' do
get api("/projects/#{public_project.id}/templates/licenses/agpl-3.0"),
- project: 'Project Placeholder',
- fullname: 'Fullname Placeholder'
+ params: {
+ project: 'Project Placeholder',
+ fullname: 'Fullname Placeholder'
+ }
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/license')
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 62b6a3ce42e..ffe4512fa6f 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -52,7 +52,7 @@ describe API::Projects do
describe 'GET /projects' do
shared_examples_for 'projects response' do
it 'returns an array of projects' do
- get api('/projects', current_user), filter
+ get api('/projects', current_user), params: filter
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -61,7 +61,7 @@ describe API::Projects do
end
it 'returns the proper security headers' do
- get api('/projects', current_user), filter
+ get api('/projects', current_user), params: filter
expect(response).to include_security_headers
end
@@ -192,7 +192,7 @@ describe API::Projects do
end
it "includes statistics if requested" do
- get api('/projects', user), statistics: true
+ get api('/projects', user), params: { statistics: true }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -210,7 +210,7 @@ describe API::Projects do
end
it "does not include license if requested" do
- get api('/projects', user), license: true
+ get api('/projects', user), params: { license: true }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -315,7 +315,7 @@ describe API::Projects do
context 'and using the visibility filter' do
it 'filters based on private visibility param' do
- get api('/projects', user), { visibility: 'private' }
+ get api('/projects', user), params: { visibility: 'private' }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -326,7 +326,7 @@ describe API::Projects do
it 'filters based on internal visibility param' do
project2.update_attribute(:visibility_level, Gitlab::VisibilityLevel::INTERNAL)
- get api('/projects', user), { visibility: 'internal' }
+ get api('/projects', user), params: { visibility: 'internal' }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -335,7 +335,7 @@ describe API::Projects do
end
it 'filters based on public visibility param' do
- get api('/projects', user), { visibility: 'public' }
+ get api('/projects', user), params: { visibility: 'public' }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -346,7 +346,7 @@ describe API::Projects do
context 'and using sorting' do
it 'returns the correct order when sorted by id' do
- get api('/projects', user), { order_by: 'id', sort: 'desc' }
+ get api('/projects', user), params: { order_by: 'id', sort: 'desc' }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -357,7 +357,7 @@ describe API::Projects do
context 'and with owned=true' do
it 'returns an array of projects the user owns' do
- get api('/projects', user4), owned: true
+ get api('/projects', user4), params: { owned: true }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -376,7 +376,7 @@ describe API::Projects do
end
it 'returns the starred projects viewable by the user' do
- get api('/projects', user3), starred: true
+ get api('/projects', user3), params: { starred: true }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -398,7 +398,7 @@ describe API::Projects do
context 'including owned filter' do
it 'returns only projects that satisfy all query parameters' do
- get api('/projects', user), { visibility: 'public', owned: true, starred: true, search: 'gitlab' }
+ get api('/projects', user), params: { visibility: 'public', owned: true, starred: true, search: 'gitlab' }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -417,7 +417,7 @@ describe API::Projects do
end
it 'returns only projects that satisfy all query parameters' do
- get api('/projects', user), { visibility: 'public', membership: true, starred: true, search: 'gitlab' }
+ get api('/projects', user), params: { visibility: 'public', membership: true, starred: true, search: 'gitlab' }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -436,7 +436,7 @@ describe API::Projects do
end
it 'returns an array of groups the user has at least developer access' do
- get api('/projects', user2), { min_access_level: 30 }
+ get api('/projects', user2), params: { min_access_level: 30 }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
@@ -479,14 +479,14 @@ describe API::Projects do
context 'maximum number of projects reached' do
it 'does not create new project and respond with 403' do
allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0)
- expect { post api('/projects', user2), name: 'foo' }
+ expect { post api('/projects', user2), params: { name: 'foo' } }
.to change {Project.count}.by(0)
expect(response).to have_gitlab_http_status(403)
end
end
it 'creates new project without path but with name and returns 201' do
- expect { post api('/projects', user), name: 'Foo Project' }
+ expect { post api('/projects', user), params: { name: 'Foo Project' } }
.to change { Project.count }.by(1)
expect(response).to have_gitlab_http_status(201)
@@ -497,7 +497,7 @@ describe API::Projects do
end
it 'creates new project without name but with path and returns 201' do
- expect { post api('/projects', user), path: 'foo_project' }
+ expect { post api('/projects', user), params: { path: 'foo_project' } }
.to change { Project.count }.by(1)
expect(response).to have_gitlab_http_status(201)
@@ -508,7 +508,7 @@ describe API::Projects do
end
it 'creates new project with name and path and returns 201' do
- expect { post api('/projects', user), path: 'path-project-Foo', name: 'Foo Project' }
+ expect { post api('/projects', user), params: { path: 'path-project-Foo', name: 'Foo Project' } }
.to change { Project.count }.by(1)
expect(response).to have_gitlab_http_status(201)
@@ -520,7 +520,7 @@ describe API::Projects do
it 'creates last project before reaching project limit' do
allow_any_instance_of(User).to receive(:projects_limit_left).and_return(1)
- post api('/projects', user2), name: 'foo'
+ post api('/projects', user2), params: { name: 'foo' }
expect(response).to have_gitlab_http_status(201)
end
@@ -544,7 +544,7 @@ describe API::Projects do
merge_method: 'ff'
})
- post api('/projects', user), project
+ post api('/projects', user), params: project
expect(response).to have_gitlab_http_status(201)
@@ -564,7 +564,7 @@ describe API::Projects do
it 'sets a project as public' do
project = attributes_for(:project, visibility: 'public')
- post api('/projects', user), project
+ post api('/projects', user), params: project
expect(json_response['visibility']).to eq('public')
end
@@ -572,7 +572,7 @@ describe API::Projects do
it 'sets a project as internal' do
project = attributes_for(:project, visibility: 'internal')
- post api('/projects', user), project
+ post api('/projects', user), params: project
expect(json_response['visibility']).to eq('internal')
end
@@ -580,7 +580,7 @@ describe API::Projects do
it 'sets a project as private' do
project = attributes_for(:project, visibility: 'private')
- post api('/projects', user), project
+ post api('/projects', user), params: project
expect(json_response['visibility']).to eq('private')
end
@@ -588,7 +588,7 @@ describe API::Projects do
it 'creates a new project initialized with a README.md' do
project = attributes_for(:project, initialize_with_readme: 1, name: 'somewhere')
- post api('/projects', user), project
+ post api('/projects', user), params: project
expect(json_response['readme_url']).to eql("#{Gitlab.config.gitlab.url}/#{json_response['namespace']['full_path']}/somewhere/blob/master/README.md")
end
@@ -596,7 +596,7 @@ describe API::Projects do
it 'sets tag list to a project' do
project = attributes_for(:project, tag_list: %w[tagFirst tagSecond])
- post api('/projects', user), project
+ post api('/projects', user), params: project
expect(json_response['tag_list']).to eq(%w[tagFirst tagSecond])
end
@@ -604,7 +604,7 @@ describe API::Projects do
it 'uploads avatar for project a project' do
project = attributes_for(:project, avatar: fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif'))
- post api('/projects', user), project
+ post api('/projects', user), params: project
project_id = json_response['id']
expect(json_response['avatar_url']).to eq("http://localhost/uploads/-/system/project/avatar/#{project_id}/banana_sample.gif")
@@ -613,7 +613,7 @@ describe API::Projects do
it 'sets a project as not allowing outdated diff discussions to automatically resolve' do
project = attributes_for(:project, resolve_outdated_diff_discussions: false)
- post api('/projects', user), project
+ post api('/projects', user), params: project
expect(json_response['resolve_outdated_diff_discussions']).to be_falsey
end
@@ -621,7 +621,7 @@ describe API::Projects do
it 'sets a project as allowing outdated diff discussions to automatically resolve' do
project = attributes_for(:project, resolve_outdated_diff_discussions: true)
- post api('/projects', user), project
+ post api('/projects', user), params: project
expect(json_response['resolve_outdated_diff_discussions']).to be_truthy
end
@@ -629,7 +629,7 @@ describe API::Projects do
it 'sets a project as allowing merge even if build fails' do
project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: false)
- post api('/projects', user), project
+ post api('/projects', user), params: project
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
end
@@ -637,7 +637,7 @@ describe API::Projects do
it 'sets a project as allowing merge only if merge_when_pipeline_succeeds' do
project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: true)
- post api('/projects', user), project
+ post api('/projects', user), params: project
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
end
@@ -645,7 +645,7 @@ describe API::Projects do
it 'sets a project as allowing merge even if discussions are unresolved' do
project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: false)
- post api('/projects', user), project
+ post api('/projects', user), params: project
expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey
end
@@ -653,7 +653,7 @@ describe API::Projects do
it 'sets a project as allowing merge if only_allow_merge_if_all_discussions_are_resolved is nil' do
project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: nil)
- post api('/projects', user), project
+ post api('/projects', user), params: project
expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey
end
@@ -661,7 +661,7 @@ describe API::Projects do
it 'sets a project as allowing merge only if all discussions are resolved' do
project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: true)
- post api('/projects', user), project
+ post api('/projects', user), params: project
expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy
end
@@ -669,7 +669,7 @@ describe API::Projects do
it 'sets the merge method of a project to rebase merge' do
project = attributes_for(:project, merge_method: 'rebase_merge')
- post api('/projects', user), project
+ post api('/projects', user), params: project
expect(json_response['merge_method']).to eq('rebase_merge')
end
@@ -677,7 +677,7 @@ describe API::Projects do
it 'rejects invalid values for merge_method' do
project = attributes_for(:project, merge_method: 'totally_not_valid_method')
- post api('/projects', user), project
+ post api('/projects', user), params: project
expect(response).to have_gitlab_http_status(400)
end
@@ -685,7 +685,7 @@ describe API::Projects do
it 'ignores import_url when it is nil' do
project = attributes_for(:project, import_url: nil)
- post api('/projects', user), project
+ post api('/projects', user), params: project
expect(response).to have_gitlab_http_status(201)
end
@@ -698,7 +698,7 @@ describe API::Projects do
end
it 'does not allow a non-admin to use a restricted visibility level' do
- post api('/projects', user), project_param
+ post api('/projects', user), params: project_param
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']['visibility_level'].first).to(
@@ -707,7 +707,7 @@ describe API::Projects do
end
it 'allows an admin to override restricted visibility settings' do
- post api('/projects', admin), project_param
+ post api('/projects', admin), params: project_param
expect(json_response['visibility']).to eq('public')
end
@@ -739,7 +739,7 @@ describe API::Projects do
private_project1.add_developer(user2)
private_project2.add_reporter(user2)
- get api("/users/#{user4.id}/projects/", user2), { min_access_level: 30 }
+ get api("/users/#{user4.id}/projects/", user2), params: { min_access_level: 30 }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -750,7 +750,7 @@ describe API::Projects do
describe 'POST /projects/user/:id' do
it 'creates new project without path but with name and return 201' do
- expect { post api("/projects/user/#{user.id}", admin), name: 'Foo Project' }.to change { Project.count }.by(1)
+ expect { post api("/projects/user/#{user.id}", admin), params: { name: 'Foo Project' } }.to change { Project.count }.by(1)
expect(response).to have_gitlab_http_status(201)
project = Project.last
@@ -760,7 +760,7 @@ describe API::Projects do
end
it 'creates new project with name and path and returns 201' do
- expect { post api("/projects/user/#{user.id}", admin), path: 'path-project-Foo', name: 'Foo Project' }
+ expect { post api("/projects/user/#{user.id}", admin), params: { path: 'path-project-Foo', name: 'Foo Project' } }
.to change { Project.count }.by(1)
expect(response).to have_gitlab_http_status(201)
@@ -787,7 +787,7 @@ describe API::Projects do
jobs_enabled: true
})
- post api("/projects/user/#{user.id}", admin), project
+ post api("/projects/user/#{user.id}", admin), params: project
expect(response).to have_gitlab_http_status(201)
@@ -801,7 +801,7 @@ describe API::Projects do
it 'sets a project as public' do
project = attributes_for(:project, visibility: 'public')
- post api("/projects/user/#{user.id}", admin), project
+ post api("/projects/user/#{user.id}", admin), params: project
expect(response).to have_gitlab_http_status(201)
expect(json_response['visibility']).to eq('public')
@@ -810,7 +810,7 @@ describe API::Projects do
it 'sets a project as internal' do
project = attributes_for(:project, visibility: 'internal')
- post api("/projects/user/#{user.id}", admin), project
+ post api("/projects/user/#{user.id}", admin), params: project
expect(response).to have_gitlab_http_status(201)
expect(json_response['visibility']).to eq('internal')
@@ -819,7 +819,7 @@ describe API::Projects do
it 'sets a project as private' do
project = attributes_for(:project, visibility: 'private')
- post api("/projects/user/#{user.id}", admin), project
+ post api("/projects/user/#{user.id}", admin), params: project
expect(json_response['visibility']).to eq('private')
end
@@ -827,7 +827,7 @@ describe API::Projects do
it 'sets a project as not allowing outdated diff discussions to automatically resolve' do
project = attributes_for(:project, resolve_outdated_diff_discussions: false)
- post api("/projects/user/#{user.id}", admin), project
+ post api("/projects/user/#{user.id}", admin), params: project
expect(json_response['resolve_outdated_diff_discussions']).to be_falsey
end
@@ -835,27 +835,27 @@ describe API::Projects do
it 'sets a project as allowing outdated diff discussions to automatically resolve' do
project = attributes_for(:project, resolve_outdated_diff_discussions: true)
- post api("/projects/user/#{user.id}", admin), project
+ post api("/projects/user/#{user.id}", admin), params: project
expect(json_response['resolve_outdated_diff_discussions']).to be_truthy
end
it 'sets a project as allowing merge even if build fails' do
project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: false)
- post api("/projects/user/#{user.id}", admin), project
+ post api("/projects/user/#{user.id}", admin), params: project
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
end
it 'sets a project as allowing merge only if pipeline succeeds' do
project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: true)
- post api("/projects/user/#{user.id}", admin), project
+ post api("/projects/user/#{user.id}", admin), params: project
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
end
it 'sets a project as allowing merge even if discussions are unresolved' do
project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: false)
- post api("/projects/user/#{user.id}", admin), project
+ post api("/projects/user/#{user.id}", admin), params: project
expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey
end
@@ -863,7 +863,7 @@ describe API::Projects do
it 'sets a project as allowing merge only if all discussions are resolved' do
project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: true)
- post api("/projects/user/#{user.id}", admin), project
+ post api("/projects/user/#{user.id}", admin), params: project
expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy
end
@@ -875,7 +875,7 @@ describe API::Projects do
end
it "uploads the file and returns its info" do
- post api("/projects/#{project.id}/uploads", user), file: fixture_file_upload("spec/fixtures/dk.png", "image/png")
+ post api("/projects/#{project.id}/uploads", user), params: { file: fixture_file_upload("spec/fixtures/dk.png", "image/png") }
expect(response).to have_gitlab_http_status(201)
expect(json_response['alt']).to eq("dk")
@@ -1020,7 +1020,7 @@ describe API::Projects do
end
it 'includes license fields when requested' do
- get api("/projects/#{project.id}", user), license: true
+ get api("/projects/#{project.id}", user), params: { license: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response['license']).to eq({
@@ -1040,7 +1040,7 @@ describe API::Projects do
end
it "includes statistics if requested" do
- get api("/projects/#{project.id}", user), statistics: true
+ get api("/projects/#{project.id}", user), params: { statistics: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response).to include 'statistics'
@@ -1397,7 +1397,7 @@ describe API::Projects do
expires_at = 10.days.from_now.to_date
expect do
- post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER, expires_at: expires_at
+ post api("/projects/#{project.id}/share", user), params: { group_id: group.id, group_access: Gitlab::Access::DEVELOPER, expires_at: expires_at }
end.to change { ProjectGroupLink.count }.by(1)
expect(response).to have_gitlab_http_status(201)
@@ -1407,37 +1407,37 @@ describe API::Projects do
end
it "returns a 400 error when group id is not given" do
- post api("/projects/#{project.id}/share", user), group_access: Gitlab::Access::DEVELOPER
+ post api("/projects/#{project.id}/share", user), params: { group_access: Gitlab::Access::DEVELOPER }
expect(response).to have_gitlab_http_status(400)
end
it "returns a 400 error when access level is not given" do
- post api("/projects/#{project.id}/share", user), group_id: group.id
+ post api("/projects/#{project.id}/share", user), params: { group_id: group.id }
expect(response).to have_gitlab_http_status(400)
end
it "returns a 400 error when sharing is disabled" do
project.namespace.update(share_with_group_lock: true)
- post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER
+ post api("/projects/#{project.id}/share", user), params: { group_id: group.id, group_access: Gitlab::Access::DEVELOPER }
expect(response).to have_gitlab_http_status(400)
end
it 'returns a 404 error when user cannot read group' do
private_group = create(:group, :private)
- post api("/projects/#{project.id}/share", user), group_id: private_group.id, group_access: Gitlab::Access::DEVELOPER
+ post api("/projects/#{project.id}/share", user), params: { group_id: private_group.id, group_access: Gitlab::Access::DEVELOPER }
expect(response).to have_gitlab_http_status(404)
end
it 'returns a 404 error when group does not exist' do
- post api("/projects/#{project.id}/share", user), group_id: 1234, group_access: Gitlab::Access::DEVELOPER
+ post api("/projects/#{project.id}/share", user), params: { group_id: 1234, group_access: Gitlab::Access::DEVELOPER }
expect(response).to have_gitlab_http_status(404)
end
it "returns a 400 error when wrong params passed" do
- post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234
+ post api("/projects/#{project.id}/share", user), params: { group_id: group.id, group_access: 1234 }
expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq 'group_access does not have a valid value'
@@ -1498,7 +1498,7 @@ describe API::Projects do
it 'returns 400 when nothing sent' do
project_param = {}
- put api("/projects/#{project.id}", user), project_param
+ put api("/projects/#{project.id}", user), params: project_param
expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to match('at least one parameter must be provided')
@@ -1508,7 +1508,7 @@ describe API::Projects do
it 'returns authentication error' do
project_param = { name: 'bar' }
- put api("/projects/#{project.id}"), project_param
+ put api("/projects/#{project.id}"), params: project_param
expect(response).to have_gitlab_http_status(401)
end
@@ -1518,7 +1518,7 @@ describe API::Projects do
it 'updates name' do
project_param = { name: 'bar' }
- put api("/projects/#{project.id}", user), project_param
+ put api("/projects/#{project.id}", user), params: project_param
expect(response).to have_gitlab_http_status(200)
@@ -1530,7 +1530,7 @@ describe API::Projects do
it 'updates visibility_level' do
project_param = { visibility: 'public' }
- put api("/projects/#{project3.id}", user), project_param
+ put api("/projects/#{project3.id}", user), params: project_param
expect(response).to have_gitlab_http_status(200)
@@ -1543,7 +1543,7 @@ describe API::Projects do
project3.update({ visibility_level: Gitlab::VisibilityLevel::PUBLIC })
project_param = { visibility: 'private' }
- put api("/projects/#{project3.id}", user), project_param
+ put api("/projects/#{project3.id}", user), params: project_param
expect(response).to have_gitlab_http_status(200)
@@ -1557,7 +1557,7 @@ describe API::Projects do
it 'does not update name to existing name' do
project_param = { name: project3.name }
- put api("/projects/#{project.id}", user), project_param
+ put api("/projects/#{project.id}", user), params: project_param
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']['name']).to eq(['has already been taken'])
@@ -1566,7 +1566,7 @@ describe API::Projects do
it 'updates request_access_enabled' do
project_param = { request_access_enabled: false }
- put api("/projects/#{project.id}", user), project_param
+ put api("/projects/#{project.id}", user), params: project_param
expect(response).to have_gitlab_http_status(200)
expect(json_response['request_access_enabled']).to eq(false)
@@ -1575,7 +1575,7 @@ describe API::Projects do
it 'updates path & name to existing path & name in different namespace' do
project_param = { path: project4.path, name: project4.name }
- put api("/projects/#{project3.id}", user), project_param
+ put api("/projects/#{project3.id}", user), params: project_param
expect(response).to have_gitlab_http_status(200)
@@ -1587,7 +1587,7 @@ describe API::Projects do
it 'updates jobs_enabled' do
project_param = { jobs_enabled: true }
- put api("/projects/#{project3.id}", user), project_param
+ put api("/projects/#{project3.id}", user), params: project_param
expect(response).to have_gitlab_http_status(200)
@@ -1599,7 +1599,7 @@ describe API::Projects do
it 'updates merge_method' do
project_param = { merge_method: 'ff' }
- put api("/projects/#{project3.id}", user), project_param
+ put api("/projects/#{project3.id}", user), params: project_param
expect(response).to have_gitlab_http_status(200)
@@ -1611,7 +1611,7 @@ describe API::Projects do
it 'rejects to update merge_method when merge_method is invalid' do
project_param = { merge_method: 'invalid' }
- put api("/projects/#{project3.id}", user), project_param
+ put api("/projects/#{project3.id}", user), params: project_param
expect(response).to have_gitlab_http_status(400)
end
@@ -1622,7 +1622,7 @@ describe API::Projects do
'image/gif')
}
- put api("/projects/#{project3.id}", user), project_param
+ put api("/projects/#{project3.id}", user), params: project_param
expect(response).to have_gitlab_http_status(200)
expect(json_response['avatar_url']).to eq('http://localhost/uploads/'\
@@ -1634,7 +1634,7 @@ describe API::Projects do
context 'when authenticated as project maintainer' do
it 'updates path' do
project_param = { path: 'bar' }
- put api("/projects/#{project3.id}", user4), project_param
+ put api("/projects/#{project3.id}", user4), params: project_param
expect(response).to have_gitlab_http_status(200)
project_param.each_pair do |k, v|
expect(json_response[k.to_s]).to eq(v)
@@ -1649,7 +1649,7 @@ describe API::Projects do
merge_method: 'ff',
description: 'new description' }
- put api("/projects/#{project3.id}", user4), project_param
+ put api("/projects/#{project3.id}", user4), params: project_param
expect(response).to have_gitlab_http_status(200)
project_param.each_pair do |k, v|
expect(json_response[k.to_s]).to eq(v)
@@ -1658,20 +1658,20 @@ describe API::Projects do
it 'does not update path to existing path' do
project_param = { path: project.path }
- put api("/projects/#{project3.id}", user4), project_param
+ put api("/projects/#{project3.id}", user4), params: project_param
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']['path']).to eq(['has already been taken'])
end
it 'does not update name' do
project_param = { name: 'bar' }
- put api("/projects/#{project3.id}", user4), project_param
+ put api("/projects/#{project3.id}", user4), params: project_param
expect(response).to have_gitlab_http_status(403)
end
it 'does not update visibility_level' do
project_param = { visibility: 'public' }
- put api("/projects/#{project3.id}", user4), project_param
+ put api("/projects/#{project3.id}", user4), params: project_param
expect(response).to have_gitlab_http_status(403)
end
end
@@ -1685,7 +1685,7 @@ describe API::Projects do
merge_requests_enabled: true,
description: 'new description',
request_access_enabled: true }
- put api("/projects/#{project.id}", user3), project_param
+ put api("/projects/#{project.id}", user3), params: project_param
expect(response).to have_gitlab_http_status(403)
end
end
@@ -1906,7 +1906,7 @@ describe API::Projects do
let(:group) { create(:group) }
let(:group2) do
group = create(:group, name: 'group2_name')
- group.add_owner(user2)
+ group.add_maintainer(user2)
group
end
@@ -1971,41 +1971,41 @@ describe API::Projects do
end
it 'forks with explicit own user namespace id' do
- post api("/projects/#{project.id}/fork", user2), namespace: user2.namespace.id
+ post api("/projects/#{project.id}/fork", user2), params: { namespace: user2.namespace.id }
expect(response).to have_gitlab_http_status(201)
expect(json_response['owner']['id']).to eq(user2.id)
end
it 'forks with explicit own user name as namespace' do
- post api("/projects/#{project.id}/fork", user2), namespace: user2.username
+ post api("/projects/#{project.id}/fork", user2), params: { namespace: user2.username }
expect(response).to have_gitlab_http_status(201)
expect(json_response['owner']['id']).to eq(user2.id)
end
it 'forks to another user when admin' do
- post api("/projects/#{project.id}/fork", admin), namespace: user2.username
+ post api("/projects/#{project.id}/fork", admin), params: { namespace: user2.username }
expect(response).to have_gitlab_http_status(201)
expect(json_response['owner']['id']).to eq(user2.id)
end
it 'fails if trying to fork to another user when not admin' do
- post api("/projects/#{project.id}/fork", user2), namespace: admin.namespace.id
+ post api("/projects/#{project.id}/fork", user2), params: { namespace: admin.namespace.id }
expect(response).to have_gitlab_http_status(404)
end
it 'fails if trying to fork to non-existent namespace' do
- post api("/projects/#{project.id}/fork", user2), namespace: 42424242
+ post api("/projects/#{project.id}/fork", user2), params: { namespace: 42424242 }
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Target Namespace Not Found')
end
it 'forks to owned group' do
- post api("/projects/#{project.id}/fork", user2), namespace: group2.name
+ post api("/projects/#{project.id}/fork", user2), params: { namespace: group2.name }
expect(response).to have_gitlab_http_status(201)
expect(json_response['namespace']['name']).to eq(group2.name)
@@ -2013,7 +2013,7 @@ describe API::Projects do
it 'forks to owned subgroup' do
full_path = "#{group2.path}/#{group3.path}"
- post api("/projects/#{project.id}/fork", user2), namespace: full_path
+ post api("/projects/#{project.id}/fork", user2), params: { namespace: full_path }
expect(response).to have_gitlab_http_status(201)
expect(json_response['namespace']['name']).to eq(group3.name)
@@ -2021,13 +2021,13 @@ describe API::Projects do
end
it 'fails to fork to not owned group' do
- post api("/projects/#{project.id}/fork", user2), namespace: group.name
+ post api("/projects/#{project.id}/fork", user2), params: { namespace: group.name }
expect(response).to have_gitlab_http_status(404)
end
it 'forks to not owned group when admin' do
- post api("/projects/#{project.id}/fork", admin), namespace: group.name
+ post api("/projects/#{project.id}/fork", admin), params: { namespace: group.name }
expect(response).to have_gitlab_http_status(201)
expect(json_response['namespace']['name']).to eq(group.name)
@@ -2100,19 +2100,19 @@ describe API::Projects do
it 'transfers the project to the new namespace' do
group.add_owner(user)
- put api("/projects/#{project.id}/transfer", user), namespace: group.id
+ put api("/projects/#{project.id}/transfer", user), params: { namespace: group.id }
expect(response).to have_gitlab_http_status(200)
end
it 'fails when transferring to a non owned namespace' do
- put api("/projects/#{project.id}/transfer", user), namespace: group.id
+ put api("/projects/#{project.id}/transfer", user), params: { namespace: group.id }
expect(response).to have_gitlab_http_status(404)
end
it 'fails when transferring to an unknown namespace' do
- put api("/projects/#{project.id}/transfer", user), namespace: 'unknown'
+ put api("/projects/#{project.id}/transfer", user), params: { namespace: 'unknown' }
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/requests/api/protected_branches_spec.rb b/spec/requests/api/protected_branches_spec.rb
index 69a601d7b40..f90558d77a9 100644
--- a/spec/requests/api/protected_branches_spec.rb
+++ b/spec/requests/api/protected_branches_spec.rb
@@ -14,7 +14,7 @@ describe API::ProtectedBranches do
shared_examples_for 'protected branches' do
it 'returns the protected branches' do
- get api(route, user), per_page: 100
+ get api(route, user), params: { per_page: 100 }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -114,7 +114,7 @@ describe API::ProtectedBranches do
end
it 'protects a single branch' do
- post post_endpoint, name: branch_name
+ post post_endpoint, params: { name: branch_name }
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(branch_name)
@@ -123,7 +123,7 @@ describe API::ProtectedBranches do
end
it 'protects a single branch and developers can push' do
- post post_endpoint, name: branch_name, push_access_level: 30
+ post post_endpoint, params: { name: branch_name, push_access_level: 30 }
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(branch_name)
@@ -132,7 +132,7 @@ describe API::ProtectedBranches do
end
it 'protects a single branch and developers can merge' do
- post post_endpoint, name: branch_name, merge_access_level: 30
+ post post_endpoint, params: { name: branch_name, merge_access_level: 30 }
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(branch_name)
@@ -141,7 +141,7 @@ describe API::ProtectedBranches do
end
it 'protects a single branch and developers can push and merge' do
- post post_endpoint, name: branch_name, push_access_level: 30, merge_access_level: 30
+ post post_endpoint, params: { name: branch_name, push_access_level: 30, merge_access_level: 30 }
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(branch_name)
@@ -150,7 +150,7 @@ describe API::ProtectedBranches do
end
it 'protects a single branch and no one can push' do
- post post_endpoint, name: branch_name, push_access_level: 0
+ post post_endpoint, params: { name: branch_name, push_access_level: 0 }
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(branch_name)
@@ -159,7 +159,7 @@ describe API::ProtectedBranches do
end
it 'protects a single branch and no one can merge' do
- post post_endpoint, name: branch_name, merge_access_level: 0
+ post post_endpoint, params: { name: branch_name, merge_access_level: 0 }
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(branch_name)
@@ -168,7 +168,7 @@ describe API::ProtectedBranches do
end
it 'protects a single branch and no one can push or merge' do
- post post_endpoint, name: branch_name, push_access_level: 0, merge_access_level: 0
+ post post_endpoint, params: { name: branch_name, push_access_level: 0, merge_access_level: 0 }
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(branch_name)
@@ -177,7 +177,7 @@ describe API::ProtectedBranches do
end
it 'returns a 409 error if the same branch is protected twice' do
- post post_endpoint, name: protected_name
+ post post_endpoint, params: { name: protected_name }
expect(response).to have_gitlab_http_status(409)
end
@@ -186,7 +186,7 @@ describe API::ProtectedBranches do
let(:branch_name) { 'feature/*' }
it "protects multiple branches with a wildcard in the name" do
- post post_endpoint, name: branch_name
+ post post_endpoint, params: { name: branch_name }
expect_protection_to_be_successful
expect(json_response['push_access_levels'][0]['access_level']).to eq(Gitlab::Access::MAINTAINER)
@@ -201,7 +201,7 @@ describe API::ProtectedBranches do
end
it "prevents deletion of the protected branch rule" do
- post post_endpoint, name: branch_name
+ post post_endpoint, params: { name: branch_name }
expect(response).to have_gitlab_http_status(403)
end
@@ -214,7 +214,7 @@ describe API::ProtectedBranches do
end
it "returns a 403 error if guest" do
- post post_endpoint, name: branch_name
+ post post_endpoint, params: { name: branch_name }
expect(response).to have_gitlab_http_status(403)
end
diff --git a/spec/requests/api/protected_tags_spec.rb b/spec/requests/api/protected_tags_spec.rb
index f4f3ef31bc3..41363dcc1c3 100644
--- a/spec/requests/api/protected_tags_spec.rb
+++ b/spec/requests/api/protected_tags_spec.rb
@@ -15,7 +15,7 @@ describe API::ProtectedTags do
shared_examples_for 'protected tags' do
it 'returns the protected tags' do
- get api(route, user), per_page: 100
+ get api(route, user), params: { per_page: 100 }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -102,7 +102,7 @@ describe API::ProtectedTags do
end
it 'protects a single tag with maintainers can create tags' do
- post api("/projects/#{project.id}/protected_tags", user), name: tag_name
+ post api("/projects/#{project.id}/protected_tags", user), params: { name: tag_name }
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(tag_name)
@@ -111,7 +111,7 @@ describe API::ProtectedTags do
it 'protects a single tag with developers can create tags' do
post api("/projects/#{project.id}/protected_tags", user),
- name: tag_name, create_access_level: 30
+ params: { name: tag_name, create_access_level: 30 }
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(tag_name)
@@ -120,7 +120,7 @@ describe API::ProtectedTags do
it 'protects a single tag with no one can create tags' do
post api("/projects/#{project.id}/protected_tags", user),
- name: tag_name, create_access_level: 0
+ params: { name: tag_name, create_access_level: 0 }
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(tag_name)
@@ -128,15 +128,15 @@ describe API::ProtectedTags do
end
it 'returns a 422 error if the same tag is protected twice' do
- post api("/projects/#{project.id}/protected_tags", user), name: protected_name
+ post api("/projects/#{project.id}/protected_tags", user), params: { name: protected_name }
expect(response).to have_gitlab_http_status(422)
expect(json_response['message'][0]).to eq('Name has already been taken')
end
it 'returns 201 if the same tag is proteted on different projects' do
- post api("/projects/#{project.id}/protected_tags", user), name: protected_name
- post api("/projects/#{project2.id}/protected_tags", user), name: protected_name
+ post api("/projects/#{project.id}/protected_tags", user), params: { name: protected_name }
+ post api("/projects/#{project2.id}/protected_tags", user), params: { name: protected_name }
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(protected_name)
@@ -146,7 +146,7 @@ describe API::ProtectedTags do
let(:tag_name) { 'feature/*' }
it 'protects multiple tags with a wildcard in the name' do
- post api("/projects/#{project.id}/protected_tags", user), name: tag_name
+ post api("/projects/#{project.id}/protected_tags", user), params: { name: tag_name }
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(tag_name)
@@ -161,7 +161,7 @@ describe API::ProtectedTags do
end
it 'returns a 403 error if guest' do
- post api("/projects/#{project.id}/protected_tags/", user), name: tag_name
+ post api("/projects/#{project.id}/protected_tags/", user), params: { name: tag_name }
expect(response).to have_gitlab_http_status(403)
end
diff --git a/spec/requests/api/redacted_events_spec.rb b/spec/requests/api/redacted_events_spec.rb
deleted file mode 100644
index 086dd3df9ba..00000000000
--- a/spec/requests/api/redacted_events_spec.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-require 'spec_helper'
-
-describe 'Redacted events in API::Events' do
- shared_examples 'private events are redacted' do
- it 'redacts events the user does not have access to' do
- expect_any_instance_of(Event).to receive(:visible_to_user?).and_call_original
-
- get api(path), user
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to contain_exactly(
- 'project_id' => nil,
- 'action_name' => nil,
- 'target_id' => nil,
- 'target_iid' => nil,
- 'target_type' => nil,
- 'author_id' => nil,
- 'target_title' => 'Confidential event',
- 'created_at' => nil,
- 'author_username' => nil
- )
- end
- end
-
- describe '/users/:id/events' do
- let(:project) { create(:project, :public) }
- let(:path) { "/users/#{project.owner.id}/events" }
- let(:issue) { create(:issue, :confidential, project: project) }
-
- before do
- EventCreateService.new.open_issue(issue, issue.author)
- end
-
- context 'unauthenticated user views another user with private events' do
- let(:user) { nil }
-
- include_examples 'private events are redacted'
- end
-
- context 'authenticated user without access views another user with private events' do
- let(:user) { create(:user) }
-
- include_examples 'private events are redacted'
- end
- end
-
- describe '/projects/:id/events' do
- let(:project) { create(:project, :public) }
- let(:path) { "/projects/#{project.id}/events" }
- let(:issue) { create(:issue, :confidential, project: project) }
-
- before do
- EventCreateService.new.open_issue(issue, issue.author)
- end
-
- context 'unauthenticated user views public project' do
- let(:user) { nil }
-
- include_examples 'private events are redacted'
- end
-
- context 'authenticated user without access views public project' do
- let(:user) { create(:user) }
-
- include_examples 'private events are redacted'
- end
- end
-end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index de141377793..181fe6246ae 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -297,7 +297,7 @@ describe API::Repositories do
expect(::Gitlab::Git::Compare).to receive(:new).with(anything, anything, anything, {
straight: false
}).and_call_original
- get api(route, current_user), from: 'master', to: 'feature'
+ get api(route, current_user), params: { from: 'master', to: 'feature' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['commits']).to be_present
@@ -308,7 +308,7 @@ describe API::Repositories do
expect(::Gitlab::Git::Compare).to receive(:new).with(anything, anything, anything, {
straight: false
}).and_call_original
- get api(route, current_user), from: 'master', to: 'feature', straight: false
+ get api(route, current_user), params: { from: 'master', to: 'feature', straight: false }
expect(response).to have_gitlab_http_status(200)
expect(json_response['commits']).to be_present
@@ -319,7 +319,7 @@ describe API::Repositories do
expect(::Gitlab::Git::Compare).to receive(:new).with(anything, anything, anything, {
straight: true
}).and_call_original
- get api(route, current_user), from: 'master', to: 'feature', straight: true
+ get api(route, current_user), params: { from: 'master', to: 'feature', straight: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response['commits']).to be_present
@@ -327,7 +327,7 @@ describe API::Repositories do
end
it "compares tags" do
- get api(route, current_user), from: 'v1.0.0', to: 'v1.1.0'
+ get api(route, current_user), params: { from: 'v1.0.0', to: 'v1.1.0' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['commits']).to be_present
@@ -335,7 +335,7 @@ describe API::Repositories do
end
it "compares commits" do
- get api(route, current_user), from: sample_commit.id, to: sample_commit.parent_id
+ get api(route, current_user), params: { from: sample_commit.id, to: sample_commit.parent_id }
expect(response).to have_gitlab_http_status(200)
expect(json_response['commits']).to be_empty
@@ -344,7 +344,7 @@ describe API::Repositories do
end
it "compares commits in reverse order" do
- get api(route, current_user), from: sample_commit.parent_id, to: sample_commit.id
+ get api(route, current_user), params: { from: sample_commit.parent_id, to: sample_commit.id }
expect(response).to have_gitlab_http_status(200)
expect(json_response['commits']).to be_present
@@ -352,7 +352,7 @@ describe API::Repositories do
end
it "compares same refs" do
- get api(route, current_user), from: 'master', to: 'master'
+ get api(route, current_user), params: { from: 'master', to: 'master' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['commits']).to be_empty
@@ -410,7 +410,7 @@ describe API::Repositories do
context 'using sorting' do
context 'by commits desc' do
it 'returns the repository contribuors sorted by commits desc' do
- get api(route, current_user), { order_by: 'commits', sort: 'desc' }
+ get api(route, current_user), params: { order_by: 'commits', sort: 'desc' }
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('contributors')
@@ -420,7 +420,7 @@ describe API::Repositories do
context 'by name desc' do
it 'returns the repository contribuors sorted by name asc case insensitive' do
- get api(route, current_user), { order_by: 'name', sort: 'asc' }
+ get api(route, current_user), params: { order_by: 'name', sort: 'asc' }
expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('contributors')
@@ -478,7 +478,7 @@ describe API::Repositories do
end
subject(:request) do
- get(api("/projects/#{project.id}/repository/merge_base", current_user), refs: refs)
+ get(api("/projects/#{project.id}/repository/merge_base", current_user), params: { refs: refs })
end
shared_examples 'merge base' do
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index b36087b86a7..c63621fe7d1 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -25,7 +25,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when invalid token is provided' do
it 'returns 403 error' do
- post api('/runners'), token: 'invalid'
+ post api('/runners'), params: { token: 'invalid' }
expect(response).to have_gitlab_http_status 403
end
@@ -33,7 +33,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when valid token is provided' do
it 'creates runner with default values' do
- post api('/runners'), token: registration_token
+ post api('/runners'), params: { token: registration_token }
runner = Ci::Runner.first
@@ -50,7 +50,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
let(:project) { create(:project) }
it 'creates project runner' do
- post api('/runners'), token: project.runners_token
+ post api('/runners'), params: { token: project.runners_token }
expect(response).to have_gitlab_http_status 201
expect(project.runners.size).to eq(1)
@@ -65,7 +65,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
let(:group) { create(:group) }
it 'creates a group runner' do
- post api('/runners'), token: group.runners_token
+ post api('/runners'), params: { token: group.runners_token }
expect(response).to have_http_status 201
expect(group.runners.size).to eq(1)
@@ -79,8 +79,10 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when runner description is provided' do
it 'creates runner' do
- post api('/runners'), token: registration_token,
- description: 'server.hostname'
+ post api('/runners'), params: {
+ token: registration_token,
+ description: 'server.hostname'
+ }
expect(response).to have_gitlab_http_status 201
expect(Ci::Runner.first.description).to eq('server.hostname')
@@ -89,8 +91,10 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when runner tags are provided' do
it 'creates runner' do
- post api('/runners'), token: registration_token,
- tag_list: 'tag1, tag2'
+ post api('/runners'), params: {
+ token: registration_token,
+ tag_list: 'tag1, tag2'
+ }
expect(response).to have_gitlab_http_status 201
expect(Ci::Runner.first.tag_list.sort).to eq(%w(tag1 tag2))
@@ -100,9 +104,11 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when option for running untagged jobs is provided' do
context 'when tags are provided' do
it 'creates runner' do
- post api('/runners'), token: registration_token,
- run_untagged: false,
- tag_list: ['tag']
+ post api('/runners'), params: {
+ token: registration_token,
+ run_untagged: false,
+ tag_list: ['tag']
+ }
expect(response).to have_gitlab_http_status 201
expect(Ci::Runner.first.run_untagged).to be false
@@ -112,8 +118,10 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when tags are not provided' do
it 'returns 400 error' do
- post api('/runners'), token: registration_token,
- run_untagged: false
+ post api('/runners'), params: {
+ token: registration_token,
+ run_untagged: false
+ }
expect(response).to have_gitlab_http_status 400
expect(json_response['message']).to include(
@@ -124,8 +132,10 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when option for locking Runner is provided' do
it 'creates runner' do
- post api('/runners'), token: registration_token,
- locked: true
+ post api('/runners'), params: {
+ token: registration_token,
+ locked: true
+ }
expect(response).to have_gitlab_http_status 201
expect(Ci::Runner.first.locked).to be true
@@ -135,8 +145,10 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when option for activating a Runner is provided' do
context 'when active is set to true' do
it 'creates runner' do
- post api('/runners'), token: registration_token,
- active: true
+ post api('/runners'), params: {
+ token: registration_token,
+ active: true
+ }
expect(response).to have_gitlab_http_status 201
expect(Ci::Runner.first.active).to be true
@@ -145,8 +157,10 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when active is set to false' do
it 'creates runner' do
- post api('/runners'), token: registration_token,
- active: false
+ post api('/runners'), params: {
+ token: registration_token,
+ active: false
+ }
expect(response).to have_gitlab_http_status 201
expect(Ci::Runner.first.active).to be false
@@ -156,8 +170,10 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when maximum job timeout is specified' do
it 'creates runner' do
- post api('/runners'), token: registration_token,
- maximum_timeout: 9000
+ post api('/runners'), params: {
+ token: registration_token,
+ maximum_timeout: 9000
+ }
expect(response).to have_gitlab_http_status 201
expect(Ci::Runner.first.maximum_timeout).to eq(9000)
@@ -165,8 +181,10 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when maximum job timeout is empty' do
it 'creates runner' do
- post api('/runners'), token: registration_token,
- maximum_timeout: ''
+ post api('/runners'), params: {
+ token: registration_token,
+ maximum_timeout: ''
+ }
expect(response).to have_gitlab_http_status 201
expect(Ci::Runner.first.maximum_timeout).to be_nil
@@ -179,8 +197,10 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
let(:value) { "#{param}_value" }
it "updates provided Runner's parameter" do
- post api('/runners'), token: registration_token,
- info: { param => value }
+ post api('/runners'), params: {
+ token: registration_token,
+ info: { param => value }
+ }
expect(response).to have_gitlab_http_status 201
expect(Ci::Runner.first.read_attribute(param.to_sym)).to eq(value)
@@ -190,8 +210,8 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
it "sets the runner's ip_address" do
post api('/runners'),
- { token: registration_token },
- { 'REMOTE_ADDR' => '123.111.123.111' }
+ params: { token: registration_token },
+ headers: { 'REMOTE_ADDR' => '123.111.123.111' }
expect(response).to have_gitlab_http_status 201
expect(Ci::Runner.first.ip_address).to eq('123.111.123.111')
@@ -209,7 +229,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when invalid token is provided' do
it 'returns 403 error' do
- delete api('/runners'), token: 'invalid'
+ delete api('/runners'), params: { token: 'invalid' }
expect(response).to have_gitlab_http_status 403
end
@@ -219,7 +239,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
let(:runner) { create(:ci_runner) }
it 'deletes Runner' do
- delete api('/runners'), token: runner.token
+ delete api('/runners'), params: { token: runner.token }
expect(response).to have_gitlab_http_status 204
expect(Ci::Runner.count).to eq(0)
@@ -245,7 +265,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when invalid token is provided' do
it 'returns 403 error' do
- post api('/runners/verify'), token: 'invalid-token'
+ post api('/runners/verify'), params: { token: 'invalid-token' }
expect(response).to have_gitlab_http_status 403
end
@@ -253,7 +273,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when valid token is provided' do
it 'verifies Runner credentials' do
- post api('/runners/verify'), token: runner.token
+ post api('/runners/verify'), params: { token: runner.token }
expect(response).to have_gitlab_http_status 200
end
@@ -342,7 +362,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when invalid token is provided' do
it 'returns 403 error' do
- post api('/jobs/request'), token: 'invalid'
+ post api('/jobs/request'), params: { token: 'invalid' }
expect(response).to have_gitlab_http_status 403
end
@@ -499,8 +519,8 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
it "sets the runner's ip_address" do
post api('/jobs/request'),
- { token: runner.token },
- { 'User-Agent' => user_agent, 'REMOTE_ADDR' => '123.222.123.222' }
+ params: { token: runner.token },
+ headers: { 'User-Agent' => user_agent, 'REMOTE_ADDR' => '123.222.123.222' }
expect(response).to have_gitlab_http_status 201
expect(runner.reload.ip_address).to eq('123.222.123.222')
@@ -752,7 +772,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
def request_job(token = runner.token, **params)
new_params = params.merge(token: token, last_update: last_update)
- post api('/jobs/request'), new_params, { 'User-Agent' => user_agent }
+ post api('/jobs/request'), params: new_params, headers: { 'User-Agent' => user_agent }
end
end
end
@@ -893,7 +913,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
def update_job(token = job.token, **params)
new_params = params.merge(token: token)
- put api("/jobs/#{job.id}"), new_params
+ put api("/jobs/#{job.id}"), params: new_params
end
def update_job_after_time(update_interval = 20.minutes, state = 'running')
@@ -1113,7 +1133,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
Timecop.travel(job.updated_at + update_interval) do
- patch api("/jobs/#{job.id}/trace"), content, request_headers
+ patch api("/jobs/#{job.id}/trace"), params: content, headers: request_headers
job.reload
end
end
@@ -1244,7 +1264,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
def authorize_artifacts(params = {}, request_headers = headers)
- post api("/jobs/#{job.id}/artifacts/authorize"), params, request_headers
+ post api("/jobs/#{job.id}/artifacts/authorize"), params: params, headers: request_headers
end
def authorize_artifacts_with_token_in_params(params = {}, request_headers = headers)
@@ -1347,7 +1367,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'when artifacts post request does not contain file' do
it 'fails to post artifacts without file' do
- post api("/jobs/#{job.id}/artifacts"), {}, headers_with_token
+ post api("/jobs/#{job.id}/artifacts"), params: {}, headers: headers_with_token
expect(response).to have_gitlab_http_status(400)
end
@@ -1355,7 +1375,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
context 'GitLab Workhorse is not configured' do
it 'fails to post artifacts without GitLab-Workhorse' do
- post api("/jobs/#{job.id}/artifacts"), { token: job.token }, {}
+ post api("/jobs/#{job.id}/artifacts"), params: { token: job.token }, headers: {}
expect(response).to have_gitlab_http_status(403)
end
@@ -1372,7 +1392,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
before do
stub_application_setting(default_artifacts_expire_in: default_artifacts_expire_in)
- post(api("/jobs/#{job.id}/artifacts"), post_data, headers_with_token)
+ post(api("/jobs/#{job.id}/artifacts"), params: post_data, headers: headers_with_token)
end
context 'when an expire_in is given' do
@@ -1427,7 +1447,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
let(:stored_metadata_sha256) { job.reload.job_artifacts_metadata.file_sha256 }
before do
- post(api("/jobs/#{job.id}/artifacts"), post_data, headers_with_token)
+ post(api("/jobs/#{job.id}/artifacts"), params: post_data, headers: headers_with_token)
end
context 'when posts data accelerated by workhorse is correct' do
@@ -1545,7 +1565,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
'file.name' => file.original_filename
})
- post api("/jobs/#{job.id}/artifacts"), params, headers
+ post api("/jobs/#{job.id}/artifacts"), params: params, headers: headers
end
end
@@ -1631,7 +1651,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
params = params.merge(token: token)
job.reload
- get api("/jobs/#{job.id}/artifacts"), params, request_headers
+ get api("/jobs/#{job.id}/artifacts"), params: params, headers: request_headers
end
end
end
diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb
index 49a79d2ccf9..7f11c8c9fe8 100644
--- a/spec/requests/api/runners_spec.rb
+++ b/spec/requests/api/runners_spec.rb
@@ -400,14 +400,14 @@ describe API::Runners do
end
def update_runner(id, user, args)
- put api("/runners/#{id}", user), args
+ put api("/runners/#{id}", user), params: args
end
end
context 'authorized user' do
context 'when runner is shared' do
it 'does not update runner' do
- put api("/runners/#{shared_runner.id}", user), description: 'test'
+ put api("/runners/#{shared_runner.id}", user), params: { description: 'test' }
expect(response).to have_gitlab_http_status(403)
end
@@ -415,14 +415,14 @@ describe API::Runners do
context 'when runner is not shared' do
it 'does not update project runner without access to it' do
- put api("/runners/#{project_runner.id}", user2), description: 'test'
+ put api("/runners/#{project_runner.id}", user2), params: { description: 'test' }
expect(response).to have_http_status(403)
end
it 'updates project runner with access to it' do
description = project_runner.description
- put api("/runners/#{project_runner.id}", admin), description: 'test'
+ put api("/runners/#{project_runner.id}", admin), params: { description: 'test' }
project_runner.reload
expect(response).to have_gitlab_http_status(200)
@@ -741,14 +741,14 @@ describe API::Runners do
it 'enables specific runner' do
expect do
- post api("/projects/#{project.id}/runners", user), runner_id: project_runner2.id
+ post api("/projects/#{project.id}/runners", user), params: { runner_id: project_runner2.id }
end.to change { project.runners.count }.by(+1)
expect(response).to have_gitlab_http_status(201)
end
it 'avoids changes when enabling already enabled runner' do
expect do
- post api("/projects/#{project.id}/runners", user), runner_id: project_runner.id
+ post api("/projects/#{project.id}/runners", user), params: { runner_id: project_runner.id }
end.to change { project.runners.count }.by(0)
expect(response).to have_gitlab_http_status(400)
end
@@ -757,20 +757,20 @@ describe API::Runners do
project_runner2.update(locked: true)
expect do
- post api("/projects/#{project.id}/runners", user), runner_id: project_runner2.id
+ post api("/projects/#{project.id}/runners", user), params: { runner_id: project_runner2.id }
end.to change { project.runners.count }.by(0)
expect(response).to have_gitlab_http_status(403)
end
it 'does not enable shared runner' do
- post api("/projects/#{project.id}/runners", user), runner_id: shared_runner.id
+ post api("/projects/#{project.id}/runners", user), params: { runner_id: shared_runner.id }
expect(response).to have_gitlab_http_status(403)
end
it 'does not enable group runner' do
- post api("/projects/#{project.id}/runners", user), runner_id: group_runner.id
+ post api("/projects/#{project.id}/runners", user), params: { runner_id: group_runner.id }
expect(response).to have_http_status(403)
end
@@ -781,7 +781,7 @@ describe API::Runners do
it 'enables any specific runner' do
expect do
- post api("/projects/#{project.id}/runners", admin), runner_id: new_project_runner.id
+ post api("/projects/#{project.id}/runners", admin), params: { runner_id: new_project_runner.id }
end.to change { project.runners.count }.by(+1)
expect(response).to have_gitlab_http_status(201)
end
@@ -789,7 +789,7 @@ describe API::Runners do
it 'enables a instance type runner' do
expect do
- post api("/projects/#{project.id}/runners", admin), runner_id: shared_runner.id
+ post api("/projects/#{project.id}/runners", admin), params: { runner_id: shared_runner.id }
end.to change { project.runners.count }.by(1)
expect(shared_runner.reload).not_to be_instance_type
@@ -808,7 +808,7 @@ describe API::Runners do
let!(:new_project_runner) { create(:ci_runner, :project) }
it 'does not enable runner without access to' do
- post api("/projects/#{project.id}/runners", user), runner_id: new_project_runner.id
+ post api("/projects/#{project.id}/runners", user), params: { runner_id: new_project_runner.id }
expect(response).to have_gitlab_http_status(403)
end
diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb
index f8e468be170..831f47debeb 100644
--- a/spec/requests/api/search_spec.rb
+++ b/spec/requests/api/search_spec.rb
@@ -16,7 +16,7 @@ describe API::Search do
describe 'GET /search' do
context 'when user is not authenticated' do
it 'returns 401 error' do
- get api('/search'), scope: 'projects', search: 'awesome'
+ get api('/search'), params: { scope: 'projects', search: 'awesome' }
expect(response).to have_gitlab_http_status(401)
end
@@ -24,7 +24,7 @@ describe API::Search do
context 'when scope is not supported' do
it 'returns 400 error' do
- get api('/search', user), scope: 'unsupported', search: 'awesome'
+ get api('/search', user), params: { scope: 'unsupported', search: 'awesome' }
expect(response).to have_gitlab_http_status(400)
end
@@ -32,7 +32,7 @@ describe API::Search do
context 'when scope is missing' do
it 'returns 400 error' do
- get api('/search', user), search: 'awesome'
+ get api('/search', user), params: { search: 'awesome' }
expect(response).to have_gitlab_http_status(400)
end
@@ -41,7 +41,7 @@ describe API::Search do
context 'with correct params' do
context 'for projects scope' do
before do
- get api('/search', user), scope: 'projects', search: 'awesome'
+ get api('/search', user), params: { scope: 'projects', search: 'awesome' }
end
it_behaves_like 'response is correct', schema: 'public_api/v4/projects'
@@ -51,7 +51,7 @@ describe API::Search do
before do
create(:issue, project: project, title: 'awesome issue')
- get api('/search', user), scope: 'issues', search: 'awesome'
+ get api('/search', user), params: { scope: 'issues', search: 'awesome' }
end
it_behaves_like 'response is correct', schema: 'public_api/v4/issues'
@@ -61,7 +61,7 @@ describe API::Search do
before do
create(:merge_request, source_project: repo_project, title: 'awesome mr')
- get api('/search', user), scope: 'merge_requests', search: 'awesome'
+ get api('/search', user), params: { scope: 'merge_requests', search: 'awesome' }
end
it_behaves_like 'response is correct', schema: 'public_api/v4/merge_requests'
@@ -71,7 +71,7 @@ describe API::Search do
before do
create(:milestone, project: project, title: 'awesome milestone')
- get api('/search', user), scope: 'milestones', search: 'awesome'
+ get api('/search', user), params: { scope: 'milestones', search: 'awesome' }
end
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
@@ -81,7 +81,7 @@ describe API::Search do
before do
create(:snippet, :public, title: 'awesome snippet', content: 'snippet content')
- get api('/search', user), scope: 'snippet_titles', search: 'awesome'
+ get api('/search', user), params: { scope: 'snippet_titles', search: 'awesome' }
end
it_behaves_like 'response is correct', schema: 'public_api/v4/snippets'
@@ -91,7 +91,7 @@ describe API::Search do
before do
create(:snippet, :public, title: 'awesome snippet', content: 'snippet content')
- get api('/search', user), scope: 'snippet_blobs', search: 'content'
+ get api('/search', user), params: { scope: 'snippet_blobs', search: 'content' }
end
it_behaves_like 'response is correct', schema: 'public_api/v4/snippets'
@@ -102,7 +102,7 @@ describe API::Search do
describe "GET /groups/:id/search" do
context 'when user is not authenticated' do
it 'returns 401 error' do
- get api("/groups/#{group.id}/search"), scope: 'projects', search: 'awesome'
+ get api("/groups/#{group.id}/search"), params: { scope: 'projects', search: 'awesome' }
expect(response).to have_gitlab_http_status(401)
end
@@ -110,7 +110,7 @@ describe API::Search do
context 'when scope is not supported' do
it 'returns 400 error' do
- get api("/groups/#{group.id}/search", user), scope: 'unsupported', search: 'awesome'
+ get api("/groups/#{group.id}/search", user), params: { scope: 'unsupported', search: 'awesome' }
expect(response).to have_gitlab_http_status(400)
end
@@ -118,7 +118,7 @@ describe API::Search do
context 'when scope is missing' do
it 'returns 400 error' do
- get api("/groups/#{group.id}/search", user), search: 'awesome'
+ get api("/groups/#{group.id}/search", user), params: { search: 'awesome' }
expect(response).to have_gitlab_http_status(400)
end
@@ -126,7 +126,7 @@ describe API::Search do
context 'when group does not exist' do
it 'returns 404 error' do
- get api('/groups/9999/search', user), scope: 'issues', search: 'awesome'
+ get api('/groups/9999/search', user), params: { scope: 'issues', search: 'awesome' }
expect(response).to have_gitlab_http_status(404)
end
@@ -136,7 +136,7 @@ describe API::Search do
it 'returns 404 error' do
private_group = create(:group, :private)
- get api("/groups/#{private_group.id}/search", user), scope: 'issues', search: 'awesome'
+ get api("/groups/#{private_group.id}/search", user), params: { scope: 'issues', search: 'awesome' }
expect(response).to have_gitlab_http_status(404)
end
@@ -145,7 +145,7 @@ describe API::Search do
context 'with correct params' do
context 'for projects scope' do
before do
- get api("/groups/#{group.id}/search", user), scope: 'projects', search: 'awesome'
+ get api("/groups/#{group.id}/search", user), params: { scope: 'projects', search: 'awesome' }
end
it_behaves_like 'response is correct', schema: 'public_api/v4/projects'
@@ -155,7 +155,7 @@ describe API::Search do
before do
create(:issue, project: project, title: 'awesome issue')
- get api("/groups/#{group.id}/search", user), scope: 'issues', search: 'awesome'
+ get api("/groups/#{group.id}/search", user), params: { scope: 'issues', search: 'awesome' }
end
it_behaves_like 'response is correct', schema: 'public_api/v4/issues'
@@ -165,7 +165,7 @@ describe API::Search do
before do
create(:merge_request, source_project: repo_project, title: 'awesome mr')
- get api("/groups/#{group.id}/search", user), scope: 'merge_requests', search: 'awesome'
+ get api("/groups/#{group.id}/search", user), params: { scope: 'merge_requests', search: 'awesome' }
end
it_behaves_like 'response is correct', schema: 'public_api/v4/merge_requests'
@@ -175,7 +175,7 @@ describe API::Search do
before do
create(:milestone, project: project, title: 'awesome milestone')
- get api("/groups/#{group.id}/search", user), scope: 'milestones', search: 'awesome'
+ get api("/groups/#{group.id}/search", user), params: { scope: 'milestones', search: 'awesome' }
end
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
@@ -187,7 +187,7 @@ describe API::Search do
create(:milestone, project: project, title: 'awesome milestone')
create(:milestone, project: another_project, title: 'awesome milestone other project')
- get api("/groups/#{CGI.escape(group.full_path)}/search", user), scope: 'milestones', search: 'awesome'
+ get api("/groups/#{CGI.escape(group.full_path)}/search", user), params: { scope: 'milestones', search: 'awesome' }
end
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
@@ -198,7 +198,7 @@ describe API::Search do
describe "GET /projects/:id/search" do
context 'when user is not authenticated' do
it 'returns 401 error' do
- get api("/projects/#{project.id}/search"), scope: 'issues', search: 'awesome'
+ get api("/projects/#{project.id}/search"), params: { scope: 'issues', search: 'awesome' }
expect(response).to have_gitlab_http_status(401)
end
@@ -206,7 +206,7 @@ describe API::Search do
context 'when scope is not supported' do
it 'returns 400 error' do
- get api("/projects/#{project.id}/search", user), scope: 'unsupported', search: 'awesome'
+ get api("/projects/#{project.id}/search", user), params: { scope: 'unsupported', search: 'awesome' }
expect(response).to have_gitlab_http_status(400)
end
@@ -214,7 +214,7 @@ describe API::Search do
context 'when scope is missing' do
it 'returns 400 error' do
- get api("/projects/#{project.id}/search", user), search: 'awesome'
+ get api("/projects/#{project.id}/search", user), params: { search: 'awesome' }
expect(response).to have_gitlab_http_status(400)
end
@@ -222,7 +222,7 @@ describe API::Search do
context 'when project does not exist' do
it 'returns 404 error' do
- get api('/projects/9999/search', user), scope: 'issues', search: 'awesome'
+ get api('/projects/9999/search', user), params: { scope: 'issues', search: 'awesome' }
expect(response).to have_gitlab_http_status(404)
end
@@ -232,7 +232,7 @@ describe API::Search do
it 'returns 404 error' do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
- get api("/projects/#{project.id}/search", user), scope: 'issues', search: 'awesome'
+ get api("/projects/#{project.id}/search", user), params: { scope: 'issues', search: 'awesome' }
expect(response).to have_gitlab_http_status(404)
end
@@ -243,7 +243,7 @@ describe API::Search do
before do
create(:issue, project: project, title: 'awesome issue')
- get api("/projects/#{project.id}/search", user), scope: 'issues', search: 'awesome'
+ get api("/projects/#{project.id}/search", user), params: { scope: 'issues', search: 'awesome' }
end
it_behaves_like 'response is correct', schema: 'public_api/v4/issues'
@@ -253,7 +253,7 @@ describe API::Search do
before do
create(:merge_request, source_project: repo_project, title: 'awesome mr')
- get api("/projects/#{repo_project.id}/search", user), scope: 'merge_requests', search: 'awesome'
+ get api("/projects/#{repo_project.id}/search", user), params: { scope: 'merge_requests', search: 'awesome' }
end
it_behaves_like 'response is correct', schema: 'public_api/v4/merge_requests'
@@ -263,7 +263,7 @@ describe API::Search do
before do
create(:milestone, project: project, title: 'awesome milestone')
- get api("/projects/#{project.id}/search", user), scope: 'milestones', search: 'awesome'
+ get api("/projects/#{project.id}/search", user), params: { scope: 'milestones', search: 'awesome' }
end
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
@@ -273,7 +273,7 @@ describe API::Search do
before do
create(:note_on_merge_request, project: project, note: 'awesome note')
- get api("/projects/#{project.id}/search", user), scope: 'notes', search: 'awesome'
+ get api("/projects/#{project.id}/search", user), params: { scope: 'notes', search: 'awesome' }
end
it_behaves_like 'response is correct', schema: 'public_api/v4/notes'
@@ -284,7 +284,7 @@ describe API::Search do
wiki = create(:project_wiki, project: project)
create(:wiki_page, wiki: wiki, attrs: { title: 'home', content: "Awesome page" })
- get api("/projects/#{project.id}/search", user), scope: 'wiki_blobs', search: 'awesome'
+ get api("/projects/#{project.id}/search", user), params: { scope: 'wiki_blobs', search: 'awesome' }
end
it_behaves_like 'response is correct', schema: 'public_api/v4/blobs'
@@ -292,7 +292,7 @@ describe API::Search do
context 'for commits scope' do
before do
- get api("/projects/#{repo_project.id}/search", user), scope: 'commits', search: '498214de67004b1da3d820901307bed2a68a8ef6'
+ get api("/projects/#{repo_project.id}/search", user), params: { scope: 'commits', search: '498214de67004b1da3d820901307bed2a68a8ef6' }
end
it_behaves_like 'response is correct', schema: 'public_api/v4/commits_details'
@@ -300,7 +300,7 @@ describe API::Search do
context 'for commits scope with project path as id' do
before do
- get api("/projects/#{CGI.escape(repo_project.full_path)}/search", user), scope: 'commits', search: '498214de67004b1da3d820901307bed2a68a8ef6'
+ get api("/projects/#{CGI.escape(repo_project.full_path)}/search", user), params: { scope: 'commits', search: '498214de67004b1da3d820901307bed2a68a8ef6' }
end
it_behaves_like 'response is correct', schema: 'public_api/v4/commits_details'
@@ -308,14 +308,14 @@ describe API::Search do
context 'for blobs scope' do
before do
- get api("/projects/#{repo_project.id}/search", user), scope: 'blobs', search: 'monitors'
+ get api("/projects/#{repo_project.id}/search", user), params: { scope: 'blobs', search: 'monitors' }
end
it_behaves_like 'response is correct', schema: 'public_api/v4/blobs', size: 2
context 'filters' do
it 'by filename' do
- get api("/projects/#{repo_project.id}/search", user), scope: 'blobs', search: 'mon filename:PROCESS.md'
+ get api("/projects/#{repo_project.id}/search", user), params: { scope: 'blobs', search: 'mon filename:PROCESS.md' }
expect(response).to have_gitlab_http_status(200)
expect(json_response.size).to eq(2)
@@ -323,14 +323,14 @@ describe API::Search do
end
it 'by path' do
- get api("/projects/#{repo_project.id}/search", user), scope: 'blobs', search: 'mon path:markdown'
+ get api("/projects/#{repo_project.id}/search", user), params: { scope: 'blobs', search: 'mon path:markdown' }
expect(response).to have_gitlab_http_status(200)
expect(json_response.size).to eq(8)
end
it 'by extension' do
- get api("/projects/#{repo_project.id}/search", user), scope: 'blobs', search: 'mon extension:md'
+ get api("/projects/#{repo_project.id}/search", user), params: { scope: 'blobs', search: 'mon extension:md' }
expect(response).to have_gitlab_http_status(200)
expect(json_response.size).to eq(11)
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 236f8d7faf5..e260aa21e25 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -14,7 +14,7 @@ describe API::Services do
include_context service
it "updates #{service} settings" do
- put api("/projects/#{project.id}/services/#{dashed_service}", user), service_attrs
+ put api("/projects/#{project.id}/services/#{dashed_service}", user), params: service_attrs
expect(response).to have_gitlab_http_status(200)
@@ -22,7 +22,7 @@ describe API::Services do
event = current_service.event_names.empty? ? "foo" : current_service.event_names.first
state = current_service[event] || false
- put api("/projects/#{project.id}/services/#{dashed_service}?#{event}=#{!state}", user), service_attrs
+ put api("/projects/#{project.id}/services/#{dashed_service}?#{event}=#{!state}", user), params: service_attrs
expect(response).to have_gitlab_http_status(200)
expect(project.services.first[event]).not_to eq(state) unless event == "foo"
@@ -44,7 +44,7 @@ describe API::Services do
expected_code = 400
end
- put api("/projects/#{project.id}/services/#{dashed_service}", user), attrs
+ put api("/projects/#{project.id}/services/#{dashed_service}", user), params: attrs
expect(response.status).to eq(expected_code)
end
@@ -127,7 +127,7 @@ describe API::Services do
end
it 'when the service is inactive' do
- post api("/projects/#{project.id}/services/#{service_name}/trigger"), params
+ post api("/projects/#{project.id}/services/#{service_name}/trigger"), params: params
expect(response).to have_gitlab_http_status(404)
end
@@ -142,7 +142,7 @@ describe API::Services do
end
it 'returns status 200' do
- post api("/projects/#{project.id}/services/#{service_name}/trigger"), params
+ post api("/projects/#{project.id}/services/#{service_name}/trigger"), params: params
expect(response).to have_gitlab_http_status(200)
end
@@ -150,7 +150,7 @@ describe API::Services do
context 'when the project can not be found' do
it 'returns a generic 404' do
- post api("/projects/404/services/#{service_name}/trigger"), params
+ post api("/projects/404/services/#{service_name}/trigger"), params: params
expect(response).to have_gitlab_http_status(404)
expect(json_response["message"]).to eq("404 Service Not Found")
@@ -170,7 +170,7 @@ describe API::Services do
end
it 'returns status 200' do
- post api("/projects/#{project.id}/services/#{service_name}/trigger"), token: 'token', text: 'help'
+ post api("/projects/#{project.id}/services/#{service_name}/trigger"), params: { token: 'token', text: 'help' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['response_type']).to eq("ephemeral")
@@ -192,7 +192,7 @@ describe API::Services do
end
it 'accepts a username for update' do
- put api("/projects/#{project.id}/services/mattermost", user), params.merge(username: 'new_username')
+ put api("/projects/#{project.id}/services/mattermost", user), params: params.merge(username: 'new_username')
expect(response).to have_gitlab_http_status(200)
expect(json_response['properties']['username']).to eq('new_username')
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 84c7210f6bb..cfbda63bb30 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -42,27 +42,29 @@ describe API::Settings, 'Settings' do
it "updates application settings" do
put api("/application/settings", admin),
- default_projects_limit: 3,
- password_authentication_enabled_for_web: false,
- repository_storages: ['custom'],
- plantuml_enabled: true,
- plantuml_url: 'http://plantuml.example.com',
- default_snippet_visibility: 'internal',
- restricted_visibility_levels: ['public'],
- default_artifacts_expire_in: '2 days',
- help_page_text: 'custom help text',
- help_page_hide_commercial_content: true,
- help_page_support_url: 'http://example.com/help',
- project_export_enabled: false,
- rsa_key_restriction: ApplicationSetting::FORBIDDEN_KEY_VALUE,
- dsa_key_restriction: 2048,
- ecdsa_key_restriction: 384,
- ed25519_key_restriction: 256,
- enforce_terms: true,
- terms: 'Hello world!',
- performance_bar_allowed_group_path: group.full_path,
- instance_statistics_visibility_private: true,
- diff_max_patch_bytes: 150_000
+ params: {
+ default_projects_limit: 3,
+ password_authentication_enabled_for_web: false,
+ repository_storages: ['custom'],
+ plantuml_enabled: true,
+ plantuml_url: 'http://plantuml.example.com',
+ default_snippet_visibility: 'internal',
+ restricted_visibility_levels: ['public'],
+ default_artifacts_expire_in: '2 days',
+ help_page_text: 'custom help text',
+ help_page_hide_commercial_content: true,
+ help_page_support_url: 'http://example.com/help',
+ project_export_enabled: false,
+ rsa_key_restriction: ApplicationSetting::FORBIDDEN_KEY_VALUE,
+ dsa_key_restriction: 2048,
+ ecdsa_key_restriction: 384,
+ ed25519_key_restriction: 256,
+ enforce_terms: true,
+ terms: 'Hello world!',
+ performance_bar_allowed_group_path: group.full_path,
+ instance_statistics_visibility_private: true,
+ diff_max_patch_bytes: 150_000
+ }
expect(response).to have_gitlab_http_status(200)
expect(json_response['default_projects_limit']).to eq(3)
@@ -91,7 +93,7 @@ describe API::Settings, 'Settings' do
it "supports legacy performance_bar_allowed_group_id" do
put api("/application/settings", admin),
- performance_bar_allowed_group_id: group.full_path
+ params: { performance_bar_allowed_group_id: group.full_path }
expect(response).to have_gitlab_http_status(200)
expect(json_response['performance_bar_allowed_group_id']).to eq(group.id)
@@ -99,8 +101,10 @@ describe API::Settings, 'Settings' do
it "supports legacy performance_bar_enabled" do
put api("/application/settings", admin),
- performance_bar_enabled: false,
- performance_bar_allowed_group_id: group.full_path
+ params: {
+ performance_bar_enabled: false,
+ performance_bar_allowed_group_id: group.full_path
+ }
expect(response).to have_gitlab_http_status(200)
expect(json_response['performance_bar_allowed_group_id']).to be_nil
@@ -108,7 +112,7 @@ describe API::Settings, 'Settings' do
context "missing plantuml_url value when plantuml_enabled is true" do
it "returns a blank parameter error message" do
- put api("/application/settings", admin), plantuml_enabled: true
+ put api("/application/settings", admin), params: { plantuml_enabled: true }
expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq('plantuml_url is missing')
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index c546ba3e127..7c8512f7589 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -143,7 +143,7 @@ describe API::Snippets do
it 'creates a new snippet' do
expect do
- post api("/snippets/", user), params
+ post api("/snippets/", user), params: params
end.to change { PersonalSnippet.count }.by(1)
expect(response).to have_gitlab_http_status(201)
@@ -156,14 +156,14 @@ describe API::Snippets do
it 'returns 400 for missing parameters' do
params.delete(:title)
- post api("/snippets/", user), params
+ post api("/snippets/", user), params: params
expect(response).to have_gitlab_http_status(400)
end
context 'when the snippet is spam' do
def create_snippet(snippet_params = {})
- post api('/snippets', user), params.merge(snippet_params)
+ post api('/snippets', user), params: params.merge(snippet_params)
end
before do
@@ -205,7 +205,7 @@ describe API::Snippets do
new_content = 'New content'
new_description = 'New description'
- put api("/snippets/#{snippet.id}", user), content: new_content, description: new_description
+ put api("/snippets/#{snippet.id}", user), params: { content: new_content, description: new_description }
expect(response).to have_gitlab_http_status(200)
snippet.reload
@@ -214,14 +214,14 @@ describe API::Snippets do
end
it 'returns 404 for invalid snippet id' do
- put api("/snippets/1234", user), title: 'foo'
+ put api("/snippets/1234", user), params: { title: 'foo' }
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Snippet Not Found')
end
it "returns 404 for another user's snippet" do
- put api("/snippets/#{snippet.id}", other_user), title: 'fubar'
+ put api("/snippets/#{snippet.id}", other_user), params: { title: 'fubar' }
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Snippet Not Found')
@@ -235,7 +235,7 @@ describe API::Snippets do
context 'when the snippet is spam' do
def update_snippet(snippet_params = {})
- put api("/snippets/#{snippet.id}", user), snippet_params
+ put api("/snippets/#{snippet.id}", user), params: snippet_params
end
before do
diff --git a/spec/requests/api/submodules_spec.rb b/spec/requests/api/submodules_spec.rb
index fa447c028c2..c482a85c68f 100644
--- a/spec/requests/api/submodules_spec.rb
+++ b/spec/requests/api/submodules_spec.rb
@@ -31,7 +31,7 @@ describe API::Submodules do
describe "PUT /projects/:id/repository/submodule/:submodule" do
context 'when unauthenticated' do
it 'returns 401' do
- put api(route(submodule)), params
+ put api(route(submodule)), params: params
expect(response).to have_gitlab_http_status(401)
end
@@ -39,7 +39,7 @@ describe API::Submodules do
context 'when authenticated', 'as a guest' do
it 'returns 403' do
- put api(route(submodule), guest), params
+ put api(route(submodule), guest), params: params
expect(response).to have_gitlab_http_status(403)
end
@@ -53,13 +53,13 @@ describe API::Submodules do
end
it 'returns 400 if branch is missing' do
- put api(route(submodule), user), params.except(:branch)
+ put api(route(submodule), user), params: params.except(:branch)
expect(response).to have_gitlab_http_status(400)
end
it 'returns 400 if commit_sha is missing' do
- put api(route(submodule), user), params.except(:commit_sha)
+ put api(route(submodule), user), params: params.except(:commit_sha)
expect(response).to have_gitlab_http_status(400)
end
@@ -67,7 +67,7 @@ describe API::Submodules do
it 'returns the commmit' do
head_commit = project.repository.commit.id
- put api(route(submodule), user), params
+ put api(route(submodule), user), params: params
expect(response).to have_gitlab_http_status(200)
expect(json_response['message']).to eq commit_message
@@ -87,7 +87,7 @@ describe API::Submodules do
.with(any_args, hash_including(submodule: submodule))
.and_call_original
- put api(route(encoded_submodule), user), params
+ put api(route(encoded_submodule), user), params: params
expect(response).to have_gitlab_http_status(200)
expect(json_response['id']).to eq project.repository.commit(branch).id
diff --git a/spec/requests/api/suggestions_spec.rb b/spec/requests/api/suggestions_spec.rb
new file mode 100644
index 00000000000..3c2842e5725
--- /dev/null
+++ b/spec/requests/api/suggestions_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Suggestions do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+
+ let(:merge_request) do
+ create(:merge_request, source_project: project,
+ target_project: project)
+ end
+
+ let(:position) do
+ Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 9,
+ diff_refs: merge_request.diff_refs)
+ end
+
+ let(:diff_note) do
+ create(:diff_note_on_merge_request, noteable: merge_request,
+ position: position,
+ project: project)
+ end
+
+ describe "PUT /suggestions/:id/apply" do
+ let(:url) { "/suggestions/#{suggestion.id}/apply" }
+
+ context 'when successfully applies patch' do
+ let(:suggestion) do
+ create(:suggestion, note: diff_note,
+ from_content: " raise RuntimeError, \"System commands must be given as an array of strings\"\n",
+ to_content: " raise RuntimeError, 'Explosion'\n # explosion?")
+ end
+
+ it 'returns 200 with json content' do
+ project.add_maintainer(user)
+
+ put api(url, user), params: { id: suggestion.id }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response)
+ .to include('id', 'from_original_line', 'to_original_line',
+ 'from_line', 'to_line', 'appliable', 'applied',
+ 'from_content', 'to_content')
+ end
+ end
+
+ context 'when not able to apply patch' do
+ let(:suggestion) do
+ create(:suggestion, :unappliable, note: diff_note)
+ end
+
+ it 'returns 400 with json content' do
+ project.add_maintainer(user)
+
+ put api(url, user), params: { id: suggestion.id }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response).to eq({ 'message' => 'Suggestion is not appliable' })
+ end
+ end
+
+ context 'when unauthorized' do
+ let(:suggestion) do
+ create(:suggestion, note: diff_note,
+ from_content: " raise RuntimeError, \"System commands must be given as an array of strings\"\n",
+ to_content: " raise RuntimeError, 'Explosion'\n # explosion?")
+ end
+
+ it 'returns 403 with json content' do
+ project.add_reporter(user)
+
+ put api(url, user), params: { id: suggestion.id }
+
+ expect(response).to have_gitlab_http_status(403)
+ expect(json_response).to eq({ 'message' => '403 Forbidden' })
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index 6c57d443cbf..b6e8d74c2e9 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -45,7 +45,7 @@ describe API::SystemHooks do
describe "POST /hooks" do
it "creates new hook" do
expect do
- post api("/hooks", admin), url: 'http://example.com'
+ post api("/hooks", admin), params: { url: 'http://example.com' }
end.to change { SystemHook.count }.by(1)
end
@@ -56,7 +56,7 @@ describe API::SystemHooks do
end
it "responds with 400 if url is invalid" do
- post api("/hooks", admin), url: 'hp://mep.mep'
+ post api("/hooks", admin), params: { url: 'hp://mep.mep' }
expect(response).to have_gitlab_http_status(400)
end
@@ -68,7 +68,7 @@ describe API::SystemHooks do
end
it 'sets default values for events' do
- post api('/hooks', admin), url: 'http://mep.mep'
+ post api('/hooks', admin), params: { url: 'http://mep.mep' }
expect(response).to have_gitlab_http_status(201)
expect(json_response['enable_ssl_verification']).to be true
@@ -79,11 +79,13 @@ describe API::SystemHooks do
it 'sets explicit values for events' do
post api('/hooks', admin),
- url: 'http://mep.mep',
- enable_ssl_verification: false,
- push_events: true,
- tag_push_events: true,
- merge_requests_events: true
+ params: {
+ url: 'http://mep.mep',
+ enable_ssl_verification: false,
+ push_events: true,
+ tag_push_events: true,
+ merge_requests_events: true
+ }
expect(response).to have_http_status(201)
expect(json_response['enable_ssl_verification']).to be false
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index 98f995df06f..12cfac96d31 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -193,7 +193,7 @@ describe API::Tags do
shared_examples_for 'repository new tag' do
it 'creates a new tag' do
- post api(route, current_user), tag_name: tag_name, ref: 'master'
+ post api(route, current_user), params: { tag_name: tag_name, ref: 'master' }
expect(response).to have_gitlab_http_status(201)
expect(response).to match_response_schema('public_api/v4/tag')
@@ -248,26 +248,26 @@ describe API::Tags do
end
it 'returns 400 if tag name is invalid' do
- post api(route, current_user), tag_name: 'new design', ref: 'master'
+ post api(route, current_user), params: { tag_name: 'new design', ref: 'master' }
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq('Tag name invalid')
end
it 'returns 400 if tag already exists' do
- post api(route, current_user), tag_name: 'new_design1', ref: 'master'
+ post api(route, current_user), params: { tag_name: 'new_design1', ref: 'master' }
expect(response).to have_gitlab_http_status(201)
expect(response).to match_response_schema('public_api/v4/tag')
- post api(route, current_user), tag_name: 'new_design1', ref: 'master'
+ post api(route, current_user), params: { tag_name: 'new_design1', ref: 'master' }
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq('Tag new_design1 already exists')
end
it 'returns 400 if ref name is invalid' do
- post api(route, current_user), tag_name: 'new_design3', ref: 'foo'
+ post api(route, current_user), params: { tag_name: 'new_design3', ref: 'foo' }
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq('Target foo is invalid')
@@ -275,7 +275,7 @@ describe API::Tags do
context 'lightweight tags with release notes' do
it 'creates a new tag' do
- post api(route, current_user), tag_name: tag_name, ref: 'master', release_description: 'Wow'
+ post api(route, current_user), params: { tag_name: tag_name, ref: 'master', release_description: 'Wow' }
expect(response).to have_gitlab_http_status(201)
expect(response).to match_response_schema('public_api/v4/tag')
@@ -294,7 +294,7 @@ describe API::Tags do
system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.name #{user.name}))
system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.email #{user.email}))
- post api(route, current_user), tag_name: 'v7.1.0', ref: 'master', message: 'Release 7.1.0'
+ post api(route, current_user), params: { tag_name: 'v7.1.0', ref: 'master', message: 'Release 7.1.0' }
expect(response).to have_gitlab_http_status(201)
expect(response).to match_response_schema('public_api/v4/tag')
@@ -360,7 +360,7 @@ describe API::Tags do
shared_examples_for 'repository new release' do
it 'creates description for existing git tag' do
- post api(route, user), description: description
+ post api(route, user), params: { description: description }
expect(response).to have_gitlab_http_status(201)
expect(response).to match_response_schema('public_api/v4/release')
@@ -372,7 +372,7 @@ describe API::Tags do
let(:tag_name) { 'unknown' }
it_behaves_like '404 response' do
- let(:request) { post api(route, current_user), description: description }
+ let(:request) { post api(route, current_user), params: { description: description } }
let(:message) { 'Tag does not exist' }
end
end
@@ -381,7 +381,7 @@ describe API::Tags do
include_context 'disabled repository'
it_behaves_like '403 response' do
- let(:request) { post api(route, current_user), description: description }
+ let(:request) { post api(route, current_user), params: { description: description } }
end
end
end
@@ -404,7 +404,7 @@ describe API::Tags do
end
it 'returns 409 if there is already a release' do
- post api(route, user), description: description
+ post api(route, user), params: { description: description }
expect(response).to have_gitlab_http_status(409)
expect(json_response['message']).to eq('Release already exists')
@@ -426,7 +426,7 @@ describe API::Tags do
end
it 'updates the release description' do
- put api(route, current_user), description: new_description
+ put api(route, current_user), params: { description: new_description }
expect(response).to have_gitlab_http_status(200)
expect(json_response['tag_name']).to eq(tag_name)
@@ -438,7 +438,7 @@ describe API::Tags do
let(:tag_name) { 'unknown' }
it_behaves_like '404 response' do
- let(:request) { put api(route, current_user), description: new_description }
+ let(:request) { put api(route, current_user), params: { description: new_description } }
let(:message) { 'Tag does not exist' }
end
end
@@ -447,7 +447,7 @@ describe API::Tags do
include_context 'disabled repository'
it_behaves_like '403 response' do
- let(:request) { put api(route, current_user), description: new_description }
+ let(:request) { put api(route, current_user), params: { description: new_description } }
end
end
end
@@ -465,7 +465,7 @@ describe API::Tags do
context 'when release does not exist' do
it_behaves_like '404 response' do
- let(:request) { put api(route, current_user), description: new_description }
+ let(:request) { put api(route, current_user), params: { description: new_description } }
let(:message) { 'Release does not exist' }
end
end
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
index b5cf04e7f22..f121a1d3b78 100644
--- a/spec/requests/api/todos_spec.rb
+++ b/spec/requests/api/todos_spec.rb
@@ -49,7 +49,7 @@ describe API::Todos do
context 'and using the author filter' do
it 'filters based on author_id param' do
- get api('/todos', john_doe), { author_id: author_2.id }
+ get api('/todos', john_doe), params: { author_id: author_2.id }
expect(response.status).to eq(200)
expect(response).to include_pagination_headers
@@ -62,7 +62,7 @@ describe API::Todos do
it 'filters based on type param' do
create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request)
- get api('/todos', john_doe), { type: 'MergeRequest' }
+ get api('/todos', john_doe), params: { type: 'MergeRequest' }
expect(response.status).to eq(200)
expect(response).to include_pagination_headers
@@ -73,7 +73,7 @@ describe API::Todos do
context 'and using the state filter' do
it 'filters based on state param' do
- get api('/todos', john_doe), { state: 'done' }
+ get api('/todos', john_doe), params: { state: 'done' }
expect(response.status).to eq(200)
expect(response).to include_pagination_headers
@@ -84,7 +84,7 @@ describe API::Todos do
context 'and using the project filter' do
it 'filters based on project_id param' do
- get api('/todos', john_doe), { project_id: project_2.id }
+ get api('/todos', john_doe), params: { project_id: project_2.id }
expect(response.status).to eq(200)
expect(response).to include_pagination_headers
@@ -95,7 +95,7 @@ describe API::Todos do
context 'and using the group filter' do
it 'filters based on project_id param' do
- get api('/todos', john_doe), { group_id: group.id, sort: :target_id }
+ get api('/todos', john_doe), params: { group_id: group.id, sort: :target_id }
expect(response.status).to eq(200)
expect(response).to include_pagination_headers
@@ -106,7 +106,7 @@ describe API::Todos do
context 'and using the action filter' do
it 'filters based on action param' do
- get api('/todos', john_doe), { action: 'mentioned' }
+ get api('/todos', john_doe), params: { action: 'mentioned' }
expect(response.status).to eq(200)
expect(response).to include_pagination_headers
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index 658df6945d2..15dc901d06e 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -26,13 +26,13 @@ describe API::Triggers do
context 'Handles errors' do
it 'returns bad request if token is missing' do
- post api("/projects/#{project.id}/trigger/pipeline"), ref: 'master'
+ post api("/projects/#{project.id}/trigger/pipeline"), params: { ref: 'master' }
expect(response).to have_gitlab_http_status(400)
end
it 'returns not found if project is not found' do
- post api('/projects/0/trigger/pipeline'), options.merge(ref: 'master')
+ post api('/projects/0/trigger/pipeline'), params: options.merge(ref: 'master')
expect(response).to have_gitlab_http_status(404)
end
@@ -42,7 +42,7 @@ describe API::Triggers do
let(:pipeline) { project.ci_pipelines.last }
it 'creates pipeline' do
- post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'master')
+ post api("/projects/#{project.id}/trigger/pipeline"), params: options.merge(ref: 'master')
expect(response).to have_gitlab_http_status(201)
expect(json_response).to include('id' => pipeline.id)
@@ -52,7 +52,7 @@ describe API::Triggers do
end
it 'returns bad request with no pipeline created if there\'s no commit for that ref' do
- post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'other-branch')
+ post api("/projects/#{project.id}/trigger/pipeline"), params: options.merge(ref: 'other-branch')
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq('base' => ["Reference not found"])
@@ -64,21 +64,21 @@ describe API::Triggers do
end
it 'validates variables to be a hash' do
- post api("/projects/#{project.id}/trigger/pipeline"), options.merge(variables: 'value', ref: 'master')
+ post api("/projects/#{project.id}/trigger/pipeline"), params: options.merge(variables: 'value', ref: 'master')
expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq('variables is invalid')
end
it 'validates variables needs to be a map of key-valued strings' do
- post api("/projects/#{project.id}/trigger/pipeline"), options.merge(variables: { key: %w(1 2) }, ref: 'master')
+ post api("/projects/#{project.id}/trigger/pipeline"), params: options.merge(variables: { key: %w(1 2) }, ref: 'master')
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq('variables needs to be a map of key-valued strings')
end
it 'creates trigger request with variables' do
- post api("/projects/#{project.id}/trigger/pipeline"), options.merge(variables: variables, ref: 'master')
+ post api("/projects/#{project.id}/trigger/pipeline"), params: options.merge(variables: variables, ref: 'master')
expect(response).to have_gitlab_http_status(201)
expect(pipeline.variables.map { |v| { v.key => v.value } }.last).to eq(variables)
@@ -91,7 +91,7 @@ describe API::Triggers do
end
it 'creates pipeline' do
- post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'master')
+ post api("/projects/#{project.id}/trigger/pipeline"), params: options.merge(ref: 'master')
expect(response).to have_gitlab_http_status(201)
expect(json_response).to include('id' => pipeline.id)
@@ -104,14 +104,14 @@ describe API::Triggers do
context 'when triggering a pipeline from a trigger token' do
it 'does not leak the presence of project when token is for different project' do
- post api("/projects/#{project2.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' }
+ post api("/projects/#{project2.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), params: { ref: 'refs/heads/other-branch' }
expect(response).to have_gitlab_http_status(404)
end
it 'creates builds from the ref given in the URL, not in the body' do
expect do
- post api("/projects/#{project.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' }
+ post api("/projects/#{project.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), params: { ref: 'refs/heads/other-branch' }
end.to change(project.builds, :count).by(5)
expect(response).to have_gitlab_http_status(201)
@@ -122,7 +122,7 @@ describe API::Triggers do
project.repository.create_file(user, '.gitlab/gitlabhq/new_feature.md', 'something valid', message: 'new_feature', branch_name: 'v.1-branch')
expect do
- post api("/projects/#{project.id}/ref/v.1-branch/trigger/pipeline?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' }
+ post api("/projects/#{project.id}/ref/v.1-branch/trigger/pipeline?token=#{trigger_token}"), params: { ref: 'refs/heads/other-branch' }
end.to change(project.builds, :count).by(4)
expect(response).to have_gitlab_http_status(201)
@@ -199,7 +199,7 @@ describe API::Triggers do
it 'creates trigger' do
expect do
post api("/projects/#{project.id}/triggers", user),
- description: 'trigger'
+ params: { description: 'trigger' }
end.to change {project.triggers.count}.by(1)
expect(response).to have_gitlab_http_status(201)
@@ -219,7 +219,7 @@ describe API::Triggers do
context 'authenticated user with invalid permissions' do
it 'does not create trigger' do
post api("/projects/#{project.id}/triggers", user2),
- description: 'trigger'
+ params: { description: 'trigger' }
expect(response).to have_gitlab_http_status(403)
end
@@ -228,7 +228,7 @@ describe API::Triggers do
context 'unauthenticated user' do
it 'does not create trigger' do
post api("/projects/#{project.id}/triggers"),
- description: 'trigger'
+ params: { description: 'trigger' }
expect(response).to have_gitlab_http_status(401)
end
@@ -241,7 +241,7 @@ describe API::Triggers do
it 'updates description' do
put api("/projects/#{project.id}/triggers/#{trigger.id}", user),
- description: new_description
+ params: { description: new_description }
expect(response).to have_gitlab_http_status(200)
expect(json_response).to include('description' => new_description)
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index bb913ae0e79..f3431e0be3d 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -43,7 +43,7 @@ describe API::Users do
end
it "returns the user when a valid `username` parameter is passed" do
- get api("/users"), username: user.username
+ get api("/users"), params: { username: user.username }
expect(response).to match_response_schema('public_api/v4/user/basics')
expect(json_response.size).to eq(1)
@@ -52,7 +52,7 @@ describe API::Users do
end
it "returns the user when a valid `username` parameter is passed (case insensitive)" do
- get api("/users"), username: user.username.upcase
+ get api("/users"), params: { username: user.username.upcase }
expect(response).to match_response_schema('public_api/v4/user/basics')
expect(json_response.size).to eq(1)
@@ -61,7 +61,7 @@ describe API::Users do
end
it "returns an empty response when an invalid `username` parameter is passed" do
- get api("/users"), username: 'invalid'
+ get api("/users"), params: { username: 'invalid' }
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
@@ -74,7 +74,7 @@ describe API::Users do
end
it "returns authorization error when the `username` parameter refers to an inaccessible user" do
- get api("/users"), username: user.username
+ get api("/users"), params: { username: user.username }
expect(response).to have_gitlab_http_status(403)
end
@@ -243,7 +243,7 @@ describe API::Users do
admin
user
- get api('/users', admin), { order_by: 'id', sort: 'asc' }
+ get api('/users', admin), params: { order_by: 'id', sort: 'asc' }
expect(response).to match_response_schema('public_api/v4/user/admins')
expect(json_response.size).to eq(2)
@@ -256,7 +256,7 @@ describe API::Users do
user
user_with_2fa = create(:user, :two_factor_via_otp)
- get api('/users', admin), { two_factor: 'enabled' }
+ get api('/users', admin), params: { two_factor: 'enabled' }
expect(response).to match_response_schema('public_api/v4/user/admins')
expect(json_response.size).to eq(1)
@@ -264,7 +264,7 @@ describe API::Users do
end
it 'returns 400 when provided incorrect sort params' do
- get api('/users', admin), { order_by: 'magic', sort: 'asc' }
+ get api('/users', admin), params: { order_by: 'magic', sort: 'asc' }
expect(response).to have_gitlab_http_status(400)
end
@@ -375,12 +375,12 @@ describe API::Users do
it "creates user" do
expect do
- post api("/users", admin), attributes_for(:user, projects_limit: 3)
+ post api("/users", admin), params: attributes_for(:user, projects_limit: 3)
end.to change { User.count }.by(1)
end
it "creates user with correct attributes" do
- post api('/users', admin), attributes_for(:user, admin: true, can_create_group: true)
+ post api('/users', admin), params: attributes_for(:user, admin: true, can_create_group: true)
expect(response).to have_gitlab_http_status(201)
user_id = json_response['id']
new_user = User.find(user_id)
@@ -393,13 +393,13 @@ describe API::Users do
optional_attributes = { confirm: true }
attributes = attributes_for(:user).merge(optional_attributes)
- post api('/users', admin), attributes
+ post api('/users', admin), params: attributes
expect(response).to have_gitlab_http_status(201)
end
it "creates non-admin user" do
- post api('/users', admin), attributes_for(:user, admin: false, can_create_group: false)
+ post api('/users', admin), params: attributes_for(:user, admin: false, can_create_group: false)
expect(response).to have_gitlab_http_status(201)
user_id = json_response['id']
new_user = User.find(user_id)
@@ -409,7 +409,7 @@ describe API::Users do
end
it "creates non-admin users by default" do
- post api('/users', admin), attributes_for(:user)
+ post api('/users', admin), params: attributes_for(:user)
expect(response).to have_gitlab_http_status(201)
user_id = json_response['id']
new_user = User.find(user_id)
@@ -418,12 +418,12 @@ describe API::Users do
end
it "returns 201 Created on success" do
- post api("/users", admin), attributes_for(:user, projects_limit: 3)
+ post api("/users", admin), params: attributes_for(:user, projects_limit: 3)
expect(response).to have_gitlab_http_status(201)
end
it 'creates non-external users by default' do
- post api("/users", admin), attributes_for(:user)
+ post api("/users", admin), params: attributes_for(:user)
expect(response).to have_gitlab_http_status(201)
user_id = json_response['id']
@@ -433,7 +433,7 @@ describe API::Users do
end
it 'allows an external user to be created' do
- post api("/users", admin), attributes_for(:user, external: true)
+ post api("/users", admin), params: attributes_for(:user, external: true)
expect(response).to have_gitlab_http_status(201)
user_id = json_response['id']
@@ -443,7 +443,7 @@ describe API::Users do
end
it "creates user with reset password" do
- post api('/users', admin), attributes_for(:user, reset_password: true).except(:password)
+ post api('/users', admin), params: attributes_for(:user, reset_password: true).except(:password)
expect(response).to have_gitlab_http_status(201)
@@ -455,7 +455,7 @@ describe API::Users do
end
it "creates user with private profile" do
- post api('/users', admin), attributes_for(:user, private_profile: true)
+ post api('/users', admin), params: attributes_for(:user, private_profile: true)
expect(response).to have_gitlab_http_status(201)
@@ -468,40 +468,44 @@ describe API::Users do
it "does not create user with invalid email" do
post api('/users', admin),
- email: 'invalid email',
- password: 'password',
- name: 'test'
+ params: {
+ email: 'invalid email',
+ password: 'password',
+ name: 'test'
+ }
expect(response).to have_gitlab_http_status(400)
end
it 'returns 400 error if name not given' do
- post api('/users', admin), attributes_for(:user).except(:name)
+ post api('/users', admin), params: attributes_for(:user).except(:name)
expect(response).to have_gitlab_http_status(400)
end
it 'returns 400 error if password not given' do
- post api('/users', admin), attributes_for(:user).except(:password)
+ post api('/users', admin), params: attributes_for(:user).except(:password)
expect(response).to have_gitlab_http_status(400)
end
it 'returns 400 error if email not given' do
- post api('/users', admin), attributes_for(:user).except(:email)
+ post api('/users', admin), params: attributes_for(:user).except(:email)
expect(response).to have_gitlab_http_status(400)
end
it 'returns 400 error if username not given' do
- post api('/users', admin), attributes_for(:user).except(:username)
+ post api('/users', admin), params: attributes_for(:user).except(:username)
expect(response).to have_gitlab_http_status(400)
end
it 'returns 400 error if user does not validate' do
post api('/users', admin),
- password: 'pass',
- email: 'test@example.com',
- username: 'test!',
- name: 'test',
- bio: 'g' * 256,
- projects_limit: -1
+ params: {
+ password: 'pass',
+ email: 'test@example.com',
+ username: 'test!',
+ name: 'test',
+ bio: 'g' * 256,
+ projects_limit: -1
+ }
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']['password'])
.to eq(['is too short (minimum is 8 characters)'])
@@ -514,26 +518,30 @@ describe API::Users do
end
it "is not available for non admin users" do
- post api("/users", user), attributes_for(:user)
+ post api("/users", user), params: attributes_for(:user)
expect(response).to have_gitlab_http_status(403)
end
context 'with existing user' do
before do
post api('/users', admin),
- email: 'test@example.com',
- password: 'password',
- username: 'test',
- name: 'foo'
+ params: {
+ email: 'test@example.com',
+ password: 'password',
+ username: 'test',
+ name: 'foo'
+ }
end
it 'returns 409 conflict error if user with same email exists' do
expect do
post api('/users', admin),
- name: 'foo',
- email: 'test@example.com',
- password: 'password',
- username: 'foo'
+ params: {
+ name: 'foo',
+ email: 'test@example.com',
+ password: 'password',
+ username: 'foo'
+ }
end.to change { User.count }.by(0)
expect(response).to have_gitlab_http_status(409)
expect(json_response['message']).to eq('Email has already been taken')
@@ -542,10 +550,12 @@ describe API::Users do
it 'returns 409 conflict error if same username exists' do
expect do
post api('/users', admin),
- name: 'foo',
- email: 'foo@example.com',
- password: 'password',
- username: 'test'
+ params: {
+ name: 'foo',
+ email: 'foo@example.com',
+ password: 'password',
+ username: 'test'
+ }
end.to change { User.count }.by(0)
expect(response).to have_gitlab_http_status(409)
expect(json_response['message']).to eq('Username has already been taken')
@@ -554,17 +564,19 @@ describe API::Users do
it 'returns 409 conflict error if same username exists (case insensitive)' do
expect do
post api('/users', admin),
- name: 'foo',
- email: 'foo@example.com',
- password: 'password',
- username: 'TEST'
+ params: {
+ name: 'foo',
+ email: 'foo@example.com',
+ password: 'password',
+ username: 'TEST'
+ }
end.to change { User.count }.by(0)
expect(response).to have_gitlab_http_status(409)
expect(json_response['message']).to eq('Username has already been taken')
end
it 'creates user with new identity' do
- post api("/users", admin), attributes_for(:user, provider: 'github', extern_uid: '67890')
+ post api("/users", admin), params: attributes_for(:user, provider: 'github', extern_uid: '67890')
expect(response).to have_gitlab_http_status(201)
expect(json_response['identities'].first['extern_uid']).to eq('67890')
@@ -593,7 +605,7 @@ describe API::Users do
let!(:admin_user) { create(:admin) }
it "updates user with new bio" do
- put api("/users/#{user.id}", admin), { bio: 'new test bio' }
+ put api("/users/#{user.id}", admin), params: { bio: 'new test bio' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['bio']).to eq('new test bio')
@@ -601,14 +613,14 @@ describe API::Users do
end
it "updates user with new password and forces reset on next login" do
- put api("/users/#{user.id}", admin), password: '12345678'
+ put api("/users/#{user.id}", admin), params: { password: '12345678' }
expect(response).to have_gitlab_http_status(200)
expect(user.reload.password_expires_at).to be <= Time.now
end
it "updates user with organization" do
- put api("/users/#{user.id}", admin), { organization: 'GitLab' }
+ put api("/users/#{user.id}", admin), params: { organization: 'GitLab' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['organization']).to eq('GitLab')
@@ -616,7 +628,7 @@ describe API::Users do
end
it 'updates user with avatar' do
- put api("/users/#{user.id}", admin), { avatar: fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif') }
+ put api("/users/#{user.id}", admin), params: { avatar: fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif') }
user.reload
@@ -628,7 +640,7 @@ describe API::Users do
it 'updates user with a new email' do
old_email = user.email
old_notification_email = user.notification_email
- put api("/users/#{user.id}", admin), email: 'new@email.com'
+ put api("/users/#{user.id}", admin), params: { email: 'new@email.com' }
user.reload
@@ -640,7 +652,7 @@ describe API::Users do
end
it 'skips reconfirmation when requested' do
- put api("/users/#{user.id}", admin), email: 'new@email.com', skip_reconfirmation: true
+ put api("/users/#{user.id}", admin), params: { email: 'new@email.com', skip_reconfirmation: true }
user.reload
@@ -650,7 +662,7 @@ describe API::Users do
end
it 'updates user with his own username' do
- put api("/users/#{user.id}", admin), username: user.username
+ put api("/users/#{user.id}", admin), params: { username: user.username }
expect(response).to have_gitlab_http_status(200)
expect(json_response['username']).to eq(user.username)
@@ -658,14 +670,14 @@ describe API::Users do
end
it "updates user's existing identity" do
- put api("/users/#{omniauth_user.id}", admin), provider: 'ldapmain', extern_uid: '654321'
+ put api("/users/#{omniauth_user.id}", admin), params: { provider: 'ldapmain', extern_uid: '654321' }
expect(response).to have_gitlab_http_status(200)
expect(omniauth_user.reload.identities.first.extern_uid).to eq('654321')
end
it 'updates user with new identity' do
- put api("/users/#{user.id}", admin), provider: 'github', extern_uid: 'john'
+ put api("/users/#{user.id}", admin), params: { provider: 'github', extern_uid: 'john' }
expect(response).to have_gitlab_http_status(200)
expect(user.reload.identities.first.extern_uid).to eq('john')
@@ -673,14 +685,14 @@ describe API::Users do
end
it "updates admin status" do
- put api("/users/#{user.id}", admin), { admin: true }
+ put api("/users/#{user.id}", admin), params: { admin: true }
expect(response).to have_gitlab_http_status(200)
expect(user.reload.admin).to eq(true)
end
it "updates external status" do
- put api("/users/#{user.id}", admin), { external: true }
+ put api("/users/#{user.id}", admin), params: { external: true }
expect(response.status).to eq 200
expect(json_response['external']).to eq(true)
@@ -688,14 +700,14 @@ describe API::Users do
end
it "updates private profile" do
- put api("/users/#{user.id}", admin), { private_profile: true }
+ put api("/users/#{user.id}", admin), params: { private_profile: true }
expect(response).to have_gitlab_http_status(200)
expect(user.reload.private_profile).to eq(true)
end
it "does not update admin status" do
- put api("/users/#{admin_user.id}", admin), { can_create_group: false }
+ put api("/users/#{admin_user.id}", admin), params: { can_create_group: false }
expect(response).to have_gitlab_http_status(200)
expect(admin_user.reload.admin).to eq(true)
@@ -703,7 +715,7 @@ describe API::Users do
end
it "does not allow invalid update" do
- put api("/users/#{user.id}", admin), { email: 'invalid email' }
+ put api("/users/#{user.id}", admin), params: { email: 'invalid email' }
expect(response).to have_gitlab_http_status(400)
expect(user.reload.email).not_to eq('invalid email')
@@ -712,7 +724,7 @@ describe API::Users do
context 'when the current user is not an admin' do
it "is not available" do
expect do
- put api("/users/#{user.id}", user), attributes_for(:user)
+ put api("/users/#{user.id}", user), params: attributes_for(:user)
end.not_to change { user.reload.attributes }
expect(response).to have_gitlab_http_status(403)
@@ -720,7 +732,7 @@ describe API::Users do
end
it "returns 404 for non-existing user" do
- put api("/users/999999", admin), { bio: 'update should fail' }
+ put api("/users/999999", admin), params: { bio: 'update should fail' }
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
@@ -734,12 +746,14 @@ describe API::Users do
it 'returns 400 error if user does not validate' do
put api("/users/#{user.id}", admin),
- password: 'pass',
- email: 'test@example.com',
- username: 'test!',
- name: 'test',
- bio: 'g' * 256,
- projects_limit: -1
+ params: {
+ password: 'pass',
+ email: 'test@example.com',
+ username: 'test!',
+ name: 'test',
+ bio: 'g' * 256,
+ projects_limit: -1
+ }
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']['password'])
.to eq(['is too short (minimum is 8 characters)'])
@@ -752,26 +766,26 @@ describe API::Users do
end
it 'returns 400 if provider is missing for identity update' do
- put api("/users/#{omniauth_user.id}", admin), extern_uid: '654321'
+ put api("/users/#{omniauth_user.id}", admin), params: { extern_uid: '654321' }
expect(response).to have_gitlab_http_status(400)
end
it 'returns 400 if external UID is missing for identity update' do
- put api("/users/#{omniauth_user.id}", admin), provider: 'ldap'
+ put api("/users/#{omniauth_user.id}", admin), params: { provider: 'ldap' }
expect(response).to have_gitlab_http_status(400)
end
context "with existing user" do
before do
- post api("/users", admin), { email: 'test@example.com', password: 'password', username: 'test', name: 'test' }
- post api("/users", admin), { email: 'foo@bar.com', password: 'password', username: 'john', name: 'john' }
+ post api("/users", admin), params: { email: 'test@example.com', password: 'password', username: 'test', name: 'test' }
+ post api("/users", admin), params: { email: 'foo@bar.com', password: 'password', username: 'john', name: 'john' }
@user = User.all.last
end
it 'returns 409 conflict error if email address exists' do
- put api("/users/#{@user.id}", admin), email: 'test@example.com'
+ put api("/users/#{@user.id}", admin), params: { email: 'test@example.com' }
expect(response).to have_gitlab_http_status(409)
expect(@user.reload.email).to eq(@user.email)
@@ -779,7 +793,7 @@ describe API::Users do
it 'returns 409 conflict error if username taken' do
@user_id = User.all.last.id
- put api("/users/#{@user.id}", admin), username: 'test'
+ put api("/users/#{@user.id}", admin), params: { username: 'test' }
expect(response).to have_gitlab_http_status(409)
expect(@user.reload.username).to eq(@user.username)
@@ -787,7 +801,7 @@ describe API::Users do
it 'returns 409 conflict error if username taken (case insensitive)' do
@user_id = User.all.last.id
- put api("/users/#{@user.id}", admin), username: 'TEST'
+ put api("/users/#{@user.id}", admin), params: { username: 'TEST' }
expect(response).to have_gitlab_http_status(409)
expect(@user.reload.username).to eq(@user.username)
@@ -801,14 +815,14 @@ describe API::Users do
end
it "does not create invalid ssh key" do
- post api("/users/#{user.id}/keys", admin), { title: "invalid key" }
+ post api("/users/#{user.id}/keys", admin), params: { title: "invalid key" }
expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq('key is missing')
end
it 'does not create key without title' do
- post api("/users/#{user.id}/keys", admin), key: 'some key'
+ post api("/users/#{user.id}/keys", admin), params: { key: 'some key' }
expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq('title is missing')
@@ -817,7 +831,7 @@ describe API::Users do
it "creates ssh key" do
key_attrs = attributes_for :key
expect do
- post api("/users/#{user.id}/keys", admin), key_attrs
+ post api("/users/#{user.id}/keys", admin), params: key_attrs
end.to change { user.keys.count }.by(1)
end
@@ -909,7 +923,7 @@ describe API::Users do
it 'creates GPG key' do
key_attrs = attributes_for :gpg_key
expect do
- post api("/users/#{user.id}/gpg_keys", admin), key_attrs
+ post api("/users/#{user.id}/gpg_keys", admin), params: key_attrs
expect(response).to have_gitlab_http_status(201)
end.to change { user.gpg_keys.count }.by(1)
@@ -1058,7 +1072,7 @@ describe API::Users do
end
it "does not create invalid email" do
- post api("/users/#{user.id}/emails", admin), {}
+ post api("/users/#{user.id}/emails", admin), params: {}
expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq('email is missing')
@@ -1067,7 +1081,7 @@ describe API::Users do
it "creates unverified email" do
email_attrs = attributes_for :email
expect do
- post api("/users/#{user.id}/emails", admin), email_attrs
+ post api("/users/#{user.id}/emails", admin), params: email_attrs
end.to change { user.emails.count }.by(1)
email = Email.find_by(user_id: user.id, email: email_attrs[:email])
@@ -1084,7 +1098,7 @@ describe API::Users do
email_attrs = attributes_for :email
email_attrs[:skip_confirmation] = true
- post api("/users/#{user.id}/emails", admin), email_attrs
+ post api("/users/#{user.id}/emails", admin), params: email_attrs
expect(response).to have_gitlab_http_status(201)
@@ -1379,32 +1393,32 @@ describe API::Users do
it "creates ssh key" do
key_attrs = attributes_for :key
expect do
- post api("/user/keys", user), key_attrs
+ post api("/user/keys", user), params: key_attrs
end.to change { user.keys.count }.by(1)
expect(response).to have_gitlab_http_status(201)
end
it "returns a 401 error if unauthorized" do
- post api("/user/keys"), title: 'some title', key: 'some key'
+ post api("/user/keys"), params: { title: 'some title', key: 'some key' }
expect(response).to have_gitlab_http_status(401)
end
it "does not create ssh key without key" do
- post api("/user/keys", user), title: 'title'
+ post api("/user/keys", user), params: { title: 'title' }
expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq('key is missing')
end
it 'does not create ssh key without title' do
- post api('/user/keys', user), key: 'some key'
+ post api('/user/keys', user), params: { key: 'some key' }
expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq('title is missing')
end
it "does not create ssh key without title" do
- post api("/user/keys", user), key: "somekey"
+ post api("/user/keys", user), params: { key: "somekey" }
expect(response).to have_gitlab_http_status(400)
end
end
@@ -1523,14 +1537,14 @@ describe API::Users do
it 'creates a GPG key' do
key_attrs = attributes_for :gpg_key
expect do
- post api('/user/gpg_keys', user), key_attrs
+ post api('/user/gpg_keys', user), params: key_attrs
expect(response).to have_gitlab_http_status(201)
end.to change { user.gpg_keys.count }.by(1)
end
it 'returns a 401 error if unauthorized' do
- post api('/user/gpg_keys'), key: 'some key'
+ post api('/user/gpg_keys'), params: { key: 'some key' }
expect(response).to have_gitlab_http_status(401)
end
@@ -1685,18 +1699,18 @@ describe API::Users do
it "creates email" do
email_attrs = attributes_for :email
expect do
- post api("/user/emails", user), email_attrs
+ post api("/user/emails", user), params: email_attrs
end.to change { user.emails.count }.by(1)
expect(response).to have_gitlab_http_status(201)
end
it "returns a 401 error if unauthorized" do
- post api("/user/emails"), email: 'some email'
+ post api("/user/emails"), params: { email: 'some email' }
expect(response).to have_gitlab_http_status(401)
end
it "does not create email with invalid email" do
- post api("/user/emails", user), {}
+ post api("/user/emails", user), params: {}
expect(response).to have_gitlab_http_status(400)
expect(json_response['error']).to eq('email is missing')
@@ -1864,14 +1878,14 @@ describe API::Users do
describe 'PUT /user/status' do
it 'saves the status' do
- put api('/user/status', user), { emoji: 'smirk', message: 'hello world' }
+ put api('/user/status', user), params: { emoji: 'smirk', message: 'hello world' }
expect(response).to have_gitlab_http_status(:success)
expect(json_response['emoji']).to eq('smirk')
end
it 'renders errors when the status was invalid' do
- put api('/user/status', user), { emoji: 'does not exist', message: 'hello world' }
+ put api('/user/status', user), params: { emoji: 'does not exist', message: 'hello world' }
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']['emoji']).to be_present
@@ -1950,8 +1964,10 @@ describe API::Users do
it 'returns a 404 error if user not found' do
post api("/users/#{not_existing_user_id}/impersonation_tokens", admin),
- name: name,
- expires_at: expires_at
+ params: {
+ name: name,
+ expires_at: expires_at
+ }
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 User Not Found')
@@ -1959,8 +1975,10 @@ describe API::Users do
it 'returns a 403 error when authenticated as normal user' do
post api("/users/#{user.id}/impersonation_tokens", user),
- name: name,
- expires_at: expires_at
+ params: {
+ name: name,
+ expires_at: expires_at
+ }
expect(response).to have_gitlab_http_status(403)
expect(json_response['message']).to eq('403 Forbidden')
@@ -1968,10 +1986,12 @@ describe API::Users do
it 'creates a impersonation token' do
post api("/users/#{user.id}/impersonation_tokens", admin),
- name: name,
- expires_at: expires_at,
- scopes: scopes,
- impersonation: impersonation
+ params: {
+ name: name,
+ expires_at: expires_at,
+ scopes: scopes,
+ impersonation: impersonation
+ }
expect(response).to have_gitlab_http_status(201)
expect(json_response['name']).to eq(name)
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
index be333df1d78..cdac5b2f400 100644
--- a/spec/requests/api/variables_spec.rb
+++ b/spec/requests/api/variables_spec.rb
@@ -73,7 +73,7 @@ describe API::Variables do
context 'authorized user with proper permissions' do
it 'creates variable' do
expect do
- post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true
+ post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true }
end.to change {project.variables.count}.by(1)
expect(response).to have_gitlab_http_status(201)
@@ -84,7 +84,7 @@ describe API::Variables do
it 'creates variable with optional attributes' do
expect do
- post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2'
+ post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2' }
end.to change {project.variables.count}.by(1)
expect(response).to have_gitlab_http_status(201)
@@ -95,7 +95,7 @@ describe API::Variables do
it 'does not allow to duplicate variable key' do
expect do
- post api("/projects/#{project.id}/variables", user), key: variable.key, value: 'VALUE_2'
+ post api("/projects/#{project.id}/variables", user), params: { key: variable.key, value: 'VALUE_2' }
end.to change {project.variables.count}.by(0)
expect(response).to have_gitlab_http_status(400)
@@ -125,7 +125,7 @@ describe API::Variables do
initial_variable = project.variables.reload.first
value_before = initial_variable.value
- put api("/projects/#{project.id}/variables/#{variable.key}", user), value: 'VALUE_1_UP', protected: true
+ put api("/projects/#{project.id}/variables/#{variable.key}", user), params: { value: 'VALUE_1_UP', protected: true }
updated_variable = project.variables.reload.first
diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb
index 08bada44178..f5092e8e2b5 100644
--- a/spec/requests/api/wikis_spec.rb
+++ b/spec/requests/api/wikis_spec.rb
@@ -39,7 +39,7 @@ describe API::Wikis do
end
it 'returns the list of wiki pages with content' do
- get api(url, user), with_content: 1
+ get api(url, user), params: { with_content: 1 }
expect(response).to have_gitlab_http_status(200)
expect(json_response.size).to eq(2)
@@ -74,7 +74,7 @@ describe API::Wikis do
shared_examples_for 'creates wiki page' do
it 'creates the wiki page' do
- post(api(url, user), payload)
+ post(api(url, user), params: payload)
expect(response).to have_gitlab_http_status(201)
expect(json_response.size).to eq(4)
@@ -89,7 +89,7 @@ describe API::Wikis do
it "responds with validation error on empty #{part}" do
payload.delete(part)
- post(api(url, user), payload)
+ post(api(url, user), params: payload)
expect(response).to have_gitlab_http_status(400)
expect(json_response.size).to eq(1)
@@ -143,7 +143,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), payload)
+ post(api(url, user), params: payload)
expect(response).to have_gitlab_http_status(201)
expect(json_response).to eq result_hash.deep_stringify_keys
@@ -152,7 +152,7 @@ describe API::Wikis do
it 'responds with validation error on empty file' do
payload.delete(:file)
- post(api(url, user), payload)
+ post(api(url, user), params: payload)
expect(response).to have_gitlab_http_status(400)
expect(json_response.size).to eq(1)
@@ -162,7 +162,7 @@ describe API::Wikis do
it 'responds with validation error on invalid temp file' do
payload[:file] = { tempfile: '/etc/hosts' }
- post(api(url, user), payload)
+ post(api(url, user), params: payload)
expect(response).to have_gitlab_http_status(400)
expect(json_response.size).to eq(1)
@@ -395,7 +395,7 @@ describe API::Wikis do
context 'when user is guest' do
before do
- post(api(url), payload)
+ post(api(url), params: payload)
end
include_examples '404 Project Not Found'
@@ -404,7 +404,7 @@ describe API::Wikis do
context 'when user is developer' do
before do
project.add_developer(user)
- post(api(url, user), payload)
+ post(api(url, user), params: payload)
end
include_examples '403 Forbidden'
@@ -413,7 +413,7 @@ describe API::Wikis do
context 'when user is maintainer' do
before do
project.add_maintainer(user)
- post(api(url, user), payload)
+ post(api(url, user), params: payload)
end
include_examples '403 Forbidden'
@@ -425,7 +425,7 @@ describe API::Wikis do
context 'when user is guest' do
before do
- post(api(url), payload)
+ post(api(url), params: payload)
end
include_examples '404 Project Not Found'
@@ -453,7 +453,7 @@ describe API::Wikis do
context 'when user is guest' do
before do
- post(api(url), payload)
+ post(api(url), params: payload)
end
include_examples '404 Project Not Found'
@@ -487,7 +487,7 @@ describe API::Wikis do
context 'when user is guest' do
before do
- put(api(url), payload)
+ put(api(url), params: payload)
end
include_examples '404 Project Not Found'
@@ -497,7 +497,7 @@ describe API::Wikis do
before do
project.add_developer(user)
- put(api(url, user), payload)
+ put(api(url, user), params: payload)
end
include_examples '403 Forbidden'
@@ -507,7 +507,7 @@ describe API::Wikis do
before do
project.add_maintainer(user)
- put(api(url, user), payload)
+ put(api(url, user), params: payload)
end
include_examples '403 Forbidden'
@@ -519,7 +519,7 @@ describe API::Wikis do
context 'when user is guest' do
before do
- put(api(url), payload)
+ put(api(url), params: payload)
end
include_examples '404 Project Not Found'
@@ -529,7 +529,7 @@ describe API::Wikis do
before do
project.add_developer(user)
- put(api(url, user), payload)
+ put(api(url, user), params: payload)
end
include_examples 'updates wiki page'
@@ -545,7 +545,7 @@ describe API::Wikis do
before do
project.add_maintainer(user)
- put(api(url, user), payload)
+ put(api(url, user), params: payload)
end
include_examples 'updates wiki page'
@@ -563,7 +563,7 @@ describe API::Wikis do
context 'when user is guest' do
before do
- put(api(url), payload)
+ put(api(url), params: payload)
end
include_examples '404 Project Not Found'
@@ -573,7 +573,7 @@ describe API::Wikis do
before do
project.add_developer(user)
- put(api(url, user), payload)
+ put(api(url, user), params: payload)
end
include_examples 'updates wiki page'
@@ -589,7 +589,7 @@ describe API::Wikis do
before do
project.add_maintainer(user)
- put(api(url, user), payload)
+ put(api(url, user), params: payload)
end
include_examples 'updates wiki page'
@@ -606,7 +606,7 @@ describe API::Wikis do
let(:project) { create(:project, :wiki_repo, namespace: group) }
before do
- put(api(url, user), payload)
+ put(api(url, user), params: payload)
end
include_examples 'updates wiki page'
@@ -751,7 +751,7 @@ describe API::Wikis do
context 'when user is guest' do
before do
- post(api(url), payload)
+ post(api(url), params: payload)
end
include_examples '404 Project Not Found'
@@ -760,7 +760,7 @@ describe API::Wikis do
context 'when user is developer' do
before do
project.add_developer(user)
- post(api(url, user), payload)
+ post(api(url, user), params: payload)
end
include_examples '403 Forbidden'
@@ -769,7 +769,7 @@ describe API::Wikis do
context 'when user is maintainer' do
before do
project.add_maintainer(user)
- post(api(url, user), payload)
+ post(api(url, user), params: payload)
end
include_examples '403 Forbidden'
@@ -781,7 +781,7 @@ describe API::Wikis do
context 'when user is guest' do
before do
- post(api(url), payload)
+ post(api(url), params: payload)
end
include_examples '404 Project Not Found'
@@ -809,7 +809,7 @@ describe API::Wikis do
context 'when user is guest' do
before do
- post(api(url), payload)
+ post(api(url), params: payload)
end
include_examples '404 Project Not Found'
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 0dc459d9b5a..939e870ec53 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -726,7 +726,7 @@ describe 'Git HTTP requests' do
let(:params) { { service: 'git-upload-pack' } }
before do
- get path, params
+ get path, params: params
end
it "redirects to the .git suffix version" do
@@ -738,7 +738,7 @@ describe 'Git HTTP requests' do
let(:params) { { service: 'git-receive-pack' } }
before do
- get path, params
+ get path, params: params
end
it "redirects to the .git suffix version" do
@@ -750,7 +750,7 @@ describe 'Git HTTP requests' do
let(:params) { { service: 'git-implode-pack' } }
before do
- get path, params
+ get path, params: params
end
it "redirects to the sign-in page" do
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index e042d772718..4bb3b848e17 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -11,7 +11,7 @@ describe JwtController do
end
context 'existing service' do
- subject! { get '/jwt/auth', parameters }
+ subject! { get '/jwt/auth', params: parameters }
it { expect(response).to have_gitlab_http_status(200) }
@@ -29,9 +29,9 @@ describe JwtController do
let(:headers) { { authorization: credentials('gitlab-ci-token', build.token) } }
context 'project with enabled CI' do
- subject! { get '/jwt/auth', parameters, headers }
+ subject! { get '/jwt/auth', params: parameters, headers: headers }
- it { expect(service_class).to have_received(:new).with(project, nil, parameters) }
+ it { expect(service_class).to have_received(:new).with(project, nil, ActionController::Parameters.new(parameters).permit!) }
end
context 'project with disabled CI' do
@@ -39,7 +39,7 @@ describe JwtController do
project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED)
end
- subject! { get '/jwt/auth', parameters, headers }
+ subject! { get '/jwt/auth', params: parameters, headers: headers }
it { expect(response).to have_gitlab_http_status(401) }
end
@@ -53,11 +53,11 @@ describe JwtController do
stub_container_registry_config(enabled: true)
end
- subject! { get '/jwt/auth', parameters, headers }
+ subject! { get '/jwt/auth', params: parameters, headers: headers }
it 'authenticates correctly' do
expect(response).to have_gitlab_http_status(200)
- expect(service_class).to have_received(:new).with(nil, user, parameters)
+ expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters).permit!)
end
end
end
@@ -66,9 +66,9 @@ describe JwtController do
let(:user) { create(:user) }
let(:headers) { { authorization: credentials(user.username, user.password) } }
- subject! { get '/jwt/auth', parameters, headers }
+ subject! { get '/jwt/auth', params: parameters, headers: headers }
- it { expect(service_class).to have_received(:new).with(nil, user, parameters) }
+ it { expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters).permit!) }
context 'when passing a flat array of scopes' do
# We use this trick to make rails to generate a query_string:
@@ -83,7 +83,7 @@ describe JwtController do
end
let(:service_parameters) do
- { service: service_name, scopes: %w(scope1 scope2) }
+ ActionController::Parameters.new({ service: service_name, scopes: %w(scope1 scope2) }).permit!
end
it { expect(service_class).to have_received(:new).with(nil, user, service_parameters) }
@@ -115,7 +115,7 @@ describe JwtController do
context 'when internal auth is enabled' do
it 'rejects the authorization attempt' do
- get '/jwt/auth', parameters, headers
+ get '/jwt/auth', params: parameters, headers: headers
expect(response).to have_gitlab_http_status(401)
expect(response.body).not_to include('You must use a personal access token with \'api\' scope for Git over HTTP')
@@ -125,7 +125,7 @@ describe JwtController do
context 'when internal auth is disabled' do
it 'rejects the authorization attempt with personal access token message' do
allow_any_instance_of(ApplicationSetting).to receive(:password_authentication_enabled_for_git?) { false }
- get '/jwt/auth', parameters, headers
+ get '/jwt/auth', params: parameters, headers: headers
expect(response).to have_gitlab_http_status(401)
expect(response.body).to include('You must use a personal access token with \'api\' scope for Git over HTTP')
@@ -136,7 +136,7 @@ describe JwtController do
context 'when using unauthenticated request' do
it 'accepts the authorization attempt' do
- get '/jwt/auth', parameters
+ get '/jwt/auth', params: parameters
expect(response).to have_gitlab_http_status(200)
end
@@ -144,12 +144,12 @@ describe JwtController do
it 'allows read access' do
expect(service).to receive(:execute).with(authentication_abilities: Gitlab::Auth.read_authentication_abilities)
- get '/jwt/auth', parameters
+ get '/jwt/auth', params: parameters
end
end
context 'unknown service' do
- subject! { get '/jwt/auth', service: 'unknown' }
+ subject! { get '/jwt/auth', params: { service: 'unknown' } }
it { expect(response).to have_gitlab_http_status(404) }
end
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index e349181b794..3cc29a7076d 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -79,7 +79,7 @@ describe 'Git LFS API and storage' do
end
it 'responds with a 501 message on download' do
- get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers
+ get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", params: {}, headers: headers
expect(response).to have_gitlab_http_status(501)
end
@@ -97,7 +97,7 @@ describe 'Git LFS API and storage' do
end
it 'responds with a 501 message on download' do
- get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers
+ get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", params: {}, headers: headers
expect(response).to have_gitlab_http_status(501)
end
@@ -123,7 +123,7 @@ describe 'Git LFS API and storage' do
end
it 'responds with a 403 message on download' do
- get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers
+ get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", params: {}, headers: headers
expect(response).to have_gitlab_http_status(403)
expect(json_response).to include('message' => 'Access forbidden. Check your access level.')
@@ -143,7 +143,7 @@ describe 'Git LFS API and storage' do
end
it 'responds with a 200 message on download' do
- get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers
+ get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", params: {}, headers: headers
expect(response).to have_gitlab_http_status(200)
end
@@ -172,7 +172,7 @@ describe 'Git LFS API and storage' do
let(:authorization) { authorize_user }
before do
- get "#{project.http_url_to_repo}/info/lfs/objects/#{sample_oid}", nil, headers
+ get "#{project.http_url_to_repo}/info/lfs/objects/#{sample_oid}", params: {}, headers: headers
end
it_behaves_like 'a deprecated'
@@ -197,7 +197,7 @@ describe 'Git LFS API and storage' do
enable_lfs
update_permissions
before_get
- get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers
+ get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", params: {}, headers: headers
end
context 'and request comes from gitlab-workhorse' do
@@ -1347,8 +1347,9 @@ describe 'Git LFS API and storage' do
context 'when pushing the same lfs object to the second project' do
before do
- put "#{second_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", nil,
- headers.merge('X-Gitlab-Lfs-Tmp' => lfs_tmp_file).compact
+ put "#{second_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}",
+ params: {},
+ headers: headers.merge('X-Gitlab-Lfs-Tmp' => lfs_tmp_file).compact
end
it 'responds with status 200' do
@@ -1366,7 +1367,7 @@ describe 'Git LFS API and storage' do
authorize_headers = headers
authorize_headers.merge!(workhorse_internal_api_request_header) if verified
- put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize", nil, authorize_headers
+ put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize", params: {}, headers: authorize_headers
end
def put_finalize(lfs_tmp = lfs_tmp_file, with_tempfile: false, args: {})
@@ -1387,7 +1388,7 @@ describe 'Git LFS API and storage' do
end
def put_finalize_with_args(args)
- put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", args, headers
+ put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", params: args, headers: headers
end
def lfs_tmp_file
@@ -1420,7 +1421,10 @@ describe 'Git LFS API and storage' do
end
def post_lfs_json(url, body = nil, headers = nil)
- post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE))
+ params = body.try(:to_json)
+ headers = (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE)
+
+ post(url, params: params, headers: headers)
end
def json_response
diff --git a/spec/requests/lfs_locks_api_spec.rb b/spec/requests/lfs_locks_api_spec.rb
index a44b43a591f..28cb90e450e 100644
--- a/spec/requests/lfs_locks_api_spec.rb
+++ b/spec/requests/lfs_locks_api_spec.rb
@@ -146,11 +146,11 @@ describe 'Git LFS File Locking API' do
end
def post_lfs_json(url, body = nil, headers = nil)
- post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE))
+ post(url, params: body.try(:to_json), headers: (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE))
end
def do_get(url, params = nil, headers = nil)
- get(url, (params || {}), (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE))
+ get(url, params: (params || {}), headers: (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE))
end
def json_response
diff --git a/spec/requests/oauth_tokens_spec.rb b/spec/requests/oauth_tokens_spec.rb
index 000c3a2b868..3873e754060 100644
--- a/spec/requests/oauth_tokens_spec.rb
+++ b/spec/requests/oauth_tokens_spec.rb
@@ -6,11 +6,13 @@ describe 'OAuth Tokens requests' do
def request_access_token(user)
post '/oauth/token',
- grant_type: 'authorization_code',
- code: generate_access_grant(user).token,
- redirect_uri: application.redirect_uri,
- client_id: application.uid,
- client_secret: application.secret
+ params: {
+ grant_type: 'authorization_code',
+ code: generate_access_grant(user).token,
+ redirect_uri: application.redirect_uri,
+ client_id: application.uid,
+ client_secret: application.secret
+ }
end
def generate_access_grant(user)
diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb
index b1cf7a531f4..2b148c1b563 100644
--- a/spec/requests/openid_connect_spec.rb
+++ b/spec/requests/openid_connect_spec.rb
@@ -47,15 +47,17 @@ describe 'OpenID Connect requests' do
login_as user
post '/oauth/token',
- grant_type: 'authorization_code',
- code: access_grant.token,
- redirect_uri: application.redirect_uri,
- client_id: application.uid,
- client_secret: application.secret
+ params: {
+ grant_type: 'authorization_code',
+ code: access_grant.token,
+ redirect_uri: application.redirect_uri,
+ client_id: application.uid,
+ client_secret: application.secret
+ }
end
def request_user_info!
- get '/oauth/userinfo', nil, 'Authorization' => "Bearer #{access_token.token}"
+ get '/oauth/userinfo', params: {}, headers: { 'Authorization' => "Bearer #{access_token.token}" }
end
context 'Application without OpenID scope' do
@@ -102,7 +104,7 @@ describe 'OpenID Connect requests' do
expect(json_response).to match(id_token_claims.merge(user_info_claims))
expected_groups = [group1.full_path, group3.full_path]
- expected_groups << group4.full_path if Group.supports_nested_groups?
+ expected_groups << group4.full_path if Group.supports_nested_objects?
expect(json_response['groups']).to match_array(expected_groups)
end
diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb
index c0a3ea397df..49021f5d1b7 100644
--- a/spec/requests/rack_attack_global_spec.rb
+++ b/spec/requests/rack_attack_global_spec.rb
@@ -177,7 +177,7 @@ describe 'Rack Attack global throttles' do
context 'when the request is to the api internal endpoints' do
it 'allows requests over the rate limit' do
(1 + requests_per_period).times do
- get url_api_internal, secret_token: Gitlab::Shell.secret_token
+ get url_api_internal, params: { secret_token: Gitlab::Shell.secret_token }
expect(response).to have_http_status 200
end
end
@@ -345,7 +345,7 @@ describe 'Rack Attack global throttles' do
end
def api_get_args_with_token_headers(partial_url, token_headers)
- ["/api/#{API::API.version}#{partial_url}", nil, token_headers]
+ ["/api/#{API::API.version}#{partial_url}", params: nil, headers: token_headers]
end
def rss_url(user)
diff --git a/spec/requests/request_profiler_spec.rb b/spec/requests/request_profiler_spec.rb
index 9afeb2983b0..284a51fcc32 100644
--- a/spec/requests/request_profiler_spec.rb
+++ b/spec/requests/request_profiler_spec.rb
@@ -18,7 +18,7 @@ describe 'Request Profiler' do
path = "/#{project.full_path}"
Timecop.freeze(time) do
- get path, nil, 'X-Profile-Token' => Gitlab::RequestProfiler.profile_token
+ get path, params: {}, headers: { 'X-Profile-Token' => Gitlab::RequestProfiler.profile_token }
end
profile_path = "#{Gitlab.config.shared.path}/tmp/requests_profiles/#{path.tr('/', '|')}_#{time.to_i}.html"
diff --git a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
new file mode 100644
index 00000000000..08ffc3c3a53
--- /dev/null
+++ b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
@@ -0,0 +1,133 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../rubocop/cop/inject_enterprise_edition_module'
+
+describe RuboCop::Cop::InjectEnterpriseEditionModule do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ it 'flags the use of `prepend EE` in the middle of a file' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ prepend 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 'flags the use of `prepend ::EE` in the middle of a file' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ prepend ::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 'flags the use of `include EE` in the middle of a file' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ include 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 'flags the use of `include ::EE` in the middle of a file' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ include ::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 'flags the use of `extend EE` in the middle of a file' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ extend 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 'flags the use of `extend ::EE` in the middle of a file' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ extend ::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 prepending of regular modules' do
+ expect_no_offenses(<<~SOURCE)
+ class Foo
+ prepend Foo
+ end
+ SOURCE
+ end
+
+ it 'does not flag including of regular modules' do
+ expect_no_offenses(<<~SOURCE)
+ class Foo
+ include Foo
+ end
+ SOURCE
+ end
+
+ it 'does not flag extending using regular modules' do
+ expect_no_offenses(<<~SOURCE)
+ class Foo
+ extend Foo
+ end
+ SOURCE
+ end
+
+ it 'does not flag the use of `prepend EE` on the last line' do
+ expect_no_offenses(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.prepend(EE::Foo)
+ SOURCE
+ end
+
+ it 'does not flag the use of `include EE` on the last line' do
+ expect_no_offenses(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.include(EE::Foo)
+ SOURCE
+ end
+
+ it 'does not flag the use of `extend EE` on the last line' do
+ expect_no_offenses(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.extend(EE::Foo)
+ SOURCE
+ end
+
+ it 'autocorrects offenses by just disabling the Cop' do
+ source = <<~SOURCE
+ class Foo
+ prepend EE::Foo
+ include Bar
+ end
+ SOURCE
+
+ expect(autocorrect_source(source)).to eq(<<~SOURCE)
+ class Foo
+ prepend EE::Foo # rubocop: disable Cop/InjectEnterpriseEditionModule
+ include Bar
+ end
+ SOURCE
+ end
+end
diff --git a/spec/rubocop/cop/migration/add_timestamps_spec.rb b/spec/rubocop/cop/migration/add_timestamps_spec.rb
index 3a41c91add2..fae0177d5f5 100644
--- a/spec/rubocop/cop/migration/add_timestamps_spec.rb
+++ b/spec/rubocop/cop/migration/add_timestamps_spec.rb
@@ -11,7 +11,7 @@ describe RuboCop::Cop::Migration::AddTimestamps do
subject(:cop) { described_class.new }
let(:migration_with_add_timestamps) do
%q(
- class Users < ActiveRecord::Migration
+ class Users < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
@@ -24,7 +24,7 @@ describe RuboCop::Cop::Migration::AddTimestamps do
let(:migration_without_add_timestamps) do
%q(
- class Users < ActiveRecord::Migration
+ class Users < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
@@ -36,7 +36,7 @@ describe RuboCop::Cop::Migration::AddTimestamps do
let(:migration_with_add_timestamps_with_timezone) do
%q(
- class Users < ActiveRecord::Migration
+ class Users < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
diff --git a/spec/rubocop/cop/migration/datetime_spec.rb b/spec/rubocop/cop/migration/datetime_spec.rb
index 9e844325371..f2d9483d8d3 100644
--- a/spec/rubocop/cop/migration/datetime_spec.rb
+++ b/spec/rubocop/cop/migration/datetime_spec.rb
@@ -12,7 +12,7 @@ describe RuboCop::Cop::Migration::Datetime do
let(:migration_with_datetime) do
%q(
- class Users < ActiveRecord::Migration
+ class Users < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
@@ -25,7 +25,7 @@ describe RuboCop::Cop::Migration::Datetime do
let(:migration_with_timestamp) do
%q(
- class Users < ActiveRecord::Migration
+ class Users < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
@@ -38,7 +38,7 @@ describe RuboCop::Cop::Migration::Datetime do
let(:migration_without_datetime) do
%q(
- class Users < ActiveRecord::Migration
+ class Users < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
@@ -50,7 +50,7 @@ describe RuboCop::Cop::Migration::Datetime do
let(:migration_with_datetime_with_timezone) do
%q(
- class Users < ActiveRecord::Migration
+ class Users < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
diff --git a/spec/rubocop/cop/migration/timestamps_spec.rb b/spec/rubocop/cop/migration/timestamps_spec.rb
index 685bdb21803..1812818692a 100644
--- a/spec/rubocop/cop/migration/timestamps_spec.rb
+++ b/spec/rubocop/cop/migration/timestamps_spec.rb
@@ -11,7 +11,7 @@ describe RuboCop::Cop::Migration::Timestamps do
subject(:cop) { described_class.new }
let(:migration_with_timestamps) do
%q(
- class Users < ActiveRecord::Migration
+ class Users < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
@@ -27,7 +27,7 @@ describe RuboCop::Cop::Migration::Timestamps do
let(:migration_without_timestamps) do
%q(
- class Users < ActiveRecord::Migration
+ class Users < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
@@ -42,7 +42,7 @@ describe RuboCop::Cop::Migration::Timestamps do
let(:migration_with_timestamps_with_timezone) do
%q(
- class Users < ActiveRecord::Migration
+ class Users < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
diff --git a/spec/serializers/diff_file_entity_spec.rb b/spec/serializers/diff_file_entity_spec.rb
index 7497b8f27bd..073c13c2cbb 100644
--- a/spec/serializers/diff_file_entity_spec.rb
+++ b/spec/serializers/diff_file_entity_spec.rb
@@ -13,39 +13,6 @@ describe DiffFileEntity do
subject { entity.as_json }
- shared_examples 'diff file entity' do
- it 'exposes correct attributes' do
- expect(subject).to include(
- :submodule, :submodule_link, :submodule_tree_url, :file_path,
- :deleted_file, :old_path, :new_path, :mode_changed,
- :a_mode, :b_mode, :text, :old_path_html,
- :new_path_html, :highlighted_diff_lines, :parallel_diff_lines,
- :blob, :file_hash, :added_lines, :removed_lines, :diff_refs, :content_sha,
- :stored_externally, :external_storage, :too_large, :collapsed, :new_file,
- :context_lines_path
- )
- end
-
- it 'includes viewer' do
- expect(subject[:viewer].with_indifferent_access)
- .to match_schema('entities/diff_viewer')
- end
-
- # Converted diff files from GitHub import does not contain blob file
- # and content sha.
- context 'when diff file does not have a blob and content sha' do
- it 'exposes some attributes as nil' do
- allow(diff_file).to receive(:content_sha).and_return(nil)
- allow(diff_file).to receive(:blob).and_return(nil)
-
- expect(subject[:context_lines_path]).to be_nil
- expect(subject[:view_path]).to be_nil
- expect(subject[:highlighted_diff_lines]).to be_nil
- expect(subject[:can_modify_blob]).to be_nil
- end
- end
- end
-
context 'when there is no merge request' do
it_behaves_like 'diff file entity'
end
diff --git a/spec/serializers/discussion_diff_file_entity_spec.rb b/spec/serializers/discussion_diff_file_entity_spec.rb
new file mode 100644
index 00000000000..101ac918a98
--- /dev/null
+++ b/spec/serializers/discussion_diff_file_entity_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe DiscussionDiffFileEntity do
+ include RepoHelpers
+
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+ let(:commit) { project.commit(sample_commit.id) }
+ let(:diff_refs) { commit.diff_refs }
+ let(:diff) { commit.raw_diffs.first }
+ let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) }
+ let(:entity) { described_class.new(diff_file, request: {}) }
+
+ subject { entity.as_json }
+
+ context 'when there is no merge request' do
+ it_behaves_like 'diff file discussion entity'
+ end
+
+ context 'when there is a merge request' do
+ let(:user) { create(:user) }
+ let(:request) { EntityRequest.new(project: project, current_user: user) }
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+ let(:entity) { described_class.new(diff_file, request: request, merge_request: merge_request) }
+
+ it_behaves_like 'diff file discussion entity'
+
+ it 'exposes additional attributes' do
+ expect(subject).to include(:edit_path)
+ end
+
+ it 'exposes no diff lines' do
+ expect(subject).not_to include(:highlighted_diff_lines,
+ :parallel_diff_lines)
+ end
+ end
+end
diff --git a/spec/serializers/discussion_entity_spec.rb b/spec/serializers/discussion_entity_spec.rb
index 0590304e832..138749b0fdf 100644
--- a/spec/serializers/discussion_entity_spec.rb
+++ b/spec/serializers/discussion_entity_spec.rb
@@ -74,13 +74,5 @@ describe DiscussionEntity do
:active
)
end
-
- context 'when diff file is a image' do
- it 'exposes image attributes' do
- allow(discussion).to receive(:on_image?).and_return(true)
-
- expect(subject.keys).to include(:image_diff_html)
- end
- end
end
end
diff --git a/spec/serializers/entity_date_helper_spec.rb b/spec/serializers/entity_date_helper_spec.rb
index 36da8d33a44..ae0f917415c 100644
--- a/spec/serializers/entity_date_helper_spec.rb
+++ b/spec/serializers/entity_date_helper_spec.rb
@@ -50,7 +50,7 @@ describe EntityDateHelper do
end
context 'when less than 31 days remaining' do
- let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, due_date: 12.days.from_now.utc)) }
+ let(:milestone_remaining) { date_helper_class.remaining_days_in_words(12.days.from_now.utc.to_date) }
it 'returns days remaining' do
expect(milestone_remaining).to eq("<strong>12</strong> days remaining")
@@ -58,7 +58,7 @@ describe EntityDateHelper do
end
context 'when less than 1 year and more than 30 days remaining' do
- let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, due_date: 2.months.from_now.utc)) }
+ let(:milestone_remaining) { date_helper_class.remaining_days_in_words(2.months.from_now.utc.to_date) }
it 'returns months remaining' do
expect(milestone_remaining).to eq("<strong>2</strong> months remaining")
@@ -66,7 +66,7 @@ describe EntityDateHelper do
end
context 'when more than 1 year remaining' do
- let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, due_date: (1.year.from_now + 2.days).utc)) }
+ let(:milestone_remaining) { date_helper_class.remaining_days_in_words((1.year.from_now + 2.days).utc.to_date) }
it 'returns years remaining' do
expect(milestone_remaining).to eq("<strong>1</strong> year remaining")
@@ -74,7 +74,7 @@ describe EntityDateHelper do
end
context 'when milestone is expired' do
- let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, due_date: 2.days.ago.utc)) }
+ let(:milestone_remaining) { date_helper_class.remaining_days_in_words(2.days.ago.utc.to_date) }
it 'returns "Past due"' do
expect(milestone_remaining).to eq("<strong>Past due</strong>")
@@ -82,7 +82,7 @@ describe EntityDateHelper do
end
context 'when milestone has start_date in the future' do
- let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, start_date: 2.days.from_now.utc)) }
+ let(:milestone_remaining) { date_helper_class.remaining_days_in_words(nil, 2.days.from_now.utc.to_date) }
it 'returns "Upcoming"' do
expect(milestone_remaining).to eq("<strong>Upcoming</strong>")
@@ -90,7 +90,7 @@ describe EntityDateHelper do
end
context 'when milestone has start_date in the past' do
- let(:milestone_remaining) { date_helper_class.remaining_days_in_words(build_stubbed(:milestone, start_date: 2.days.ago.utc)) }
+ let(:milestone_remaining) { date_helper_class.remaining_days_in_words(nil, 2.days.ago.utc.to_date) }
it 'returns days elapsed' do
expect(milestone_remaining).to eq("<strong>2</strong> days elapsed")
diff --git a/spec/serializers/environment_entity_spec.rb b/spec/serializers/environment_entity_spec.rb
index b7324a26ed2..791b64dc356 100644
--- a/spec/serializers/environment_entity_spec.rb
+++ b/spec/serializers/environment_entity_spec.rb
@@ -40,4 +40,34 @@ describe EnvironmentEntity do
expect(subject).to include(:metrics_path)
end
end
+
+ context 'with deployment platform' do
+ let(:project) { create(:project, :repository) }
+ let(:environment) { create(:environment, project: project) }
+
+ context 'when deployment platform is a cluster' do
+ before do
+ create(:cluster,
+ :provided_by_gcp,
+ :project,
+ environment_scope: '*',
+ projects: [project])
+ end
+
+ it 'should include cluster_type' do
+ expect(subject).to include(:cluster_type)
+ expect(subject[:cluster_type]).to eq('project_type')
+ end
+ end
+
+ context 'when deployment platform is a Kubernetes Service' do
+ before do
+ create(:kubernetes_service, project: project)
+ end
+
+ it 'should not include cluster_type' do
+ expect(subject).not_to include(:cluster_type)
+ end
+ end
+ end
end
diff --git a/spec/serializers/issue_board_entity_spec.rb b/spec/serializers/issue_board_entity_spec.rb
index 06d9d3657e6..f6fa2a794f6 100644
--- a/spec/serializers/issue_board_entity_spec.rb
+++ b/spec/serializers/issue_board_entity_spec.rb
@@ -3,21 +3,40 @@
require 'spec_helper'
describe IssueBoardEntity do
- let(:project) { create(:project) }
- let(:resource) { create(:issue, project: project) }
- let(:user) { create(:user) }
-
- let(:request) { double('request', current_user: user) }
+ let(:project) { create(:project) }
+ let(:resource) { create(:issue, project: project) }
+ let(:user) { create(:user) }
+ let(:milestone) { create(:milestone, project: project) }
+ let(:label) { create(:label, project: project, title: 'Test Label') }
+ let(:request) { double('request', current_user: user) }
subject { described_class.new(resource, request: request).as_json }
it 'has basic attributes' do
expect(subject).to include(:id, :iid, :title, :confidential, :due_date, :project_id, :relative_position,
- :project, :labels)
+ :labels, :assignees, project: hash_including(:id, :path))
end
it 'has path and endpoints' do
expect(subject).to include(:reference_path, :real_path, :issue_sidebar_endpoint,
:toggle_subscription_endpoint, :assignable_labels_endpoint)
end
+
+ it 'has milestone attributes' do
+ resource.milestone = milestone
+
+ expect(subject).to include(milestone: hash_including(:id, :title))
+ end
+
+ it 'has assignee attributes' do
+ resource.assignees = [user]
+
+ expect(subject).to include(assignees: array_including(hash_including(:id, :name, :username, :avatar_url)))
+ end
+
+ it 'has label attributes' do
+ resource.labels = [label]
+
+ expect(subject).to include(labels: array_including(hash_including(:id, :title, :color, :description, :text_color, :priority)))
+ end
end
diff --git a/spec/serializers/issue_serializer_spec.rb b/spec/serializers/issue_serializer_spec.rb
index e8c46c0cdee..b8255e004d0 100644
--- a/spec/serializers/issue_serializer_spec.rb
+++ b/spec/serializers/issue_serializer_spec.rb
@@ -20,11 +20,19 @@ describe IssueSerializer do
context 'sidebar issue serialization' do
let(:serializer) { 'sidebar' }
- it 'matches sidebar issue json schema' do
+ it 'matches issue_sidebar json schema' do
expect(json_entity).to match_schema('entities/issue_sidebar')
end
end
+ context 'sidebar extras issue serialization' do
+ let(:serializer) { 'sidebar_extras' }
+
+ it 'matches issue_sidebar_extras json schema' do
+ expect(json_entity).to match_schema('entities/issue_sidebar_extras')
+ end
+ end
+
context 'board issue serialization' do
let(:serializer) { 'board' }
diff --git a/spec/serializers/merge_request_basic_serializer_spec.rb b/spec/serializers/merge_request_basic_serializer_spec.rb
deleted file mode 100644
index 1fad8e6bc5d..00000000000
--- a/spec/serializers/merge_request_basic_serializer_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-require 'spec_helper'
-
-describe MergeRequestBasicSerializer do
- let(:resource) { create(:merge_request) }
- let(:user) { create(:user) }
-
- let(:json_entity) do
- described_class.new(current_user: user)
- .represent(resource, serializer: 'basic')
- .with_indifferent_access
- end
-
- it 'matches basic merge request json' do
- expect(json_entity).to match_schema('entities/merge_request_basic')
- end
-end
diff --git a/spec/serializers/merge_request_serializer_spec.rb b/spec/serializers/merge_request_serializer_spec.rb
index b259cb92962..276e0f6ff3d 100644
--- a/spec/serializers/merge_request_serializer_spec.rb
+++ b/spec/serializers/merge_request_serializer_spec.rb
@@ -20,8 +20,16 @@ describe MergeRequestSerializer do
context 'sidebar merge request serialization' do
let(:serializer) { 'sidebar' }
- it 'matches basic merge request json schema' do
- expect(json_entity).to match_schema('entities/merge_request_basic')
+ it 'matches merge_request_sidebar json schema' do
+ expect(json_entity).to match_schema('entities/merge_request_sidebar')
+ end
+ end
+
+ context 'sidebar_extras merge request serialization' do
+ let(:serializer) { 'sidebar_extras' }
+
+ it 'matches merge_request_sidebar_extras json schema' do
+ expect(json_entity).to match_schema('entities/merge_request_sidebar_extras')
end
end
diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb
index e67d12b7a89..774486dcb6d 100644
--- a/spec/serializers/pipeline_entity_spec.rb
+++ b/spec/serializers/pipeline_entity_spec.rb
@@ -44,7 +44,7 @@ describe PipelineEntity do
expect(subject).to include :flags
expect(subject[:flags])
.to include :latest, :stuck, :auto_devops,
- :yaml_errors, :retryable, :cancelable
+ :yaml_errors, :retryable, :cancelable, :merge_request
end
end
diff --git a/spec/serializers/suggestion_entity_spec.rb b/spec/serializers/suggestion_entity_spec.rb
new file mode 100644
index 00000000000..047571f161c
--- /dev/null
+++ b/spec/serializers/suggestion_entity_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe SuggestionEntity do
+ include RepoHelpers
+
+ let(:user) { create(:user) }
+ let(:request) { double('request', current_user: user) }
+ let(:suggestion) { create(:suggestion) }
+ let(:entity) { described_class.new(suggestion, request: request) }
+
+ subject { entity.as_json }
+
+ it 'exposes correct attributes' do
+ expect(subject).to include(:id, :from_original_line, :to_original_line, :from_line,
+ :to_line, :appliable, :applied, :from_content, :to_content)
+ end
+
+ it 'exposes current user abilities' do
+ expect(subject[:current_user]).to include(:can_apply)
+ end
+end
diff --git a/spec/serializers/trigger_variable_entity_spec.rb b/spec/serializers/trigger_variable_entity_spec.rb
new file mode 100644
index 00000000000..66567c05f52
--- /dev/null
+++ b/spec/serializers/trigger_variable_entity_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe TriggerVariableEntity do
+ let(:project) { create(:project) }
+ let(:request) { double('request') }
+ let(:user) { create(:user) }
+ let(:variable) { { key: 'TEST_KEY', value: 'TEST_VALUE' } }
+
+ subject { described_class.new(variable, request: request).as_json }
+
+ before do
+ allow(request).to receive(:current_user).and_return(user)
+ allow(request).to receive(:project).and_return(project)
+ end
+
+ it 'exposes the variable key' do
+ expect(subject).to include(:key)
+ end
+
+ context 'when user has access to the value' do
+ context 'when user is maintainer' do
+ before do
+ project.team.add_maintainer(user)
+ end
+
+ it 'exposes the variable value' do
+ expect(subject).to include(:value)
+ end
+ end
+
+ context 'when user is owner' do
+ let(:user) { project.owner }
+
+ it 'exposes the variable value' do
+ expect(subject).to include(:value)
+ end
+ end
+ end
+
+ context 'when user does not have access to the value' do
+ before do
+ project.team.add_developer(user)
+ end
+
+ it 'does not expose the variable value' do
+ expect(subject).not_to include(:value)
+ end
+ end
+end
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index ccc6b0ef1c7..ffa47d527f7 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -810,6 +810,95 @@ describe Ci::CreatePipelineService do
end
end
end
+
+ context "when config uses regular expression for only keyword" do
+ let(:config) do
+ {
+ build: {
+ stage: 'build',
+ script: 'echo',
+ only: ["/^#{ref_name}$/"]
+ }
+ }
+ end
+
+ context 'when merge request is specified' do
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: ref_name,
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ it 'does not create a 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 config uses variables for only keyword" do
+ let(:config) do
+ {
+ build: {
+ stage: 'build',
+ script: 'echo',
+ only: {
+ variables: %w($CI)
+ }
+ }
+ }
+ end
+
+ context 'when merge request is specified' do
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: ref_name,
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ it 'does not create a 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 config has 'except: [tags]'" do
+ let(:config) do
+ {
+ build: {
+ stage: 'build',
+ script: 'echo',
+ except: ['tags']
+ }
+ }
+ end
+
+ context 'when merge request is specified' do
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: ref_name,
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ it 'does not create a 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
end
context 'when source is web' do
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index e779675744c..87185891470 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -20,9 +20,9 @@ describe Ci::RetryBuildService do
CLONE_ACCESSORS = described_class::CLONE_ACCESSORS
REJECT_ACCESSORS =
- %i[id status user token coverage trace runner artifacts_expire_at
- artifacts_file artifacts_metadata artifacts_size created_at
- updated_at started_at finished_at queued_at erased_by
+ %i[id status user token token_encrypted coverage trace runner
+ artifacts_expire_at artifacts_file artifacts_metadata artifacts_size
+ created_at updated_at started_at finished_at queued_at erased_by
erased_at auto_canceled_by job_artifacts job_artifacts_archive
job_artifacts_metadata job_artifacts_trace job_artifacts_junit
job_artifacts_sast job_artifacts_dependency_scanning
diff --git a/spec/services/clusters/applications/create_service_spec.rb b/spec/services/clusters/applications/create_service_spec.rb
index 0bd7719345e..1a2ca23748a 100644
--- a/spec/services/clusters/applications/create_service_spec.rb
+++ b/spec/services/clusters/applications/create_service_spec.rb
@@ -31,6 +31,31 @@ describe Clusters::Applications::CreateService do
subject
end
+ context 'cert manager application' do
+ let(:params) do
+ {
+ application: 'cert_manager',
+ email: 'test@example.com'
+ }
+ end
+
+ before do
+ allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute)
+ end
+
+ it 'creates the application' do
+ expect do
+ subject
+
+ cluster.reload
+ end.to change(cluster, :application_cert_manager)
+ end
+
+ it 'sets the email' do
+ expect(subject.email).to eq('test@example.com')
+ end
+ end
+
context 'jupyter application' do
let(:params) do
{
diff --git a/spec/services/clusters/build_service_spec.rb b/spec/services/clusters/build_service_spec.rb
new file mode 100644
index 00000000000..da0cb42b3a1
--- /dev/null
+++ b/spec/services/clusters/build_service_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::BuildService do
+ describe '#execute' do
+ subject { described_class.new(cluster_subject).execute }
+
+ describe 'when cluster subject is a project' do
+ let(:cluster_subject) { build(:project) }
+
+ it 'sets the cluster_type to project_type' do
+ is_expected.to be_project_type
+ end
+ end
+
+ describe 'when cluster subject is a group' do
+ let(:cluster_subject) { build(:group) }
+
+ it 'sets the cluster_type to group_type' do
+ is_expected.to be_group_type
+ end
+ end
+ end
+end
diff --git a/spec/services/clusters/gcp/finalize_creation_service_spec.rb b/spec/services/clusters/gcp/finalize_creation_service_spec.rb
index d69678c1277..2664649df47 100644
--- a/spec/services/clusters/gcp/finalize_creation_service_spec.rb
+++ b/spec/services/clusters/gcp/finalize_creation_service_spec.rb
@@ -20,7 +20,7 @@ describe Clusters::Gcp::FinalizeCreationService, '#execute' do
subject { described_class.new.execute(provider) }
before do
- allow(ClusterPlatformConfigureWorker).to receive(:perform_async)
+ allow(ClusterConfigureWorker).to receive(:perform_async)
end
shared_examples 'success' do
@@ -43,8 +43,8 @@ describe Clusters::Gcp::FinalizeCreationService, '#execute' do
expect(platform.token).to eq(token)
end
- it 'calls ClusterPlatformConfigureWorker in a ascync fashion' do
- expect(ClusterPlatformConfigureWorker).to receive(:perform_async).with(cluster.id)
+ it 'calls ClusterConfigureWorker in a ascync fashion' do
+ expect(ClusterConfigureWorker).to receive(:perform_async).with(cluster.id)
subject
end
diff --git a/spec/services/clusters/update_service_spec.rb b/spec/services/clusters/update_service_spec.rb
index 73f9be242a3..b2e6ebecd4a 100644
--- a/spec/services/clusters/update_service_spec.rb
+++ b/spec/services/clusters/update_service_spec.rb
@@ -37,7 +37,7 @@ describe Clusters::UpdateService do
end
before do
- allow(ClusterPlatformConfigureWorker).to receive(:perform_async)
+ allow(ClusterConfigureWorker).to receive(:perform_async)
stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace')
end
diff --git a/spec/services/create_release_service_spec.rb b/spec/services/create_release_service_spec.rb
index ac0a0458f56..1a2dd0b39ee 100644
--- a/spec/services/create_release_service_spec.rb
+++ b/spec/services/create_release_service_spec.rb
@@ -6,6 +6,8 @@ describe CreateReleaseService do
let(:tag_name) { project.repository.tag_names.first }
let(:description) { 'Awesome release!' }
let(:service) { described_class.new(project, user) }
+ let(:tag) { project.repository.find_tag(tag_name) }
+ let(:sha) { tag.dereferenced_target.sha }
it 'creates a new release' do
result = service.execute(tag_name, description)
@@ -13,6 +15,9 @@ describe CreateReleaseService do
release = project.releases.find_by(tag: tag_name)
expect(release).not_to be_nil
expect(release.description).to eq(description)
+ expect(release.name).to eq(tag_name)
+ expect(release.sha).to eq(sha)
+ expect(release.author).to eq(user)
end
it 'raises an error if the tag does not exist' do
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
index 224e933bebc..fe6a8691ae0 100644
--- a/spec/services/groups/create_service_spec.rb
+++ b/spec/services/groups/create_service_spec.rb
@@ -55,7 +55,7 @@ describe Groups::CreateService, '#execute' do
context 'when nested groups feature is disabled' do
it 'does not save group and returns an error' do
- allow(Group).to receive(:supports_nested_groups?).and_return(false)
+ allow(Group).to receive(:supports_nested_objects?).and_return(false)
is_expected.not_to be_persisted
expect(subject.errors[:parent_id]).to include('You don’t have permission to create a subgroup in this group.')
@@ -66,7 +66,7 @@ describe Groups::CreateService, '#execute' do
context 'when nested groups feature is enabled' do
before do
- allow(Group).to receive(:supports_nested_groups?).and_return(true)
+ allow(Group).to receive(:supports_nested_objects?).and_return(true)
end
context 'as guest' do
diff --git a/spec/services/groups/nested_create_service_spec.rb b/spec/services/groups/nested_create_service_spec.rb
index 86fdd43c1e5..75d6ddb0a2c 100644
--- a/spec/services/groups/nested_create_service_spec.rb
+++ b/spec/services/groups/nested_create_service_spec.rb
@@ -30,7 +30,7 @@ describe Groups::NestedCreateService do
let(:params) { { group_path: 'a-group' } }
before do
- allow(Group).to receive(:supports_nested_groups?) { false }
+ allow(Group).to receive(:supports_nested_objects?) { false }
end
it 'creates the group' do
diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb
index dd8a1cee074..6b48c993c57 100644
--- a/spec/services/groups/transfer_service_spec.rb
+++ b/spec/services/groups/transfer_service_spec.rb
@@ -9,7 +9,7 @@ describe Groups::TransferService, :postgresql do
shared_examples 'ensuring allowed transfer for a group' do
context 'with other database than PostgreSQL' do
before do
- allow(Group).to receive(:supports_nested_groups?).and_return(false)
+ allow(Group).to receive(:supports_nested_objects?).and_return(false)
end
it 'should return false' do
diff --git a/spec/services/issuable/common_system_notes_service_spec.rb b/spec/services/issuable/common_system_notes_service_spec.rb
index fa1a421d528..fa5d5ebac5c 100644
--- a/spec/services/issuable/common_system_notes_service_spec.rb
+++ b/spec/services/issuable/common_system_notes_service_spec.rb
@@ -5,7 +5,7 @@ describe Issuable::CommonSystemNotesService do
let(:project) { create(:project) }
let(:issuable) { create(:issue) }
- describe '#execute' do
+ context 'on issuable update' do
it_behaves_like 'system note creation', { title: 'New title' }, 'changed title'
it_behaves_like 'system note creation', { description: 'New description' }, 'changed the description'
it_behaves_like 'system note creation', { discussion_locked: true }, 'locked this issue'
@@ -20,7 +20,7 @@ describe Issuable::CommonSystemNotesService do
end
it 'creates a resource label event' do
- described_class.new(project, user).execute(issuable, [])
+ described_class.new(project, user).execute(issuable, old_labels: [])
event = issuable.reload.resource_label_events.last
expect(event).not_to be_nil
@@ -68,4 +68,47 @@ describe Issuable::CommonSystemNotesService do
end
end
end
+
+ context 'on issuable create' do
+ let(:issuable) { build(:issue) }
+
+ subject { described_class.new(project, user).execute(issuable, old_labels: [], is_update: false) }
+
+ it 'does not create system note for title and description' do
+ issuable.save
+
+ expect { subject }.not_to change { issuable.notes.count }
+ end
+
+ it 'creates a resource label event for labels added' do
+ label = create(:label, project: project)
+
+ issuable.labels << label
+ issuable.save
+
+ expect { subject }.to change { issuable.resource_label_events.count }.from(0).to(1)
+
+ event = issuable.reload.resource_label_events.last
+
+ expect(event).not_to be_nil
+ expect(event.label_id).to eq label.id
+ expect(event.user_id).to eq user.id
+ end
+
+ it 'creates a system note for milestone set' do
+ issuable.milestone = create(:milestone, project: project)
+ issuable.save
+
+ expect { subject }.to change { issuable.notes.count }.from(0).to(1)
+ expect(issuable.notes.last.note).to match('changed milestone')
+ end
+
+ it 'creates a system note for due_date set' do
+ issuable.due_date = Date.today
+ issuable.save
+
+ expect { subject }.to change { issuable.notes.count }.from(0).to(1)
+ expect(issuable.notes.last.note).to match('changed due date')
+ end
+ end
end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index d29a1091d95..1d9c75dedce 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -621,4 +621,77 @@ describe MergeRequests::RefreshService do
@fork_build_failed_todo.reload
end
end
+
+ describe 'updating merge_commit' do
+ let(:service) { described_class.new(project, user) }
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+
+ let(:oldrev) { TestEnv::BRANCH_SHA['merge-commit-analyze-before'] }
+ let(:newrev) { TestEnv::BRANCH_SHA['merge-commit-analyze-after'] } # Pretend branch is now updated
+
+ let!(:merge_request) do
+ create(
+ :merge_request,
+ source_project: project,
+ source_branch: 'merge-commit-analyze-after',
+ target_branch: 'merge-commit-analyze-before',
+ target_project: project,
+ merge_user: user
+ )
+ end
+
+ let!(:merge_request_side_branch) do
+ create(
+ :merge_request,
+ source_project: project,
+ source_branch: 'merge-commit-analyze-side-branch',
+ target_branch: 'merge-commit-analyze-before',
+ target_project: project,
+ merge_user: user
+ )
+ end
+
+ subject { service.execute(oldrev, newrev, 'refs/heads/merge-commit-analyze-before') }
+
+ context 'feature enabled' do
+ before do
+ stub_feature_flags(branch_push_merge_commit_analyze: true)
+ end
+
+ it "updates merge requests' merge_commits" do
+ expect(Gitlab::BranchPushMergeCommitAnalyzer).to receive(:new).and_wrap_original do |original_method, commits|
+ expect(commits.map(&:id)).to eq(%w{646ece5cfed840eca0a4feb21bcd6a81bb19bda3 29284d9bcc350bcae005872d0be6edd016e2efb5 5f82584f0a907f3b30cfce5bb8df371454a90051 8a994512e8c8f0dfcf22bb16df6e876be7a61036 689600b91aabec706e657e38ea706ece1ee8268f db46a1c5a5e474aa169b6cdb7a522d891bc4c5f9})
+
+ original_method.call(commits)
+ end
+
+ subject
+
+ merge_request.reload
+ merge_request_side_branch.reload
+
+ expect(merge_request.merge_commit.id).to eq('646ece5cfed840eca0a4feb21bcd6a81bb19bda3')
+ expect(merge_request_side_branch.merge_commit.id).to eq('29284d9bcc350bcae005872d0be6edd016e2efb5')
+ end
+ end
+
+ context 'when feature is disabled' do
+ before do
+ stub_feature_flags(branch_push_merge_commit_analyze: false)
+ end
+
+ it "does not trigger analysis" do
+ expect(Gitlab::BranchPushMergeCommitAnalyzer).not_to receive(:new)
+
+ subject
+
+ merge_request.reload
+ merge_request_side_branch.reload
+
+ expect(merge_request.merge_commit).to eq(nil)
+ expect(merge_request_side_branch.merge_commit).to eq(nil)
+ end
+ end
+ end
end
diff --git a/spec/services/notes/update_service_spec.rb b/spec/services/notes/update_service_spec.rb
index 533dcdcd6cd..fd9bff46a06 100644
--- a/spec/services/notes/update_service_spec.rb
+++ b/spec/services/notes/update_service_spec.rb
@@ -20,6 +20,29 @@ describe Notes::UpdateService do
@note.reload
end
+ context 'suggestions' do
+ it 'refreshes note suggestions' do
+ markdown = <<-MARKDOWN.strip_heredoc
+ ```suggestion
+ foo
+ ```
+
+ ```suggestion
+ bar
+ ```
+ MARKDOWN
+
+ suggestion = create(:suggestion)
+ note = suggestion.note
+
+ expect { described_class.new(project, user, note: markdown).execute(note) }
+ .to change { note.suggestions.count }.from(1).to(2)
+
+ expect(note.suggestions.order(:relative_order).map(&:to_content))
+ .to eq([" foo\n", " bar\n"])
+ end
+ end
+
context 'todos' do
let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) }
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 2d8da7673dc..d20e712d365 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -2146,6 +2146,60 @@ describe NotificationService, :mailer do
end
end
+ describe 'Repository cleanup' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ describe '#repository_cleanup_success' do
+ it 'emails the specified user only' do
+ notification.repository_cleanup_success(project, user)
+
+ should_email(user)
+ end
+ end
+
+ describe '#repository_cleanup_failure' do
+ it 'emails the specified user only' do
+ notification.repository_cleanup_failure(project, user, 'Some error')
+
+ should_email(user)
+ end
+ end
+ end
+
+ context 'Remote mirror notifications' do
+ describe '#remote_mirror_update_failed' do
+ let(:project) { create(:project) }
+ let(:remote_mirror) { create(:remote_mirror, project: project) }
+ let(:u_blocked) { create(:user, :blocked) }
+ let(:u_silence) { create_user_with_notification(:disabled, 'silent-maintainer', project) }
+ let(:u_owner) { project.owner }
+ let(:u_maintainer1) { create(:user) }
+ let(:u_maintainer2) { create(:user) }
+ let(:u_developer) { create(:user) }
+
+ before do
+ project.add_maintainer(u_blocked)
+ project.add_maintainer(u_silence)
+ project.add_maintainer(u_maintainer1)
+ project.add_maintainer(u_maintainer2)
+ project.add_developer(u_developer)
+
+ # Mock remote update
+ allow(project.repository).to receive(:async_remove_remote)
+ allow(project.repository).to receive(:add_remote)
+
+ reset_delivered_emails!
+ end
+
+ it 'emails current watching maintainers' do
+ notification.remote_mirror_update_failed(remote_mirror)
+
+ should_only_email(u_maintainer1, u_maintainer2, u_owner)
+ end
+ end
+ end
+
def build_team(project)
@u_watcher = create_global_setting_for(create(:user), :watch)
@u_participating = create_global_setting_for(create(:user), :participating)
@@ -2196,7 +2250,7 @@ describe NotificationService, :mailer do
# Creates a nested group only if supported
# to avoid errors on MySQL
def create_nested_group
- if Group.supports_nested_groups?
+ if Group.supports_nested_objects?
parent_group = create(:group, :public)
child_group = create(:group, :public, parent: parent_group)
@@ -2223,7 +2277,7 @@ describe NotificationService, :mailer do
end
def add_member_for_parent_group(user, project)
- return unless Group.supports_nested_groups?
+ return unless Group.supports_nested_objects?
project.reload
@@ -2231,13 +2285,13 @@ describe NotificationService, :mailer do
end
def should_email_nested_group_user(user, times: 1, recipients: email_recipients)
- return unless Group.supports_nested_groups?
+ return unless Group.supports_nested_objects?
should_email(user, times: 1, recipients: email_recipients)
end
def should_not_email_nested_group_user(user, recipients: email_recipients)
- return unless Group.supports_nested_groups?
+ return unless Group.supports_nested_objects?
should_not_email(user, recipients: email_recipients)
end
diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb
index b69977c812a..458cb8f1f31 100644
--- a/spec/services/preview_markdown_service_spec.rb
+++ b/spec/services/preview_markdown_service_spec.rb
@@ -19,6 +19,31 @@ describe PreviewMarkdownService do
end
end
+ describe 'suggestions' do
+ let(:params) { { text: "```suggestion\nfoo\n```", preview_suggestions: preview_suggestions } }
+ let(:service) { described_class.new(project, user, params) }
+
+ context 'when preview markdown param is present' do
+ let(:preview_suggestions) { true }
+
+ it 'returns users referenced in text' do
+ result = service.execute
+
+ expect(result[:suggestions]).to eq(['foo'])
+ end
+ end
+
+ context 'when preview markdown param is not present' do
+ let(:preview_suggestions) { false }
+
+ it 'returns users referenced in text' do
+ result = service.execute
+
+ expect(result[:suggestions]).to eq([])
+ end
+ end
+ end
+
context 'new note with quick actions' do
let(:issue) { create(:issue, project: project) }
let(:params) do
diff --git a/spec/services/projects/after_rename_service_spec.rb b/spec/services/projects/after_rename_service_spec.rb
index b4718a07204..59c08b30f9f 100644
--- a/spec/services/projects/after_rename_service_spec.rb
+++ b/spec/services/projects/after_rename_service_spec.rb
@@ -99,6 +99,17 @@ describe Projects::AfterRenameService do
expect(rugged_config['gitlab.fullpath']).to eq(project.full_path)
end
+
+ it 'updates storage location' do
+ allow(project_storage).to receive(:rename_repo).and_return(true)
+
+ described_class.new(project).execute
+
+ expect(project.project_repository).to have_attributes(
+ disk_path: project.disk_path,
+ shard_name: project.repository_storage
+ )
+ end
end
context 'using hashed storage' do
@@ -193,6 +204,15 @@ describe Projects::AfterRenameService do
expect(rugged_config['gitlab.fullpath']).to eq(project.full_path)
end
+
+ it 'updates storage location' do
+ described_class.new(project).execute
+
+ expect(project.project_repository).to have_attributes(
+ disk_path: project.disk_path,
+ shard_name: project.repository_storage
+ )
+ end
end
end
end
diff --git a/spec/services/projects/cleanup_service_spec.rb b/spec/services/projects/cleanup_service_spec.rb
new file mode 100644
index 00000000000..3d4587ce2a1
--- /dev/null
+++ b/spec/services/projects/cleanup_service_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe Projects::CleanupService do
+ let(:project) { create(:project, :repository, bfg_object_map: fixture_file_upload('spec/fixtures/bfg_object_map.txt')) }
+ let(:object_map) { project.bfg_object_map }
+
+ subject(:service) { described_class.new(project) }
+
+ describe '#execute' do
+ it 'runs the apply_bfg_object_map gitaly RPC' do
+ expect_next_instance_of(Gitlab::Git::RepositoryCleaner) do |cleaner|
+ expect(cleaner).to receive(:apply_bfg_object_map).with(kind_of(IO))
+ end
+
+ service.execute
+ end
+
+ it 'runs garbage collection on the repository' do
+ expect_next_instance_of(GitGarbageCollectWorker) do |worker|
+ expect(worker).to receive(:perform)
+ end
+
+ service.execute
+ end
+
+ it 'clears the repository cache' do
+ expect(project.repository).to receive(:expire_all_method_caches)
+
+ service.execute
+ end
+
+ it 'removes the object map file' do
+ service.execute
+
+ expect(object_map.exists?).to be_falsy
+ end
+
+ it 'raises an error if no object map can be found' do
+ object_map.remove!
+
+ expect { service.execute }.to raise_error(described_class::NoUploadError)
+ end
+ end
+end
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index a3d24ae312a..26e8d829345 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -2,7 +2,8 @@ require 'spec_helper'
describe Projects::ForkService do
include ProjectForksHelper
- let(:gitlab_shell) { Gitlab::Shell.new }
+ include Gitlab::ShellAdapter
+
context 'when forking a new project' do
describe 'fork by user' do
before do
@@ -235,6 +236,33 @@ describe Projects::ForkService do
end
end
+ context 'when forking with object pools' do
+ let(:fork_from_project) { create(:project, :public) }
+ let(:forker) { create(:user) }
+
+ before do
+ stub_feature_flags(object_pools: true)
+ end
+
+ context 'when no pool exists' do
+ it 'creates a new object pool' do
+ forked_project = fork_project(fork_from_project, forker)
+
+ expect(forked_project.pool_repository).to eq(fork_from_project.pool_repository)
+ end
+ end
+
+ context 'when a pool already exists' do
+ let!(:pool_repository) { create(:pool_repository, source_project: fork_from_project) }
+
+ it 'joins the object pool' do
+ forked_project = fork_project(fork_from_project, forker)
+
+ expect(forked_project.pool_repository).to eq(fork_from_project.pool_repository)
+ end
+ end
+ end
+
context 'when linking fork to an existing project' do
let(:fork_from_project) { create(:project, :public) }
let(:fork_to_project) { create(:project, :public) }
diff --git a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
index 6af5bfc7689..d7d7f1874eb 100644
--- a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
@@ -54,6 +54,18 @@ describe Projects::LfsPointers::LfsDownloadService do
end
end
+ context 'when a bad URL is used' do
+ where(download_link: ['/etc/passwd', 'ftp://example.com', 'http://127.0.0.2'])
+
+ with_them do
+ it 'does not download the file' do
+ expect(subject).not_to receive(:download_and_save_file)
+
+ expect { subject.execute(oid, download_link) }.not_to change { LfsObject.count }
+ end
+ end
+ end
+
context 'when an lfs object with the same oid already exists' do
before do
create(:lfs_object, oid: 'oid')
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 132ad9a2646..766276fdba3 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -63,6 +63,15 @@ describe Projects::TransferService do
expect(rugged_config['gitlab.fullpath']).to eq "#{group.full_path}/#{project.path}"
end
+ it 'updates storage location' do
+ transfer_project(project, user, group)
+
+ expect(project.project_repository).to have_attributes(
+ disk_path: "#{group.full_path}/#{project.path}",
+ shard_name: project.repository_storage
+ )
+ end
+
context 'new group has a kubernetes cluster' do
let(:group_cluster) { create(:cluster, :group, :provided_by_gcp) }
let(:group) { group_cluster.group }
@@ -139,6 +148,17 @@ describe Projects::TransferService do
expect(service).not_to receive(:execute_system_hooks)
end
end
+
+ it 'does not update storage location' do
+ create(:project_repository, project: project)
+
+ attempt_project_transfer
+
+ expect(project.project_repository).to have_attributes(
+ disk_path: project.disk_path,
+ shard_name: project.repository_storage
+ )
+ end
end
context 'namespace -> no namespace' do
diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb
new file mode 100644
index 00000000000..3a483717756
--- /dev/null
+++ b/spec/services/suggestions/apply_service_spec.rb
@@ -0,0 +1,229 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Suggestions::ApplyService do
+ include ProjectForksHelper
+
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user, :commit_email) }
+
+ let(:position) do
+ Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 9,
+ diff_refs: merge_request.diff_refs)
+ end
+
+ let(:suggestion) do
+ create(:suggestion, note: diff_note,
+ from_content: " raise RuntimeError, \"System commands must be given as an array of strings\"\n",
+ to_content: " raise RuntimeError, 'Explosion'\n # explosion?\n")
+ end
+
+ subject { described_class.new(user) }
+
+ context 'patch is appliable' do
+ let(:expected_content) do
+ <<-CONTENT.strip_heredoc
+ require 'fileutils'
+ require 'open3'
+
+ module Popen
+ extend self
+
+ def popen(cmd, path=nil)
+ unless cmd.is_a?(Array)
+ raise RuntimeError, 'Explosion'
+ # explosion?
+ end
+
+ path ||= Dir.pwd
+
+ vars = {
+ "PWD" => path
+ }
+
+ options = {
+ chdir: path
+ }
+
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ @cmd_status = 0
+
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ @cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
+ end
+
+ return @cmd_output, @cmd_status
+ end
+ end
+ CONTENT
+ end
+
+ context 'non-fork project' do
+ let(:merge_request) do
+ create(:merge_request, source_project: project,
+ target_project: project)
+ end
+
+ let!(:diff_note) do
+ create(:diff_note_on_merge_request, noteable: merge_request,
+ position: position,
+ project: project)
+ end
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ it 'updates the file with the new contents' do
+ subject.execute(suggestion)
+
+ blob = project.repository.blob_at_branch(merge_request.source_branch,
+ position.new_path)
+
+ expect(blob.data).to eq(expected_content)
+ end
+
+ it 'returns success status' do
+ result = subject.execute(suggestion)
+
+ expect(result[:status]).to eq(:success)
+ end
+
+ it 'updates suggestion applied and commit_id columns' do
+ expect { subject.execute(suggestion) }
+ .to change(suggestion, :applied)
+ .from(false).to(true)
+ .and change(suggestion, :commit_id)
+ .from(nil)
+ end
+
+ it 'created commit has users email and name' do
+ subject.execute(suggestion)
+
+ commit = project.repository.commit
+
+ expect(user.commit_email).not_to eq(user.email)
+ expect(commit.author_email).to eq(user.commit_email)
+ expect(commit.committer_email).to eq(user.commit_email)
+ expect(commit.author_name).to eq(user.name)
+ end
+ end
+
+ context 'fork-project' do
+ let(:project) { create(:project, :public, :repository) }
+
+ let(:forked_project) do
+ fork_project_with_submodules(project, user)
+ end
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_branch: 'conflict-resolvable-fork', source_project: forked_project,
+ target_branch: 'conflict-start', target_project: project)
+ end
+
+ let!(:diff_note) do
+ create(:diff_note_on_merge_request, noteable: merge_request, position: position, project: project)
+ end
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ it 'updates file in the source project' do
+ expect(Files::UpdateService).to receive(:new)
+ .with(merge_request.source_project, user, anything)
+ .and_call_original
+
+ subject.execute(suggestion)
+ end
+ end
+ end
+
+ context 'no permission' do
+ let(:merge_request) do
+ create(:merge_request, source_project: project,
+ target_project: project)
+ end
+
+ let(:diff_note) do
+ create(:diff_note_on_merge_request, noteable: merge_request,
+ position: position,
+ project: project)
+ end
+
+ context 'user cannot write in project repo' do
+ before do
+ project.add_reporter(user)
+ end
+
+ it 'returns error' do
+ result = subject.execute(suggestion)
+
+ expect(result).to eq(message: "You are not allowed to push into this branch",
+ status: :error)
+ end
+ end
+ end
+
+ context 'patch is not appliable' do
+ let(:merge_request) do
+ create(:merge_request, source_project: project,
+ target_project: project)
+ end
+
+ let(:diff_note) do
+ create(:diff_note_on_merge_request, noteable: merge_request,
+ position: position,
+ project: project)
+ end
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ context 'suggestion was already applied' do
+ it 'returns success status' do
+ result = subject.execute(suggestion)
+
+ expect(result[:status]).to eq(:success)
+ end
+ end
+
+ context 'note is outdated' do
+ before do
+ allow(diff_note).to receive(:active?) { false }
+ end
+
+ it 'returns error message' do
+ result = subject.execute(suggestion)
+
+ expect(result).to eq(message: 'Suggestion is not appliable',
+ status: :error)
+ end
+ end
+
+ context 'suggestion was already applied' do
+ before do
+ suggestion.update!(applied: true, commit_id: 'sha')
+ end
+
+ it 'returns error message' do
+ result = subject.execute(suggestion)
+
+ expect(result).to eq(message: 'Suggestion is not appliable',
+ status: :error)
+ end
+ end
+ end
+end
diff --git a/spec/services/suggestions/create_service_spec.rb b/spec/services/suggestions/create_service_spec.rb
new file mode 100644
index 00000000000..f1142c88a69
--- /dev/null
+++ b/spec/services/suggestions/create_service_spec.rb
@@ -0,0 +1,110 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Suggestions::CreateService do
+ let(:project_with_repo) { create(:project, :repository) }
+ let(:merge_request) do
+ create(:merge_request, source_project: project_with_repo,
+ target_project: project_with_repo)
+ end
+
+ let(:position) do
+ Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 14,
+ diff_refs: merge_request.diff_refs)
+ end
+
+ let(:markdown) do
+ <<-MARKDOWN.strip_heredoc
+ ```suggestion
+ foo
+ bar
+ ```
+
+ ```
+ nothing
+ ```
+
+ ```suggestion
+ xpto
+ baz
+ ```
+
+ ```thing
+ this is not a suggestion, it's a thing
+ ```
+ MARKDOWN
+ end
+
+ subject { described_class.new(note) }
+
+ describe '#execute' do
+ context 'should not try to parse suggestions' do
+ context 'when not a diff note for merge requests' do
+ let(:note) do
+ create(:diff_note_on_commit, project: project_with_repo,
+ note: markdown)
+ end
+
+ it 'does not try to parse suggestions' do
+ expect(Banzai::SuggestionsParser).not_to receive(:parse)
+
+ subject.execute
+ end
+ end
+
+ context 'when diff note is not for text' do
+ let(:note) do
+ create(:diff_note_on_merge_request, project: project_with_repo,
+ noteable: merge_request,
+ position: position,
+ note: markdown)
+ end
+
+ it 'does not try to parse suggestions' do
+ allow(note).to receive(:on_text?) { false }
+
+ expect(Banzai::SuggestionsParser).not_to receive(:parse)
+
+ subject.execute
+ end
+ end
+ end
+
+ context 'should create suggestions' do
+ let(:note) do
+ create(:diff_note_on_merge_request, project: project_with_repo,
+ noteable: merge_request,
+ position: position,
+ note: markdown)
+ end
+
+ context 'single line suggestions' do
+ it 'persists suggestion records' do
+ expect { subject.execute }
+ .to change { note.suggestions.count }
+ .from(0)
+ .to(2)
+ end
+
+ it 'persists original from_content lines and suggested lines' do
+ subject.execute
+
+ suggestions = note.suggestions.order(:relative_order)
+
+ suggestion_1 = suggestions.first
+ suggestion_2 = suggestions.last
+
+ expect(suggestion_1).to have_attributes(from_content: " vars = {\n",
+ to_content: " foo\n bar\n")
+
+ expect(suggestion_2).to have_attributes(from_content: " vars = {\n",
+ to_content: " xpto\n baz\n")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 3fedb9ed48c..89357056c93 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -115,7 +115,7 @@ RSpec.configure do |config|
TestEnv.clean_test_path
end
- config.before(:example) do
+ config.before do
# Enable all features by default for testing
allow(Feature).to receive(:enabled?) { true }
@@ -136,11 +136,11 @@ RSpec.configure do |config|
RequestStore.clear!
end
- config.after(:example) do
+ config.after do
Fog.unmock! if Fog.mock?
end
- config.after(:example) do
+ config.after do
Gitlab::CurrentSettings.clear_in_memory_application_settings!
end
@@ -216,15 +216,19 @@ RSpec.configure do |config|
# Each example may call `migrate!`, so we must ensure we are migrated down every time
config.before(:each, :migration) do
+ use_fake_application_settings
+
schema_migrate_down!
end
config.after(:context, :migration) do
schema_migrate_up!
+
+ Gitlab::CurrentSettings.clear_in_memory_application_settings!
end
config.around(:each, :nested_groups) do |example|
- example.run if Group.supports_nested_groups?
+ example.run if Group.supports_nested_objects?
end
config.around(:each, :postgresql) do |example|
@@ -235,10 +239,6 @@ RSpec.configure do |config|
example.run if Gitlab::Database.mysql?
end
- config.around(:each, :rails5) do |example|
- example.run if Gitlab.rails5?
- end
-
# This makes sure the `ApplicationController#can?` method is stubbed with the
# original implementation for all view specs.
config.before(:each, type: :view) do
diff --git a/spec/support/api/boards_shared_examples.rb b/spec/support/api/boards_shared_examples.rb
index 943c1f6ffd7..592962ebf7c 100644
--- a/spec/support/api/boards_shared_examples.rb
+++ b/spec/support/api/boards_shared_examples.rb
@@ -88,7 +88,7 @@ shared_examples_for 'group and project boards' do |route_definition, ee = false|
let(:url) { "#{root_url}/#{board.id}/lists" }
it 'creates a new issue board list for labels' do
- post api(url, user), label_id: ux_label.id
+ post api(url, user), params: { label_id: ux_label.id }
expect(response).to have_gitlab_http_status(201)
expect(json_response['label']['name']).to eq(ux_label.title)
@@ -96,13 +96,13 @@ shared_examples_for 'group and project boards' do |route_definition, ee = false|
end
it 'returns 400 when creating a new list if label_id is invalid' do
- post api(url, user), label_id: 23423
+ post api(url, user), params: { label_id: 23423 }
expect(response).to have_gitlab_http_status(400)
end
it 'returns 403 for members with guest role' do
- put api("#{url}/#{test_list.id}", guest), position: 1
+ put api("#{url}/#{test_list.id}", guest), params: { position: 1 }
expect(response).to have_gitlab_http_status(403)
end
@@ -112,23 +112,20 @@ shared_examples_for 'group and project boards' do |route_definition, ee = false|
let(:url) { "#{root_url}/#{board.id}/lists" }
it "updates a list" do
- put api("#{url}/#{test_list.id}", user),
- position: 1
+ put api("#{url}/#{test_list.id}", user), params: { position: 1 }
expect(response).to have_gitlab_http_status(200)
expect(json_response['position']).to eq(1)
end
it "returns 404 error if list id not found" do
- put api("#{url}/44444", user),
- position: 1
+ put api("#{url}/44444", user), params: { position: 1 }
expect(response).to have_gitlab_http_status(404)
end
it "returns 403 for members with guest role" do
- put api("#{url}/#{test_list.id}", guest),
- position: 1
+ put api("#{url}/#{test_list.id}", guest), params: { position: 1 }
expect(response).to have_gitlab_http_status(403)
end
diff --git a/spec/support/api/milestones_shared_examples.rb b/spec/support/api/milestones_shared_examples.rb
index 3bebb7aae90..5f709831ce1 100644
--- a/spec/support/api/milestones_shared_examples.rb
+++ b/spec/support/api/milestones_shared_examples.rb
@@ -45,7 +45,7 @@ shared_examples_for 'group and project milestones' do |route_definition|
it 'returns an array of milestones specified by iids' do
other_milestone = create(:milestone, project: try(:project), group: try(:group))
- get api(route, user), iids: [closed_milestone.iid, other_milestone.iid]
+ get api(route, user), params: { iids: [closed_milestone.iid, other_milestone.iid] }
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
@@ -54,7 +54,7 @@ shared_examples_for 'group and project milestones' do |route_definition|
end
it 'does not return any milestone if none found' do
- get api(route, user), iids: [Milestone.maximum(:iid).succ]
+ get api(route, user), params: { iids: [Milestone.maximum(:iid).succ] }
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
@@ -73,7 +73,7 @@ shared_examples_for 'group and project milestones' do |route_definition|
end
it 'returns a milestone by searching for title' do
- get api(route, user), search: 'version2'
+ get api(route, user), params: { search: 'version2' }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -83,7 +83,7 @@ shared_examples_for 'group and project milestones' do |route_definition|
end
it 'returns a milestones by searching for description' do
- get api(route, user), search: 'open'
+ get api(route, user), params: { search: 'open' }
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
@@ -117,7 +117,7 @@ shared_examples_for 'group and project milestones' do |route_definition|
describe "POST #{route_definition}" do
it 'creates a new milestone' do
- post api(route, user), title: 'new milestone'
+ post api(route, user), params: { title: 'new milestone' }
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('new milestone')
@@ -125,8 +125,7 @@ shared_examples_for 'group and project milestones' do |route_definition|
end
it 'creates a new milestone with description and dates' do
- post api(route, user),
- title: 'new milestone', description: 'release', due_date: '2013-03-02', start_date: '2013-02-02'
+ post api(route, user), params: { title: 'new milestone', description: 'release', due_date: '2013-03-02', start_date: '2013-02-02' }
expect(response).to have_gitlab_http_status(201)
expect(json_response['description']).to eq('release')
@@ -141,14 +140,13 @@ shared_examples_for 'group and project milestones' do |route_definition|
end
it 'returns a 400 error if params are invalid (duplicate title)' do
- post api(route, user),
- title: milestone.title, description: 'release', due_date: '2013-03-02'
+ post api(route, user), params: { title: milestone.title, description: 'release', due_date: '2013-03-02' }
expect(response).to have_gitlab_http_status(400)
end
it 'creates a new milestone with reserved html characters' do
- post api(route, user), title: 'foo & bar 1.1 -> 2.2'
+ post api(route, user), params: { title: 'foo & bar 1.1 -> 2.2' }
expect(response).to have_gitlab_http_status(201)
expect(json_response['title']).to eq('foo & bar 1.1 -> 2.2')
@@ -158,8 +156,7 @@ shared_examples_for 'group and project milestones' do |route_definition|
describe "PUT #{route_definition}/:milestone_id" do
it 'updates a milestone' do
- put api(resource_route, user),
- title: 'updated title'
+ put api(resource_route, user), params: { title: 'updated title' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['title']).to eq('updated title')
@@ -168,29 +165,27 @@ shared_examples_for 'group and project milestones' do |route_definition|
it 'removes a due date if nil is passed' do
milestone.update!(due_date: "2016-08-05")
- put api(resource_route, user), due_date: nil
+ put api(resource_route, user), params: { due_date: nil }
expect(response).to have_gitlab_http_status(200)
expect(json_response['due_date']).to be_nil
end
it 'returns a 404 error if milestone id not found' do
- put api("#{route}/1234", user),
- title: 'updated title'
+ put api("#{route}/1234", user), params: { title: 'updated title' }
expect(response).to have_gitlab_http_status(404)
end
it 'closes milestone' do
- put api(resource_route, user),
- state_event: 'close'
+ put api(resource_route, user), params: { state_event: 'close' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['state']).to eq('closed')
end
it 'updates milestone with only start date' do
- put api(resource_route, user), start_date: Date.tomorrow
+ put api(resource_route, user), params: { start_date: Date.tomorrow }
expect(response).to have_gitlab_http_status(200)
end
diff --git a/spec/support/api/scopes/read_user_shared_examples.rb b/spec/support/api/scopes/read_user_shared_examples.rb
index d7cef137989..683234264a8 100644
--- a/spec/support/api/scopes/read_user_shared_examples.rb
+++ b/spec/support/api/scopes/read_user_shared_examples.rb
@@ -77,7 +77,7 @@ shared_examples_for 'does not allow the "read_user" scope' do
let(:token) { create(:personal_access_token, scopes: ['read_user'], user: user) }
it 'returns a "403" response' do
- post api_call.call(path, user, personal_access_token: token), attributes_for(:user, projects_limit: 3)
+ post api_call.call(path, user, personal_access_token: token), params: attributes_for(:user, projects_limit: 3)
expect(response).to have_gitlab_http_status(403)
end
diff --git a/spec/support/api/time_tracking_shared_examples.rb b/spec/support/api/time_tracking_shared_examples.rb
index fee464c15a3..e883d33f671 100644
--- a/spec/support/api/time_tracking_shared_examples.rb
+++ b/spec/support/api/time_tracking_shared_examples.rb
@@ -7,13 +7,13 @@ shared_examples 'time tracking endpoints' do |issuable_name|
describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_estimate" do
context 'with an unauthorized user' do
- subject { post(api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", non_member), duration: '1w') }
+ subject { post(api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", non_member), params: { duration: '1w' }) }
it_behaves_like 'an unauthorized API user'
end
it "sets the time estimate for #{issuable_name}" do
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: '1w'
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), params: { duration: '1w' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['human_time_estimate']).to eq('1w')
@@ -21,12 +21,12 @@ shared_examples 'time tracking endpoints' do |issuable_name|
describe 'updating the current estimate' do
before do
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: '1w'
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), params: { duration: '1w' }
end
context 'when duration has a bad format' do
it 'does not modify the original estimate' do
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: 'foo'
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), params: { duration: 'foo' }
expect(response).to have_gitlab_http_status(400)
expect(issuable.reload.human_time_estimate).to eq('1w')
@@ -35,7 +35,7 @@ shared_examples 'time tracking endpoints' do |issuable_name|
context 'with a valid duration' do
it 'updates the estimate' do
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), duration: '3w1h'
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/time_estimate", user), params: { duration: '3w1h' }
expect(response).to have_gitlab_http_status(200)
expect(issuable.reload.human_time_estimate).to eq('3w 1h')
@@ -62,8 +62,7 @@ shared_examples 'time tracking endpoints' do |issuable_name|
describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/add_spent_time" do
context 'with an unauthorized user' do
subject do
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", non_member),
- duration: '2h'
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", non_member), params: { duration: '2h' }
end
it_behaves_like 'an unauthorized API user'
@@ -72,8 +71,7 @@ shared_examples 'time tracking endpoints' do |issuable_name|
it "add spent time for #{issuable_name}" do
Timecop.travel(1.minute.from_now) do
expect do
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user),
- duration: '2h'
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), params: { duration: '2h' }
end.to change { issuable.reload.updated_at }
end
@@ -89,8 +87,7 @@ shared_examples 'time tracking endpoints' do |issuable_name|
end.to change { issuable.reload.updated_at }
end
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user),
- duration: '-1h'
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), params: { duration: '-1h' }
expect(response).to have_gitlab_http_status(201)
expect(json_response['total_time_spent']).to eq(3600)
@@ -103,8 +100,7 @@ shared_examples 'time tracking endpoints' do |issuable_name|
Timecop.travel(1.minute.from_now) do
expect do
- post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user),
- duration: '-1w'
+ post api("/projects/#{project.id}/#{issuable_collection_name}/#{issuable.iid}/add_spent_time", user), params: { duration: '-1w' }
end.not_to change { issuable.reload.updated_at }
end
diff --git a/spec/support/carrierwave.rb b/spec/support/carrierwave.rb
index b4b016e408f..b376822d530 100644
--- a/spec/support/carrierwave.rb
+++ b/spec/support/carrierwave.rb
@@ -1,7 +1,7 @@
CarrierWave.root = File.expand_path('tmp/tests/public', Rails.root)
RSpec.configure do |config|
- config.after(:each) do
+ config.after do
FileUtils.rm_rf(CarrierWave.root)
end
end
diff --git a/spec/support/controllers/githubish_import_controller_shared_examples.rb b/spec/support/controllers/githubish_import_controller_shared_examples.rb
index 140490f2dc2..697f999e4c4 100644
--- a/spec/support/controllers/githubish_import_controller_shared_examples.rb
+++ b/spec/support/controllers/githubish_import_controller_shared_examples.rb
@@ -17,7 +17,7 @@ shared_examples 'a GitHub-ish import controller: POST personal_access_token' do
allow_any_instance_of(Gitlab::LegacyGithubImport::Client)
.to receive(:user).and_return(true)
- post :personal_access_token, personal_access_token: token
+ post :personal_access_token, params: { personal_access_token: token }
expect(session[:"#{provider}_access_token"]).to eq(token)
expect(controller).to redirect_to(status_import_url)
@@ -29,7 +29,7 @@ shared_examples 'a GitHub-ish import controller: POST personal_access_token' do
allow_any_instance_of(Gitlab::LegacyGithubImport::Client)
.to receive(:user).and_return(true)
- post :personal_access_token, personal_access_token: " #{token} "
+ post :personal_access_token, params: { personal_access_token: " #{token} " }
expect(session[:"#{provider}_access_token"]).to eq(token)
expect(controller).to redirect_to(status_import_url)
@@ -214,7 +214,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
expect(Gitlab::LegacyGithubImport::ProjectCreator)
.to receive(:new).and_return(double(execute: project))
- expect { post :create, target_namespace: provider_repo.name, format: :json }.to change(Namespace, :count).by(1)
+ expect { post :create, params: { target_namespace: provider_repo.name }, format: :json }.to change(Namespace, :count).by(1)
end
it "takes the new namespace" do
@@ -222,7 +222,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
.to receive(:new).with(provider_repo, provider_repo.name, an_instance_of(Group), user, access_params, type: provider)
.and_return(double(execute: project))
- post :create, target_namespace: provider_repo.name, format: :json
+ post :create, params: { target_namespace: provider_repo.name }, format: :json
end
end
@@ -261,7 +261,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
.to receive(:new).with(provider_repo, test_name, test_namespace, user, access_params, type: provider)
.and_return(double(execute: project))
- post :create, { target_namespace: test_namespace.name, new_name: test_name, format: :json }
+ post :create, params: { target_namespace: test_namespace.name, new_name: test_name }, format: :json
end
it 'takes the selected name and default namespace' do
@@ -269,7 +269,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
.to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider)
.and_return(double(execute: project))
- post :create, { new_name: test_name, format: :json }
+ post :create, params: { new_name: test_name }, format: :json
end
end
@@ -288,7 +288,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
.to receive(:new).with(provider_repo, test_name, nested_namespace, user, access_params, type: provider)
.and_return(double(execute: project))
- post :create, { target_namespace: nested_namespace.full_path, new_name: test_name, format: :json }
+ post :create, params: { target_namespace: nested_namespace.full_path, new_name: test_name }, format: :json
end
end
@@ -300,7 +300,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
.to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
.and_return(double(execute: project))
- post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json }
+ post :create, params: { target_namespace: 'foo/bar', new_name: test_name }, format: :json
end
it 'creates the namespaces' do
@@ -308,7 +308,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
.to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
.and_return(double(execute: project))
- expect { post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json } }
+ expect { post :create, params: { target_namespace: 'foo/bar', new_name: test_name }, format: :json }
.to change { Namespace.count }.by(2)
end
@@ -317,7 +317,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
.to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
.and_return(double(execute: project))
- post :create, { target_namespace: 'foo/bar', new_name: test_name, format: :json }
+ post :create, params: { target_namespace: 'foo/bar', new_name: test_name }, format: :json
expect(Namespace.find_by_path_or_name('bar').parent.path).to eq('foo')
end
@@ -336,7 +336,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
.to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
.and_return(double(execute: project))
- post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :json }
+ post :create, params: { target_namespace: 'foo/foobar/bar', new_name: test_name }, format: :json
end
it 'creates the namespaces' do
@@ -344,7 +344,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
.to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
.and_return(double(execute: project))
- expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :json } }
+ expect { post :create, params: { target_namespace: 'foo/foobar/bar', new_name: test_name }, format: :json }
.to change { Namespace.count }.by(2)
end
@@ -353,7 +353,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
.to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider)
.and_return(double(execute: build_stubbed(:project)))
- expect { post :create, { target_namespace: "#{user.namespace_path}/test_group", new_name: test_name, format: :js } }
+ expect { post :create, params: { target_namespace: "#{user.namespace_path}/test_group", new_name: test_name }, format: :js }
.not_to change { Namespace.count }
end
end
@@ -367,7 +367,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
.to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider)
.and_return(double(execute: build_stubbed(:project)))
- post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js }
+ post :create, params: { target_namespace: 'foo/foobar/bar', new_name: test_name }, format: :js
end
it 'does not create the namespaces' do
@@ -375,7 +375,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
.to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
.and_return(double(execute: build_stubbed(:project)))
- expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js } }
+ expect { post :create, params: { target_namespace: 'foo/foobar/bar', new_name: test_name }, format: :js }
.not_to change { Namespace.count }
end
end
@@ -392,7 +392,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
.to receive(:new).with(provider_repo, test_name, group, user, access_params, type: provider)
.and_return(double(execute: build_stubbed(:project)))
- post :create, { target_namespace: 'foo', new_name: test_name, format: :js }
+ post :create, params: { target_namespace: 'foo', new_name: test_name }, format: :js
end
end
@@ -400,7 +400,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
it 'returns 422 response' do
other_namespace = create(:group, name: 'other_namespace')
- post :create, { target_namespace: other_namespace.name, format: :json }
+ post :create, params: { target_namespace: other_namespace.name }, format: :json
expect(response).to have_gitlab_http_status(422)
end
diff --git a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
index 7e4958f177a..355555d9d19 100644
--- a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
+++ b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
@@ -16,14 +16,14 @@ shared_examples 'authenticates sessionless user' do |path, format, params|
.and increment(:user_session_override_counter)
.and increment(:user_sessionless_authentication_counter)
- get path, default_params.merge(private_token: personal_access_token.token)
+ get path, params: default_params.merge(private_token: personal_access_token.token)
expect(response).to have_gitlab_http_status(200)
expect(controller.current_user).to eq(user)
end
it 'does not log the user in if page is public', if: params[:public] do
- get path, default_params
+ get path, params: default_params
expect(response).to have_gitlab_http_status(200)
expect(controller.current_user).to be_nil
@@ -37,7 +37,7 @@ shared_examples 'authenticates sessionless user' do |path, format, params|
personal_access_token.update(scopes: [:read_user])
- get path, default_params.merge(private_token: personal_access_token.token)
+ get path, params: default_params.merge(private_token: personal_access_token.token)
expect(response).not_to have_gitlab_http_status(200)
end
@@ -51,7 +51,7 @@ shared_examples 'authenticates sessionless user' do |path, format, params|
.and increment(:user_sessionless_authentication_counter)
@request.headers['PRIVATE-TOKEN'] = personal_access_token.token
- get path, default_params
+ get path, params: default_params
expect(response).to have_gitlab_http_status(200)
end
@@ -64,7 +64,7 @@ shared_examples 'authenticates sessionless user' do |path, format, params|
.and increment(:user_session_override_counter)
.and increment(:user_sessionless_authentication_counter)
- get path, default_params.merge(feed_token: user.feed_token)
+ get path, params: default_params.merge(feed_token: user.feed_token)
expect(response).to have_gitlab_http_status 200
end
@@ -75,7 +75,7 @@ shared_examples 'authenticates sessionless user' do |path, format, params|
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
- get path, default_params.merge(feed_token: 'token')
+ get path, params: default_params.merge(feed_token: 'token')
expect(response.status).not_to eq 200
end
@@ -85,7 +85,7 @@ shared_examples 'authenticates sessionless user' do |path, format, params|
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
- get path, default_params.merge(private_token: 'token')
+ get path, params: default_params.merge(private_token: 'token')
expect(response.status).not_to eq(200)
end
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index 5edc5de2a09..34b9efaaecd 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -23,7 +23,7 @@ RSpec.configure do |config|
DatabaseCleaner.clean_with(:deletion, cache_tables: false)
end
- config.before(:each) do
+ config.before do
DatabaseCleaner.strategy = :transaction
end
@@ -39,11 +39,11 @@ RSpec.configure do |config|
DatabaseCleaner.strategy = :deletion, { cache_tables: false }
end
- config.before(:each) do
+ config.before do
DatabaseCleaner.start
end
- config.append_after(:each) do
+ config.append_after do
DatabaseCleaner.clean
end
end
diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb
index 922f3df144d..42a086d58d2 100644
--- a/spec/support/features/discussion_comments_shared_example.rb
+++ b/spec/support/features/discussion_comments_shared_example.rb
@@ -178,6 +178,16 @@ shared_examples 'discussion comments' do |resource_name|
let(:note_id) { find("#{comments_selector} .note:first-child", match: :first)['data-note-id'] }
let(:reply_id) { find("#{comments_selector} .note:last-child", match: :first)['data-note-id'] }
+ it 'can be replied to after resolving' do
+ click_button "Resolve discussion"
+ wait_for_requests
+
+ refresh
+ wait_for_requests
+
+ submit_reply('to reply or not reply')
+ end
+
it 'shows resolved discussion when toggled' do
submit_reply('a')
diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb
index bce1fb01355..0a464d77cb7 100644
--- a/spec/support/features/variable_list_shared_examples.rb
+++ b/spec/support/features/variable_list_shared_examples.rb
@@ -63,6 +63,52 @@ shared_examples 'variable list' do
end
end
+ context 'defaults to the application setting' do
+ context 'application setting is true' do
+ before do
+ stub_application_setting(protected_ci_variables: true)
+
+ visit page_path
+ end
+
+ it 'defaults to protected' do
+ page.within('.js-ci-variable-list-section .js-row:last-child') do
+ find('.js-ci-variable-input-key').set('key')
+ end
+
+ values = all('.js-ci-variable-input-protected', visible: false).map(&:value)
+
+ expect(values).to eq %w(false true true)
+ end
+
+ it 'shows a message regarding the changed default' do
+ expect(page).to have_content 'Environment variables are configured by your administrator to be protected by default'
+ end
+ end
+
+ context 'application setting is false' do
+ before do
+ stub_application_setting(protected_ci_variables: false)
+
+ visit page_path
+ end
+
+ it 'defaults to unprotected' do
+ page.within('.js-ci-variable-list-section .js-row:last-child') do
+ find('.js-ci-variable-input-key').set('key')
+ end
+
+ values = all('.js-ci-variable-input-protected', visible: false).map(&:value)
+
+ expect(values).to eq %w(false false false)
+ end
+
+ it 'does not show a message regarding the default' do
+ expect(page).not_to have_content 'Environment variables are configured by your administrator to be protected by default'
+ end
+ end
+ end
+
it 'reveals and hides variables' do
page.within('.js-ci-variable-list-section') do
expect(first('.js-ci-variable-input-key').value).to eq(variable.key)
diff --git a/spec/support/helpers/email_helpers.rb b/spec/support/helpers/email_helpers.rb
index 1fb8252459f..ad6e1064499 100644
--- a/spec/support/helpers/email_helpers.rb
+++ b/spec/support/helpers/email_helpers.rb
@@ -34,4 +34,13 @@ module EmailHelpers
def find_email_for(user)
ActionMailer::Base.deliveries.find { |d| d.to.include?(user.notification_email) }
end
+
+ def have_referable_subject(referable, include_project: true, reply: false)
+ prefix = (include_project && referable.project ? "#{referable.project.name} | " : '').freeze
+ prefix = "Re: #{prefix}" if reply
+
+ suffix = "#{referable.title} (#{referable.to_reference})"
+
+ have_subject [prefix, suffix].compact.join
+ end
end
diff --git a/spec/support/helpers/fake_migration_classes.rb b/spec/support/helpers/fake_migration_classes.rb
index b0fc8422857..c7766df7a52 100644
--- a/spec/support/helpers/fake_migration_classes.rb
+++ b/spec/support/helpers/fake_migration_classes.rb
@@ -1,4 +1,4 @@
-class FakeRenameReservedPathMigrationV1 < ActiveRecord::Migration
+class FakeRenameReservedPathMigrationV1 < ActiveRecord::Migration[4.2]
include Gitlab::Database::RenameReservedPathsMigration::V1
def version
diff --git a/spec/support/helpers/features/sorting_helpers.rb b/spec/support/helpers/features/sorting_helpers.rb
index a1ae428586e..003ecb251fe 100644
--- a/spec/support/helpers/features/sorting_helpers.rb
+++ b/spec/support/helpers/features/sorting_helpers.rb
@@ -13,9 +13,9 @@ module Spec
module Features
module SortingHelpers
def sort_by(value)
- find('.filter-dropdown-container button.dropdown-menu-toggle').click
+ find('.filter-dropdown-container .dropdown').click
- page.within('.content ul.dropdown-menu.dropdown-menu-right li') do
+ page.within('ul.dropdown-menu.dropdown-menu-right li') do
click_link(value)
end
end
diff --git a/spec/support/helpers/git_http_helpers.rb b/spec/support/helpers/git_http_helpers.rb
index 9a5845af90c..cd49bb148f2 100644
--- a/spec/support/helpers/git_http_helpers.rb
+++ b/spec/support/helpers/git_http_helpers.rb
@@ -1,18 +1,18 @@
module GitHttpHelpers
def clone_get(project, options = {})
- get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password, :spnego_request_token))
+ get "/#{project}/info/refs", params: { service: 'git-upload-pack' }, headers: auth_env(*options.values_at(:user, :password, :spnego_request_token))
end
def clone_post(project, options = {})
- post "/#{project}/git-upload-pack", {}, auth_env(*options.values_at(:user, :password, :spnego_request_token))
+ post "/#{project}/git-upload-pack", headers: auth_env(*options.values_at(:user, :password, :spnego_request_token))
end
def push_get(project, options = {})
- get "/#{project}/info/refs", { service: 'git-receive-pack' }, auth_env(*options.values_at(:user, :password, :spnego_request_token))
+ get "/#{project}/info/refs", params: { service: 'git-receive-pack' }, headers: auth_env(*options.values_at(:user, :password, :spnego_request_token))
end
def push_post(project, options = {})
- post "/#{project}/git-receive-pack", {}, auth_env(*options.values_at(:user, :password, :spnego_request_token))
+ post "/#{project}/git-receive-pack", headers: auth_env(*options.values_at(:user, :password, :spnego_request_token))
end
def download(project, user: nil, password: nil, spnego_request_token: nil)
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index 75827df80dc..dd32ea3985f 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -107,7 +107,7 @@ module GraphqlHelpers
end
def post_graphql(query, current_user: nil, variables: nil)
- post api('/', current_user, version: 'graphql'), query: query, variables: variables
+ post api('/', current_user, version: 'graphql'), params: { query: query, variables: variables }
end
def post_graphql_mutation(mutation, current_user: nil)
diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb
index 086a345dca8..89c5ec7a718 100644
--- a/spec/support/helpers/javascript_fixtures_helpers.rb
+++ b/spec/support/helpers/javascript_fixtures_helpers.rb
@@ -6,6 +6,13 @@ module JavaScriptFixturesHelpers
FIXTURE_PATH = 'spec/javascripts/fixtures'.freeze
+ def self.included(base)
+ base.around do |example|
+ # pick an arbitrary date from the past, so tests are not time dependent
+ Timecop.freeze(Time.utc(2015, 7, 3, 10)) { example.run }
+ end
+ end
+
# Public: Removes all fixture files from given directory
#
# directory_name - directory of the fixtures (relative to FIXTURE_PATH)
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index bef951e1517..39bd305d88a 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -34,6 +34,17 @@ module KubernetesHelpers
WebMock.stub_request(:get, deployments_url).to_return(response || kube_deployments_response)
end
+ def stub_kubeclient_knative_services(**options)
+ options[:name] ||= "kubetest"
+ options[:namespace] ||= "default"
+ options[:domain] ||= "example.com"
+
+ stub_kubeclient_discover(service.api_url)
+ knative_url = service.api_url + "/apis/serving.knative.dev/v1alpha1/services"
+
+ WebMock.stub_request(:get, knative_url).to_return(kube_response(kube_knative_services_body(options)))
+ end
+
def stub_kubeclient_get_secret(api_url, **options)
options[:metadata_name] ||= "default-token-1"
options[:namespace] ||= "default"
@@ -181,6 +192,13 @@ module KubernetesHelpers
}
end
+ def kube_knative_services_body(**options)
+ {
+ "kind" => "List",
+ "items" => [kube_service(options)]
+ }
+ end
+
# This is a partial response, it will have many more elements in reality but
# these are the ones we care about at the moment
def kube_pod(name: "kube-pod", app: "valid-pod-label", status: "Running", track: nil)
@@ -224,6 +242,54 @@ module KubernetesHelpers
}
end
+ def kube_service(name: "kubetest", namespace: "default", domain: "example.com")
+ {
+ "metadata" => {
+ "creationTimestamp" => "2018-11-21T06:16:33Z",
+ "name" => name,
+ "namespace" => namespace,
+ "selfLink" => "/apis/serving.knative.dev/v1alpha1/namespaces/#{namespace}/services/#{name}"
+ },
+ "spec" => {
+ "generation" => 2
+ },
+ "status" => {
+ "domain" => "#{name}.#{namespace}.#{domain}",
+ "domainInternal" => "#{name}.#{namespace}.svc.cluster.local",
+ "latestCreatedRevisionName" => "#{name}-00002",
+ "latestReadyRevisionName" => "#{name}-00002",
+ "observedGeneration" => 2
+ }
+ }
+ end
+
+ def kube_service_full(name: "kubetest", namespace: "kube-ns", domain: "example.com")
+ {
+ "metadata" => {
+ "creationTimestamp" => "2018-11-21T06:16:33Z",
+ "name" => name,
+ "namespace" => namespace,
+ "selfLink" => "/apis/serving.knative.dev/v1alpha1/namespaces/#{namespace}/services/#{name}",
+ "annotation" => {
+ "description" => "This is a test description"
+ }
+ },
+ "spec" => {
+ "generation" => 2,
+ "build" => {
+ "template" => "go-1.10.3"
+ }
+ },
+ "status" => {
+ "domain" => "#{name}.#{namespace}.#{domain}",
+ "domainInternal" => "#{name}.#{namespace}.svc.cluster.local",
+ "latestCreatedRevisionName" => "#{name}-00002",
+ "latestReadyRevisionName" => "#{name}-00002",
+ "observedGeneration" => 2
+ }
+ }
+ end
+
def kube_terminals(service, pod)
pod_name = pod['metadata']['name']
containers = pod['spec']['containers']
diff --git a/spec/support/helpers/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb
index 5887c3eab74..cc1a28cb264 100644
--- a/spec/support/helpers/migrations_helpers.rb
+++ b/spec/support/helpers/migrations_helpers.rb
@@ -62,6 +62,22 @@ module MigrationsHelpers
klass.reset_column_information
end
+ # In some migration tests, we're using factories to create records,
+ # however those models might be depending on a schema version which
+ # doesn't have the columns we want in application_settings.
+ # In these cases, we'll need to use the fake application settings
+ # as if we have migrations pending
+ def use_fake_application_settings
+ # We stub this way because we can't stub on
+ # `current_application_settings` due to `method_missing` is
+ # depending on current_application_settings...
+ allow(ActiveRecord::Base.connection)
+ .to receive(:active?)
+ .and_return(false)
+
+ stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+ end
+
def previous_migration
migrations.each_cons(2) do |previous, migration|
break previous if migration.name == described_class.name
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 1f00cdf7e92..d52c40ff4f1 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -54,6 +54,9 @@ module TestEnv
'add_images_and_changes' => '010d106',
'update-gitlab-shell-v-6-0-1' => '2f61d70',
'update-gitlab-shell-v-6-0-3' => 'de78448',
+ 'merge-commit-analyze-before' => '1adbdef',
+ 'merge-commit-analyze-side-branch' => '8a99451',
+ 'merge-commit-analyze-after' => '646ece5',
'2-mb-file' => 'bf12d25',
'before-create-delete-modify-move' => '845009f',
'between-create-delete-modify-move' => '3f5f443',
diff --git a/spec/support/helpers/test_request_helpers.rb b/spec/support/helpers/test_request_helpers.rb
index 187a0e07891..5a84d67bdfc 100644
--- a/spec/support/helpers/test_request_helpers.rb
+++ b/spec/support/helpers/test_request_helpers.rb
@@ -2,10 +2,6 @@
module TestRequestHelpers
def test_request(remote_ip: '127.0.0.1')
- if Gitlab.rails5?
- ActionController::TestRequest.new({ remote_ip: remote_ip }, ActionController::TestSession.new)
- else
- ActionController::TestRequest.new(remote_ip: remote_ip)
- end
+ ActionController::TestRequest.new({ remote_ip: remote_ip }, ActionController::TestSession.new)
end
end
diff --git a/spec/support/import_export/export_file_helper.rb b/spec/support/import_export/export_file_helper.rb
index a49036c3b80..ac320934f5a 100644
--- a/spec/support/import_export/export_file_helper.rb
+++ b/spec/support/import_export/export_file_helper.rb
@@ -133,6 +133,6 @@ module ExportFileHelper
end
def file_permissions(file)
- File.stat(file).mode & 0777
+ File.lstat(file).mode & 0777
end
end
diff --git a/spec/support/issuables_requiring_filter_shared_examples.rb b/spec/support/issuables_requiring_filter_shared_examples.rb
index 439ef5ed92e..71bcc82ee58 100644
--- a/spec/support/issuables_requiring_filter_shared_examples.rb
+++ b/spec/support/issuables_requiring_filter_shared_examples.rb
@@ -10,6 +10,6 @@ shared_examples 'issuables requiring filter' do |action|
it "loads issuables if at least one filter is set" do
expect_any_instance_of(described_class).to receive(:issuables_collection).and_call_original
- get action, author_id: user.id
+ get action, params: { author_id: user.id }
end
end
diff --git a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
index 1478c6b5a47..62ae95df8c0 100644
--- a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
+++ b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
@@ -86,7 +86,7 @@ shared_examples "migrating a deleted user's associated records to the ghost user
end
it "blocks the user before #{record_class_name} migration begins" do
- expect(service).to receive("migrate_#{record_class_name.parameterize('_').pluralize}".to_sym) do
+ expect(service).to receive("migrate_#{record_class_name.parameterize(separator: '_').pluralize}".to_sym) do
expect(user.reload).to be_blocked
end
diff --git a/spec/support/setup_builds_storage.rb b/spec/support/setup_builds_storage.rb
index 2e7c88bfc09..1d2a4856724 100644
--- a/spec/support/setup_builds_storage.rb
+++ b/spec/support/setup_builds_storage.rb
@@ -11,7 +11,7 @@ RSpec.configure do |config|
FileUtils.mkdir_p(builds_path)
end
- config.before(:each) do
+ config.before do
FileUtils.rm_rf(builds_path)
FileUtils.mkdir_p(builds_path)
end
diff --git a/spec/support/shared_examples/common_system_notes_examples.rb b/spec/support/shared_examples/common_system_notes_examples.rb
index 96ef30b7513..da5a4f3e319 100644
--- a/spec/support/shared_examples/common_system_notes_examples.rb
+++ b/spec/support/shared_examples/common_system_notes_examples.rb
@@ -1,5 +1,5 @@
shared_examples 'system note creation' do |update_params, note_text|
- subject { described_class.new(project, user).execute(issuable, [])}
+ subject { described_class.new(project, user).execute(issuable, old_labels: []) }
before do
issuable.assign_attributes(update_params)
@@ -16,7 +16,7 @@ shared_examples 'system note creation' do |update_params, note_text|
end
shared_examples 'WIP notes creation' do |wip_action|
- subject { described_class.new(project, user).execute(issuable, []) }
+ subject { described_class.new(project, user).execute(issuable, old_labels: []) }
it 'creates WIP toggle and title change notes' do
expect { subject }.to change { Note.count }.from(0).to(2)
diff --git a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
index 95e69328080..dbdca99b5aa 100644
--- a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
@@ -2,7 +2,7 @@ shared_examples 'issuable notes filter' do
it 'sets discussion filter' do
notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
- get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter }
expect(user.reload.notes_filter_for(issuable)).to eq(notes_filter)
expect(UserPreference.count).to eq(1)
@@ -13,7 +13,7 @@ shared_examples 'issuable notes filter' do
expect_any_instance_of(issuable.class).to receive(:expire_note_etag_cache)
- get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter }
end
it 'does not expires notes e-tag cache for issuable if filter did not change' do
@@ -22,14 +22,14 @@ shared_examples 'issuable notes filter' do
expect_any_instance_of(issuable.class).not_to receive(:expire_note_etag_cache)
- get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter }
end
it 'does not set notes filter when database is in read only mode' do
allow(Gitlab::Database).to receive(:read_only?).and_return(true)
notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
- get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid, notes_filter: notes_filter }
expect(user.reload.notes_filter_for(issuable)).to eq(0)
end
@@ -37,7 +37,7 @@ shared_examples 'issuable notes filter' do
it 'returns only user comments' do
user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issuable)
- get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid }
discussions = JSON.parse(response.body)
expect(discussions.count).to eq(1)
@@ -47,7 +47,7 @@ shared_examples 'issuable notes filter' do
it 'returns only activity notes' do
user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_activity], issuable)
- get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid }
discussions = JSON.parse(response.body)
expect(discussions.count).to eq(1)
@@ -60,7 +60,7 @@ shared_examples 'issuable notes filter' do
expect(ResourceEvents::MergeIntoNotesService).not_to receive(:new)
- get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issuable.iid }
end
end
end
diff --git a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb
new file mode 100644
index 00000000000..d86838719d4
--- /dev/null
+++ b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb
@@ -0,0 +1,32 @@
+shared_examples 'set sort order from user preference' do
+ describe '#set_sort_order_from_user_preference' do
+ # There is no issuable_sorting_field defined in any CE controllers yet,
+ # however any other field present in user_preferences table can be used for testing.
+ let(:sorting_field) { :issue_notes_filter }
+ let(:sorting_param) { 'any' }
+
+ before do
+ allow(controller).to receive(:issuable_sorting_field).and_return(sorting_field)
+ end
+
+ context 'when database is in read-only mode' do
+ it 'it does not update user preference' do
+ allow(Gitlab::Database).to receive(:read_only?).and_return(true)
+
+ expect_any_instance_of(UserPreference).not_to receive(:update_attribute).with(sorting_field, sorting_param)
+
+ get :index, params: { namespace_id: project.namespace, project_id: project, sort: sorting_param }
+ end
+ end
+
+ context 'when database is not in read-only mode' do
+ it 'updates user preference' do
+ allow(Gitlab::Database).to receive(:read_only?).and_return(false)
+
+ expect_any_instance_of(UserPreference).to receive(:update_attribute).with(sorting_field, sorting_param)
+
+ get :index, params: { namespace_id: project.namespace, project_id: project, sort: sorting_param }
+ end
+ end
+ end
+end
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 7088fb1e5fb..59708173716 100644
--- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
@@ -8,8 +8,7 @@ shared_examples 'handle uploads' do
describe "POST #create" do
context 'when a user is not authorized to upload a file' do
it 'returns 404 status' do
- post :create, params.merge(file: jpg, format: :json)
-
+ post :create, params: params.merge(file: jpg), format: :json
expect(response.status).to eq(404)
end
end
@@ -22,7 +21,7 @@ shared_examples 'handle uploads' do
context "without params['file']" do
it "returns an error" do
- post :create, params.merge(format: :json)
+ post :create, params: params, format: :json
expect(response).to have_gitlab_http_status(422)
end
@@ -30,7 +29,7 @@ shared_examples 'handle uploads' do
context 'with valid image' do
before do
- post :create, params.merge(file: jpg, format: :json)
+ post :create, params: params.merge(file: jpg), format: :json
end
it 'returns a content with original filename, new link, and correct type.' do
@@ -54,7 +53,7 @@ shared_examples 'handle uploads' do
context 'with valid non-image file' do
before do
- post :create, params.merge(file: txt, format: :json)
+ post :create, params: params.merge(file: txt), format: :json
end
it 'returns a content with original filename, new link, and correct type.' do
@@ -67,7 +66,7 @@ shared_examples 'handle uploads' do
describe "GET #show" do
let(:show_upload) do
- get :show, params.merge(secret: secret, filename: "rails_sample.jpg")
+ get :show, params: params.merge(secret: secret, filename: "rails_sample.jpg")
end
before do
diff --git a/spec/support/shared_examples/file_finder.rb b/spec/support/shared_examples/file_finder.rb
index ef144bdf61c..0dc351b5149 100644
--- a/spec/support/shared_examples/file_finder.rb
+++ b/spec/support/shared_examples/file_finder.rb
@@ -3,18 +3,19 @@ shared_examples 'file finder' do
let(:search_results) { subject.find(query) }
it 'finds by name' do
- filename, blob = search_results.find { |_, blob| blob.filename == expected_file_by_name }
- expect(filename).to eq(expected_file_by_name)
- expect(blob).to be_a(Gitlab::SearchResults::FoundBlob)
+ blob = search_results.find { |blob| blob.filename == expected_file_by_name }
+
+ expect(blob.filename).to eq(expected_file_by_name)
+ expect(blob).to be_a(Gitlab::Search::FoundBlob)
expect(blob.ref).to eq(subject.ref)
expect(blob.data).not_to be_empty
end
it 'finds by content' do
- filename, blob = search_results.find { |_, blob| blob.filename == expected_file_by_content }
+ blob = search_results.find { |blob| blob.filename == expected_file_by_content }
- expect(filename).to eq(expected_file_by_content)
- expect(blob).to be_a(Gitlab::SearchResults::FoundBlob)
+ expect(blob.filename).to eq(expected_file_by_content)
+ expect(blob).to be_a(Gitlab::Search::FoundBlob)
expect(blob.ref).to eq(subject.ref)
expect(blob.data).not_to be_empty
end
diff --git a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb
index f4bc6f8efa5..90d67fd00fc 100644
--- a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb
+++ b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb
@@ -3,9 +3,9 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
def get_action(action, project)
if action
- get action, author_id: project.creator.id
+ get action, params: { author_id: project.creator.id }
else
- get :index, namespace_id: project.namespace, project_id: project
+ get :index, params: { namespace_id: project.namespace, project_id: project }
end
end
@@ -51,9 +51,9 @@ shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
it "doesn't execute any queries with false conditions" do
get_empty =
if action
- proc { get action, author_id: project.creator.id }
+ proc { get action, params: { author_id: project.creator.id } }
else
- proc { get :index, namespace_id: project2.namespace, project_id: project2 }
+ proc { get :index, params: { namespace_id: project2.namespace, project_id: project2 } }
end
expect(&get_empty).not_to make_queries_matching(/WHERE (?:1=0|0=1)/)
diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
new file mode 100644
index 00000000000..1f688c0f9d3
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
@@ -0,0 +1,44 @@
+shared_examples 'backfill migration for project repositories' do |storage|
+ describe '#perform' do
+ let(:storage_versions) { storage == :legacy ? [nil, 0] : [1, 2] }
+ let(:storage_version) { storage_versions.first }
+ let(:namespaces) { table(:namespaces) }
+ let(:project_repositories) { table(:project_repositories) }
+ let(:projects) { table(:projects) }
+ let(:shards) { table(:shards) }
+ let(:group) { namespaces.create!(name: 'foo', path: 'foo') }
+ let(:shard) { shards.create!(name: 'default') }
+
+ it "creates a project_repository row for projects on #{storage} storage that needs one" do
+ storage_versions.each_with_index do |storage_version, index|
+ projects.create!(name: "foo-#{index}", path: "foo-#{index}", namespace_id: group.id, storage_version: storage_version)
+ end
+
+ expect { described_class.new.perform(1, projects.last.id) }.to change(project_repositories, :count).by(2)
+ end
+
+ it "does nothing for projects on #{storage} storage that have already a project_repository row" do
+ projects.create!(id: 1, name: 'foo', path: 'foo', namespace_id: group.id, storage_version: storage_version)
+ project_repositories.create!(project_id: 1, disk_path: 'phony/foo/bar', shard_id: shard.id)
+
+ expect { described_class.new.perform(1, projects.last.id) }.not_to change(project_repositories, :count)
+ end
+
+ it "does nothing for projects on #{storage == :legacy ? 'hashed' : 'legacy'} storage" do
+ projects.create!(name: 'foo', path: 'foo', namespace_id: group.id, storage_version: storage == :legacy ? 1 : nil)
+
+ expect { described_class.new.perform(1, projects.last.id) }.not_to change(project_repositories, :count)
+ end
+
+ it 'inserts rows in a single query' do
+ projects.create!(name: 'foo', path: 'foo', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name)
+
+ control_count = ActiveRecord::QueryRecorder.new { described_class.new.perform(1, projects.last.id) }
+
+ projects.create!(name: 'bar', path: 'bar', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name)
+ projects.create!(name: 'zoo', path: 'zoo', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name)
+
+ expect { described_class.new.perform(1, projects.last.id) }.not_to exceed_query_limit(control_count)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/milestone_tabs_examples.rb b/spec/support/shared_examples/milestone_tabs_examples.rb
index 70b499198bf..8b757586941 100644
--- a/spec/support/shared_examples/milestone_tabs_examples.rb
+++ b/spec/support/shared_examples/milestone_tabs_examples.rb
@@ -10,7 +10,7 @@ shared_examples 'milestone tabs' do
{ namespace_id: project.namespace.to_param, project_id: project, id: milestone.iid }
end
- get path, params.merge(extra_params)
+ get path, params: params.merge(extra_params)
end
describe '#merge_requests' do
diff --git a/spec/support/shared_examples/models/member_shared_examples.rb b/spec/support/shared_examples/models/member_shared_examples.rb
new file mode 100644
index 00000000000..77376496854
--- /dev/null
+++ b/spec/support/shared_examples/models/member_shared_examples.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+shared_examples_for 'inherited access level as a member of entity' do
+ let(:parent_entity) { create(:group) }
+ let(:user) { create(:user) }
+ let(:member) { entity.is_a?(Group) ? entity.group_member(user) : entity.project_member(user) }
+
+ context 'with root parent_entity developer member' do
+ before do
+ parent_entity.add_developer(user)
+ end
+
+ it 'is allowed to be a maintainer of the entity' do
+ entity.add_maintainer(user)
+
+ expect(member.access_level).to eq(Gitlab::Access::MAINTAINER)
+ end
+
+ it 'is not allowed to be a reporter of the entity' do
+ entity.add_reporter(user)
+
+ expect(member).to be_nil
+ end
+
+ it 'is allowed to change to be a developer of the entity' do
+ entity.add_maintainer(user)
+
+ expect { member.update(access_level: Gitlab::Access::DEVELOPER) }
+ .to change { member.access_level }.to(Gitlab::Access::DEVELOPER)
+ end
+
+ it 'is not allowed to change to be a guest of the entity' do
+ entity.add_maintainer(user)
+
+ expect { member.update(access_level: Gitlab::Access::GUEST) }
+ .not_to change { member.reload.access_level }
+ end
+
+ it "shows an error if the member can't be updated" do
+ entity.add_maintainer(user)
+
+ member.update(access_level: Gitlab::Access::REPORTER)
+
+ expect(member.errors.full_messages).to eq(["Access level should be higher than Developer inherited membership from group #{parent_entity.name}"])
+ end
+
+ it 'allows changing the level from a non existing member' do
+ non_member_user = create(:user)
+
+ entity.add_maintainer(non_member_user)
+
+ non_member = entity.is_a?(Group) ? entity.group_member(non_member_user) : entity.project_member(non_member_user)
+
+ expect { non_member.update(access_level: Gitlab::Access::GUEST) }
+ .to change { non_member.reload.access_level }
+ end
+ end
+end
+
+shared_examples_for '#valid_level_roles' do |entity_name|
+ let(:member_user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:entity) { create(entity_name) }
+ let(:entity_member) { create("#{entity_name}_member", :developer, source: entity, user: member_user) }
+ let(:presenter) { described_class.new(entity_member, current_user: member_user) }
+ let(:expected_roles) { { 'Developer' => 30, 'Maintainer' => 40, 'Reporter' => 20 } }
+
+ it 'returns all roles when no parent member is present' do
+ expect(presenter.valid_level_roles).to eq(entity_member.class.access_level_roles)
+ end
+
+ it 'returns higher roles when a parent member is present' do
+ group.add_reporter(member_user)
+
+ expect(presenter.valid_level_roles).to eq(expected_roles)
+ end
+end
diff --git a/spec/support/shared_examples/models/with_uploads_shared_examples.rb b/spec/support/shared_examples/models/with_uploads_shared_examples.rb
index 47ad0c6345d..1d11b855459 100644
--- a/spec/support/shared_examples/models/with_uploads_shared_examples.rb
+++ b/spec/support/shared_examples/models/with_uploads_shared_examples.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-shared_examples_for 'model with mounted uploader' do |supports_fileuploads|
+shared_examples_for 'model with uploads' do |supports_fileuploads|
describe '.destroy' do
before do
stub_uploads_object_storage(uploader_class)
@@ -8,16 +8,62 @@ shared_examples_for 'model with mounted uploader' do |supports_fileuploads|
model_object.public_send(upload_attribute).migrate!(ObjectStorage::Store::REMOTE)
end
- it 'deletes remote uploads' do
- expect_any_instance_of(CarrierWave::Storage::Fog::File).to receive(:delete).and_call_original
+ context 'with mounted uploader' do
+ it 'deletes remote uploads' do
+ expect_any_instance_of(CarrierWave::Storage::Fog::File).to receive(:delete).and_call_original
- expect { model_object.destroy }.to change { Upload.count }.by(-1)
+ expect { model_object.destroy }.to change { Upload.count }.by(-1)
+ end
end
- it 'deletes any FileUploader uploads which are not mounted', skip: !supports_fileuploads do
- create(:upload, uploader: FileUploader, model: model_object)
+ context 'with not mounted uploads', :sidekiq, skip: !supports_fileuploads do
+ context 'with local files' do
+ let!(:uploads) { create_list(:upload, 2, uploader: FileUploader, model: model_object) }
- expect { model_object.destroy }.to change { Upload.count }.by(-2)
+ it 'deletes any FileUploader uploads which are not mounted' do
+ expect { model_object.destroy }.to change { Upload.count }.by(-3)
+ end
+
+ it 'deletes local files' do
+ expect_any_instance_of(Uploads::Local).to receive(:delete_keys).with(uploads.map(&:absolute_path))
+
+ model_object.destroy
+ end
+ end
+
+ context 'with remote files' do
+ let!(:uploads) { create_list(:upload, 2, :object_storage, uploader: FileUploader, model: model_object) }
+
+ it 'deletes any FileUploader uploads which are not mounted' do
+ expect { model_object.destroy }.to change { Upload.count }.by(-3)
+ end
+
+ it 'deletes remote files' do
+ expect_any_instance_of(Uploads::Fog).to receive(:delete_keys).with(uploads.map(&:path))
+
+ model_object.destroy
+ end
+ end
+
+ describe 'destroy strategy depending on feature flag' do
+ let!(:upload) { create(:upload, uploader: FileUploader, model: model_object) }
+
+ it 'does not destroy uploads by default' do
+ expect(model_object).to receive(:delete_uploads)
+ expect(model_object).not_to receive(:destroy_uploads)
+
+ model_object.destroy
+ end
+
+ it 'uses before destroy callback if feature flag is disabled' do
+ stub_feature_flags(fast_destroy_uploads: false)
+
+ expect(model_object).to receive(:destroy_uploads)
+ expect(model_object).not_to receive(:delete_uploads)
+
+ model_object.destroy
+ end
+ end
end
end
end
diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb
index 66536e80db2..a38354060cf 100644
--- a/spec/support/shared_examples/notify_shared_examples.rb
+++ b/spec/support/shared_examples/notify_shared_examples.rb
@@ -1,5 +1,5 @@
shared_context 'gitlab email notification' do
- set(:project) { create(:project, :repository) }
+ set(:project) { create(:project, :repository, name: 'a-known-name') }
set(:recipient) { create(:user, email: 'recipient@example.com') }
let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name }
@@ -62,9 +62,11 @@ end
shared_examples 'an email with X-GitLab headers containing project details' do
it 'has X-GitLab-Project headers' do
aggregate_failures do
+ full_path_as_domain = "#{project.name}.#{project.namespace.path}"
is_expected.to have_header('X-GitLab-Project', /#{project.name}/)
is_expected.to have_header('X-GitLab-Project-Id', /#{project.id}/)
is_expected.to have_header('X-GitLab-Project-Path', /#{project.full_path}/)
+ is_expected.to have_header('List-Id', "#{project.full_path} <#{project.id}.#{full_path_as_domain}.#{Gitlab.config.gitlab.host}>")
end
end
end
diff --git a/spec/support/shared_examples/only_except_policy_examples.rb b/spec/support/shared_examples/only_except_policy_examples.rb
deleted file mode 100644
index 35240af1d74..00000000000
--- a/spec/support/shared_examples/only_except_policy_examples.rb
+++ /dev/null
@@ -1,167 +0,0 @@
-# frozen_string_literal: true
-
-shared_examples 'correct only except policy' do
- context 'when using simplified policy' do
- describe 'validations' do
- context 'when entry config value is valid' do
- context 'when config is a branch or tag name' do
- let(:config) { %w[master feature/branch] }
-
- describe '#valid?' do
- it 'is valid' do
- expect(entry).to be_valid
- end
- end
-
- describe '#value' do
- it 'returns refs hash' do
- expect(entry.value).to eq(refs: config)
- end
- end
- end
-
- context 'when config is a regexp' do
- let(:config) { ['/^issue-.*$/'] }
-
- describe '#valid?' do
- it 'is valid' do
- expect(entry).to be_valid
- end
- end
- end
-
- context 'when config is a special keyword' do
- let(:config) { %w[tags triggers branches] }
-
- describe '#valid?' do
- it 'is valid' do
- expect(entry).to be_valid
- end
- end
- end
- end
-
- context 'when entry value is not valid' do
- let(:config) { [1] }
-
- describe '#errors' do
- it 'saves errors' do
- expect(entry.errors)
- .to include /policy config should be an array of strings or regexps/
- end
- end
- end
- end
- end
-
- context 'when using complex policy' do
- context 'when specifying refs policy' do
- let(:config) { { refs: ['master'] } }
-
- it 'is a correct configuraton' do
- expect(entry).to be_valid
- expect(entry.value).to eq(refs: %w[master])
- end
- end
-
- context 'when specifying kubernetes policy' do
- let(:config) { { kubernetes: 'active' } }
-
- it 'is a correct configuraton' do
- expect(entry).to be_valid
- expect(entry.value).to eq(kubernetes: 'active')
- end
- end
-
- context 'when specifying invalid kubernetes policy' do
- let(:config) { { kubernetes: 'something' } }
-
- it 'reports an error about invalid policy' do
- expect(entry.errors).to include /unknown value: something/
- end
- end
-
- context 'when specifying valid variables expressions policy' do
- let(:config) { { variables: ['$VAR == null'] } }
-
- it 'is a correct configuraton' do
- expect(entry).to be_valid
- expect(entry.value).to eq(config)
- end
- end
-
- context 'when specifying variables expressions in invalid format' do
- let(:config) { { variables: '$MY_VAR' } }
-
- it 'reports an error about invalid format' do
- expect(entry.errors).to include /should be an array of strings/
- end
- end
-
- context 'when specifying invalid variables expressions statement' do
- let(:config) { { variables: ['$MY_VAR =='] } }
-
- it 'reports an error about invalid statement' do
- expect(entry.errors).to include /invalid expression syntax/
- end
- end
-
- context 'when specifying invalid variables expressions token' do
- let(:config) { { variables: ['$MY_VAR == 123'] } }
-
- it 'reports an error about invalid expression' do
- expect(entry.errors).to include /invalid expression syntax/
- end
- end
-
- context 'when using invalid variables expressions regexp' do
- let(:config) { { variables: ['$MY_VAR =~ /some ( thing/'] } }
-
- it 'reports an error about invalid expression' do
- expect(entry.errors).to include /invalid expression syntax/
- end
- end
-
- context 'when specifying a valid changes policy' do
- let(:config) { { changes: %w[some/* paths/**/*.rb] } }
-
- it 'is a correct configuraton' do
- expect(entry).to be_valid
- expect(entry.value).to eq(config)
- end
- end
-
- context 'when changes policy is invalid' do
- let(:config) { { changes: [1, 2] } }
-
- it 'returns errors' do
- expect(entry.errors).to include /changes should be an array of strings/
- end
- end
-
- context 'when specifying unknown policy' do
- let(:config) { { refs: ['master'], invalid: :something } }
-
- it 'returns error about invalid key' do
- expect(entry.errors).to include /unknown keys: invalid/
- end
- end
-
- context 'when policy is empty' do
- let(:config) { {} }
-
- it 'is not a valid configuration' do
- expect(entry.errors).to include /can't be blank/
- end
- end
- end
-
- context 'when policy strategy does not match' do
- let(:config) { 'string strategy' }
-
- it 'returns information about errors' do
- expect(entry.errors)
- .to include /has to be either an array of conditions or a hash/
- end
- end
-end
diff --git a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb
index 9fc2fbef449..8a7fcf856a1 100644
--- a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb
@@ -9,7 +9,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name|
context 'with an unauthorized user' do
it 'does not filter by custom attributes' do
- get api("/#{attributable_name}", user), custom_attributes: { foo: 'foo', bar: 'bar' }
+ get api("/#{attributable_name}", user), params: { custom_attributes: { foo: 'foo', bar: 'bar' } }
expect(response).to have_gitlab_http_status(200)
expect(json_response.size).to be 2
@@ -19,7 +19,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name|
context 'with an authorized user' do
it 'filters by custom attributes' do
- get api("/#{attributable_name}", admin), custom_attributes: { foo: 'foo', bar: 'bar' }
+ get api("/#{attributable_name}", admin), params: { custom_attributes: { foo: 'foo', bar: 'bar' } }
expect(response).to have_gitlab_http_status(200)
expect(json_response.size).to be 1
@@ -35,7 +35,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name|
context 'with an unauthorized user' do
it 'does not include custom attributes' do
- get api("/#{attributable_name}", user), with_custom_attributes: true
+ get api("/#{attributable_name}", user), params: { with_custom_attributes: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response.size).to be 2
@@ -54,7 +54,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name|
end
it 'includes custom attributes if requested' do
- get api("/#{attributable_name}", admin), with_custom_attributes: true
+ get api("/#{attributable_name}", admin), params: { with_custom_attributes: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response.size).to be 2
@@ -75,7 +75,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name|
describe "GET /#{attributable_name}/:id with custom attributes" do
context 'with an unauthorized user' do
it 'does not include custom attributes' do
- get api("/#{attributable_name}/#{attributable.id}", user), with_custom_attributes: true
+ get api("/#{attributable_name}/#{attributable.id}", user), params: { with_custom_attributes: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response).not_to include 'custom_attributes'
@@ -91,7 +91,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name|
end
it 'includes custom attributes if requested' do
- get api("/#{attributable_name}/#{attributable.id}", admin), with_custom_attributes: true
+ get api("/#{attributable_name}/#{attributable.id}", admin), params: { with_custom_attributes: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response['custom_attributes']).to contain_exactly(
@@ -141,7 +141,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name|
describe "PUT /#{attributable_name}/:id/custom_attributes/:key" do
context 'with an unauthorized user' do
- subject { put api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", user), value: 'new' }
+ subject { put api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", user), params: { value: 'new' } }
it_behaves_like 'an unauthorized API user'
end
@@ -149,7 +149,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name|
context 'with an authorized user' do
it 'creates a new custom attribute' do
expect do
- put api("/#{attributable_name}/#{attributable.id}/custom_attributes/new", admin), value: 'new'
+ put api("/#{attributable_name}/#{attributable.id}/custom_attributes/new", admin), params: { value: 'new' }
end.to change { attributable.custom_attributes.count }.by(1)
expect(response).to have_gitlab_http_status(200)
@@ -159,7 +159,7 @@ shared_examples 'custom attributes endpoints' do |attributable_name|
it 'updates an existing custom attribute' do
expect do
- put api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin), value: 'new'
+ put api("/#{attributable_name}/#{attributable.id}/custom_attributes/foo", admin), params: { value: 'new' }
end.not_to change { attributable.custom_attributes.count }
expect(response).to have_gitlab_http_status(200)
diff --git a/spec/support/shared_examples/requests/api/diff_discussions.rb b/spec/support/shared_examples/requests/api/diff_discussions.rb
index 85a4bd8ca27..366c2955359 100644
--- a/spec/support/shared_examples/requests/api/diff_discussions.rb
+++ b/spec/support/shared_examples/requests/api/diff_discussions.rb
@@ -27,7 +27,8 @@ shared_examples 'diff discussions API' do |parent_type, noteable_type, id_name|
it "creates a new diff note" do
position = diff_note.position.to_h
- post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), body: 'hi!', position: position
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user),
+ params: { body: 'hi!', position: position }
expect(response).to have_gitlab_http_status(201)
expect(json_response['notes'].first['body']).to eq('hi!')
@@ -38,7 +39,8 @@ shared_examples 'diff discussions API' do |parent_type, noteable_type, id_name|
it "returns a 400 bad request error when position is invalid" do
position = diff_note.position.to_h.merge(new_line: '100000')
- post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), body: 'hi!', position: position
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user),
+ params: { body: 'hi!', position: position }
expect(response).to have_gitlab_http_status(400)
end
@@ -47,7 +49,7 @@ shared_examples 'diff discussions API' do |parent_type, noteable_type, id_name|
describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes" do
it 'adds a new note to the diff discussion' do
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
- "discussions/#{diff_note.discussion_id}/notes", user), body: 'hi!'
+ "discussions/#{diff_note.discussion_id}/notes", user), params: { body: 'hi!' }
expect(response).to have_gitlab_http_status(201)
expect(json_response['body']).to eq('hi!')
diff --git a/spec/support/shared_examples/requests/api/discussions.rb b/spec/support/shared_examples/requests/api/discussions.rb
index b6aeb30d69c..e44da4faa5a 100644
--- a/spec/support/shared_examples/requests/api/discussions.rb
+++ b/spec/support/shared_examples/requests/api/discussions.rb
@@ -42,7 +42,7 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name|
describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do
it "creates a new note" do
- post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), body: 'hi!'
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), params: { body: 'hi!' }
expect(response).to have_gitlab_http_status(201)
expect(json_response['notes'].first['body']).to eq('hi!')
@@ -56,7 +56,7 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name|
end
it "returns a 401 unauthorized error if user not authenticated" do
- post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions"), body: 'hi!'
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions"), params: { body: 'hi!' }
expect(response).to have_gitlab_http_status(401)
end
@@ -65,7 +65,7 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name|
it 'accepts the creation date to be set' do
creation_time = 2.weeks.ago
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user),
- body: 'hi!', created_at: creation_time
+ params: { body: 'hi!', created_at: creation_time }
expect(response).to have_gitlab_http_status(201)
expect(json_response['notes'].first['body']).to eq('hi!')
@@ -81,7 +81,7 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name|
it 'responds with 404' do
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", private_user),
- body: 'Foo'
+ params: { body: 'Foo' }
expect(response).to have_gitlab_http_status(404)
end
@@ -91,7 +91,7 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name|
describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes" do
it 'adds a new note to the discussion' do
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
- "discussions/#{note.discussion_id}/notes", user), body: 'Hello!'
+ "discussions/#{note.discussion_id}/notes", user), params: { body: 'Hello!' }
expect(response).to have_gitlab_http_status(201)
expect(json_response['body']).to eq('Hello!')
@@ -109,7 +109,7 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name|
note.update_attribute(:type, nil)
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
- "discussions/#{note.discussion_id}/notes", user), body: 'hi!'
+ "discussions/#{note.discussion_id}/notes", user), params: { body: 'hi!' }
expect(response).to have_gitlab_http_status(400)
end
@@ -118,7 +118,7 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name|
describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes/:note_id" do
it 'returns modified note' do
put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
- "discussions/#{note.discussion_id}/notes/#{note.id}", user), body: 'Hello!'
+ "discussions/#{note.discussion_id}/notes/#{note.id}", user), params: { body: 'Hello!' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['body']).to eq('Hello!')
@@ -127,7 +127,7 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name|
it 'returns a 404 error when note id not found' do
put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
"discussions/#{note.discussion_id}/notes/12345", user),
- body: 'Hello!'
+ params: { body: 'Hello!' }
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/support/shared_examples/requests/api/merge_requests_list.rb b/spec/support/shared_examples/requests/api/merge_requests_list.rb
index 92d4dd598d5..453f42251c8 100644
--- a/spec/support/shared_examples/requests/api/merge_requests_list.rb
+++ b/spec/support/shared_examples/requests/api/merge_requests_list.rb
@@ -123,7 +123,7 @@ shared_examples 'merge requests list' do
end
it 'returns an empty array if no issue matches milestone' do
- get api(endpoint_path, user), milestone: '1.0.0'
+ get api(endpoint_path, user), params: { milestone: '1.0.0' }
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
@@ -131,7 +131,7 @@ shared_examples 'merge requests list' do
end
it 'returns an empty array if milestone does not exist' do
- get api(endpoint_path, user), milestone: 'foo'
+ get api(endpoint_path, user), params: { milestone: 'foo' }
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
@@ -139,7 +139,7 @@ shared_examples 'merge requests list' do
end
it 'returns an array of merge requests in given milestone' do
- get api(endpoint_path, user), milestone: '0.9'
+ get api(endpoint_path, user), params: { milestone: '0.9' }
closed_issues = json_response.select { |mr| mr['id'] == merge_request_closed.id }
expect(closed_issues.length).to eq(1)
@@ -147,7 +147,7 @@ shared_examples 'merge requests list' do
end
it 'returns an array of merge requests matching state in milestone' do
- get api(endpoint_path, user), milestone: '0.9', state: 'closed'
+ get api(endpoint_path, user), params: { milestone: '0.9', state: 'closed' }
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
@@ -187,7 +187,7 @@ shared_examples 'merge requests list' do
end
it 'returns an array of merge requests with any label when filtering by any label' do
- get api(endpoint_path, user), labels: IssuesFinder::FILTER_ANY
+ get api(endpoint_path, user), params: { labels: IssuesFinder::FILTER_ANY }
expect_paginated_array_response
expect(json_response.length).to eq(1)
@@ -195,7 +195,7 @@ shared_examples 'merge requests list' do
end
it 'returns an array of merge requests without a label when filtering by no label' do
- get api(endpoint_path, user), labels: IssuesFinder::FILTER_NONE
+ get api(endpoint_path, user), params: { labels: IssuesFinder::FILTER_NONE }
response_ids = json_response.map { |merge_request| merge_request['id'] }
@@ -286,7 +286,7 @@ shared_examples 'merge requests list' do
context 'source_branch param' do
it 'returns merge requests with the given source branch' do
- get api(endpoint_path, user), source_branch: merge_request_closed.source_branch, state: 'all'
+ get api(endpoint_path, user), params: { source_branch: merge_request_closed.source_branch, state: 'all' }
expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked)
end
@@ -294,7 +294,7 @@ shared_examples 'merge requests list' do
context 'target_branch param' do
it 'returns merge requests with the given target branch' do
- get api(endpoint_path, user), target_branch: merge_request_closed.target_branch, state: 'all'
+ get api(endpoint_path, user), params: { target_branch: merge_request_closed.target_branch, state: 'all' }
expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked)
end
diff --git a/spec/support/shared_examples/requests/api/notes.rb b/spec/support/shared_examples/requests/api/notes.rb
index 0e20dfe0725..71499e85654 100644
--- a/spec/support/shared_examples/requests/api/notes.rb
+++ b/spec/support/shared_examples/requests/api/notes.rb
@@ -86,7 +86,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes" do
it "creates a new note" do
- post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), body: 'hi!'
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: { body: 'hi!' }
expect(response).to have_gitlab_http_status(201)
expect(json_response['body']).to eq('hi!')
@@ -100,7 +100,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
end
it "returns a 401 unauthorized error if user not authenticated" do
- post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes"), body: 'hi!'
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes"), params: { body: 'hi!' }
expect(response).to have_gitlab_http_status(401)
end
@@ -108,7 +108,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
it "creates an activity event when a note is created" do
expect(Event).to receive(:create!)
- post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), body: 'hi!'
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: { body: 'hi!' }
end
context 'setting created_at' do
@@ -118,7 +118,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
context 'by an admin' do
it 'sets the creation time on the new note' do
admin = create(:admin)
- post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", admin), params
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", admin), params: params
expect(response).to have_gitlab_http_status(201)
expect(json_response['body']).to eq('hi!')
@@ -131,7 +131,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
if parent_type == 'projects'
context 'by a project owner' do
it 'sets the creation time on the new note' do
- post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params
expect(response).to have_gitlab_http_status(201)
expect(json_response['body']).to eq('hi!')
@@ -149,7 +149,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
parent.update!(namespace: group)
user2.refresh_authorized_projects
- post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user2), params
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user2), params: params
expect(response).to have_gitlab_http_status(201)
expect(json_response['body']).to eq('hi!')
@@ -161,7 +161,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
elsif parent_type == 'groups'
context 'by a group owner' do
it 'sets the creation time on the new note' do
- post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params
expect(response).to have_gitlab_http_status(201)
expect(json_response['body']).to eq('hi!')
@@ -176,7 +176,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
it 'ignores the given creation time' do
user2 = create(:user)
parent.add_developer(user2)
- post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user2), params
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user2), params: params
expect(response).to have_gitlab_http_status(201)
expect(json_response['body']).to eq('hi!')
@@ -190,7 +190,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
context 'when the user is posting an award emoji on a noteable created by someone else' do
it 'creates a new note' do
parent.add_developer(private_user)
- post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", private_user), body: ':+1:'
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", private_user), params: { body: ':+1:' }
expect(response).to have_gitlab_http_status(201)
expect(json_response['body']).to eq(':+1:')
@@ -199,7 +199,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
context 'when the user is posting an award emoji on his/her own noteable' do
it 'creates a new note' do
- post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), body: ':+1:'
+ post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: { body: ':+1:' }
expect(response).to have_gitlab_http_status(201)
expect(json_response['body']).to eq(':+1:')
@@ -213,7 +213,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
it 'responds with 404' do
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", private_user),
- body: 'Foo'
+ params: { body: 'Foo' }
expect(response).to have_gitlab_http_status(404)
end
@@ -223,7 +223,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes/:note_id" do
it 'returns modified note' do
put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
- "notes/#{note.id}", user), body: 'Hello!'
+ "notes/#{note.id}", user), params: { body: 'Hello!' }
expect(response).to have_gitlab_http_status(200)
expect(json_response['body']).to eq('Hello!')
@@ -231,7 +231,7 @@ shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
it 'returns a 404 error when note id not found' do
put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes/12345", user),
- body: 'Hello!'
+ params: { body: 'Hello!' }
expect(response).to have_gitlab_http_status(404)
end
diff --git a/spec/support/shared_examples/requests/api/resolvable_discussions.rb b/spec/support/shared_examples/requests/api/resolvable_discussions.rb
index 408ad08cc48..7e2416b23f3 100644
--- a/spec/support/shared_examples/requests/api/resolvable_discussions.rb
+++ b/spec/support/shared_examples/requests/api/resolvable_discussions.rb
@@ -2,7 +2,7 @@ shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_
describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id" do
it "resolves discussion if resolved is true" do
put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
- "discussions/#{note.discussion_id}", user), resolved: true
+ "discussions/#{note.discussion_id}", user), params: { resolved: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response['notes'].size).to eq(1)
@@ -11,7 +11,7 @@ shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_
it "unresolves discussion if resolved is false" do
put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
- "discussions/#{note.discussion_id}", user), resolved: false
+ "discussions/#{note.discussion_id}", user), params: { resolved: false }
expect(response).to have_gitlab_http_status(200)
expect(json_response['notes'].size).to eq(1)
@@ -27,14 +27,14 @@ shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_
it "returns a 401 unauthorized error if user is not authenticated" do
put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
- "discussions/#{note.discussion_id}"), resolved: true
+ "discussions/#{note.discussion_id}"), params: { resolved: true }
expect(response).to have_gitlab_http_status(401)
end
it "returns a 403 error if user resolves discussion of someone else" do
put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
- "discussions/#{note.discussion_id}", private_user), resolved: true
+ "discussions/#{note.discussion_id}", private_user), params: { resolved: true }
expect(response).to have_gitlab_http_status(403)
end
@@ -46,7 +46,7 @@ shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_
it 'responds with 404' do
put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
- "discussions/#{note.discussion_id}", private_user), resolved: true
+ "discussions/#{note.discussion_id}", private_user), params: { resolved: true }
expect(response).to have_gitlab_http_status(404)
end
@@ -56,7 +56,7 @@ shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_
describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes/:note_id" do
it 'returns resolved note when resolved parameter is true' do
put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
- "discussions/#{note.discussion_id}/notes/#{note.id}", user), resolved: true
+ "discussions/#{note.discussion_id}/notes/#{note.id}", user), params: { resolved: true }
expect(response).to have_gitlab_http_status(200)
expect(json_response['resolved']).to eq(true)
@@ -65,7 +65,7 @@ shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_
it 'returns a 404 error when note id not found' do
put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
"discussions/#{note.discussion_id}/notes/12345", user),
- body: 'Hello!'
+ params: { body: 'Hello!' }
expect(response).to have_gitlab_http_status(404)
end
@@ -79,7 +79,7 @@ shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_
it "returns a 403 error if user resolves note of someone else" do
put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\
- "discussions/#{note.discussion_id}/notes/#{note.id}", private_user), resolved: true
+ "discussions/#{note.discussion_id}/notes/#{note.id}", private_user), params: { resolved: true }
expect(response).to have_gitlab_http_status(403)
end
diff --git a/spec/support/shared_examples/requests/api/status_shared_examples.rb b/spec/support/shared_examples/requests/api/status_shared_examples.rb
index 0ed917e448a..ebfc5fed3bb 100644
--- a/spec/support/shared_examples/requests/api/status_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/status_shared_examples.rb
@@ -54,7 +54,7 @@ shared_examples_for '412 response' do
context 'for a modified ressource' do
before do
- delete request, params, { 'HTTP_IF_UNMODIFIED_SINCE' => '1990-01-12T00:00:48-0600' }
+ delete request, params: params, headers: { 'HTTP_IF_UNMODIFIED_SINCE' => '1990-01-12T00:00:48-0600' }
end
it 'returns 412' do
@@ -64,7 +64,7 @@ shared_examples_for '412 response' do
context 'for an unmodified ressource' do
before do
- delete request, params, { 'HTTP_IF_UNMODIFIED_SINCE' => Time.now }
+ delete request, params: params, headers: { 'HTTP_IF_UNMODIFIED_SINCE' => Time.now }
end
it 'returns accepted' do
diff --git a/spec/support/shared_examples/serializers/diff_file_entity_examples.rb b/spec/support/shared_examples/serializers/diff_file_entity_examples.rb
new file mode 100644
index 00000000000..1770308f789
--- /dev/null
+++ b/spec/support/shared_examples/serializers/diff_file_entity_examples.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+shared_examples 'diff file base entity' do
+ it 'exposes essential attributes' do
+ expect(subject).to include(:content_sha, :submodule, :submodule_link,
+ :submodule_tree_url, :old_path_html,
+ :new_path_html, :blob, :can_modify_blob,
+ :file_hash, :file_path, :old_path, :new_path,
+ :collapsed, :text, :diff_refs, :stored_externally,
+ :external_storage, :renamed_file, :deleted_file,
+ :mode_changed, :a_mode, :b_mode, :new_file)
+ end
+
+ # Converted diff files from GitHub import does not contain blob file
+ # and content sha.
+ context 'when diff file does not have a blob and content sha' do
+ it 'exposes some attributes as nil' do
+ allow(diff_file).to receive(:content_sha).and_return(nil)
+ allow(diff_file).to receive(:blob).and_return(nil)
+
+ expect(subject[:context_lines_path]).to be_nil
+ expect(subject[:view_path]).to be_nil
+ expect(subject[:highlighted_diff_lines]).to be_nil
+ expect(subject[:can_modify_blob]).to be_nil
+ end
+ end
+end
+
+shared_examples 'diff file entity' do
+ it_behaves_like 'diff file base entity'
+
+ it 'exposes correct attributes' do
+ expect(subject).to include(:too_large, :added_lines, :removed_lines,
+ :context_lines_path, :highlighted_diff_lines,
+ :parallel_diff_lines, :empty)
+ end
+
+ it 'includes viewer' do
+ expect(subject[:viewer].with_indifferent_access)
+ .to match_schema('entities/diff_viewer')
+ end
+end
+
+shared_examples 'diff file discussion entity' do
+ it_behaves_like 'diff file base entity'
+end
diff --git a/spec/support/shared_examples/update_invalid_issuable.rb b/spec/support/shared_examples/update_invalid_issuable.rb
index 1490287681b..64568de424e 100644
--- a/spec/support/shared_examples/update_invalid_issuable.rb
+++ b/spec/support/shared_examples/update_invalid_issuable.rb
@@ -26,7 +26,7 @@ shared_examples 'update invalid issuable' do |klass|
end
it 'renders edit when format is html' do
- put :update, params
+ put :update, params: params
expect(response).to render_template(:edit)
expect(assigns[:conflict]).to be_truthy
@@ -35,7 +35,7 @@ shared_examples 'update invalid issuable' do |klass|
it 'renders json error message when format is json' do
params[:format] = "json"
- put :update, params
+ put :update, params: params
expect(response.status).to eq(409)
expect(JSON.parse(response.body)).to have_key('errors')
@@ -49,7 +49,7 @@ shared_examples 'update invalid issuable' do |klass|
end
it 'renders edit when merge request is invalid' do
- put :update, params
+ put :update, params: params
expect(response).to render_template(:edit)
end
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index 8c4360d4cf0..3b8f7f5fe7d 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -74,6 +74,7 @@ describe 'gitlab:app namespace rake task' do
it 'invokes restoration on match' do
allow(YAML).to receive(:load_file)
.and_return({ gitlab_version: gitlab_version })
+
expect(Rake::Task['gitlab:db:drop_tables']).to receive(:invoke)
expect(Rake::Task['gitlab:backup:db:restore']).to receive(:invoke)
expect(Rake::Task['gitlab:backup:repo:restore']).to receive(:invoke)
diff --git a/spec/tasks/gitlab/storage_rake_spec.rb b/spec/tasks/gitlab/storage_rake_spec.rb
index 233076ad6fa..be902d7c679 100644
--- a/spec/tasks/gitlab/storage_rake_spec.rb
+++ b/spec/tasks/gitlab/storage_rake_spec.rb
@@ -46,6 +46,16 @@ describe 'rake gitlab:storage:*' do
describe 'gitlab:storage:migrate_to_hashed' do
let(:task) { 'gitlab:storage:migrate_to_hashed' }
+ context 'read-only database' do
+ it 'does nothing' do
+ expect(Gitlab::Database).to receive(:read_only?).and_return(true)
+
+ expect(Project).not_to receive(:with_unmigrated_storage)
+
+ expect { run_rake_task(task) }.to output(/This task requires database write access. Exiting./).to_stderr
+ end
+ end
+
context '0 legacy projects' do
it 'does nothing' do
expect(StorageMigratorWorker).not_to receive(:perform_async)
@@ -92,7 +102,7 @@ describe 'rake gitlab:storage:*' do
stub_env('ID_FROM', 99999)
stub_env('ID_TO', 99999)
- expect { run_rake_task(task) }.to output(/There are no projects requiring storage migration with ID=99999/).to_stdout
+ expect { run_rake_task(task) }.to output(/There are no projects requiring storage migration with ID=99999/).to_stderr
end
it 'displays a message when project exists but its already migrated' do
@@ -100,7 +110,7 @@ describe 'rake gitlab:storage:*' do
stub_env('ID_FROM', project.id)
stub_env('ID_TO', project.id)
- expect { run_rake_task(task) }.to output(/There are no projects requiring storage migration with ID=#{project.id}/).to_stdout
+ expect { run_rake_task(task) }.to output(/There are no projects requiring storage migration with ID=#{project.id}/).to_stderr
end
it 'enqueues migration when project can be found' do
diff --git a/spec/tasks/gitlab/web_hook_rake_spec.rb b/spec/tasks/gitlab/web_hook_rake_spec.rb
new file mode 100644
index 00000000000..7bdf33ff6b0
--- /dev/null
+++ b/spec/tasks/gitlab/web_hook_rake_spec.rb
@@ -0,0 +1,92 @@
+require 'rake_helper'
+
+describe 'gitlab:web_hook namespace rake tasks' do
+ set(:group) { create(:group) }
+
+ set(:project1) { create(:project, namespace: group) }
+ set(:project2) { create(:project, namespace: group) }
+ set(:other_group_project) { create(:project) }
+
+ let(:url) { 'http://example.com' }
+ let(:hook_urls) { (project1.hooks + project2.hooks).map(&:url) }
+ let(:other_group_hook_urls) { other_group_project.hooks.map(&:url) }
+
+ before do
+ Rake.application.rake_require 'tasks/gitlab/web_hook'
+ end
+
+ describe 'gitlab:web_hook:add' do
+ it 'adds a web hook to all projects' do
+ stub_env('URL' => url)
+ run_rake_task('gitlab:web_hook:add')
+
+ expect(hook_urls).to contain_exactly(url, url)
+ expect(other_group_hook_urls).to contain_exactly(url)
+ end
+
+ it 'adds a web hook to projects in the specified namespace' do
+ stub_env('URL' => url, 'NAMESPACE' => group.full_path)
+ run_rake_task('gitlab:web_hook:add')
+
+ expect(hook_urls).to contain_exactly(url, url)
+ expect(other_group_hook_urls).to be_empty
+ end
+
+ it 'raises an error if an unknown namespace is specified' do
+ stub_env('URL' => url, 'NAMESPACE' => group.full_path)
+
+ group.destroy
+
+ expect { run_rake_task('gitlab:web_hook:add') }.to raise_error(SystemExit)
+ end
+ end
+
+ describe 'gitlab:web_hook:rm' do
+ let!(:hook1) { create(:project_hook, project: project1, url: url) }
+ let!(:hook2) { create(:project_hook, project: project2, url: url) }
+ let!(:other_group_hook) { create(:project_hook, project: other_group_project, url: url) }
+ let!(:other_url_hook) { create(:project_hook, url: other_url, project: project1) }
+
+ let(:other_url) { 'http://other.example.com' }
+
+ it 'removes a web hook from all projects by URL' do
+ stub_env('URL' => url)
+ run_rake_task('gitlab:web_hook:rm')
+
+ expect(hook_urls).to contain_exactly(other_url)
+ expect(other_group_hook_urls).to be_empty
+ end
+
+ it 'removes a web hook from projects in the specified namespace by URL' do
+ stub_env('NAMESPACE' => group.full_path, 'URL' => url)
+ run_rake_task('gitlab:web_hook:rm')
+
+ expect(hook_urls).to contain_exactly(other_url)
+ expect(other_group_hook_urls).to contain_exactly(url)
+ end
+
+ it 'raises an error if an unknown namespace is specified' do
+ stub_env('URL' => url, 'NAMESPACE' => group.full_path)
+
+ group.destroy
+
+ expect { run_rake_task('gitlab:web_hook:rm') }.to raise_error(SystemExit)
+ end
+ end
+
+ describe 'gitlab:web_hook:list' do
+ let!(:hook1) { create(:project_hook, project: project1) }
+ let!(:hook2) { create(:project_hook, project: project2) }
+ let!(:other_group_hook) { create(:project_hook, project: other_group_project) }
+
+ it 'lists all web hooks' do
+ expect { run_rake_task('gitlab:web_hook:list') }.to output(/3 webhooks found/).to_stdout
+ end
+
+ it 'lists web hooks in a particular namespace' do
+ stub_env('NAMESPACE', group.full_path)
+
+ expect { run_rake_task('gitlab:web_hook:list') }.to output(/2 webhooks found/).to_stdout
+ end
+ end
+end
diff --git a/spec/uploaders/namespace_file_uploader_spec.rb b/spec/uploaders/namespace_file_uploader_spec.rb
index d09725ee4be..77401814194 100644
--- a/spec/uploaders/namespace_file_uploader_spec.rb
+++ b/spec/uploaders/namespace_file_uploader_spec.rb
@@ -1,18 +1,22 @@
require 'spec_helper'
-IDENTIFIER = %r{\h+/\S+}
-
describe NamespaceFileUploader do
let(:group) { build_stubbed(:group) }
let(:uploader) { described_class.new(group) }
let(:upload) { create(:upload, :namespace_upload, model: group) }
+ let(:identifier) { %r{\h+/\S+} }
subject { uploader }
- it_behaves_like 'builds correct paths',
- store_dir: %r[uploads/-/system/namespace/\d+],
- upload_path: IDENTIFIER,
- absolute_path: %r[#{CarrierWave.root}/uploads/-/system/namespace/\d+/#{IDENTIFIER}]
+ it_behaves_like 'builds correct paths' do
+ let(:patterns) do
+ {
+ store_dir: %r[uploads/-/system/namespace/\d+],
+ upload_path: identifier,
+ absolute_path: %r[#{CarrierWave.root}/uploads/-/system/namespace/\d+/#{identifier}]
+ }
+ end
+ end
context "object_store is REMOTE" do
before do
@@ -21,9 +25,14 @@ describe NamespaceFileUploader do
include_context 'with storage', described_class::Store::REMOTE
- it_behaves_like 'builds correct paths',
- store_dir: %r[namespace/\d+/\h+],
- upload_path: IDENTIFIER
+ it_behaves_like 'builds correct paths' do
+ let(:patterns) do
+ {
+ store_dir: %r[namespace/\d+/\h+],
+ upload_path: identifier
+ }
+ end
+ end
end
context '.base_dir' do
diff --git a/spec/uploaders/personal_file_uploader_spec.rb b/spec/uploaders/personal_file_uploader_spec.rb
index 7700b14ce6b..2896e9a112d 100644
--- a/spec/uploaders/personal_file_uploader_spec.rb
+++ b/spec/uploaders/personal_file_uploader_spec.rb
@@ -1,18 +1,22 @@
require 'spec_helper'
-IDENTIFIER = %r{\h+/\S+}
-
describe PersonalFileUploader do
let(:model) { create(:personal_snippet) }
let(:uploader) { described_class.new(model) }
let(:upload) { create(:upload, :personal_snippet_upload) }
+ let(:identifier) { %r{\h+/\S+} }
subject { uploader }
- it_behaves_like 'builds correct paths',
- store_dir: %r[uploads/-/system/personal_snippet/\d+],
- upload_path: IDENTIFIER,
- absolute_path: %r[#{CarrierWave.root}/uploads/-/system/personal_snippet/\d+/#{IDENTIFIER}]
+ it_behaves_like 'builds correct paths' do
+ let(:patterns) do
+ {
+ store_dir: %r[uploads/-/system/personal_snippet/\d+],
+ upload_path: identifier,
+ absolute_path: %r[#{CarrierWave.root}/uploads/-/system/personal_snippet/\d+/#{identifier}]
+ }
+ end
+ end
context "object_store is REMOTE" do
before do
@@ -21,9 +25,14 @@ describe PersonalFileUploader do
include_context 'with storage', described_class::Store::REMOTE
- it_behaves_like 'builds correct paths',
- store_dir: %r[\d+/\h+],
- upload_path: IDENTIFIER
+ it_behaves_like 'builds correct paths' do
+ let(:patterns) do
+ {
+ store_dir: %r[\d+/\h+],
+ upload_path: identifier
+ }
+ end
+ end
end
describe '#to_h' do
diff --git a/spec/validators/url_validator_spec.rb b/spec/validators/url_validator_spec.rb
index 082d09d3f16..f3f3386382f 100644
--- a/spec/validators/url_validator_spec.rb
+++ b/spec/validators/url_validator_spec.rb
@@ -143,4 +143,33 @@ describe UrlValidator do
end
end
end
+
+ context 'when ascii_only is' do
+ let(:url) { 'https://𝕘itⅼαƄ.com/foo/foo.bar'}
+ let(:validator) { described_class.new(attributes: [:link_url], ascii_only: ascii_only) }
+
+ context 'true' do
+ let(:ascii_only) { true }
+
+ it 'prevents unicode characters' do
+ badge.link_url = url
+
+ subject
+
+ expect(badge.errors.empty?).to be false
+ end
+ end
+
+ context 'false (default)' do
+ let(:ascii_only) { false }
+
+ it 'does not prevent unicode characters' do
+ badge.link_url = url
+
+ subject
+
+ expect(badge.errors.empty?).to be true
+ end
+ end
+ end
end
diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index 98d4456b277..ec20c346234 100644
--- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -49,4 +49,30 @@ describe 'layouts/nav/sidebar/_project' do
expect(rendered).to have_css('.sidebar-top-level-items > li.active', text: 'Registry')
end
end
+
+ describe 'releases entry' do
+ describe 'when releases feature flag is disabled' do
+ before do
+ stub_feature_flags(releases_page: false)
+ end
+
+ it 'does not render releases link' do
+ render
+
+ expect(rendered).not_to have_link('Releases', href: project_releases_path(project))
+ end
+ end
+
+ describe 'when releases feature flags is enabled' do
+ before do
+ stub_feature_flags(releases_page: true)
+ end
+
+ it 'renders releases link' do
+ render
+
+ expect(rendered).to have_link('Releases', href: project_releases_path(project))
+ end
+ end
+ end
end
diff --git a/spec/views/notify/changed_milestone_email.html.haml_spec.rb b/spec/views/notify/changed_milestone_email.html.haml_spec.rb
new file mode 100644
index 00000000000..194b58840a3
--- /dev/null
+++ b/spec/views/notify/changed_milestone_email.html.haml_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'notify/changed_milestone_email.html.haml' do
+ let(:milestone) { create(:milestone, title: 'some-milestone') }
+ let(:milestone_link) { milestone_url(milestone) }
+
+ before do
+ assign(:milestone, milestone)
+ assign(:milestone_url, milestone_link)
+ end
+
+ context 'milestone without start and due dates' do
+ it 'renders without date range' do
+ render
+
+ expect(rendered).to have_content('Milestone changed to some-milestone', exact: true)
+ expect(rendered).to have_link('some-milestone', href: milestone_link)
+ end
+ end
+
+ context 'milestone with start and due dates' do
+ before do
+ milestone.update(start_date: '2018-01-01', due_date: '2018-12-31')
+ end
+
+ it 'renders with date range' do
+ render
+
+ expect(rendered).to have_content('Milestone changed to some-milestone (Jan 1, 2018–Dec 31, 2018)', exact: true)
+ expect(rendered).to have_link('some-milestone', href: milestone_link)
+ end
+ end
+end
diff --git a/spec/views/projects/_home_panel.html.haml_spec.rb b/spec/views/projects/_home_panel.html.haml_spec.rb
index fc1fe5739c3..006c93686d5 100644
--- a/spec/views/projects/_home_panel.html.haml_spec.rb
+++ b/spec/views/projects/_home_panel.html.haml_spec.rb
@@ -23,7 +23,7 @@ describe 'projects/_home_panel' do
it 'makes it possible to set notification level' do
render
- expect(view).to render_template('shared/notifications/_button')
+ expect(view).to render_template('projects/buttons/_notifications')
expect(rendered).to have_selector('.notification-dropdown')
end
end
diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb
index fa6c4ce4ac8..b0042be339c 100644
--- a/spec/views/projects/merge_requests/show.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb
@@ -32,6 +32,11 @@ describe 'projects/merge_requests/show.html.haml' do
assign(:noteable, closed_merge_request)
assign(:notes, [])
assign(:pipelines, Ci::Pipeline.none)
+ assign(
+ :issuable_sidebar,
+ MergeRequestSerializer.new(current_user: user, project: project)
+ .represent(closed_merge_request, serializer: 'sidebar')
+ )
preload_view_requirements
diff --git a/spec/workers/cluster_platform_configure_worker_spec.rb b/spec/workers/cluster_configure_worker_spec.rb
index 0eead0ab13d..6918ee3d7d8 100644
--- a/spec/workers/cluster_platform_configure_worker_spec.rb
+++ b/spec/workers/cluster_configure_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe ClusterPlatformConfigureWorker, '#perform' do
+describe ClusterConfigureWorker, '#perform' do
let(:worker) { described_class.new }
context 'when group cluster' do
diff --git a/spec/workers/cluster_provision_worker_spec.rb b/spec/workers/cluster_provision_worker_spec.rb
index 0a2dfef36a4..da32f29fec0 100644
--- a/spec/workers/cluster_provision_worker_spec.rb
+++ b/spec/workers/cluster_provision_worker_spec.rb
@@ -23,7 +23,7 @@ describe ClusterProvisionWorker do
end
it 'configures kubernetes platform' do
- expect(ClusterPlatformConfigureWorker).to receive(:perform_async).with(cluster.id)
+ expect(ClusterConfigureWorker).to receive(:perform_async).with(cluster.id)
described_class.new.perform(cluster.id)
end
@@ -32,7 +32,7 @@ describe ClusterProvisionWorker do
context 'when cluster does not exist' do
it 'does not provision a cluster' do
expect_any_instance_of(Clusters::Gcp::ProvisionService).not_to receive(:execute)
- expect(ClusterPlatformConfigureWorker).not_to receive(:perform_async)
+ expect(ClusterConfigureWorker).not_to receive(:perform_async)
described_class.new.perform(123)
end
diff --git a/spec/workers/object_pool/create_worker_spec.rb b/spec/workers/object_pool/create_worker_spec.rb
new file mode 100644
index 00000000000..06416489472
--- /dev/null
+++ b/spec/workers/object_pool/create_worker_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ObjectPool::CreateWorker do
+ let(:pool) { create(:pool_repository, :scheduled) }
+
+ subject { described_class.new }
+
+ describe '#perform' do
+ context 'when the pool creation is successful' do
+ it 'marks the pool as ready' do
+ subject.perform(pool.id)
+
+ expect(pool.reload).to be_ready
+ end
+ end
+
+ context 'when a the pool already exists' do
+ before do
+ pool.create_object_pool
+ end
+
+ it 'cleans up the pool' do
+ expect do
+ subject.perform(pool.id)
+ end.to raise_error(GRPC::FailedPrecondition)
+
+ expect(pool.reload.failed?).to be(true)
+ end
+ end
+
+ context 'when the server raises an unknown error' do
+ before do
+ allow_any_instance_of(PoolRepository).to receive(:create_object_pool).and_raise(GRPC::Internal)
+ end
+
+ it 'marks the pool as failed' do
+ expect do
+ subject.perform(pool.id)
+ end.to raise_error(GRPC::Internal)
+
+ expect(pool.reload.failed?).to be(true)
+ end
+ end
+
+ context 'when the pool creation failed before' do
+ let(:pool) { create(:pool_repository, :failed) }
+
+ it 'deletes the pool first' do
+ expect_any_instance_of(PoolRepository).to receive(:delete_object_pool)
+
+ subject.perform(pool.id)
+
+ expect(pool.reload).to be_ready
+ end
+ end
+ end
+end
diff --git a/spec/workers/object_pool/destroy_worker_spec.rb b/spec/workers/object_pool/destroy_worker_spec.rb
new file mode 100644
index 00000000000..ef74f0ba87c
--- /dev/null
+++ b/spec/workers/object_pool/destroy_worker_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+describe ObjectPool::DestroyWorker do
+ describe '#perform' do
+ context 'when no pool is in the database' do
+ it "doesn't raise an error" do
+ expect do
+ described_class.new.perform(987654321)
+ end.not_to raise_error
+ end
+ end
+
+ context 'when a pool is present' do
+ let(:pool) { create(:pool_repository, :obsolete) }
+
+ subject { described_class.new }
+
+ it 'requests Gitaly to remove the object pool' do
+ expect(Gitlab::GitalyClient).to receive(:call).with(pool.shard_name, :object_pool_service, :delete_object_pool, Object)
+
+ subject.perform(pool.id)
+ end
+
+ it 'destroys the pool' do
+ subject.perform(pool.id)
+
+ expect(PoolRepository.find_by_id(pool.id)).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/workers/object_pool/join_worker_spec.rb b/spec/workers/object_pool/join_worker_spec.rb
new file mode 100644
index 00000000000..906bc22c8d2
--- /dev/null
+++ b/spec/workers/object_pool/join_worker_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ObjectPool::JoinWorker do
+ let(:pool) { create(:pool_repository, :ready) }
+ let(:project) { pool.source_project }
+ let(:repository) { project.repository }
+
+ subject { described_class.new }
+
+ describe '#perform' do
+ context "when the pool is not joinable" do
+ let(:pool) { create(:pool_repository, :scheduled) }
+
+ it "doesn't raise an error" do
+ expect do
+ subject.perform(pool.id, project.id)
+ end.not_to raise_error
+ end
+ end
+
+ context 'when the pool has been joined before' do
+ before do
+ pool.link_repository(repository)
+ end
+
+ it 'succeeds in joining' do
+ expect do
+ subject.perform(pool.id, project.id)
+ end.not_to raise_error
+ end
+ end
+ end
+end
diff --git a/spec/workers/prune_web_hook_logs_worker_spec.rb b/spec/workers/prune_web_hook_logs_worker_spec.rb
index d7d64a1f641..b3ec71d4a00 100644
--- a/spec/workers/prune_web_hook_logs_worker_spec.rb
+++ b/spec/workers/prune_web_hook_logs_worker_spec.rb
@@ -5,18 +5,20 @@ describe PruneWebHookLogsWorker do
before do
hook = create(:project_hook)
- 5.times do
- create(:web_hook_log, web_hook: hook, created_at: 5.months.ago)
- end
-
+ create(:web_hook_log, web_hook: hook, created_at: 5.months.ago)
+ create(:web_hook_log, web_hook: hook, created_at: 4.months.ago)
+ create(:web_hook_log, web_hook: hook, created_at: 91.days.ago)
+ create(:web_hook_log, web_hook: hook, created_at: 89.days.ago)
+ create(:web_hook_log, web_hook: hook, created_at: 2.months.ago)
+ create(:web_hook_log, web_hook: hook, created_at: 1.month.ago)
create(:web_hook_log, web_hook: hook, response_status: '404')
end
- it 'removes all web hook logs older than one month' do
+ it 'removes all web hook logs older than 90 days' do
described_class.new.perform
- expect(WebHookLog.count).to eq(1)
- expect(WebHookLog.first.response_status).to eq('404')
+ expect(WebHookLog.count).to eq(4)
+ expect(WebHookLog.last.response_status).to eq('404')
end
end
end
diff --git a/spec/workers/rebase_worker_spec.rb b/spec/workers/rebase_worker_spec.rb
index 936b9deaecc..900332ed6b3 100644
--- a/spec/workers/rebase_worker_spec.rb
+++ b/spec/workers/rebase_worker_spec.rb
@@ -19,7 +19,7 @@ describe RebaseWorker, '#perform' do
expect(MergeRequests::RebaseService)
.to receive(:new).with(forked_project, merge_request.author).and_call_original
- subject.perform(merge_request, merge_request.author)
+ subject.perform(merge_request.id, merge_request.author.id)
end
end
end
diff --git a/spec/workers/remove_old_web_hook_logs_worker_spec.rb b/spec/workers/remove_old_web_hook_logs_worker_spec.rb
deleted file mode 100644
index 6d26ba5dfa0..00000000000
--- a/spec/workers/remove_old_web_hook_logs_worker_spec.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require 'spec_helper'
-
-describe RemoveOldWebHookLogsWorker do
- subject { described_class.new }
-
- describe '#perform' do
- let!(:week_old_record) { create(:web_hook_log, created_at: Time.now - 1.week) }
- let!(:three_days_old_record) { create(:web_hook_log, created_at: Time.now - 3.days) }
- let!(:one_day_old_record) { create(:web_hook_log, created_at: Time.now - 1.day) }
-
- it 'removes web hook logs older than 2 days' do
- subject.perform
-
- expect(WebHookLog.all).to include(one_day_old_record)
- expect(WebHookLog.all).not_to include(week_old_record, three_days_old_record)
- end
- end
-end
diff --git a/spec/workers/repository_cleanup_worker_spec.rb b/spec/workers/repository_cleanup_worker_spec.rb
new file mode 100644
index 00000000000..3adae0b6cfa
--- /dev/null
+++ b/spec/workers/repository_cleanup_worker_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe RepositoryCleanupWorker do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ subject(:worker) { described_class.new }
+
+ describe '#perform' do
+ it 'executes the cleanup service and sends a success notification' do
+ expect_next_instance_of(Projects::CleanupService) do |service|
+ expect(service.project).to eq(project)
+ expect(service.current_user).to eq(user)
+
+ expect(service).to receive(:execute)
+ end
+
+ expect_next_instance_of(NotificationService) do |service|
+ expect(service).to receive(:repository_cleanup_success).with(project, user)
+ end
+
+ worker.perform(project.id, user.id)
+ end
+
+ it 'raises an error if the project cannot be found' do
+ project.destroy
+
+ expect { worker.perform(project.id, user.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'raises an error if the user cannot be found' do
+ user.destroy
+
+ expect { worker.perform(project.id, user.id) }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ describe '#sidekiq_retries_exhausted' do
+ let(:job) { { 'args' => [project.id, user.id], 'error_message' => 'Error' } }
+
+ it 'does not send a failure notification for a RecordNotFound error' do
+ expect(NotificationService).not_to receive(:new)
+
+ described_class.sidekiq_retries_exhausted_block.call(job, ActiveRecord::RecordNotFound.new)
+ end
+
+ it 'sends a failure notification' do
+ expect_next_instance_of(NotificationService) do |service|
+ expect(service).to receive(:repository_cleanup_failure).with(project, user, 'Error')
+ end
+
+ described_class.sidekiq_retries_exhausted_block.call(job, StandardError.new)
+ end
+ end
+end
diff --git a/spec/workers/repository_update_remote_mirror_worker_spec.rb b/spec/workers/repository_update_remote_mirror_worker_spec.rb
index 4f1ad2474f5..d73b0b53713 100644
--- a/spec/workers/repository_update_remote_mirror_worker_spec.rb
+++ b/spec/workers/repository_update_remote_mirror_worker_spec.rb
@@ -25,12 +25,19 @@ describe RepositoryUpdateRemoteMirrorWorker do
it 'sets status as failed when update remote mirror service executes with errors' do
error_message = 'fail!'
- expect_any_instance_of(Projects::UpdateRemoteMirrorService).to receive(:execute).with(remote_mirror).and_return(status: :error, message: error_message)
+ expect_next_instance_of(Projects::UpdateRemoteMirrorService) do |service|
+ expect(service).to receive(:execute).with(remote_mirror).and_return(status: :error, message: error_message)
+ end
+
+ # Mock the finder so that it returns an object we can set expectations on
+ expect_next_instance_of(RemoteMirrorFinder) do |finder|
+ expect(finder).to receive(:execute).and_return(remote_mirror)
+ end
+ expect(remote_mirror).to receive(:mark_as_failed).with(error_message)
+
expect do
subject.perform(remote_mirror.id, Time.now)
end.to raise_error(RepositoryUpdateRemoteMirrorWorker::UpdateError, error_message)
-
- expect(remote_mirror.reload.update_status).to eq('failed')
end
it 'does nothing if last_update_started_at is higher than the time the job was scheduled in' do
diff --git a/spec/workers/stuck_merge_jobs_worker_spec.rb b/spec/workers/stuck_merge_jobs_worker_spec.rb
index c2c2a5f9121..5aaff27a6b2 100644
--- a/spec/workers/stuck_merge_jobs_worker_spec.rb
+++ b/spec/workers/stuck_merge_jobs_worker_spec.rb
@@ -38,7 +38,8 @@ describe StuckMergeJobsWorker do
create(:merge_request, :locked, merge_jid: '123')
create(:merge_request, :locked, merge_jid: '456')
- expect(Rails).to receive_message_chain(:logger, :info).with('Updated state of locked merge jobs. JIDs: 123, 456')
+ expect(described_class).to receive_message_chain(:logger, :info)
+ .with('Updated state of locked merge jobs. JIDs: 123, 456')
worker.perform
end
diff --git a/vendor/gitignore/CMake.gitignore b/vendor/gitignore/CMake.gitignore
index 9ea395f15ee..7e25564f9ec 100644
--- a/vendor/gitignore/CMake.gitignore
+++ b/vendor/gitignore/CMake.gitignore
@@ -1,3 +1,4 @@
+CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
diff --git a/vendor/gitignore/Drupal.gitignore b/vendor/gitignore/Drupal.gitignore
index 072b683190f..50d3eef8a33 100644
--- a/vendor/gitignore/Drupal.gitignore
+++ b/vendor/gitignore/Drupal.gitignore
@@ -1,39 +1,46 @@
-# Ignore configuration files that may contain sensitive information.
-sites/*/*settings*.php
-sites/example.sites.php
+# gitignore template for Drupal 8 projects
+#
+# earlier versions of Drupal are tracked in `community/Python/`
-# Ignore paths that contain generated content.
-files/
-sites/*/files
-sites/*/private
-sites/*/translations
+# Ignore configuration files that may contain sensitive information
+/sites/*/*settings*.php
+/sites/*/*services*.yml
-# Ignore default text files
-robots.txt
-/CHANGELOG.txt
-/COPYRIGHT.txt
-/INSTALL*.txt
+# Ignore paths that may contain user-generated content
+/sites/*/files
+/sites/*/public
+/sites/*/private
+/sites/*/files-public
+/sites/*/files-private
+
+# Ignore paths that may contain temporary files
+/sites/*/translations
+/sites/*/tmp
+/sites/*/cache
+
+# Ignore drupal core (if not versioning drupal sources)
+/core
+/modules/README.txt
+/profiles/README.txt
+/sites/README.txt
+/sites/example.sites.php
+/sites/example.settings.local.php
+/sites/development.services.yml
+/themes/README.txt
+/vendor
+/.csslintrc
+/.editorconfig
+/.eslintignore
+/.eslintrc.json
+/.gitattributes
+/.htaccess
+/autoload.php
+/composer.json
+/composer.lock
+/example.gitignore
+/index.php
/LICENSE.txt
-/MAINTAINERS.txt
-/UPGRADE.txt
/README.txt
-sites/README.txt
-sites/all/libraries/README.txt
-sites/all/modules/README.txt
-sites/all/themes/README.txt
-
-# Ignore everything but the "sites" folder ( for non core developer )
-.htaccess
-web.config
-authorize.php
-cron.php
-index.php
-install.php
-update.php
-xmlrpc.php
-/includes
-/misc
-/modules
-/profiles
-/scripts
-/themes
+/robots.txt
+/update.php
+/web.config
diff --git a/vendor/gitignore/Global/Emacs.gitignore b/vendor/gitignore/Global/Emacs.gitignore
index 3ac7904dcd2..d40e86599b5 100644
--- a/vendor/gitignore/Global/Emacs.gitignore
+++ b/vendor/gitignore/Global/Emacs.gitignore
@@ -43,3 +43,7 @@ flycheck_*.el
# directory configuration
.dir-locals.el
+
+# network security
+/network-security.data
+
diff --git a/vendor/gitignore/Global/PuTTY.gitignore b/vendor/gitignore/Global/PuTTY.gitignore
new file mode 100644
index 00000000000..c37466b1c79
--- /dev/null
+++ b/vendor/gitignore/Global/PuTTY.gitignore
@@ -0,0 +1,2 @@
+# Private key
+*.ppk
diff --git a/vendor/gitignore/Global/Virtuoso.gitignore b/vendor/gitignore/Global/Virtuoso.gitignore
new file mode 100644
index 00000000000..2de03673a6c
--- /dev/null
+++ b/vendor/gitignore/Global/Virtuoso.gitignore
@@ -0,0 +1,18 @@
+# Gitignore for Cadence Virtuoso
+################################################################
+
+# Log files
+*.log
+panic*.log.*
+
+# OpenAccess database lock files
+*.cdslck*
+
+# Run directories for layout vs. schematic and design rule check
+lvsRunDir/*
+drcRunDir/*
+
+# Abstract generation tool
+abstract.log*
+abstract.record*
+
diff --git a/vendor/gitignore/Global/Xcode.gitignore b/vendor/gitignore/Global/Xcode.gitignore
index b01314d3a64..cd0c7d3e45a 100644
--- a/vendor/gitignore/Global/Xcode.gitignore
+++ b/vendor/gitignore/Global/Xcode.gitignore
@@ -2,11 +2,17 @@
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
-## Build generated
+## User settings
+xcuserdata/
+
+## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
+*.xcscmblueprint
+*.xccheckout
+
+## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
-
-## Various settings
+*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
@@ -15,65 +21,3 @@ DerivedData/
!default.mode2v3
*.perspectivev3
!default.perspectivev3
-xcuserdata/
-
-## Other
-*.moved-aside
-*.xccheckout
-*.xcscmblueprint
-
-## Obj-C/Swift specific
-*.hmap
-*.ipa
-*.dSYM.zip
-*.dSYM
-
-## Playgrounds
-timeline.xctimeline
-playground.xcworkspace
-
-# Swift Package Manager
-#
-# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
-# Packages/
-# Package.pins
-# Package.resolved
-.build/
-
-# CocoaPods
-#
-# We recommend against adding the Pods directory to your .gitignore. However
-# you should judge for yourself, the pros and cons are mentioned at:
-# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
-#
-# Pods/
-#
-# Add this line if you want to avoid checking in source code from the Xcode workspace
-# *.xcworkspace
-
-# Carthage
-#
-# Add this line if you want to avoid checking in source code from Carthage dependencies.
-# Carthage/Checkouts
-
-Carthage/Build
-
-# fastlane
-#
-# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
-# screenshots whenever they are needed.
-# For more information about the recommended setup visit:
-# https://docs.fastlane.tools/best-practices/source-control/#source-control
-
-fastlane/report.xml
-fastlane/Preview.html
-fastlane/screenshots/**/*.png
-fastlane/test_output
-
-# Code Injection
-#
-# After new code Injection tools there's a generated folder /iOSInjectionProject
-# https://github.com/johnno1962/injectionforxcode
-
-iOSInjectionProject/
-
diff --git a/vendor/gitignore/Node.gitignore b/vendor/gitignore/Node.gitignore
index e1da6ae8ea5..ebfe43954dc 100644
--- a/vendor/gitignore/Node.gitignore
+++ b/vendor/gitignore/Node.gitignore
@@ -70,7 +70,10 @@ typings/
.vuepress/dist
# Serverless directories
-.serverless
+.serverless/
# FuseBox cache
.fusebox/
+
+#DynamoDB Local files
+.dynamodb/
diff --git a/vendor/gitignore/Python.gitignore b/vendor/gitignore/Python.gitignore
index 510c73d0fdb..11614af2870 100644
--- a/vendor/gitignore/Python.gitignore
+++ b/vendor/gitignore/Python.gitignore
@@ -20,6 +20,7 @@ parts/
sdist/
var/
wheels/
+share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
diff --git a/vendor/gitignore/Smalltalk.gitignore b/vendor/gitignore/Smalltalk.gitignore
index 943995e1172..178d87af45b 100644
--- a/vendor/gitignore/Smalltalk.gitignore
+++ b/vendor/gitignore/Smalltalk.gitignore
@@ -1,8 +1,11 @@
# changes file
*.changes
+*.chg
# system image
*.image
+*.img7
+*.img
# Pharo Smalltalk Debug log file
PharoDebug.log
@@ -10,6 +13,12 @@ PharoDebug.log
# Squeak Smalltalk Debug log file
SqueakDebug.log
+# Dolphin Smalltalk source file
+*.sml
+
+# Dolphin Smalltalk error file
+*.errors
+
# Monticello package cache
/package-cache
diff --git a/vendor/gitignore/TeX.gitignore b/vendor/gitignore/TeX.gitignore
index 753f2b954ff..edd1f60b726 100644
--- a/vendor/gitignore/TeX.gitignore
+++ b/vendor/gitignore/TeX.gitignore
@@ -205,6 +205,10 @@ pythontex-files-*/
# todonotes
*.tdo
+# vhistory
+*.hst
+*.ver
+
# easy-todo
*.lod
diff --git a/vendor/gitignore/Unity.gitignore b/vendor/gitignore/Unity.gitignore
index 833e6d4291c..93c9ce52191 100644
--- a/vendor/gitignore/Unity.gitignore
+++ b/vendor/gitignore/Unity.gitignore
@@ -35,3 +35,7 @@ sysinfo.txt
# Builds
*.apk
*.unitypackage
+
+# Crashlytics generated file
+Assets/StreamingAssets/crashlytics-build.properties
+
diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore
index 4d13c54854e..4ba92b04afb 100644
--- a/vendor/gitignore/VisualStudio.gitignore
+++ b/vendor/gitignore/VisualStudio.gitignore
@@ -20,6 +20,8 @@
[Rr]eleases/
x64/
x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
@@ -229,6 +231,8 @@ orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
+# ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true
+**/wwwroot/lib/
# RIA/Silverlight projects
Generated_Code/
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index f6fd1efaa83..d706d76358a 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -67,10 +67,15 @@
@babel/template,7.1.2,MIT
@babel/traverse,7.1.0,MIT
@babel/types,7.1.2,MIT
-@gitlab/svgs,1.35.0,MIT
-@gitlab/ui,1.11.0,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
+@types/async,2.0.50,MIT
@types/jquery,2.0.48,MIT
+@types/node,10.12.9,MIT
+@types/semver,5.5.0,MIT
+@types/zen-observable,0.8.0,MIT
@vue/component-compiler-utils,2.2.0,MIT
@webassemblyjs/ast,1.7.6,MIT
@webassemblyjs/floating-point-hex-parser,1.7.6,MIT
@@ -98,13 +103,14 @@ accepts,1.3.5,MIT
ace-rails-ap,4.1.2,MIT
acorn,5.7.3,MIT
acorn-dynamic-import,3.0.0,MIT
-actionmailer,4.2.10,MIT
-actionpack,4.2.10,MIT
-actionview,4.2.10,MIT
-activejob,4.2.10,MIT
-activemodel,4.2.10,MIT
-activerecord,4.2.10,MIT
-activesupport,4.2.10,MIT
+actioncable,5.0.7,MIT
+actionmailer,5.0.7,MIT
+actionpack,5.0.7,MIT
+actionview,5.0.7,MIT
+activejob,5.0.7,MIT
+activemodel,5.0.7,MIT
+activerecord,5.0.7,MIT
+activesupport,5.0.7,MIT
acts-as-taggable-on,5.0.0,MIT
addressable,2.5.2,Apache 2.0
aes_key_wrap,1.0.1,MIT
@@ -119,24 +125,35 @@ ansi-regex,3.0.0,MIT
ansi-styles,2.2.1,MIT
ansi-styles,3.2.1,MIT
anymatch,2.0.0,ISC
+apollo-boost,0.1.20,MIT
+apollo-cache,1.1.20,MIT
+apollo-cache-inmemory,1.3.9,MIT
+apollo-client,2.4.5,MIT
+apollo-link,1.2.3,MIT
+apollo-link-dedup,1.0.10,MIT
+apollo-link-error,1.1.1,MIT
+apollo-link-http,1.5.5,MIT
+apollo-link-http-common,0.2.5,MIT
+apollo-link-state,0.4.2,MIT
+apollo-utilities,1.0.25,MIT
aproba,1.2.0,ISC
are-we-there-yet,1.1.4,ISC
-arel,6.0.4,MIT
+arel,7.1.4,MIT
arr-diff,4.0.0,MIT
arr-flatten,1.1.0,MIT
arr-union,3.1.0,MIT
array-flatten,1.1.1,MIT
array-uniq,1.0.3,MIT
array-unique,0.3.2,MIT
-asana,0.6.0,MIT
-asciidoctor,1.5.6.2,MIT
+asana,0.8.1,MIT
+asciidoctor,1.5.8,MIT
asciidoctor-plantuml,0.0.8,MIT
asn1.js,4.10.1,MIT
assert,1.4.1,MIT
assign-symbols,1.0.0,MIT
async-each,1.0.1,MIT
async-limiter,1.0.0,MIT
-atob,2.0.3,(MIT OR 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
attr_required,1.0.0,MIT
@@ -147,12 +164,13 @@ babel-code-frame,6.26.0,MIT
babel-loader,8.0.4,MIT
babel-polyfill,6.23.0,MIT
babel-runtime,6.26.0,MIT
+babel-standalone,6.26.0,MIT
babosa,1.0.2,MIT
balanced-match,1.0.0,MIT
base,0.11.2,MIT
base32,0.3.2,MIT
base64-js,1.2.3,MIT
-batch-loader,1.2.1,MIT
+batch-loader,1.2.2,MIT
bcrypt,3.1.12,MIT
bcrypt_pbkdf,1.0.0,MIT
bfj,6.1.1,MIT
@@ -163,7 +181,7 @@ bindata,2.4.3,ruby
bluebird,3.5.1,MIT
bn.js,4.11.8,MIT
body-parser,1.18.2,MIT
-bootstrap,4.1.1,MIT
+bootstrap,4.1.3,MIT
bootstrap-vue,2.0.0-rc.11,MIT
bootstrap_form,2.7.0,MIT
brace-expansion,1.1.11,MIT
@@ -216,7 +234,7 @@ clipboard,1.7.1,MIT
cliui,4.0.0,ISC
clone-response,1.0.2,MIT
code-point-at,1.1.0,MIT
-codesandbox-api,0.0.18,MIT
+codesandbox-api,0.0.20,MIT
codesandbox-import-util-types,1.2.11,LGPL
codesandbox-import-utils,1.2.11,LGPL
coercible,1.0.0,MIT
@@ -224,14 +242,15 @@ collection-visit,1.0.0,MIT
color-convert,1.9.3,MIT
color-name,1.1.3,MIT
commander,2.13.0,MIT
-commander,2.18.0,MIT
+commander,2.19.0,MIT
commondir,1.0.1,MIT
commonmarker,0.17.13,MIT
component-emitter,1.2.1,MIT
compression-webpack-plugin,2.0.0,MIT
concat-map,0.0.1,MIT
concat-stream,1.6.2,MIT
-concurrent-ruby-ext,1.0.5,MIT
+concurrent-ruby-ext,1.1.3,MIT
+config-chain,1.1.12,MIT
connection_pool,2.2.2,MIT
console-browserify,1.1.0,MIT
console-control-strings,1.1.0,ISC
@@ -244,6 +263,7 @@ cookie,0.3.1,MIT
cookie-signature,1.0.6,MIT
copy-concurrently,1.0.5,ISC
copy-descriptor,0.1.1,MIT
+copy-to-clipboard,3.0.8,MIT
core-js,2.3.0,MIT
core-js,2.5.7,MIT
core-util-is,1.0.2,MIT
@@ -262,7 +282,6 @@ css-selector-tokenizer,0.7.0,MIT
css_parser,1.5.0,MIT
cssesc,0.1.0,MIT
cyclist,0.2.2,MIT*
-d3,4.12.2,New BSD
d3,4.13.0,New BSD
d3-array,1.2.1,New BSD
d3-axis,1.0.8,New BSD
@@ -275,7 +294,6 @@ d3-drag,1.2.1,New BSD
d3-dsv,1.0.8,New BSD
d3-ease,1.0.3,New BSD
d3-force,1.1.0,New BSD
-d3-format,1.2.1,New BSD
d3-format,1.2.2,New BSD
d3-geo,1.9.1,New BSD
d3-hierarchy,1.1.5,New BSD
@@ -287,7 +305,6 @@ d3-queue,3.0.7,New BSD
d3-random,1.1.0,New BSD
d3-request,1.0.6,New BSD
d3-scale,1.0.7,New BSD
-d3-selection,1.2.0,New BSD
d3-selection,1.3.0,New BSD
d3-shape,1.2.0,New BSD
d3-time,1.0.8,New BSD
@@ -302,7 +319,7 @@ date-now,0.1.4,MIT
dateformat,3.0.3,MIT
de-indent,1.0.2,MIT
debug,2.6.9,MIT
-debug,3.2.5,MIT
+debug,3.2.6,MIT
debugger-ruby_core_source,1.3.8,MIT
decamelize,2.0.0,MIT
deckar01-task_list,2.0.0,MIT
@@ -310,8 +327,7 @@ declarative,0.0.10,MIT
declarative-option,0.1.0,MIT
decode-uri-component,0.2.0,MIT
decompress-response,3.3.0,MIT
-deep-extend,0.4.2,MIT
-default_value_for,3.0.2,MIT
+deep-extend,0.6.0,MIT
define-properties,1.1.3,MIT
define-property,0.2.5,MIT
define-property,1.0.0,MIT
@@ -330,6 +346,7 @@ devise-two-factor,3.0.0,MIT
diff,3.5.0,New BSD
diffie-hellman,5.0.2,MIT
diffy,3.1.0,MIT
+discordrb-webhooks-blackst0ne,3.3.0,MIT
document-register-element,1.3.0,MIT
dom-serializer,0.1.0,MIT
domain-browser,1.1.7,MIT
@@ -345,8 +362,10 @@ dropzone,4.2.0,MIT
duplexer,0.1.1,MIT
duplexer3,0.1.4,New BSD
duplexify,3.5.3,MIT
+echarts,4.2.0-rc.2,Apache 2.0
ed25519,1.2.4,MIT
editions,1.3.4,MIT
+editorconfig,0.15.2,MIT
ee-first,1.1.1,MIT
ejs,2.6.1,Apache 2.0
electron-to-chromium,1.3.73,ISC
@@ -368,7 +387,7 @@ es-to-primitive,1.1.1,MIT
es6-promise,3.0.2,MIT
escape-html,1.0.3,MIT
escape-string-regexp,1.0.5,MIT
-escape_utils,1.1.1,MIT
+escape_utils,1.2.1,MIT
escaper,2.5.3,MIT
eslint-scope,4.0.0,Simplified BSD
esrecurse,4.2.1,Simplified BSD
@@ -447,9 +466,10 @@ get-value,2.0.6,MIT
get_process_mem,0.2.0,MIT
gettext_i18n_rails,1.8.0,MIT
gettext_i18n_rails_js,1.3.0,MIT
-gitaly-proto,0.123.0,MIT
+gitaly-proto,1.3.0,MIT
github-markup,1.7.0,MIT
-gitlab-markup,1.6.4,MIT
+gitlab-default_value_for,3.1.1,MIT
+gitlab-markup,1.6.5,MIT
gitlab-sidekiq-fetcher,0.3.0,LGPL
gitlab_omniauth-ldap,2.0.4,MIT
glob,7.1.3,ISC
@@ -464,7 +484,7 @@ google-protobuf,3.6.1,New BSD
googleapis-common-protos-types,1.0.2,Apache 2.0
googleauth,0.6.6,Apache 2.0
got,8.3.0,MIT
-gpgme,2.0.13,LGPL-2.1+
+gpgme,2.0.18,LGPL-2.1+
graceful-fs,4.1.11,ISC
grape,1.1.0,MIT
grape-entity,0.7.1,MIT
@@ -473,6 +493,9 @@ grape_logging,1.7.0,MIT
graphiql-rails,1.4.10,MIT
graphlibrary,2.2.0,MIT
graphql,1.8.1,MIT
+graphql,14.0.2,MIT
+graphql-anywhere,4.1.22,MIT
+graphql-tag,2.10.0,MIT
grpc,1.15.0,Apache 2.0
gzip-size,5.0.0,MIT
hamlit,2.8.8,MIT
@@ -496,6 +519,7 @@ hashie,3.5.7,MIT
hashie-forbidden_attributes,0.1.1,MIT
he,1.1.1,MIT
health_check,2.6.0,MIT
+highlight.js,9.13.1,New BSD
hipchat,1.5.2,MIT
hmac-drbg,1.0.1,MIT
hoopy,0.1.4,MIT
@@ -503,16 +527,16 @@ html-pipeline,2.8.4,MIT
html2text,0.2.0,MIT
htmlentities,4.3.4,MIT
htmlparser2,3.9.2,MIT
-http,2.2.2,MIT
+http,3.3.0,MIT
http-cache-semantics,3.8.1,Simplified BSD
http-cookie,1.0.3,MIT
http-errors,1.6.2,MIT
-http-form_data,1.0.3,MIT
+http-form_data,2.1.1,MIT
http_parser.rb,0.6.0,MIT
httparty,0.13.7,MIT
httpclient,2.8.3,ruby
https-browserify,1.0.0,MIT
-i18n,0.9.5,MIT
+i18n,1.1.1,MIT
icalendar,2.4.1,ruby
ice_nine,0.11.2,MIT
iconv-lite,0.4.19,MIT
@@ -523,6 +547,7 @@ ieee754,1.1.11,New BSD
iferr,0.1.5,MIT
ignore-walk,3.0.1,ISC
immediate,3.0.6,MIT
+immutable-tuple,0.4.9,MIT
import-local,1.0.0,MIT
imports-loader,0.8.0,MIT
imurmurhash,0.1.4,MIT
@@ -578,12 +603,14 @@ isobject,2.1.0,MIT
isobject,3.0.1,MIT
istextorbinary,2.2.1,MIT
isurl,1.0.0,MIT
+iterall,1.2.2,MIT
jed,1.1.1,MIT
jira-ruby,1.4.1,MIT
jquery,3.3.1,MIT
jquery-atwho-rails,1.3.2,MIT
jquery-ujs,1.2.2,MIT
jquery.waitforimages,2.2.0,MIT
+js-beautify,1.8.8,MIT
js-cookie,2.1.3,MIT
js-levenshtein,1.1.4,MIT
js-tokens,3.0.2,MIT
@@ -611,7 +638,7 @@ kind-of,3.2.2,MIT
kind-of,4.0.0,MIT
kind-of,5.1.0,MIT
kind-of,6.0.2,MIT
-kubeclient,3.1.0,MIT
+kubeclient,4.0.0,MIT
lazy-cache,2.0.2,MIT
lcid,2.0.0,MIT
licensee,8.9.2,MIT
@@ -631,7 +658,7 @@ lodash.isequal,4.5.0,MIT
lodash.mergewith,4.6.0,MIT
lodash.startcase,4.4.0,MIT
lograge,0.10.0,MIT
-loofah,2.2.2,MIT
+loofah,2.2.3,MIT
loose-envify,1.4.0,MIT
lowercase-keys,1.0.0,MIT
lru-cache,4.1.3,ISC
@@ -653,17 +680,17 @@ memory-fs,0.4.1,MIT
merge-descriptors,1.0.1,MIT
merge-source-map,1.1.0,MIT
mermaid,8.0.0-rc.8,MIT
-method_source,0.9.0,MIT
+method_source,0.9.2,MIT
methods,1.1.2,MIT
micromatch,3.1.10,MIT
miller-rabin,4.0.1,MIT
mime,1.4.1,MIT
mime,2.3.1,MIT
-mime-db,1.33.0,MIT
-mime-types,2.1.18,MIT
-mime-types,3.1,MIT
-mime-types-data,3.2016.0521,MIT
-mimemagic,0.3.0,MIT
+mime-db,1.37.0,MIT
+mime-types,2.1.21,MIT
+mime-types,3.2.2,MIT
+mime-types-data,3.2018.0812,MIT
+mimemagic,0.3.2,MIT
mimic-fn,1.1.0,MIT
mimic-response,1.0.0,MIT
mini_magick,4.8.0,MIT
@@ -695,20 +722,22 @@ mustermann,1.0.3,MIT
mustermann-grape,1.0.0,MIT
mute-stream,0.0.7,ISC
mysql2,0.4.10,MIT
+nakayoshi_fork,0.0.4,MIT
nan,2.10.0,MIT
nanomatch,1.2.9,MIT
-needle,2.2.1,MIT
+needle,2.2.4,MIT
negotiator,0.6.1,MIT
neo-async,2.5.0,MIT
net-ldap,0.16.0,MIT
net-ssh,5.0.1,MIT
netrc,0.11.0,MIT
nice-try,1.0.4,MIT
+nio4r,2.3.1,MIT
node-fetch,1.6.3,MIT
node-libs-browser,2.1.0,MIT
-node-pre-gyp,0.10.0,New BSD
+node-pre-gyp,0.10.3,New BSD
node-releases,1.0.0-alpha.12,CC-BY-4.0
-nokogiri,1.8.4,MIT
+nokogiri,1.8.5,MIT
nokogumbo,1.5.0,Apache 2.0
nopt,4.0.1,ISC
normalize-path,2.1.1,MIT
@@ -752,6 +781,7 @@ onetime,2.0.1,MIT
opencollective,1.0.3,MIT
opener,1.5.1,(WTFPL OR MIT)
opn,4.0.2,MIT
+optimism,0.6.8,MIT
org-ruby,0.9.12,MIT
orm_adapter,0.5.0,MIT
os,1.0.0,MIT
@@ -818,6 +848,7 @@ process-nextick-args,1.0.7,MIT
process-nextick-args,2.0.0,MIT
prometheus-client-mmap,0.9.4,Apache 2.0
promise-inflight,1.0.1,ISC
+proto-list,1.2.4,ISC
proxy-addr,2.0.4,MIT
prr,1.0.1,MIT
pseudomap,1.0.2,ISC
@@ -835,20 +866,20 @@ qs,6.5.1,New BSD
query-string,5.1.1,MIT
querystring,0.2.0,MIT
querystring-es3,0.2.1,MIT
-rack,1.6.10,MIT
+rack,2.0.6,MIT
rack-accept,0.4.5,MIT
rack-attack,4.4.1,MIT
rack-cors,1.0.2,MIT
rack-oauth2,1.2.3,MIT
-rack-protection,2.0.3,MIT
+rack-protection,2.0.4,MIT
rack-proxy,0.6.0,MIT
rack-test,0.6.3,MIT
-rails,4.2.10,MIT
+rails,5.0.7,MIT
rails-deprecated_sanitizer,1.0.3,MIT
-rails-dom-testing,1.0.9,MIT
+rails-dom-testing,2.0.3,MIT
rails-html-sanitizer,1.0.4,MIT
-rails-i18n,4.0.9,MIT
-railties,4.2.10,MIT
+rails-i18n,5.1.1,MIT
+railties,5.0.7,MIT
rainbow,3.0.0,MIT
raindrops,0.18.0,LGPL-2.1+
rake,12.3.1,MIT
@@ -862,7 +893,7 @@ raw-loader,0.5.1,MIT
rb-fsevent,0.10.2,MIT
rb-inotify,0.9.10,MIT
rbtrace,0.4.10,MIT
-rc,1.2.5,(BSD-2-Clause OR MIT OR Apache-2.0)
+rc,1.2.8,(BSD-2-Clause OR MIT OR Apache-2.0)
rdoc,6.0.4,ruby
re2,1.1.1,New BSD
readable-stream,2.0.6,MIT
@@ -877,7 +908,7 @@ redis-activesupport,5.0.4,MIT
redis-namespace,1.6.0,MIT
redis-rack,2.0.4,MIT
redis-rails,5.0.2,MIT
-redis-store,1.4.1,MIT
+redis-store,1.6.0,MIT
regenerate,1.4.0,MIT
regenerate-unicode-properties,7.0.0,MIT
regenerator-runtime,0.10.5,MIT
@@ -920,7 +951,7 @@ ruby-fogbugz,0.2.1,MIT
ruby-prof,0.17.0,Simplified BSD
ruby-progressbar,1.9.0,MIT
ruby-saml,1.7.2,MIT
-ruby_parser,3.9.0,MIT
+ruby_parser,3.11.0,MIT
rubyntlm,0.6.2,MIT
rubypants,0.2.0,BSD
rufus-scheduler,3.4.0,MIT
@@ -949,9 +980,9 @@ seed-fu,2.3.7,MIT
select,1.1.2,MIT
select2,3.5.2-browserify,Apache*
select2-rails,3.5.9.3,MIT
-semver,5.5.1,ISC
+semver,5.6.0,ISC
send,0.16.2,MIT
-sentry-raven,2.7.2,Apache 2.0
+sentry-raven,2.7.4,Apache 2.0
serialize-javascript,1.4.0,New BSD
serve-static,1.13.2,MIT
set-blocking,2.0.0,ISC
@@ -963,18 +994,19 @@ setimmediate,1.0.5,MIT
setprototypeof,1.0.3,ISC
setprototypeof,1.1.0,ISC
settingslogic,2.0.9,MIT
-sexp_processor,4.9.0,MIT
+sexp_processor,4.11.0,MIT
sha.js,2.4.10,MIT
sha1,1.1.1,New BSD
shebang-command,1.2.0,MIT
shebang-regex,1.0.0,MIT
-sidekiq,5.2.1,LGPL
+sidekiq,5.2.3,LGPL
sidekiq-cron,0.6.0,MIT
+sigmund,1.0.1,ISC
signal-exit,3.0.2,ISC
signet,0.11.0,Apache 2.0
slack-notifier,1.5.1,MIT
slugify,1.3.1,MIT
-smooshpack,0.0.48,LGPL
+smooshpack,0.0.53,LGPL
snapdragon,0.8.1,MIT
snapdragon-node,2.1.1,MIT
snapdragon-util,3.0.1,MIT
@@ -1018,6 +1050,7 @@ style-loader,0.23.0,MIT
supports-color,2.0.0,MIT
supports-color,5.5.0,MIT
svg4everybody,2.1.9,CC0-1.0
+symbol-observable,1.2.0,MIT
sys-filesystem,1.1.6,Artistic 2.0
tapable,1.1.0,MIT
tar,4.4.4,ISC
@@ -1029,6 +1062,7 @@ thread_safe,0.3.6,Apache 2.0
three,0.84.0,MIT
three-orbit-controls,82.1.0,MIT
three-stl-loader,1.0.4,MIT
+throttle-debounce,2.0.1,MIT
through,2.3.8,MIT
through2,2.0.3,MIT
tilt,2.0.8,MIT
@@ -1043,6 +1077,7 @@ to-fast-properties,2.0.0,MIT
to-object-path,0.3.0,MIT
to-regex,3.0.2,MIT
to-regex-range,2.1.1,MIT
+toggle-selection,1.0.6,MIT
toml-rb,1.0.0,MIT
trim-right,1.0.1,MIT
trollop,2.1.3,MIT
@@ -1079,6 +1114,7 @@ urix,0.1.0,MIT
url,0.11.0,MIT
url-loader,1.1.1,MIT
url-parse-lax,3.0.0,MIT
+url-search-params-polyfill,5.0.0,MIT
url-to-options,1.0.1,MIT
use,2.0.2,MIT
util,0.10.3,MIT
@@ -1094,6 +1130,7 @@ visibilityjs,1.2.4,MIT
vm-browserify,0.0.4,MIT
vmstat,2.3.0,MIT
vue,2.5.17,MIT
+vue-apollo,3.0.0-beta.25,ISC
vue-functional-data-merge,2.0.6,MIT
vue-hot-reload-api,2.3.0,MIT
vue-loader,15.4.2,MIT
@@ -1112,6 +1149,8 @@ webpack-cli,3.1.0,MIT
webpack-rails,0.9.11,MIT
webpack-sources,1.3.0,MIT
webpack-stats-plugin,0.2.1,MIT
+websocket-driver,0.6.5,MIT
+websocket-extensions,0.1.3,MIT
which,1.3.0,ISC
which-module,2.0.0,ISC
wide-align,1.1.2,ISC
@@ -1131,3 +1170,6 @@ yallist,2.1.2,ISC
yallist,3.0.2,ISC
yargs,12.0.2,MIT
yargs-parser,10.1.0,ISC
+zen-observable,0.8.11,MIT
+zen-observable-ts,0.8.10,MIT
+zrender,4.0.5,New BSD
diff --git a/yarn.lock b/yarn.lock
index d4906a6a212..90d3d6fb7cb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -616,37 +616,45 @@
lodash "^4.17.10"
to-fast-properties "^2.0.0"
-"@gitlab/eslint-config@^1.2.0":
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/@gitlab/eslint-config/-/eslint-config-1.2.0.tgz#115568a70edabbc024f1bc13ba1ba499a9ba05a9"
- integrity sha512-TnZO5T7JjLQjw30aIGtKIsAX4pRnSbqOir3Ji5zPwtCVWY53DnG6Lcesgy7WYdsnnkt3oQPXFTOZlkymUs2PsA==
+"@gitlab/csslab@^1.8.0":
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/csslab/-/csslab-1.8.0.tgz#54a2457fdc80f006665f0e578a5532780954ccfa"
+ integrity sha512-RZylRElufH1kwsBQlIDaVcrcXMyD5IEGrU6ABUd8W3LG8/F9jJ4Y3Ys7EPTpK/qFJyx86AutTtFGRxRNlMx85w==
+ dependencies:
+ bootstrap "4.1.3"
+
+"@gitlab/eslint-config@^1.4.0":
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/eslint-config/-/eslint-config-1.4.0.tgz#2e59e55a7cd024e3a450d2a896060ec4d763a5dc"
+ integrity sha512-nkecTWRNS/KD9q5lHFSc3J6zO/g1/OV9DaKiay+0nLjnGO9jQVRArRIYpnzgbUz2p15jOMVToVafW0YbbHZkwg==
dependencies:
babel-eslint "^10.0.1"
eslint-config-airbnb-base "^13.1.0"
- eslint-config-prettier "^3.1.0"
+ eslint-config-prettier "^3.3.0"
eslint-plugin-filenames "^1.3.2"
eslint-plugin-import "^2.14.0"
eslint-plugin-promise "^4.0.1"
- eslint-plugin-vue "^5.0.0-beta.3"
+ eslint-plugin-vue "^5.0.0"
-"@gitlab/svgs@^1.40.0":
- version "1.41.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.41.0.tgz#f80e3a0e259f3550af00685556ea925e471276d3"
- integrity sha512-tKUXyqe54efWBsjQBUcvNF0AvqmE2NI2No3Bnix/gKDRImzIlcgIkM67Y8zoJv1D0w4CO87WcaG5GLpIFIT1Pg==
+"@gitlab/svgs@^1.43.0":
+ version "1.43.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.43.0.tgz#28dee2122d068cd3b925cd9884d97465ebaca12d"
+ integrity sha512-wuN3NITmyBV9bOXsFfAjtndFrjTlH6Kf3+6aqT5kHKKLe/B4w7uTU1L9H/cyR0wGD7HbOh584a05eDcuH04fPA==
-"@gitlab/ui@^1.11.0":
- version "1.11.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-1.11.0.tgz#b771c2c3d627cf9efbe98c71ee5739624f2ff51f"
- integrity sha512-hGMHM45kcv9725R6G+n/HxvF3KfVb9oBGRNf1+4n3xAGmtXJ2NlPdIXIsDaye3EeVF9PTOtjLuaqrcp6AGNqZg==
+"@gitlab/ui@^1.18.0":
+ version "1.18.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-1.18.0.tgz#5cc591b2c7958e59fa7b1b443d4235e0e8f956c9"
+ integrity sha512-JqmiRSGYmK0DbGBQJBpjeRrcgjK25rCqG6QW6/GPTVLtRjbPPZYGvVg5PyA6nJUGAnwFoeApUZVML6X3OpnV1Q==
dependencies:
babel-standalone "^6.26.0"
bootstrap-vue "^2.0.0-rc.11"
copy-to-clipboard "^3.0.8"
+ echarts "^4.2.0-rc.2"
highlight.js "^9.13.1"
js-beautify "^1.8.8"
lodash "^4.17.11"
url-search-params-polyfill "^5.0.0"
- vue "^2.5.16"
+ vue "^2.5.21"
vue-loader "^15.4.2"
"@sindresorhus/is@^0.7.0":
@@ -698,6 +706,16 @@
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==
+"@types/strip-bom@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2"
+ integrity sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=
+
+"@types/strip-json-comments@0.0.30":
+ version "0.0.30"
+ resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1"
+ integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==
+
"@types/zen-observable@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d"
@@ -882,6 +900,11 @@
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.1.tgz#5c85d662f76fa1d34575766c5dcd6615abcd30d8"
integrity sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g==
+"@yarnpkg/lockfile@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
+ integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
+
abab@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f"
@@ -915,24 +938,22 @@ acorn-globals@^4.1.0:
acorn "^6.0.1"
acorn-walk "^6.0.1"
-acorn-jsx@^4.1.1:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-4.1.1.tgz#e8e41e48ea2fe0c896740610ab6a4ffd8add225e"
- integrity sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==
- dependencies:
- acorn "^5.0.3"
+acorn-jsx@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e"
+ integrity sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==
acorn-walk@^6.0.1:
version "6.1.1"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.1.1.tgz#d363b66f5fac5f018ff9c3a1e7b6f8e310cc3913"
integrity sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==
-acorn@^5.0.0, acorn@^5.0.3, acorn@^5.5.3, acorn@^5.6.0, acorn@^5.6.2, acorn@^5.7.3:
+acorn@^5.0.0, acorn@^5.5.3, acorn@^5.6.2, acorn@^5.7.3:
version "5.7.3"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279"
integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==
-acorn@^6.0.1:
+acorn@^6.0.1, acorn@^6.0.2:
version "6.0.4"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.0.4.tgz#77377e7353b72ec5104550aa2d2097a2fd40b754"
integrity sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==
@@ -947,25 +968,15 @@ ajv-errors@^1.0.0:
resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.0.tgz#ecf021fa108fd17dfb5e6b383f2dd233e31ffc59"
integrity sha1-7PAh+hCP0X37Xms4Py3SM+Mf/Fk=
-ajv-keywords@^3.0.0, ajv-keywords@^3.1.0:
+ajv-keywords@^3.1.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a"
integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=
-ajv@^6.0.1, ajv@^6.1.0, ajv@^6.5.3:
- version "6.5.3"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.3.tgz#71a569d189ecf4f4f321224fecb166f071dd90f9"
- integrity sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==
- dependencies:
- fast-deep-equal "^2.0.1"
- fast-json-stable-stringify "^2.0.0"
- json-schema-traverse "^0.4.1"
- uri-js "^4.2.2"
-
-ajv@^6.5.5:
- version "6.5.5"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.5.tgz#cf97cdade71c6399a92c6d6c4177381291b781a1"
- integrity sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg==
+ajv@^6.1.0, ajv@^6.5.3, ajv@^6.5.5, ajv@^6.6.1:
+ version "6.6.1"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.1.tgz#6360f5ed0d80f232cc2b294c362d5dc2e538dd61"
+ integrity sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==
dependencies:
fast-deep-equal "^2.0.1"
fast-json-stable-stringify "^2.0.0"
@@ -1309,7 +1320,7 @@ asynckit@^0.4.0:
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
-atob@^2.0.0:
+atob@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
@@ -1483,7 +1494,7 @@ babel-plugin-syntax-object-rest-spread@^6.13.0:
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=
-babel-plugin-transform-es2015-modules-commonjs@^6.26.2:
+babel-plugin-transform-es2015-modules-commonjs@^6.26.0, babel-plugin-transform-es2015-modules-commonjs@^6.26.2:
version "6.26.2"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3"
integrity sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==
@@ -2195,6 +2206,11 @@ clone-response@1.0.2:
dependencies:
mimic-response "^1.0.0"
+clone@2.x:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
+ integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
+
co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@@ -2263,7 +2279,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
-commander@2, commander@^2.18.0, commander@^2.19.0:
+commander@2, commander@^2.10.0, commander@^2.18.0, commander@^2.19.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==
@@ -2350,7 +2366,7 @@ concat-stream@^1.5.0:
readable-stream "^2.2.2"
typedarray "^0.0.6"
-config-chain@~1.1.5:
+config-chain@^1.1.12:
version "1.1.12"
resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa"
integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==
@@ -2603,6 +2619,16 @@ css-selector-tokenizer@^0.7.0:
fastparse "^1.1.1"
regexpu-core "^1.0.0"
+css@^2.1.0:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929"
+ integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==
+ dependencies:
+ inherits "^2.0.3"
+ source-map "^0.6.1"
+ source-map-resolve "^0.5.2"
+ urix "^0.1.0"
+
cssesc@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4"
@@ -2937,6 +2963,13 @@ debug@^3.1.0, debug@^3.2.5:
dependencies:
ms "^2.1.1"
+debug@^4.0.1, debug@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87"
+ integrity sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==
+ dependencies:
+ ms "^2.1.1"
+
debug@~3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
@@ -3278,12 +3311,19 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
+echarts@^4.2.0-rc.2:
+ version "4.2.0-rc.2"
+ resolved "https://registry.yarnpkg.com/echarts/-/echarts-4.2.0-rc.2.tgz#6a98397aafa81b65cbf0bc15d9afdbfb244df91e"
+ integrity sha512-5Y4Kyi4eNsRM9Cnl7Q8C6PFVjznBJv1VIiMm/VSQ9zyqeo+ce1695GqUd9v4zfVx+Ow1gnwMJX67h0FNvarScw==
+ dependencies:
+ zrender "4.0.5"
+
editions@^1.3.3:
version "1.3.4"
resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b"
integrity sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==
-editorconfig@^0.15.0:
+editorconfig@^0.15.2:
version "0.15.2"
resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.2.tgz#047be983abb9ab3c2eefe5199cb2b7c5689f0702"
integrity sha512-GWjSI19PVJAM9IZRGOS+YKI8LN+/sjkSjNyvxL5ucqP9/IqtYNXBaQ/6c/hkPNYQHyOHra2KoXZI/JVpuqwmcQ==
@@ -3507,10 +3547,10 @@ eslint-config-airbnb-base@^13.1.0:
object.assign "^4.1.0"
object.entries "^1.0.4"
-eslint-config-prettier@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-3.1.0.tgz#2c26d2cdcfa3a05f0642cd7e6e4ef3316cdabfa2"
- integrity sha512-QYGfmzuc4q4J6XIhlp8vRKdI/fI0tQfQPy1dME3UOLprE+v4ssH/3W9LM2Q7h5qBcy5m0ehCrBDU2YF8q6OY8w==
+eslint-config-prettier@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-3.3.0.tgz#41afc8d3b852e757f06274ed6c44ca16f939a57d"
+ integrity sha512-Bc3bh5bAcKNvs3HOpSi6EfGA2IIp7EzWcg2tS4vP7stnXu/J1opihHDM7jI9JCIckyIDTgZLSWn7J3HY0j2JfA==
dependencies:
get-stdin "^6.0.0"
@@ -3565,12 +3605,12 @@ eslint-plugin-filenames@^1.3.2:
lodash.snakecase "4.1.1"
lodash.upperfirst "4.3.1"
-eslint-plugin-html@4.0.5:
- version "4.0.5"
- resolved "https://registry.yarnpkg.com/eslint-plugin-html/-/eslint-plugin-html-4.0.5.tgz#e8ec7e16485124460f3bff312016feb0a54d9659"
- integrity sha512-yULqYldzhYXTwZEaJXM30HhfgJdtTzuVH3LeoANybESHZ5+2ztLD72BsB2wR124/kk/PvQqZofDFSdNIk+kykw==
+eslint-plugin-html@5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-html/-/eslint-plugin-html-5.0.0.tgz#396e30a60dedee0122fe08f11d13c5ab22f20d32"
+ integrity sha512-f7p/7YQdgQUFVAX3nB4dnMQbrDeTalcA01PDhuvTLk0ZadCwM4Pb+639SRuqEf1zMkIxckLY+ckCr0hVP5zl6A==
dependencies:
- htmlparser2 "^3.8.2"
+ htmlparser2 "^3.10.0"
eslint-plugin-import@^2.14.0:
version "2.14.0"
@@ -3603,12 +3643,12 @@ eslint-plugin-promise@^4.0.1:
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.0.1.tgz#2d074b653f35a23d1ba89d8e976a985117d1c6a2"
integrity sha512-Si16O0+Hqz1gDHsys6RtFRrW7cCTB6P7p3OJmKp3Y3dxpQE2qwOA7d3xnV+0mBmrPoi0RBnxlCKvqu70te6wjg==
-eslint-plugin-vue@^5.0.0-beta.3:
- version "5.0.0-beta.3"
- resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-5.0.0-beta.3.tgz#f3fa9f109b76e20fc1e45a71ce7c6d567118924e"
- integrity sha512-EOQo3ax4CIM6Itcl522p4cGlSBgR/KZBJo2Xc29PWknbYH/DRZorGutF8NATUpbZ4HYOG+Gcyd1nL08iyYF3Tg==
+eslint-plugin-vue@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-5.0.0.tgz#4a2cc1c0e71ea45e1bd9c1a60f925bfe68bb5710"
+ integrity sha512-mSv2Ebz3RaPP+XJO/mu7F+SdR9lrMyGISSExnarLFqqf3pF5wTmwWNrhHW1o9zKzKI811UVTIIkWJJvgO6SsUQ==
dependencies:
- vue-eslint-parser "^3.2.1"
+ vue-eslint-parser "^4.0.2"
eslint-restricted-globals@^0.1.1:
version "0.1.1"
@@ -3641,16 +3681,16 @@ eslint-visitor-keys@^1.0.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==
-eslint@~5.6.0:
- version "5.6.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.6.0.tgz#b6f7806041af01f71b3f1895cbb20971ea4b6223"
- integrity sha512-/eVYs9VVVboX286mBK7bbKnO1yamUy2UCRjiY6MryhQL2PaaXCExsCQ2aO83OeYRhU2eCU/FMFP+tVMoOrzNrA==
+eslint@~5.9.0:
+ version "5.9.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.9.0.tgz#b234b6d15ef84b5849c6de2af43195a2d59d408e"
+ integrity sha512-g4KWpPdqN0nth+goDNICNXGfJF7nNnepthp46CAlJoJtC5K/cLu3NgCM3AHu1CkJ5Hzt9V0Y0PBAO6Ay/gGb+w==
dependencies:
"@babel/code-frame" "^7.0.0"
ajv "^6.5.3"
chalk "^2.1.0"
cross-spawn "^6.0.5"
- debug "^3.1.0"
+ debug "^4.0.1"
doctrine "^2.1.0"
eslint-scope "^4.0.0"
eslint-utils "^1.3.1"
@@ -3677,21 +3717,22 @@ eslint@~5.6.0:
path-is-inside "^1.0.2"
pluralize "^7.0.0"
progress "^2.0.0"
- regexpp "^2.0.0"
+ regexpp "^2.0.1"
require-uncached "^1.0.3"
semver "^5.5.1"
strip-ansi "^4.0.0"
strip-json-comments "^2.0.1"
- table "^4.0.3"
+ table "^5.0.2"
text-table "^0.2.0"
-espree@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/espree/-/espree-4.0.0.tgz#253998f20a0f82db5d866385799d912a83a36634"
- integrity sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg==
+espree@^4.0.0, espree@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-4.1.0.tgz#728d5451e0fd156c04384a7ad89ed51ff54eb25f"
+ integrity sha512-I5BycZW6FCVIub93TeVY1s7vjhP9CY6cXCznIRfiig7nRviKZYdRnj/sHEWC6A7WE9RDWOFq9+7OsWSYz8qv2w==
dependencies:
- acorn "^5.6.0"
- acorn-jsx "^4.1.1"
+ acorn "^6.0.2"
+ acorn-jsx "^5.0.0"
+ eslint-visitor-keys "^1.0.0"
esprima@2.7.x, esprima@^2.7.1:
version "2.7.3"
@@ -3982,16 +4023,18 @@ extglob@^2.0.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
-extsprintf@1.3.0:
+extract-from-css@^0.4.4:
+ version "0.4.4"
+ resolved "https://registry.yarnpkg.com/extract-from-css/-/extract-from-css-0.4.4.tgz#1ea7df2e7c7c6eb9922fa08e8adaea486f6f8f92"
+ integrity sha1-HqffLnx8brmSL6COitrqSG9vj5I=
+ dependencies:
+ css "^2.1.0"
+
+extsprintf@1.3.0, extsprintf@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
-extsprintf@^1.2.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
- integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
-
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"
@@ -4126,6 +4169,14 @@ finalhandler@1.1.1:
statuses "~1.4.0"
unpipe "~1.0.0"
+find-babel-config@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-1.1.0.tgz#acc01043a6749fec34429be6b64f542ebb5d6355"
+ integrity sha1-rMAQQ6Z0n+w0Qpvmtk9ULrtdY1U=
+ dependencies:
+ json5 "^0.5.1"
+ path-exists "^3.0.0"
+
find-cache-dir@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f"
@@ -4395,7 +4446,7 @@ glob-parent@^3.1.0:
is-glob "^3.1.0"
path-dirname "^1.0.0"
-"glob@5 - 7", glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2:
+"glob@5 - 7", glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3:
version "7.1.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
@@ -4759,17 +4810,17 @@ html-entities@^1.2.0:
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.0.tgz#41948caf85ce82fed36e4e6a0ed371a6664379e2"
integrity sha1-QZSMr4XOgv7Tbk5qDtNxpmZDeeI=
-htmlparser2@^3.8.2, htmlparser2@^3.9.0:
- version "3.9.2"
- resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338"
- integrity sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=
+htmlparser2@^3.10.0, htmlparser2@^3.9.0:
+ version "3.10.0"
+ resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464"
+ integrity sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==
dependencies:
domelementtype "^1.3.0"
domhandler "^2.3.0"
domutils "^1.5.1"
entities "^1.1.1"
inherits "^2.0.1"
- readable-stream "^2.0.2"
+ readable-stream "^3.0.6"
http-cache-semantics@3.8.1:
version "3.8.1"
@@ -4823,10 +4874,10 @@ https-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
-iconv-lite@0.4, iconv-lite@^0.4.17, iconv-lite@^0.4.22, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
- version "0.4.23"
- resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
- integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==
+iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.22, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
+ version "0.4.24"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
+ integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
@@ -4835,13 +4886,6 @@ iconv-lite@0.4.19:
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
integrity sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==
-iconv-lite@0.4.24:
- version "0.4.24"
- resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
- integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
- dependencies:
- safer-buffer ">= 2.1.2 < 3"
-
icss-replace-symbols@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
@@ -5960,13 +6004,14 @@ jquery.waitforimages@^2.2.0:
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca"
integrity sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==
-js-beautify@^1.8.8:
- version "1.8.8"
- resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.8.8.tgz#1eb175b73a3571a5f1ed8d98e7cf2b05bfa98471"
- integrity sha512-qVNq7ZZ7ZbLdzorvSlRDadS0Rh5oyItaE95v6I4wbbuSiijxn7SnnsV6dvKlcXuO2jX7lK8tn9fBulx34K/Ejg==
+js-beautify@^1.6.14, js-beautify@^1.8.8:
+ version "1.8.9"
+ resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.8.9.tgz#08e3c05ead3ecfbd4f512c3895b1cda76c87d523"
+ integrity sha512-MwPmLywK9RSX0SPsUJjN7i+RQY9w/yC17Lbrq9ViEefpLRgqAR2BgrMN2AbifkUuhDV8tRauLhLda/9+bE0YQA==
dependencies:
- config-chain "~1.1.5"
- editorconfig "^0.15.0"
+ config-chain "^1.1.12"
+ editorconfig "^0.15.2"
+ glob "^7.1.3"
mkdirp "~0.5.0"
nopt "~4.0.1"
@@ -6427,7 +6472,7 @@ lodash.upperfirst@4.3.1:
resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce"
integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984=
-lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0:
+lodash@4.x, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
@@ -6723,7 +6768,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
dependencies:
brace-expansion "^1.1.7"
-minimist@0.0.8:
+minimist@0.0.8, minimist@~0.0.1:
version "0.0.8"
resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
@@ -6733,11 +6778,6 @@ minimist@1.2.0, minimist@^1.1.1, minimist@^1.2.0:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
-minimist@~0.0.1:
- version "0.0.10"
- resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
- integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
-
minipass@^2.2.1, minipass@^2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.3.tgz#a7dcc8b7b833f5d368759cce544dccb55f50f233"
@@ -6912,6 +6952,14 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.4.tgz#d93962f6c52f2c1558c0fbda6d512819f1efe1c4"
integrity sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==
+node-cache@^4.1.1:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-4.2.0.tgz#48ac796a874e762582692004a376d26dfa875811"
+ integrity sha512-obRu6/f7S024ysheAjoYFEEBqqDWv4LOMNJEuO8vMeEw2AT4z+NCzO4hlc2lhI4vATzbCQv6kke9FVdx0RbCOw==
+ dependencies:
+ clone "2.x"
+ lodash "4.x"
+
node-fetch@1.6.3:
version "1.6.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.6.3.tgz#dc234edd6489982d58e8f0db4f695029abcd8c04"
@@ -7106,7 +7154,7 @@ oauth-sign@~0.9.0:
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
-object-assign@^4.0.1, object-assign@^4.1.0:
+object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@@ -7826,7 +7874,7 @@ pseudomap@^1.0.2:
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
-psl@^1.1.24, psl@^1.1.28:
+psl@^1.1.24:
version "1.1.29"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67"
integrity sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==
@@ -7874,17 +7922,17 @@ pumpify@^1.3.3:
inherits "^2.0.3"
pump "^2.0.0"
-punycode@1.3.2, punycode@^1.2.4:
+punycode@1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
-punycode@^1.4.1:
+punycode@^1.2.4, punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
-punycode@^2.1.0, punycode@^2.1.1:
+punycode@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
@@ -8058,6 +8106,15 @@ read-pkg@^3.0.0:
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
+readable-stream@^3.0.6:
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.0.6.tgz#351302e4c68b5abd6a2ed55376a7f9a25be3057a"
+ integrity sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg==
+ dependencies:
+ inherits "^2.0.3"
+ string_decoder "^1.1.1"
+ util-deprecate "^1.0.1"
+
readable-stream@~2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
@@ -8131,10 +8188,10 @@ regex-not@^1.0.0, regex-not@^1.0.2:
extend-shallow "^3.0.2"
safe-regex "^1.1.0"
-regexpp@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.0.tgz#b2a7534a85ca1b033bcf5ce9ff8e56d4e0755365"
- integrity sha512-g2FAVtR8Uh8GO1Nv5wpxW7VFVwHcCEr4wyA8/MHiRkO8uHoR5ntAA8Uq3P1vvMTX/BeQiRVSpDGLd+Wn5HNOTA==
+regexpp@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
+ integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==
regexpu-core@^1.0.0:
version "1.0.0"
@@ -8666,11 +8723,13 @@ slash@^1.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=
-slice-ansi@1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d"
- integrity sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==
+slice-ansi@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.0.0.tgz#5373bdb8559b45676e8541c66916cdd6251612e7"
+ integrity sha512-4j2WTWjp3GsZ+AOagyzVbzp4vWGtZ0hEZ/gDY/uTvm6MTxUfTUIsnMIFb1bn8o0RuXiqUw15H1bue8f22Vw2oQ==
dependencies:
+ ansi-styles "^3.2.0"
+ astral-regex "^1.0.0"
is-fullwidth-code-point "^2.0.0"
slugify@^1.3.1:
@@ -8800,12 +8859,12 @@ source-list-map@^2.0.0:
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085"
integrity sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==
-source-map-resolve@^0.5.0:
- version "0.5.1"
- resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.1.tgz#7ad0f593f2281598e854df80f19aae4b92d7a11a"
- integrity sha512-0KW2wvzfxm8NCTb30z0LMNyPqWCdDGE2viwzUaucqJdkTRXtZiSY3I+2A6nVAjmdOy0I4gU8DwnVVGsk9jvP2A==
+source-map-resolve@^0.5.0, source-map-resolve@^0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259"
+ integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==
dependencies:
- atob "^2.0.0"
+ atob "^2.1.1"
decode-uri-component "^0.2.0"
resolve-url "^0.2.1"
source-map-url "^0.4.0"
@@ -9068,7 +9127,7 @@ string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
-string_decoder@^1.0.0, string_decoder@~1.1.1:
+string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
@@ -9118,7 +9177,7 @@ strip-eof@^1.0.0:
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
-strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
+strip-json-comments@^2.0.0, strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
@@ -9165,16 +9224,14 @@ symbol-tree@^3.2.2:
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
integrity sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=
-table@^4.0.3:
- version "4.0.3"
- resolved "http://registry.npmjs.org/table/-/table-4.0.3.tgz#00b5e2b602f1794b9acaf9ca908a76386a7813bc"
- integrity sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==
+table@^5.0.2:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/table/-/table-5.1.1.tgz#92030192f1b7b51b6eeab23ed416862e47b70837"
+ integrity sha512-NUjapYb/qd4PeFW03HnAuOJ7OMcBkJlqeClWxeNlQ0lXGSb52oZXGzkO0/I0ARegQ2eUT1g2VDJH0eUxDRcHmw==
dependencies:
- ajv "^6.0.1"
- ajv-keywords "^3.0.0"
- chalk "^2.1.0"
- lodash "^4.17.4"
- slice-ansi "1.0.0"
+ ajv "^6.6.1"
+ lodash "^4.17.11"
+ slice-ansi "2.0.0"
string-width "^2.1.1"
tapable@^0.1.8:
@@ -9374,15 +9431,7 @@ touch@^3.1.0:
dependencies:
nopt "~1.0.10"
-tough-cookie@>=2.3.3, tough-cookie@^2.3.4:
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
- integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
- dependencies:
- psl "^1.1.28"
- punycode "^2.1.1"
-
-tough-cookie@~2.4.3:
+tough-cookie@>=2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
@@ -9407,6 +9456,16 @@ tryer@^1.0.0:
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.0.tgz#027b69fa823225e551cace3ef03b11f6ab37c1d7"
integrity sha1-Antp+oIyJeVRys4+8DsR9qs3wdc=
+tsconfig@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7"
+ integrity sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==
+ dependencies:
+ "@types/strip-bom" "^3.0.0"
+ "@types/strip-json-comments" "0.0.30"
+ strip-bom "^3.0.0"
+ strip-json-comments "^2.0.0"
+
tslib@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
@@ -9676,7 +9735,7 @@ useragent@2.2.1:
lru-cache "2.2.x"
tmp "0.0.x"
-util-deprecate@~1.0.1:
+util-deprecate@^1.0.1, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
@@ -9758,17 +9817,17 @@ vue-apollo@^3.0.0-beta.25:
chalk "^2.4.1"
throttle-debounce "^2.0.0"
-vue-eslint-parser@^3.2.1:
- version "3.2.2"
- resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-3.2.2.tgz#47c971ee4c39b0ee7d7f5e154cb621beb22f7a34"
- integrity sha512-dprI6ggKCTwV22r+i8dtUGquiOCn063xyDmb7BV/BjG5Oc/m5EoMNrWevpvTcrlGuFZmYVPs5fgsu8UIxmMKzg==
+vue-eslint-parser@^4.0.2:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-4.0.3.tgz#80cf162e484387b2640371ad21ba1f86e0c10a61"
+ integrity sha512-AUeQsYdO6+7QXCems+WvGlrXd37PHv/zcRQSQdY1xdOMwdFAPEnMBsv7zPvk0TPGulXkK/5p/ITgrjiYB7k3ag==
dependencies:
- debug "^3.1.0"
+ debug "^4.1.0"
eslint-scope "^4.0.0"
eslint-visitor-keys "^1.0.0"
- espree "^4.0.0"
+ espree "^4.1.0"
esquery "^1.0.1"
- lodash "^4.17.10"
+ lodash "^4.17.11"
vue-functional-data-merge@^2.0.5:
version "2.0.6"
@@ -9780,6 +9839,22 @@ vue-hot-reload-api@^2.3.0:
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.0.tgz#97976142405d13d8efae154749e88c4e358cf926"
integrity sha512-2j/t+wIbyVMP5NvctQoSUvLkYKoWAAk2QlQiilrM2a6/ulzFgdcLUJfTvs4XQ/3eZhHiBmmEojbjmM4AzZj8JA==
+vue-jest@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/vue-jest/-/vue-jest-3.0.1.tgz#127cded1a57cdfcf01fa8a10ce29579e2cb3a04d"
+ integrity sha512-otS+n341cTsp0pF7tuTu2x43b23x/+K0LZdAXV+ewKYIMZRqhuQaJTECWEt/cN/YZw2JC6hUM6xybdnOB4ZQ+g==
+ dependencies:
+ babel-plugin-transform-es2015-modules-commonjs "^6.26.0"
+ chalk "^2.1.0"
+ extract-from-css "^0.4.4"
+ find-babel-config "^1.1.0"
+ js-beautify "^1.6.14"
+ node-cache "^4.1.1"
+ object-assign "^4.1.1"
+ source-map "^0.5.6"
+ tsconfig "^7.0.0"
+ vue-template-es2015-compiler "^1.6.0"
+
vue-loader@^15.4.2:
version "15.4.2"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.4.2.tgz#812bb26e447dd3b84c485eb634190d914ce125e2"
@@ -9829,11 +9904,16 @@ vue-virtual-scroll-list@^1.2.5:
resolved "https://registry.yarnpkg.com/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.2.5.tgz#bcbd010f7cdb035eba8958ebf807c6214d9a167a"
integrity sha1-vL0BD3zbA166iVjr+AfGIU2aFno=
-vue@^2.5.16, vue@^2.5.17:
+vue@^2.5.17:
version "2.5.17"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.17.tgz#0f8789ad718be68ca1872629832ed533589c6ada"
integrity sha512-mFbcWoDIJi0w0Za4emyLiW72Jae0yjANHbCVquMKijcavBGypqlF7zHRgMa5k4sesdv7hv2rB4JPdZfR+TPfhQ==
+vue@^2.5.21:
+ version "2.5.21"
+ resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.21.tgz#3d33dcd03bb813912ce894a8303ab553699c4a85"
+ integrity sha512-Aejvyyfhn0zjVeLvXd70h4hrE4zZDx1wfZqia6ekkobLmUZ+vNFQer53B4fu0EjWBSiqApxPejzkO1Znt3joxQ==
+
vuex@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.0.1.tgz#e761352ebe0af537d4bb755a9b9dc4be3df7efd2"
@@ -10059,14 +10139,7 @@ which-module@^2.0.0:
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
-which@^1.1.1, which@^1.2.1, which@^1.2.9:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a"
- integrity sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==
- dependencies:
- isexe "^2.0.0"
-
-which@^1.2.12, which@^1.3.0:
+which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.9, which@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
@@ -10280,6 +10353,15 @@ yargs@^11.0.0:
y18n "^3.2.1"
yargs-parser "^9.0.2"
+yarn-deduplicate@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-1.0.5.tgz#e56016f1c29e77e323f401ea838f5e8c7cdbfd42"
+ integrity sha512-4nds6N7dxuXcfUZAVaSUVSlI4TvwEdMaZg/DRBf/KM3iFezNBdkhcTYptcwKaecAYAfVxx3g0Ex21kssSr8YsA==
+ dependencies:
+ "@yarnpkg/lockfile" "^1.1.0"
+ commander "^2.10.0"
+ semver "^5.3.0"
+
yeast@0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
@@ -10296,3 +10378,8 @@ zen-observable@^0.8.0:
version "0.8.11"
resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.11.tgz#d3415885eeeb42ee5abb9821c95bb518fcd6d199"
integrity sha512-N3xXQVr4L61rZvGMpWe8XoCGX8vhU35dPyQ4fm5CY/KDlG0F75un14hjbckPXTDuKUY6V0dqR2giT6xN8Y4GEQ==
+
+zrender@4.0.5:
+ version "4.0.5"
+ resolved "https://registry.yarnpkg.com/zrender/-/zrender-4.0.5.tgz#6e8f738971ce2cd624aac82b2156729b1c0e5a82"
+ integrity sha512-SintgipGEJPT9Sz2ABRoE4ZD7Yzy7oR7j7KP6H+C9FlbHWnLUfGVK7E8UV27pGwlxAMB0EsnrqhXx5XjAfv/KA==