summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml23
-rw-r--r--.scss-lint.yml66
-rw-r--r--CHANGELOG.md5
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile.lock2
-rw-r--r--app/assets/javascripts/copy_as_gfm.js128
-rw-r--r--app/assets/javascripts/dispatcher.js6
-rw-r--r--app/assets/javascripts/droplab/plugins/ajax_filter.js47
-rw-r--r--app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.js6
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_hint.js29
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_non_user.js2
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_user.js5
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_utils.js6
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js10
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js7
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_token_keys.js8
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_tokenizer.js3
-rw-r--r--app/assets/javascripts/filtered_search/recent_searches_root.js1
-rw-r--r--app/assets/javascripts/filtered_search/stores/recent_searches_store.js3
-rw-r--r--app/assets/javascripts/group_name.js6
-rw-r--r--app/assets/javascripts/lib/utils/ajax_cache.js18
-rw-r--r--app/assets/javascripts/lib/utils/cache.js19
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js6
-rw-r--r--app/assets/javascripts/lib/utils/users_cache.js28
-rw-r--r--app/assets/javascripts/merge_request_tabs.js2
-rw-r--r--app/assets/javascripts/notes.js7
-rw-r--r--app/assets/javascripts/project_edit.js9
-rw-r--r--app/assets/javascripts/raven/index.js4
-rw-r--r--app/assets/javascripts/raven/raven_config.js5
-rw-r--r--app/assets/javascripts/shortcuts_issuable.js8
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js3
-rw-r--r--app/assets/javascripts/sidebar/stores/sidebar_store.js4
-rw-r--r--app/assets/javascripts/single_file_diff.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js8
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js5
-rw-r--r--app/assets/javascripts/vue_shared/components/header_ci_component.vue122
-rw-r--r--app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue58
-rw-r--r--app/assets/javascripts/vue_shared/mixins/timeago.js18
-rw-r--r--app/assets/javascripts/vue_shared/vue_resource_interceptor.js2
-rw-r--r--app/assets/stylesheets/framework/awards.scss13
-rw-r--r--app/assets/stylesheets/framework/files.scss2
-rw-r--r--app/assets/stylesheets/framework/filters.scss7
-rw-r--r--app/assets/stylesheets/framework/flash.scss4
-rw-r--r--app/assets/stylesheets/framework/header.scss22
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss4
-rw-r--r--app/assets/stylesheets/framework/timeline.scss7
-rw-r--r--app/assets/stylesheets/framework/typography.scss6
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss6
-rw-r--r--app/assets/stylesheets/pages/diff.scss6
-rw-r--r--app/assets/stylesheets/pages/issuable.scss8
-rw-r--r--app/assets/stylesheets/pages/members.scss2
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss20
-rw-r--r--app/assets/stylesheets/pages/note_form.scss16
-rw-r--r--app/assets/stylesheets/pages/notes.scss109
-rw-r--r--app/assets/stylesheets/pages/projects.scss4
-rw-r--r--app/controllers/admin/hook_logs_controller.rb29
-rw-r--r--app/controllers/admin/hooks_controller.rb32
-rw-r--r--app/controllers/application_controller.rb38
-rw-r--r--app/controllers/concerns/diff_for_path.rb13
-rw-r--r--app/controllers/concerns/hooks_execution.rb15
-rw-r--r--app/controllers/dashboard/projects_controller.rb3
-rw-r--r--app/controllers/dashboard_controller.rb2
-rw-r--r--app/controllers/groups_controller.rb1
-rw-r--r--app/controllers/profiles_controller.rb8
-rw-r--r--app/controllers/projects/compare_controller.rb6
-rw-r--r--app/controllers/projects/environments_controller.rb1
-rw-r--r--app/controllers/projects/hook_logs_controller.rb33
-rw-r--r--app/controllers/projects/hooks_controller.rb17
-rw-r--r--app/controllers/projects/merge_requests_controller.rb9
-rw-r--r--app/controllers/projects/refs_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb2
-rw-r--r--app/helpers/commits_helper.rb10
-rw-r--r--app/helpers/diff_helper.rb18
-rw-r--r--app/helpers/labels_helper.rb5
-rw-r--r--app/helpers/preferences_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb1
-rw-r--r--app/helpers/rss_helper.rb2
-rw-r--r--app/helpers/system_note_helper.rb3
-rw-r--r--app/mailers/base_mailer.rb6
-rw-r--r--app/models/commit_status.rb1
-rw-r--r--app/models/concerns/discussion_on_diff.rb8
-rw-r--r--app/models/concerns/note_on_diff.rb10
-rw-r--r--app/models/diff_discussion.rb16
-rw-r--r--app/models/diff_note.rb11
-rw-r--r--app/models/hooks/service_hook.rb2
-rw-r--r--app/models/hooks/system_hook.rb4
-rw-r--r--app/models/hooks/web_hook.rb43
-rw-r--r--app/models/hooks/web_hook_log.rb13
-rw-r--r--app/models/label.rb4
-rw-r--r--app/models/legacy_diff_note.rb2
-rw-r--r--app/models/merge_request.rb54
-rw-r--r--app/models/merge_request_diff.rb28
-rw-r--r--app/models/note.rb11
-rw-r--r--app/models/project.rb10
-rw-r--r--app/models/project_services/issue_tracker_service.rb2
-rw-r--r--app/models/project_services/jira_service.rb39
-rw-r--r--app/models/project_services/kubernetes_service.rb24
-rw-r--r--app/models/system_note_metadata.rb1
-rw-r--r--app/models/user.rb14
-rw-r--r--app/services/issues/close_service.rb1
-rw-r--r--app/services/issues/reopen_service.rb1
-rw-r--r--app/services/merge_requests/close_service.rb1
-rw-r--r--app/services/merge_requests/post_merge_service.rb1
-rw-r--r--app/services/merge_requests/refresh_service.rb4
-rw-r--r--app/services/merge_requests/reopen_service.rb3
-rw-r--r--app/services/notes/diff_position_update_service.rb25
-rw-r--r--app/services/projects/transfer_service.rb11
-rw-r--r--app/services/system_note_service.rb24
-rw-r--r--app/services/web_hook_service.rb120
-rw-r--r--app/validators/dynamic_path_validator.rb215
-rw-r--r--app/views/admin/hook_logs/_index.html.haml37
-rw-r--r--app/views/admin/hook_logs/show.html.haml10
-rw-r--r--app/views/admin/hooks/edit.html.haml6
-rw-r--r--app/views/admin/requests_profiles/index.html.haml2
-rw-r--r--app/views/dashboard/_activities.html.haml3
-rw-r--r--app/views/dashboard/activity.html.haml12
-rw-r--r--app/views/dashboard/projects/index.html.haml22
-rw-r--r--app/views/dashboard/projects/starred.html.haml18
-rw-r--r--app/views/devise/shared/_signup_box.html.haml2
-rw-r--r--app/views/discussions/_diff_with_notes.html.haml2
-rw-r--r--app/views/discussions/_discussion.html.haml7
-rw-r--r--app/views/discussions/_notes.html.haml14
-rw-r--r--app/views/events/_event_last_push.html.haml14
-rw-r--r--app/views/events/event/_push.html.haml2
-rw-r--r--app/views/groups/_activities.html.haml3
-rw-r--r--app/views/groups/_head.html.haml3
-rw-r--r--app/views/groups/show.html.haml1
-rw-r--r--app/views/layouts/nav/_admin.html.haml2
-rw-r--r--app/views/notify/repository_push_email.html.haml28
-rw-r--r--app/views/notify/repository_push_email.text.haml20
-rw-r--r--app/views/profiles/accounts/_reset_token.html.haml11
-rw-r--r--app/views/profiles/accounts/show.html.haml34
-rw-r--r--app/views/profiles/preferences/show.html.haml4
-rw-r--r--app/views/projects/_activity.html.haml2
-rw-r--r--app/views/projects/_home_panel.html.haml2
-rw-r--r--app/views/projects/_last_push.html.haml34
-rw-r--r--app/views/projects/activity.html.haml2
-rw-r--r--app/views/projects/blob/show.html.haml5
-rw-r--r--app/views/projects/branches/_branch.html.haml2
-rw-r--r--app/views/projects/ci/builds/_build.html.haml2
-rw-r--r--app/views/projects/commit/show.html.haml2
-rw-r--r--app/views/projects/diffs/_content.html.haml21
-rw-r--r--app/views/projects/diffs/_diffs.html.haml10
-rw-r--r--app/views/projects/diffs/_file.html.haml12
-rw-r--r--app/views/projects/diffs/_file_header.html.haml16
-rw-r--r--app/views/projects/diffs/_image.html.haml69
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml2
-rw-r--r--app/views/projects/diffs/_stats.html.haml6
-rw-r--r--app/views/projects/diffs/_text_file.html.haml4
-rw-r--r--app/views/projects/diffs/viewers/_image.html.haml68
-rw-r--r--app/views/projects/diffs/viewers/_text.html.haml8
-rw-r--r--app/views/projects/edit.html.haml10
-rw-r--r--app/views/projects/hook_logs/_index.html.haml37
-rw-r--r--app/views/projects/hook_logs/show.html.haml11
-rw-r--r--app/views/projects/hooks/edit.html.haml8
-rw-r--r--app/views/projects/issues/_discussion.html.haml6
-rw-r--r--app/views/projects/issues/show.html.haml36
-rw-r--r--app/views/projects/merge_requests/_discussion.html.haml2
-rw-r--r--app/views/projects/merge_requests/index.html.haml4
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_versions.html.haml2
-rw-r--r--app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml2
-rw-r--r--app/views/projects/settings/_head.html.haml2
-rw-r--r--app/views/projects/snippets/show.html.haml2
-rw-r--r--app/views/projects/tree/show.html.haml1
-rw-r--r--app/views/shared/_group_form.html.haml2
-rw-r--r--app/views/shared/hook_logs/_content.html.haml44
-rw-r--r--app/views/shared/hook_logs/_status_label.html.haml3
-rwxr-xr-xapp/views/shared/icons/_icon_status_skipped.svg2
-rw-r--r--app/views/shared/icons/_icon_status_skipped_borderless.svg2
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml8
-rw-r--r--app/views/shared/issuable/_sidebar_assignees.html.haml3
-rw-r--r--app/views/shared/notes/_note.html.haml2
-rw-r--r--app/views/shared/notes/_notes_with_form.html.haml25
-rw-r--r--app/views/snippets/show.html.haml2
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--app/workers/expire_job_cache_worker.rb35
-rw-r--r--app/workers/expire_pipeline_cache_worker.rb9
-rw-r--r--app/workers/process_commit_worker.rb15
-rw-r--r--app/workers/remove_old_web_hook_logs_worker.rb10
-rw-r--r--app/workers/system_hook_worker.rb10
-rw-r--r--app/workers/web_hook_worker.rb (renamed from app/workers/project_web_hook_worker.rb)6
-rw-r--r--changelogs/unreleased/12614-fix-long-message-from-mr.yml4
-rw-r--r--changelogs/unreleased/17489-hide-code-from-guests.yml4
-rw-r--r--changelogs/unreleased/18927-reorder-issue-action-buttons.yml4
-rw-r--r--changelogs/unreleased/29852-latex-formatting.yml4
-rw-r--r--changelogs/unreleased/31448-jira-urls.yml4
-rw-r--r--changelogs/unreleased/31556-ci-coverage-paralel-rspec.yml4
-rw-r--r--changelogs/unreleased/32418-make-link-to-self-less-obvious.yml4
-rw-r--r--changelogs/unreleased/32682-skipped-ci-icon.yml4
-rw-r--r--changelogs/unreleased/32715-fix-note-padding.yml4
-rw-r--r--changelogs/unreleased/32799-remove-no_turbolink-attribute-from-haml.yml4
-rw-r--r--changelogs/unreleased/32807-company-icon.yml4
-rw-r--r--changelogs/unreleased/32851-postgres-min-version.yml4
-rw-r--r--changelogs/unreleased/add-unicode-trace-feature-test.yml4
-rw-r--r--changelogs/unreleased/ce-32623-browser-tooltip-commits-branch-list.yml5
-rw-r--r--changelogs/unreleased/ci-build-pipeline-header-vue.yml4
-rw-r--r--changelogs/unreleased/dm-consistent-last-push-event.yml4
-rw-r--r--changelogs/unreleased/dm-copy-as-gfm-without-empty-elements.yml4
-rw-r--r--changelogs/unreleased/dm-copy-gfm-when-parts-of-other-elements-are-selected.yml4
-rw-r--r--changelogs/unreleased/dm-more-dependency-linkers.yml4
-rw-r--r--changelogs/unreleased/dm-outdated-system-note.yml4
-rw-r--r--changelogs/unreleased/dm-paste-code-inside-gfm-code.yml4
-rw-r--r--changelogs/unreleased/feature-rss-scoped-token.yml4
-rw-r--r--changelogs/unreleased/fix-counter-cache-for-acts-as-taggable.yml4
-rw-r--r--changelogs/unreleased/fix-terminals-support-for-kubernetes-service.yml4
-rw-r--r--changelogs/unreleased/issuable-form-create-label-sub-groups.yml4
-rw-r--r--changelogs/unreleased/issue_19262.yml4
-rw-r--r--changelogs/unreleased/mrchrisw-catch-openssl.yml4
-rw-r--r--changelogs/unreleased/prevent-project-transfer.yml4
-rw-r--r--changelogs/unreleased/wait-for-ajax-handling-all-js-requests.yml4
-rw-r--r--changelogs/unreleased/zj-fix-pipeline-etag.yml4
-rw-r--r--changelogs/unreleased/zj-sort-env-folders.yml4
-rw-r--r--config/application.rb1
-rw-r--r--config/gitlab.yml.example2
-rw-r--r--config/initializers/0_acts_as_taggable.rb (renamed from config/initializers/acts_as_taggable.rb)4
-rw-r--r--config/initializers/1_settings.rb5
-rw-r--r--config/initializers/fast_gettext.rb1
-rw-r--r--config/routes.rb15
-rw-r--r--config/routes/admin.rb14
-rw-r--r--config/routes/git_http.rb8
-rw-r--r--config/routes/group.rb18
-rw-r--r--config/routes/profile.rb1
-rw-r--r--config/routes/project.rb29
-rw-r--r--config/routes/repository.rb6
-rw-r--r--config/routes/user.rb28
-rw-r--r--config/sidekiq_queues.yml3
-rw-r--r--config/webpack.config.js24
-rw-r--r--db/migrate/20170317203554_index_routes_path_for_like.rb5
-rw-r--r--db/migrate/20170402231018_remove_index_for_users_current_sign_in_at.rb8
-rw-r--r--db/migrate/20170427103502_create_web_hook_logs.rb22
-rw-r--r--db/migrate/20170503185032_index_redirect_routes_path_for_like.rb5
-rw-r--r--db/migrate/20170521184006_add_change_position_to_notes.rb13
-rw-r--r--db/migrate/20170523091700_add_rss_token_to_users.rb19
-rw-r--r--db/post_migrate/20170503004427_update_retried_for_ci_build.rb (renamed from db/post_migrate/20170503004427_upate_retried_for_ci_build.rb)10
-rw-r--r--db/schema.rb23
-rw-r--r--doc/README.md11
-rw-r--r--doc/development/i18n_guide.md12
-rw-r--r--doc/install/database_mysql.md2
-rw-r--r--doc/install/installation.md3
-rw-r--r--doc/install/kubernetes/gitlab_chart.md42
-rw-r--r--doc/install/kubernetes/gitlab_runner_chart.md3
-rw-r--r--doc/install/kubernetes/index.md3
-rw-r--r--doc/install/requirements.md2
-rw-r--r--doc/integration/github.md21
-rw-r--r--doc/update/9.0-to-9.1.md1
-rw-r--r--doc/user/group/subgroups/index.md6
-rw-r--r--doc/user/profile/preferences.md8
-rwxr-xr-xdoc/user/project/integrations/img/webhook_logs.pngbin0 -> 24066 bytes
-rw-r--r--doc/user/project/integrations/jira.md3
-rw-r--r--doc/user/project/integrations/webhooks.md16
-rw-r--r--doc/user/project/milestones/img/progress.pngbin0 -> 23491 bytes
-rw-r--r--doc/user/project/milestones/index.md8
-rw-r--r--doc/workflow/lfs/lfs_administration.md2
-rw-r--r--features/project/hooks.feature37
-rw-r--r--features/steps/dashboard/dashboard.rb2
-rw-r--r--features/steps/dashboard/event_filters.rb14
-rw-r--r--features/steps/dashboard/todos.rb6
-rw-r--r--features/steps/group/members.rb4
-rw-r--r--features/steps/group/milestones.rb4
-rw-r--r--features/steps/project/builds/artifacts.rb4
-rw-r--r--features/steps/project/forked_merge_requests.rb7
-rw-r--r--features/steps/project/hooks.rb75
-rw-r--r--features/steps/project/merge_requests.rb55
-rw-r--r--features/steps/project/merge_requests/acceptance.rb6
-rw-r--r--features/steps/project/merge_requests/revert.rb4
-rw-r--r--features/steps/project/project.rb4
-rw-r--r--features/steps/project/project_milestone.rb4
-rw-r--r--features/steps/project/services.rb6
-rw-r--r--features/steps/project/snippets.rb6
-rw-r--r--features/steps/project/source/browse_files.rb6
-rw-r--r--features/steps/project/source/markdown_render.rb22
-rw-r--r--features/steps/shared/active_tab.rb4
-rw-r--r--features/steps/shared/diff_note.rb4
-rw-r--r--features/steps/shared/note.rb10
-rw-r--r--features/steps/shared/paths.rb12
-rw-r--r--features/steps/snippets/snippets.rb4
-rw-r--r--features/support/env.rb4
-rw-r--r--lib/api/api.rb4
-rw-r--r--lib/api/entities.rb4
-rw-r--r--lib/api/internal.rb17
-rw-r--r--lib/api/repositories.rb2
-rw-r--r--lib/api/services.rb8
-rw-r--r--lib/api/v3/repositories.rb2
-rw-r--r--lib/backup/manager.rb6
-rw-r--r--lib/banzai/filter/ascii_doc_post_processing_filter.rb13
-rw-r--r--lib/banzai/filter/sanitization_filter.rb4
-rw-r--r--lib/banzai/pipeline/ascii_doc_pipeline.rb14
-rw-r--r--lib/constraints/group_url_constrainer.rb6
-rw-r--r--lib/constraints/project_url_constrainer.rb4
-rw-r--r--lib/constraints/user_url_constrainer.rb6
-rw-r--r--lib/gitlab/asciidoc.rb13
-rw-r--r--lib/gitlab/ci/trace/stream.rb51
-rw-r--r--lib/gitlab/database/migration_helpers.rb35
-rw-r--r--lib/gitlab/dependency_linker.rb11
-rw-r--r--lib/gitlab/dependency_linker/base_linker.rb75
-rw-r--r--lib/gitlab/dependency_linker/cartfile_linker.rb14
-rw-r--r--lib/gitlab/dependency_linker/cocoapods.rb10
-rw-r--r--lib/gitlab/dependency_linker/composer_json_linker.rb18
-rw-r--r--lib/gitlab/dependency_linker/gemfile_linker.rb23
-rw-r--r--lib/gitlab/dependency_linker/gemspec_linker.rb18
-rw-r--r--lib/gitlab/dependency_linker/godeps_json_linker.rb26
-rw-r--r--lib/gitlab/dependency_linker/json_linker.rb44
-rw-r--r--lib/gitlab/dependency_linker/method_linker.rb39
-rw-r--r--lib/gitlab/dependency_linker/package_json_linker.rb44
-rw-r--r--lib/gitlab/dependency_linker/podfile_linker.rb15
-rw-r--r--lib/gitlab/dependency_linker/podspec_json_linker.rb32
-rw-r--r--lib/gitlab/dependency_linker/podspec_linker.rb24
-rw-r--r--lib/gitlab/dependency_linker/requirements_txt_linker.rb17
-rw-r--r--lib/gitlab/diff/file.rb79
-rw-r--r--lib/gitlab/diff/file_collection/base.rb23
-rw-r--r--lib/gitlab/diff/file_collection/merge_request_diff.rb3
-rw-r--r--lib/gitlab/diff/highlight.rb6
-rw-r--r--lib/gitlab/diff/position.rb30
-rw-r--r--lib/gitlab/diff/position_tracer.rb216
-rw-r--r--lib/gitlab/etag_caching/router.rb4
-rw-r--r--lib/gitlab/git/diff.rb8
-rw-r--r--lib/gitlab/git/diff_collection.rb41
-rw-r--r--lib/gitlab/gon_helper.rb1
-rw-r--r--lib/gitlab/health_checks/fs_shards_check.rb17
-rw-r--r--lib/gitlab/i18n.rb31
-rw-r--r--lib/gitlab/path_regex.rb264
-rw-r--r--lib/gitlab/regex.rb80
-rw-r--r--lib/tasks/tokens.rake10
-rw-r--r--package.json5
-rw-r--r--spec/controllers/application_controller_spec.rb36
-rw-r--r--spec/controllers/import/bitbucket_controller_spec.rb21
-rw-r--r--spec/controllers/import/gitlab_controller_spec.rb23
-rw-r--r--spec/controllers/projects/builds_controller_spec.rb57
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb19
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb9
-rw-r--r--spec/controllers/projects_controller_spec.rb44
-rw-r--r--spec/factories/ci/builds.rb10
-rw-r--r--spec/factories/ci/pipelines.rb9
-rw-r--r--spec/factories/services.rb1
-rw-r--r--spec/factories/users.rb4
-rw-r--r--spec/factories/web_hook_log.rb14
-rw-r--r--spec/features/admin/admin_hook_logs_spec.rb40
-rw-r--r--spec/features/admin/admin_hooks_spec.rb15
-rw-r--r--spec/features/admin/admin_labels_spec.rb4
-rw-r--r--spec/features/admin/admin_users_spec.rb2
-rw-r--r--spec/features/atom/dashboard_issues_spec.rb15
-rw-r--r--spec/features/atom/dashboard_spec.rb9
-rw-r--r--spec/features/atom/issues_spec.rb23
-rw-r--r--spec/features/atom/users_spec.rb9
-rw-r--r--spec/features/auto_deploy_spec.rb2
-rw-r--r--spec/features/boards/add_issues_modal_spec.rb14
-rw-r--r--spec/features/boards/boards_spec.rb77
-rw-r--r--spec/features/boards/issue_ordering_spec.rb27
-rw-r--r--spec/features/boards/keyboard_shortcut_spec.rb4
-rw-r--r--spec/features/boards/modal_filter_spec.rb28
-rw-r--r--spec/features/boards/new_issue_spec.rb10
-rw-r--r--spec/features/boards/sidebar_spec.rb46
-rw-r--r--spec/features/boards/sub_group_project_spec.rb6
-rw-r--r--spec/features/calendar_spec.rb10
-rw-r--r--spec/features/copy_as_gfm_spec.rb39
-rw-r--r--spec/features/cycle_analytics_spec.rb11
-rw-r--r--spec/features/dashboard/activity_spec.rb6
-rw-r--r--spec/features/dashboard/datetime_on_tooltips_spec.rb4
-rw-r--r--spec/features/dashboard/groups_list_spec.rb6
-rw-r--r--spec/features/dashboard/issues_spec.rb4
-rw-r--r--spec/features/dashboard/milestone_filter_spec.rb10
-rw-r--r--spec/features/dashboard/project_member_activity_index_spec.rb2
-rw-r--r--spec/features/dashboard/projects_spec.rb2
-rw-r--r--spec/features/dashboard_issues_spec.rb4
-rw-r--r--spec/features/expand_collapse_diffs_spec.rb16
-rw-r--r--spec/features/explore/groups_list_spec.rb6
-rw-r--r--spec/features/gitlab_flavored_markdown_spec.rb2
-rw-r--r--spec/features/groups/activity_spec.rb8
-rw-r--r--spec/features/groups/issues_spec.rb10
-rw-r--r--spec/features/groups/show_spec.rb4
-rw-r--r--spec/features/issues/award_emoji_spec.rb16
-rw-r--r--spec/features/issues/award_spec.rb8
-rw-r--r--spec/features/issues/bulk_assignment_labels_spec.rb8
-rw-r--r--spec/features/issues/create_branch_merge_request_spec.rb4
-rw-r--r--spec/features/issues/filtered_search/dropdown_author_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb18
-rw-r--r--spec/features/issues/form_spec.rb38
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb10
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb6
-rw-r--r--spec/features/issues/notes_on_issues_spec.rb2
-rw-r--r--spec/features/issues/update_issues_spec.rb4
-rw-r--r--spec/features/issues/user_uses_slash_commands_spec.rb2
-rw-r--r--spec/features/issues_spec.rb12
-rw-r--r--spec/features/merge_requests/closes_issues_spec.rb4
-rw-r--r--spec/features/merge_requests/conflicts_spec.rb22
-rw-r--r--spec/features/merge_requests/create_new_mr_spec.rb4
-rw-r--r--spec/features/merge_requests/deleted_source_branch_spec.rb2
-rw-r--r--spec/features/merge_requests/diff_notes_avatars_spec.rb12
-rw-r--r--spec/features/merge_requests/discussion_spec.rb2
-rw-r--r--spec/features/merge_requests/filter_merge_requests_spec.rb2
-rw-r--r--spec/features/merge_requests/form_spec.rb2
-rw-r--r--spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb2
-rw-r--r--spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb4
-rw-r--r--spec/features/merge_requests/mini_pipeline_graph_spec.rb2
-rw-r--r--spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb24
-rw-r--r--spec/features/merge_requests/pipelines_spec.rb2
-rw-r--r--spec/features/merge_requests/update_merge_requests_spec.rb4
-rw-r--r--spec/features/merge_requests/user_posts_diff_notes_spec.rb6
-rw-r--r--spec/features/merge_requests/user_posts_notes_spec.rb4
-rw-r--r--spec/features/merge_requests/user_uses_slash_commands_spec.rb4
-rw-r--r--spec/features/merge_requests/versions_spec.rb8
-rw-r--r--spec/features/merge_requests/widget_deployments_spec.rb4
-rw-r--r--spec/features/merge_requests/widget_spec.rb34
-rw-r--r--spec/features/milestones/milestones_spec.rb6
-rw-r--r--spec/features/profile_spec.rb15
-rw-r--r--spec/features/projects/activity/rss_spec.rb4
-rw-r--r--spec/features/projects/artifacts/file_spec.rb4
-rw-r--r--spec/features/projects/blobs/blob_show_spec.rb8
-rw-r--r--spec/features/projects/blobs/edit_spec.rb2
-rw-r--r--spec/features/projects/blobs/user_create_spec.rb2
-rw-r--r--spec/features/projects/commit/cherry_pick_spec.rb4
-rw-r--r--spec/features/projects/commit/mini_pipeline_graph_spec.rb2
-rw-r--r--spec/features/projects/commit/rss_spec.rb8
-rw-r--r--spec/features/projects/compare_spec.rb8
-rw-r--r--spec/features/projects/features_visibility_spec.rb12
-rw-r--r--spec/features/projects/files/browse_files_spec.rb4
-rw-r--r--spec/features/projects/files/dockerfile_dropdown_spec.rb4
-rw-r--r--spec/features/projects/files/find_file_keyboard_spec.rb2
-rw-r--r--spec/features/projects/files/gitignore_dropdown_spec.rb4
-rw-r--r--spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb4
-rw-r--r--spec/features/projects/files/project_owner_creates_license_file_spec.rb2
-rw-r--r--spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb2
-rw-r--r--spec/features/projects/files/undo_template_spec.rb2
-rw-r--r--spec/features/projects/guest_navigation_menu_spec.rb62
-rw-r--r--spec/features/projects/issuable_templates_spec.rb14
-rw-r--r--spec/features/projects/issues/rss_spec.rb8
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb10
-rw-r--r--spec/features/projects/main/rss_spec.rb4
-rw-r--r--spec/features/projects/members/group_links_spec.rb6
-rw-r--r--spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb2
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb4
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb16
-rw-r--r--spec/features/projects/ref_switcher_spec.rb6
-rw-r--r--spec/features/projects/settings/integration_settings_spec.rb52
-rw-r--r--spec/features/projects/snippets/show_spec.rb10
-rw-r--r--spec/features/projects/sub_group_issuables_spec.rb32
-rw-r--r--spec/features/projects/tree/rss_spec.rb4
-rw-r--r--spec/features/projects/view_on_env_spec.rb12
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb34
-rw-r--r--spec/features/search_spec.rb8
-rw-r--r--spec/features/snippets/create_snippet_spec.rb4
-rw-r--r--spec/features/snippets/notes_on_personal_snippets_spec.rb2
-rw-r--r--spec/features/snippets/public_snippets_spec.rb2
-rw-r--r--spec/features/snippets/show_spec.rb10
-rw-r--r--spec/features/task_lists_spec.rb17
-rw-r--r--spec/features/todos/todos_filtering_spec.rb8
-rw-r--r--spec/features/todos/todos_spec.rb6
-rw-r--r--spec/features/u2f_spec.rb2
-rw-r--r--spec/features/uploads/user_uploads_file_to_note_spec.rb4
-rw-r--r--spec/features/users/projects_spec.rb2
-rw-r--r--spec/features/users/rss_spec.rb4
-rw-r--r--spec/features/users/snippets_spec.rb6
-rw-r--r--spec/features/users_spec.rb8
-rw-r--r--spec/helpers/notes_helper_spec.rb8
-rw-r--r--spec/helpers/rss_helper_spec.rb8
-rw-r--r--spec/javascripts/copy_as_gfm_spec.js49
-rw-r--r--spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js4
-rw-r--r--spec/javascripts/filtered_search/dropdown_user_spec.js2
-rw-r--r--spec/javascripts/filtered_search/dropdown_utils_spec.js21
-rw-r--r--spec/javascripts/filtered_search/filtered_search_manager_spec.js1
-rw-r--r--spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js22
-rw-r--r--spec/javascripts/filtered_search/services/recent_searches_service_spec.js52
-rw-r--r--spec/javascripts/lib/utils/cache_spec.js65
-rw-r--r--spec/javascripts/lib/utils/users_cache_spec.js136
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js45
-rw-r--r--spec/javascripts/raven/index_spec.js20
-rw-r--r--spec/javascripts/raven/raven_config_spec.js60
-rw-r--r--spec/javascripts/sidebar/sidebar_assignees_spec.js12
-rw-r--r--spec/javascripts/sidebar/sidebar_store_spec.js5
-rw-r--r--spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js12
-rw-r--r--spec/javascripts/vue_shared/components/header_ci_component_spec.js82
-rw-r--r--spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js68
-rw-r--r--spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb15
-rw-r--r--spec/lib/banzai/filter/sanitization_filter_spec.rb16
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb25
-rw-r--r--spec/lib/gitlab/backup/manager_spec.rb30
-rw-r--r--spec/lib/gitlab/ci/trace/stream_spec.rb43
-rw-r--r--spec/lib/gitlab/cycle_analytics/events_spec.rb10
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb19
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb52
-rw-r--r--spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb74
-rw-r--r--spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb82
-rw-r--r--spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb66
-rw-r--r--spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb84
-rw-r--r--spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb94
-rw-r--r--spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb53
-rw-r--r--spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb96
-rw-r--r--spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb69
-rw-r--r--spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb87
-rw-r--r--spec/lib/gitlab/dependency_linker_spec.rb72
-rw-r--r--spec/lib/gitlab/diff/position_spec.rb6
-rw-r--r--spec/lib/gitlab/diff/position_tracer_spec.rb315
-rw-r--r--spec/lib/gitlab/etag_caching/router_spec.rb11
-rw-r--r--spec/lib/gitlab/git/diff_collection_spec.rb40
-rw-r--r--spec/lib/gitlab/health_checks/fs_shards_check_spec.rb83
-rw-r--r--spec/lib/gitlab/highlight_spec.rb2
-rw-r--r--spec/lib/gitlab/i18n_spec.rb32
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb384
-rw-r--r--spec/lib/gitlab/regex_spec.rb24
-rw-r--r--spec/mailers/notify_spec.rb9
-rw-r--r--spec/migrations/update_retried_for_ci_builds_spec.rb (renamed from spec/migrations/upate_retried_for_ci_builds_spec.rb)4
-rw-r--r--spec/models/ci/pipeline_spec.rb2
-rw-r--r--spec/models/commit_status_spec.rb10
-rw-r--r--spec/models/concerns/discussion_on_diff_spec.rb26
-rw-r--r--spec/models/cycle_analytics/test_spec.rb3
-rw-r--r--spec/models/diff_discussion_spec.rb7
-rw-r--r--spec/models/diff_note_spec.rb4
-rw-r--r--spec/models/hooks/service_hook_spec.rb35
-rw-r--r--spec/models/hooks/system_hook_spec.rb22
-rw-r--r--spec/models/hooks/web_hook_log_spec.rb30
-rw-r--r--spec/models/hooks/web_hook_spec.rb93
-rw-r--r--spec/models/label_spec.rb17
-rw-r--r--spec/models/merge_request_spec.rb50
-rw-r--r--spec/models/namespace_spec.rb6
-rw-r--r--spec/models/project_services/jira_service_spec.rb152
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb35
-rw-r--r--spec/models/user_spec.rb16
-rw-r--r--spec/requests/api/internal_spec.rb161
-rw-r--r--spec/requests/api/users_spec.rb4
-rw-r--r--spec/requests/projects/cycle_analytics_events_spec.rb3
-rw-r--r--spec/routing/admin_routing_spec.rb12
-rw-r--r--spec/routing/project_routing_spec.rb16
-rw-r--r--spec/routing/routing_spec.rb4
-rw-r--r--spec/services/issues/close_service_spec.rb6
-rw-r--r--spec/services/issues/reopen_service_spec.rb7
-rw-r--r--spec/services/merge_requests/close_service_spec.rb2
-rw-r--r--spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb12
-rw-r--r--spec/services/merge_requests/post_merge_service_spec.rb15
-rw-r--r--spec/services/merge_requests/reopen_service_spec.rb2
-rw-r--r--spec/services/merge_requests/update_service_spec.rb11
-rw-r--r--spec/services/notes/diff_position_update_service_spec.rb20
-rw-r--r--spec/services/projects/propagate_service_template_spec.rb12
-rw-r--r--spec/services/projects/transfer_service_spec.rb1
-rw-r--r--spec/services/system_note_service_spec.rb31
-rw-r--r--spec/services/web_hook_service_spec.rb137
-rw-r--r--spec/spec_helper.rb1
-rw-r--r--spec/support/controllers/githubish_import_controller_shared_examples.rb16
-rw-r--r--spec/support/features/issuable_slash_commands_shared_examples.rb4
-rw-r--r--spec/support/features/rss_shared_examples.rb24
-rw-r--r--spec/support/issuable_shared_examples.rb7
-rw-r--r--spec/support/kubernetes_helpers.rb2
-rw-r--r--spec/support/protected_branches/access_control_ce_shared_examples.rb4
-rw-r--r--spec/support/protected_tags/access_control_ce_shared_examples.rb2
-rw-r--r--spec/support/snippets_shared_examples.rb2
-rw-r--r--spec/support/target_branch_helpers.rb2
-rw-r--r--spec/support/test_env.rb13
-rw-r--r--spec/support/time_tracking_shared_examples.rb8
-rw-r--r--spec/support/wait_for_ajax.rb18
-rw-r--r--spec/support/wait_for_requests.rb38
-rw-r--r--spec/support/wait_for_vue_resource.rb19
-rw-r--r--spec/tasks/tokens_spec.rb6
-rw-r--r--spec/validators/dynamic_path_validator_spec.rb250
-rw-r--r--spec/workers/expire_job_cache_worker_spec.rb31
-rw-r--r--spec/workers/expire_pipeline_cache_worker_spec.rb2
-rw-r--r--spec/workers/process_commit_worker_spec.rb20
-rw-r--r--spec/workers/remove_old_web_hook_logs_worker_spec.rb18
-rw-r--r--yarn.lock218
564 files changed, 7469 insertions, 3208 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 45f1638f871..dea11bb9f61 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -52,7 +52,7 @@ stages:
.use-pg: &use-pg
services:
- - postgres:latest
+ - postgres:9.2
- redis:alpine
.use-mysql: &use-mysql
@@ -63,6 +63,7 @@ stages:
.only-master-and-ee-or-mysql: &only-master-and-ee-or-mysql
only:
- /mysql/
+ - /-stable$/
- master@gitlab-org/gitlab-ce
- master@gitlab/gitlabhq
- tags@gitlab-org/gitlab-ce
@@ -149,6 +150,7 @@ stages:
# Trigger a package build on omnibus-gitlab repository
build-package:
+ image: ruby:2.3-alpine
before_script: []
services: []
variables:
@@ -485,25 +487,6 @@ lint:javascript:report:
paths:
- eslint-report.html
-# Trigger docs build
-# https://gitlab.com/gitlab-com/doc-gitlab-com/blob/master/README.md#deployment-process
-trigger_docs:
- stage: post-test
- image: "alpine"
- <<: *dedicated-runner
- before_script:
- - apk update && apk add curl
- variables:
- GIT_STRATEGY: "none"
- cache: {}
- artifacts: {}
- script:
- - "HTTP_STATUS=$(curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master -F variables[PROJECT]=${CI_PROJECT_NAME} --silent --output curl.log --write-out '%{http_code}' https://gitlab.com/api/v3/projects/1794617/trigger/builds)"
- - if [ "${HTTP_STATUS}" -ne "201" ]; then echo "Error ${HTTP_STATUS}"; cat curl.log; echo; exit 1; fi
- only:
- - master@gitlab-org/gitlab-ce
- - master@gitlab-org/gitlab-ee
-
pages:
before_script: []
stage: pages
diff --git a/.scss-lint.yml b/.scss-lint.yml
index 83c68309fa8..a708d7b224c 100644
--- a/.scss-lint.yml
+++ b/.scss-lint.yml
@@ -11,11 +11,11 @@ linters:
# !global, !important, and !optional flags.
BangFormat:
enabled: false
-
+
# Whether or not to prefer `border: 0` over `border: none`.
BorderZero:
enabled: false
-
+
# Reports when you define a rule set using a selector with chained classes
# (a.k.a. adjoining classes).
ChainedClasses:
@@ -25,13 +25,13 @@ linters:
# (e.g. `color: green` is a color keyword)
ColorKeyword:
enabled: false
-
+
# Prefer color literals (keywords or hexadecimal codes) to be used only in
# variable declarations. They should be referred to via variables everywhere
# else.
ColorVariable:
enabled: true
-
+
# Which form of comments to prefer in CSS.
Comment:
enabled: false
@@ -39,7 +39,7 @@ linters:
# Reports @debug statements (which you probably left behind accidentally).
DebugStatement:
enabled: false
-
+
# Rule sets should be ordered as follows:
# - @extend declarations
# - @include declarations without inner @content
@@ -54,19 +54,19 @@ linters:
# more information.
DisableLinterReason:
enabled: true
-
+
# Reports when you define the same property twice in a single rule set.
DuplicateProperty:
enabled: false
-
+
# Separate rule, function, and mixin declarations with empty lines.
EmptyLineBetweenBlocks:
enabled: true
-
+
# Reports when you have an empty rule set.
EmptyRule:
enabled: true
-
+
# Reports when you have an @extend directive.
ExtendDirective:
enabled: false
@@ -75,49 +75,49 @@ linters:
# when adding lines to the file, since SCM systems such as git won't
# think that you touched the last line.
FinalNewline:
- enabled: false
-
+ enabled: true
+
# HEX colors should use three-character values where possible.
HexLength:
enabled: false
-
+
# HEX color values should use lower-case colors to differentiate between
# letters and numbers, e.g. `#E3E3E3` vs. `#e3e3e3`.
HexNotation:
enabled: true
-
+
# Avoid using ID selectors.
IdSelector:
enabled: false
-
+
# The basenames of @imported SCSS partials should not begin with an
# underscore and should not include the filename extension.
ImportPath:
enabled: false
-
+
# Avoid using !important in properties. It is usually indicative of a
# misunderstanding of CSS specificity and can lead to brittle code.
ImportantRule:
enabled: false
-
+
# Indentation should always be done in increments of 2 spaces.
Indentation:
enabled: true
width: 2
-
+
# Don't write leading zeros for numeric values with a decimal point.
LeadingZero:
enabled: false
-
+
# Reports when you define the same selector twice in a single sheet.
MergeableSelector:
enabled: false
-
+
# Functions, mixins, variables, and placeholders should be declared
# with all lowercase letters and hyphens instead of underscores.
NameFormat:
enabled: false
-
+
# Avoid nesting selectors too deeply.
NestingDepth:
enabled: false
@@ -129,12 +129,12 @@ linters:
# Sort properties in a strict order.
PropertySortOrder:
enabled: false
-
+
# Reports when you use an unknown or disabled CSS property
# (ignoring vendor-prefixed properties).
PropertySpelling:
enabled: false
-
+
# Configure which units are allowed for property values.
PropertyUnits:
enabled: false
@@ -144,25 +144,25 @@ linters:
# be declared with one colon.
PseudoElement:
enabled: true
-
+
# Avoid qualifying elements in selectors (also known as "tag-qualifying").
QualifyingElement:
enabled: false
-
+
# Don't write selectors with a depth of applicability greater than 3.
SelectorDepth:
enabled: false
-
+
# Selectors should always use hyphenated-lowercase, rather than camelCase or
# snake_case.
SelectorFormat:
enabled: false
convention: hyphenated_lowercase
-
+
# Prefer the shortest shorthand form possible for properties that support it.
Shorthand:
enabled: true
-
+
# Each property should have its own line, except in the special case of
# single line rulesets.
SingleLinePerProperty:
@@ -173,11 +173,11 @@ linters:
# individual selector occupy a single line.
SingleLinePerSelector:
enabled: true
-
+
# Commas in lists should be followed by a space.
SpaceAfterComma:
enabled: false
-
+
# Properties should be formatted with a single space separating the colon
# from the property's value.
SpaceAfterPropertyColon:
@@ -197,12 +197,12 @@ linters:
# colon.
SpaceAfterVariableName:
enabled: false
-
+
# Operators should be formatted with a single space on both sides of an
# infix operator.
SpaceAroundOperator:
enabled: true
-
+
# Opening braces should be preceded by a single space.
SpaceBeforeBrace:
enabled: true
@@ -210,7 +210,7 @@ linters:
# Parentheses should not be padded with spaces.
SpaceBetweenParens:
enabled: false
-
+
# Enforces that string literals should be written with a consistent form
# of quotes (single or double).
StringQuotes:
@@ -241,7 +241,7 @@ linters:
# be unnecessary.
UnnecessaryParentReference:
enabled: false
-
+
# URLs should be valid and not contain protocols or domain names.
UrlFormat:
enabled: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 65d3a02d68f..4e6d8d398a5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,11 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 9.2.1 (2017-05-23)
+
+- Fix placement of note emoji on hover.
+- Fix migration for older PostgreSQL versions.
+
## 9.2.0 (2017-05-22)
- API: Filter merge requests by milestone and labels. (10924)
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 50e2274e6d3..2d6c0bcf19c 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-5.0.3
+5.0.4
diff --git a/Gemfile.lock b/Gemfile.lock
index 873cd8781ef..dd2c85052f3 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -341,7 +341,7 @@ GEM
grape-entity (0.6.0)
activesupport
multi_json (>= 1.3.2)
- grpc (1.2.5)
+ grpc (1.3.4)
google-protobuf (~> 3.1)
googleauth (~> 0.5.1)
haml (4.0.7)
diff --git a/app/assets/javascripts/copy_as_gfm.js b/app/assets/javascripts/copy_as_gfm.js
index 459cdd53f9b..ba9d9a3e1f7 100644
--- a/app/assets/javascripts/copy_as_gfm.js
+++ b/app/assets/javascripts/copy_as_gfm.js
@@ -18,12 +18,12 @@ const gfmRules = {
},
},
TaskListFilter: {
- 'input[type=checkbox].task-list-item-checkbox'(el, text) {
+ 'input[type=checkbox].task-list-item-checkbox'(el) {
return `[${el.checked ? 'x' : ' '}]`;
},
},
ReferenceFilter: {
- '.tooltip'(el, text) {
+ '.tooltip'(el) {
return '';
},
'a.gfm:not([data-link=true])'(el, text) {
@@ -39,15 +39,15 @@ const gfmRules = {
},
},
TableOfContentsFilter: {
- 'ul.section-nav'(el, text) {
+ 'ul.section-nav'(el) {
return '[[_TOC_]]';
},
},
EmojiFilter: {
- 'img.emoji'(el, text) {
+ 'img.emoji'(el) {
return el.getAttribute('alt');
},
- 'gl-emoji'(el, text) {
+ 'gl-emoji'(el) {
return `:${el.getAttribute('data-name')}:`;
},
},
@@ -57,13 +57,13 @@ const gfmRules = {
},
},
VideoLinkFilter: {
- '.video-container'(el, text) {
+ '.video-container'(el) {
const videoEl = el.querySelector('video');
if (!videoEl) return false;
return CopyAsGFM.nodeToGFM(videoEl);
},
- 'video'(el, text) {
+ 'video'(el) {
return `![${el.dataset.title}](${el.getAttribute('src')})`;
},
},
@@ -74,19 +74,19 @@ const gfmRules = {
'code.code.math[data-math-style=inline]'(el, text) {
return `$\`${text}\`$`;
},
- 'span.katex-display span.katex-mathml'(el, text) {
+ 'span.katex-display span.katex-mathml'(el) {
const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
if (!mathAnnotation) return false;
return `\`\`\`math\n${CopyAsGFM.nodeToGFM(mathAnnotation)}\n\`\`\``;
},
- 'span.katex-mathml'(el, text) {
+ 'span.katex-mathml'(el) {
const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
if (!mathAnnotation) return false;
return `$\`${CopyAsGFM.nodeToGFM(mathAnnotation)}\`$`;
},
- 'span.katex-html'(el, text) {
+ 'span.katex-html'(el) {
// We don't want to include the content of this element in the copied text.
return '';
},
@@ -95,7 +95,7 @@ const gfmRules = {
},
},
SanitizationFilter: {
- 'a[name]:not([href]):empty'(el, text) {
+ 'a[name]:not([href]):empty'(el) {
return el.outerHTML;
},
'dl'(el, text) {
@@ -143,7 +143,7 @@ const gfmRules = {
},
},
MarkdownFilter: {
- 'br'(el, text) {
+ 'br'(el) {
// Two spaces at the end of a line are turned into a BR
return ' ';
},
@@ -162,7 +162,7 @@ const gfmRules = {
'blockquote'(el, text) {
return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
},
- 'img'(el, text) {
+ 'img'(el) {
return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
},
'a.anchor'(el, text) {
@@ -222,10 +222,10 @@ const gfmRules = {
'sup'(el, text) {
return `^${text}`;
},
- 'hr'(el, text) {
+ 'hr'(el) {
return '-----';
},
- 'table'(el, text) {
+ 'table'(el) {
const theadEl = el.querySelector('thead');
const tbodyEl = el.querySelector('tbody');
if (!theadEl || !tbodyEl) return false;
@@ -233,11 +233,11 @@ const gfmRules = {
const theadText = CopyAsGFM.nodeToGFM(theadEl);
const tbodyText = CopyAsGFM.nodeToGFM(tbodyEl);
- return theadText + tbodyText;
+ return [theadText, tbodyText].join('\n');
},
'thead'(el, text) {
const cells = _.map(el.querySelectorAll('th'), (cell) => {
- let chars = CopyAsGFM.nodeToGFM(cell).trim().length + 2;
+ let chars = CopyAsGFM.nodeToGFM(cell).length + 2;
let before = '';
let after = '';
@@ -262,10 +262,15 @@ const gfmRules = {
return before + middle + after;
});
- return `${text}|${cells.join('|')}|`;
+ const separatorRow = `|${cells.join('|')}|`;
+
+ return [text, separatorRow].join('\n');
},
- 'tr'(el, text) {
- const cells = _.map(el.querySelectorAll('td, th'), cell => CopyAsGFM.nodeToGFM(cell).trim());
+ 'tr'(el) {
+ const cellEls = el.querySelectorAll('td, th');
+ if (cellEls.length === 0) return false;
+
+ const cells = _.map(cellEls, cell => CopyAsGFM.nodeToGFM(cell));
return `| ${cells.join(' | ')} |`;
},
},
@@ -273,12 +278,12 @@ const gfmRules = {
class CopyAsGFM {
constructor() {
- $(document).on('copy', '.md, .wiki', (e) => { this.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
- $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { this.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
- $(document).on('paste', '.js-gfm-input', this.pasteGFM.bind(this));
+ $(document).on('copy', '.md, .wiki', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
+ $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
+ $(document).on('paste', '.js-gfm-input', CopyAsGFM.pasteGFM);
}
- copyAsGFM(e, transformer) {
+ static copyAsGFM(e, transformer) {
const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return;
@@ -292,26 +297,59 @@ class CopyAsGFM {
e.stopPropagation();
clipboardData.setData('text/plain', el.textContent);
- clipboardData.setData('text/x-gfm', CopyAsGFM.nodeToGFM(el));
+ clipboardData.setData('text/x-gfm', this.nodeToGFM(el));
}
- pasteGFM(e) {
+ static pasteGFM(e) {
const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return;
+ const text = clipboardData.getData('text/plain');
const gfm = clipboardData.getData('text/x-gfm');
if (!gfm) return;
e.preventDefault();
- window.gl.utils.insertText(e.target, gfm);
+ window.gl.utils.insertText(e.target, (textBefore, textAfter) => {
+ // If the text before the cursor contains an odd number of backticks,
+ // we are either inside an inline code span that starts with 1 backtick
+ // or a code block that starts with 3 backticks.
+ // This logic still holds when there are one or more _closed_ code spans
+ // or blocks that will have 2 or 6 backticks.
+ // This will break down when the actual code block contains an uneven
+ // number of backticks, but this is a rare edge case.
+ const backtickMatch = textBefore.match(/`/g);
+ const insideCodeBlock = backtickMatch && (backtickMatch.length % 2) === 1;
+
+ if (insideCodeBlock) {
+ return text;
+ }
+
+ return gfm;
+ });
}
static transformGFMSelection(documentFragment) {
- // If the documentFragment contains more than just Markdown, don't copy as GFM.
- if (documentFragment.querySelector('.md, .wiki')) return null;
+ const gfmEls = documentFragment.querySelectorAll('.md, .wiki');
+ switch (gfmEls.length) {
+ case 0: {
+ return documentFragment;
+ }
+ case 1: {
+ return gfmEls[0];
+ }
+ default: {
+ const allGfmEl = document.createElement('div');
+
+ for (let i = 0; i < gfmEls.length; i += 1) {
+ const lineEl = gfmEls[i];
+ allGfmEl.appendChild(lineEl);
+ allGfmEl.appendChild(document.createTextNode('\n\n'));
+ }
- return documentFragment;
+ return allGfmEl;
+ }
+ }
}
static transformCodeSelection(documentFragment) {
@@ -343,7 +381,7 @@ class CopyAsGFM {
return codeEl;
}
- static nodeToGFM(node) {
+ static nodeToGFM(node, respectWhitespaceParam = false) {
if (node.nodeType === Node.COMMENT_NODE) {
return '';
}
@@ -352,7 +390,9 @@ class CopyAsGFM {
return node.textContent;
}
- const text = this.innerGFM(node);
+ const respectWhitespace = respectWhitespaceParam || (node.nodeName === 'PRE' || node.nodeName === 'CODE');
+
+ const text = this.innerGFM(node, respectWhitespace);
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
return text;
@@ -366,7 +406,17 @@ class CopyAsGFM {
if (!window.gl.utils.nodeMatchesSelector(node, selector)) continue;
- const result = func(node, text);
+ let result;
+ if (func.length === 2) {
+ // if `func` takes 2 arguments, it depends on text.
+ // if there is no text, we don't need to generate GFM for this node.
+ if (text.length === 0) continue;
+
+ result = func(node, text);
+ } else {
+ result = func(node);
+ }
+
if (result === false) continue;
return result;
@@ -376,7 +426,7 @@ class CopyAsGFM {
return text;
}
- static innerGFM(parentNode) {
+ static innerGFM(parentNode, respectWhitespace = false) {
const nodes = parentNode.childNodes;
const clonedParentNode = parentNode.cloneNode(true);
@@ -386,13 +436,19 @@ class CopyAsGFM {
const node = nodes[i];
const clonedNode = clonedNodes[i];
- const text = this.nodeToGFM(node);
+ const text = this.nodeToGFM(node, respectWhitespace);
// `clonedNode.replaceWith(text)` is not yet widely supported
clonedNode.parentNode.replaceChild(document.createTextNode(text), clonedNode);
}
- return clonedParentNode.innerText || clonedParentNode.textContent;
+ let nodeText = clonedParentNode.innerText || clonedParentNode.textContent;
+
+ if (!respectWhitespace) {
+ nodeText = nodeText.trim();
+ }
+
+ return nodeText;
}
}
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index a27abee5431..c5fffea8bb0 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -40,6 +40,7 @@ import Group from './group';
import GroupName from './group_name';
import GroupsList from './groups_list';
import ProjectsList from './projects_list';
+import setupProjectEdit from './project_edit';
import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
import Landing from './landing';
@@ -122,7 +123,7 @@ import ShortcutsBlob from './shortcuts_blob';
break;
case 'projects:merge_requests:index':
case 'projects:issues:index':
- if (gl.FilteredSearchManager) {
+ if (gl.FilteredSearchManager && document.querySelector('.filtered-search')) {
new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests');
}
Issuable.init();
@@ -264,6 +265,9 @@ import ShortcutsBlob from './shortcuts_blob';
new BlobViewer();
}
break;
+ case 'projects:edit':
+ setupProjectEdit();
+ break;
case 'projects:pipelines:builds':
case 'projects:pipelines:failures':
case 'projects:pipelines:show':
diff --git a/app/assets/javascripts/droplab/plugins/ajax_filter.js b/app/assets/javascripts/droplab/plugins/ajax_filter.js
index cfd7e2ca189..a5427417031 100644
--- a/app/assets/javascripts/droplab/plugins/ajax_filter.js
+++ b/app/assets/javascripts/droplab/plugins/ajax_filter.js
@@ -1,4 +1,5 @@
/* eslint-disable */
+import AjaxCache from '../../lib/utils/ajax_cache';
const AjaxFilter = {
init: function(hook) {
@@ -58,50 +59,24 @@ const AjaxFilter = {
this.loading = true;
var params = config.params || {};
params[config.searchKey] = searchValue;
- var self = this;
- self.cache = self.cache || {};
var url = config.endpoint + this.buildParams(params);
- var urlCachedData = self.cache[url];
- if (urlCachedData) {
- self._loadData(urlCachedData, config, self);
- } else {
- this._loadUrlData(url)
- .then(function(data) {
- self._loadData(data, config, self);
- }, config.onError).catch(config.onError);
- }
+ return AjaxCache.retrieve(url)
+ .then((data) => {
+ this._loadData(data, config);
+ })
+ .catch(config.onError);
},
- _loadUrlData: function _loadUrlData(url) {
- var self = this;
- return new Promise(function(resolve, reject) {
- var xhr = new XMLHttpRequest;
- xhr.open('GET', url, true);
- xhr.onreadystatechange = function () {
- if(xhr.readyState === XMLHttpRequest.DONE) {
- if (xhr.status === 200) {
- var data = JSON.parse(xhr.responseText);
- self.cache[url] = data;
- return resolve(data);
- } else {
- return reject([xhr.responseText, xhr.status]);
- }
- }
- };
- xhr.send();
- });
- },
-
- _loadData: function _loadData(data, config, self) {
- const list = self.hook.list;
+ _loadData(data, config) {
+ const list = this.hook.list;
if (config.loadingTemplate && list.data === undefined ||
list.data.length === 0) {
const dataLoadingTemplate = list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) {
- dataLoadingTemplate.outerHTML = self.listTemplate;
+ dataLoadingTemplate.outerHTML = this.listTemplate;
}
}
- if (!self.destroyed) {
+ if (!this.destroyed) {
var hookListChildren = list.list.children;
var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
if (onlyDynamicList && data.length === 0) {
@@ -109,7 +84,7 @@ const AjaxFilter = {
}
list.setData.call(list, data);
}
- self.notLoading();
+ this.notLoading();
list.currentIndex = 0;
},
diff --git a/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.js b/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.js
index 15052dbd362..c51d4b056af 100644
--- a/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.js
+++ b/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.js
@@ -13,13 +13,17 @@ export default {
required: false,
default: true,
},
+ allowedKeys: {
+ type: Array,
+ required: true,
+ },
},
computed: {
processedItems() {
return this.items.map((item) => {
const { tokens, searchToken }
- = gl.FilteredSearchTokenizer.processTokens(item);
+ = gl.FilteredSearchTokenizer.processTokens(item, this.allowedKeys);
const resultantTokens = tokens.map(token => ({
prefix: `${token.key}:`,
diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js
index 5d92d29c399..2af242a69df 100644
--- a/app/assets/javascripts/filtered_search/dropdown_hint.js
+++ b/app/assets/javascripts/filtered_search/dropdown_hint.js
@@ -2,14 +2,18 @@ import Filter from '~/droplab/plugins/filter';
import './filtered_search_dropdown';
class DropdownHint extends gl.FilteredSearchDropdown {
- constructor(droplab, dropdown, input, filter) {
+ constructor(droplab, dropdown, input, tokenKeys, filter) {
super(droplab, dropdown, input, filter);
this.config = {
Filter: {
template: 'hint',
- filterFunction: gl.DropdownUtils.filterHint.bind(null, input),
+ filterFunction: gl.DropdownUtils.filterHint.bind(null, {
+ input,
+ allowedKeys: tokenKeys.getKeys(),
+ }),
},
};
+ this.tokenKeys = tokenKeys;
}
itemClicked(e) {
@@ -52,20 +56,13 @@ class DropdownHint extends gl.FilteredSearchDropdown {
}
renderContent() {
- const dropdownData = [];
-
- [].forEach.call(this.input.closest('.filtered-search-box-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
- const { icon, hint, tag, type } = dropdownMenu.dataset;
- if (icon && hint && tag) {
- dropdownData.push(
- Object.assign({
- icon: `fa-${icon}`,
- hint,
- tag: `<${tag}>`,
- }, type && { type }),
- );
- }
- });
+ const dropdownData = gl.FilteredSearchTokenKeys.get()
+ .map(tokenKey => ({
+ icon: `fa-${tokenKey.icon}`,
+ hint: tokenKey.key,
+ tag: `<${tokenKey.symbol}${tokenKey.key}>`,
+ type: tokenKey.type,
+ }));
this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config);
this.droplab.setData(this.hookId, dropdownData);
diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js b/app/assets/javascripts/filtered_search/dropdown_non_user.js
index f20193eecba..34a9e34070c 100644
--- a/app/assets/javascripts/filtered_search/dropdown_non_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js
@@ -5,7 +5,7 @@ import Filter from '~/droplab/plugins/filter';
import './filtered_search_dropdown';
class DropdownNonUser extends gl.FilteredSearchDropdown {
- constructor(droplab, dropdown, input, filter, endpoint, symbol) {
+ constructor(droplab, dropdown, input, tokenKeys, filter, endpoint, symbol) {
super(droplab, dropdown, input, filter);
this.symbol = symbol;
this.config = {
diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js b/app/assets/javascripts/filtered_search/dropdown_user.js
index 42538780e50..6b4338ca1d6 100644
--- a/app/assets/javascripts/filtered_search/dropdown_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_user.js
@@ -4,7 +4,7 @@ import AjaxFilter from '~/droplab/plugins/ajax_filter';
import './filtered_search_dropdown';
class DropdownUser extends gl.FilteredSearchDropdown {
- constructor(droplab, dropdown, input, filter) {
+ constructor(droplab, dropdown, input, tokenKeys, filter) {
super(droplab, dropdown, input, filter);
this.config = {
AjaxFilter: {
@@ -25,6 +25,7 @@ class DropdownUser extends gl.FilteredSearchDropdown {
},
},
};
+ this.tokenKeys = tokenKeys;
}
itemClicked(e) {
@@ -43,7 +44,7 @@ class DropdownUser extends gl.FilteredSearchDropdown {
getSearchInput() {
const query = gl.DropdownUtils.getSearchInput(this.input);
- const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
+ const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query, this.tokenKeys.get());
let value = lastToken || '';
diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js
index bc7c1dffece..5c02a7a53d3 100644
--- a/app/assets/javascripts/filtered_search/dropdown_utils.js
+++ b/app/assets/javascripts/filtered_search/dropdown_utils.js
@@ -50,10 +50,12 @@ class DropdownUtils {
return updatedItem;
}
- static filterHint(input, item) {
+ static filterHint(config, item) {
+ const { input, allowedKeys } = config;
const updatedItem = item;
const searchInput = gl.DropdownUtils.getSearchQuery(input);
- const { lastToken, tokens } = gl.FilteredSearchTokenizer.processTokens(searchInput);
+ const { lastToken, tokens } =
+ gl.FilteredSearchTokenizer.processTokens(searchInput, allowedKeys);
const lastKey = lastToken.key || lastToken || '';
const allowMultiple = item.type === 'array';
const itemInExistingTokens = tokens.some(t => t.key === item.hint);
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index 49a6cd1ac77..6bc6bc43f51 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -2,10 +2,10 @@ import DropLab from '~/droplab/drop_lab';
import FilteredSearchContainer from './container';
class FilteredSearchDropdownManager {
- constructor(baseEndpoint = '', page) {
+ constructor(baseEndpoint = '', tokenizer, page) {
this.container = FilteredSearchContainer.container;
this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
- this.tokenizer = gl.FilteredSearchTokenizer;
+ this.tokenizer = tokenizer;
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.page = page;
@@ -98,7 +98,8 @@ class FilteredSearchDropdownManager {
if (!mappingKey.reference) {
const dl = this.droplab;
- const defaultArguments = [null, dl, element, this.filteredSearchInput, key];
+ const defaultArguments =
+ [null, dl, element, this.filteredSearchInput, this.filteredSearchTokenKeys, key];
const glArguments = defaultArguments.concat(mappingKey.extraArguments || []);
// Passing glArguments to `new gl[glClass](<arguments>)`
@@ -141,7 +142,8 @@ class FilteredSearchDropdownManager {
setDropdown() {
const query = gl.DropdownUtils.getSearchQuery(true);
- const { lastToken, searchToken } = this.tokenizer.processTokens(query);
+ const { lastToken, searchToken } =
+ this.tokenizer.processTokens(query, this.filteredSearchTokenKeys.getKeys());
if (this.currentDropdown) {
this.updateCurrentDropdownOffset();
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 57d247e11a9..58f2b75bd50 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -15,6 +15,7 @@ class FilteredSearchManager {
this.recentSearchesStore = new RecentSearchesStore({
isLocalStorageAvailable: RecentSearchesService.isAvailable(),
+ allowedKeys: this.filteredSearchTokenKeys.getKeys(),
});
const searchHistoryDropdownElement = document.querySelector('.js-filtered-search-history-dropdown');
const projectPath = searchHistoryDropdownElement ?
@@ -46,7 +47,7 @@ class FilteredSearchManager {
if (this.filteredSearchInput) {
this.tokenizer = gl.FilteredSearchTokenizer;
- this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', page);
+ this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', this.tokenizer, page);
this.recentSearchesRoot = new RecentSearchesRoot(
this.recentSearchesStore,
@@ -318,7 +319,7 @@ class FilteredSearchManager {
handleInputVisualToken() {
const input = this.filteredSearchInput;
const { tokens, searchToken }
- = gl.FilteredSearchTokenizer.processTokens(input.value);
+ = this.tokenizer.processTokens(input.value, this.filteredSearchTokenKeys.getKeys());
const { isLastVisualTokenValid }
= gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
@@ -444,7 +445,7 @@ class FilteredSearchManager {
this.saveCurrentSearchQuery();
const { tokens, searchToken }
- = this.tokenizer.processTokens(searchQuery);
+ = this.tokenizer.processTokens(searchQuery, this.filteredSearchTokenKeys.getKeys());
const currentState = gl.utils.getParameterByName('state') || 'opened';
paths.push(`state=${currentState}`);
diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
index 1abad9d1b73..025d4d8795b 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
@@ -3,21 +3,25 @@ const tokenKeys = [{
type: 'string',
param: 'username',
symbol: '@',
+ icon: 'pencil',
}, {
key: 'assignee',
type: 'string',
param: 'username',
symbol: '@',
+ icon: 'user',
}, {
key: 'milestone',
type: 'string',
param: 'title',
symbol: '%',
+ icon: 'clock-o',
}, {
key: 'label',
type: 'array',
param: 'name[]',
symbol: '~',
+ icon: 'tag',
}];
const alternativeTokenKeys = [{
@@ -56,6 +60,10 @@ class FilteredSearchTokenKeys {
return tokenKeys;
}
+ static getKeys() {
+ return tokenKeys.map(i => i.key);
+ }
+
static getAlternatives() {
return alternativeTokenKeys;
}
diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js
index aa513b3aeae..f2e66503e5e 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js
@@ -1,8 +1,7 @@
import './filtered_search_token_keys';
class FilteredSearchTokenizer {
- static processTokens(input) {
- const allowedKeys = gl.FilteredSearchTokenKeys.get().map(i => i.key);
+ static processTokens(input, allowedKeys) {
// Regex extracts `(token):(symbol)(value)`
// Values that start with a double quote must end in a double quote (same for single)
const tokenRegex = new RegExp(`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, 'g');
diff --git a/app/assets/javascripts/filtered_search/recent_searches_root.js b/app/assets/javascripts/filtered_search/recent_searches_root.js
index b2e6f63aacf..27e49d4fb96 100644
--- a/app/assets/javascripts/filtered_search/recent_searches_root.js
+++ b/app/assets/javascripts/filtered_search/recent_searches_root.js
@@ -37,6 +37,7 @@ class RecentSearchesRoot {
<recent-searches-dropdown-content
:items="recentSearches"
:is-local-storage-available="isLocalStorageAvailable"
+ :allowed-keys="allowedKeys"
/>
`,
components: {
diff --git a/app/assets/javascripts/filtered_search/stores/recent_searches_store.js b/app/assets/javascripts/filtered_search/stores/recent_searches_store.js
index 35fc15e4c87..aaa0c349d93 100644
--- a/app/assets/javascripts/filtered_search/stores/recent_searches_store.js
+++ b/app/assets/javascripts/filtered_search/stores/recent_searches_store.js
@@ -1,10 +1,11 @@
import _ from 'underscore';
class RecentSearchesStore {
- constructor(initialState = {}) {
+ constructor(initialState = {}, allowedKeys) {
this.state = Object.assign({
isLocalStorageAvailable: true,
recentSearches: [],
+ allowedKeys,
}, initialState);
}
diff --git a/app/assets/javascripts/group_name.js b/app/assets/javascripts/group_name.js
index 62675d7e67e..462d792b8d5 100644
--- a/app/assets/javascripts/group_name.js
+++ b/app/assets/javascripts/group_name.js
@@ -44,18 +44,18 @@ export default class GroupName {
showToggle() {
this.title.classList.add('wrap');
this.toggle.classList.remove('hidden');
- if (this.isHidden) this.groupTitle.classList.add('is-hidden');
+ if (this.isHidden) this.groupTitle.classList.add('hidden');
}
hideToggle() {
this.title.classList.remove('wrap');
this.toggle.classList.add('hidden');
- if (this.isHidden) this.groupTitle.classList.remove('is-hidden');
+ if (this.isHidden) this.groupTitle.classList.remove('hidden');
}
toggleGroups() {
this.isHidden = !this.isHidden;
- this.groupTitle.classList.toggle('is-hidden');
+ this.groupTitle.classList.toggle('hidden');
}
render() {
diff --git a/app/assets/javascripts/lib/utils/ajax_cache.js b/app/assets/javascripts/lib/utils/ajax_cache.js
index cf030d613df..f1fe95e12e8 100644
--- a/app/assets/javascripts/lib/utils/ajax_cache.js
+++ b/app/assets/javascripts/lib/utils/ajax_cache.js
@@ -1,21 +1,11 @@
-class AjaxCache {
+import Cache from './cache';
+
+class AjaxCache extends Cache {
constructor() {
- this.internalStorage = { };
+ super();
this.pendingRequests = { };
}
- get(endpoint) {
- return this.internalStorage[endpoint];
- }
-
- hasData(endpoint) {
- return Object.prototype.hasOwnProperty.call(this.internalStorage, endpoint);
- }
-
- remove(endpoint) {
- delete this.internalStorage[endpoint];
- }
-
retrieve(endpoint) {
if (this.hasData(endpoint)) {
return Promise.resolve(this.get(endpoint));
diff --git a/app/assets/javascripts/lib/utils/cache.js b/app/assets/javascripts/lib/utils/cache.js
new file mode 100644
index 00000000000..3141f1eeafc
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/cache.js
@@ -0,0 +1,19 @@
+class Cache {
+ constructor() {
+ this.internalStorage = { };
+ }
+
+ get(key) {
+ return this.internalStorage[key];
+ }
+
+ hasData(key) {
+ return Object.prototype.hasOwnProperty.call(this.internalStorage, key);
+ }
+
+ remove(key) {
+ delete this.internalStorage[key];
+ }
+}
+
+export default Cache;
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 7e62773ae6c..a537267643e 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -198,10 +198,12 @@
const textBefore = value.substring(0, selectionStart);
const textAfter = value.substring(selectionEnd, value.length);
- const newText = textBefore + text + textAfter;
+
+ const insertedText = text instanceof Function ? text(textBefore, textAfter) : text;
+ const newText = textBefore + insertedText + textAfter;
target.value = newText;
- target.selectionStart = target.selectionEnd = selectionStart + text.length;
+ target.selectionStart = target.selectionEnd = selectionStart + insertedText.length;
// Trigger autosave
$(target).trigger('input');
diff --git a/app/assets/javascripts/lib/utils/users_cache.js b/app/assets/javascripts/lib/utils/users_cache.js
new file mode 100644
index 00000000000..88f8a622c00
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/users_cache.js
@@ -0,0 +1,28 @@
+import Api from '../../api';
+import Cache from './cache';
+
+class UsersCache extends Cache {
+ retrieve(username) {
+ if (this.hasData(username)) {
+ return Promise.resolve(this.get(username));
+ }
+
+ return Api.users('', { username })
+ .then((users) => {
+ if (!users.length) {
+ throw new Error(`User "${username}" could not be found!`);
+ }
+
+ if (users.length > 1) {
+ throw new Error(`Expected username "${username}" to be unique!`);
+ }
+
+ const user = users[0];
+ this.internalStorage[username] = user;
+ return user;
+ });
+ // missing catch is intentional, error handling depends on use case
+ }
+}
+
+export default new UsersCache();
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 22032d0f914..894ed81b044 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -285,7 +285,7 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
// Similar to `toggler_behavior` in the discussion tab
const hash = window.gl.utils.getLocationHash();
const anchor = hash && $container.find(`[id="${hash}"]`);
- if (anchor) {
+ if (anchor && anchor.length > 0) {
const notesContent = anchor.closest('.notes_content');
const lineType = notesContent.hasClass('new') ? 'new' : 'old';
notes.toggleDiffNote({
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index a981b61f942..59c52c1e497 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -860,11 +860,12 @@ const normalizeNewlines = function(str) {
Notes.prototype.onAddDiffNote = function(e) {
e.preventDefault();
- const $link = $(e.currentTarget || e.target);
+ const link = e.currentTarget || e.target;
+ const $link = $(link);
const showReplyInput = !$link.hasClass('js-diff-comment-avatar');
this.toggleDiffNote({
target: $link,
- lineType: $link.data('lineType'),
+ lineType: link.dataset.lineType,
showReplyInput
});
};
@@ -1211,7 +1212,7 @@ const normalizeNewlines = function(str) {
`<li id="${uniqueId}" class="note being-posted fade-in-half timeline-entry">
<div class="timeline-entry-inner">
<div class="timeline-icon">
- <a href="/${currentUsername}"><span class="dummy-avatar"></span></a>
+ <a href="/${currentUsername}"><span class="avatar dummy-avatar"></span></a>
</div>
<div class="timeline-content ${discussionClass}">
<div class="note-header">
diff --git a/app/assets/javascripts/project_edit.js b/app/assets/javascripts/project_edit.js
new file mode 100644
index 00000000000..d7d284b6c86
--- /dev/null
+++ b/app/assets/javascripts/project_edit.js
@@ -0,0 +1,9 @@
+export default function setupProjectEdit() {
+ const $transferForm = $('.js-project-transfer-form');
+ const $selectNamespace = $transferForm.find('.select2');
+
+ $selectNamespace.on('change', () => {
+ $transferForm.find(':submit').prop('disabled', !$selectNamespace.val());
+ });
+ $selectNamespace.trigger('change');
+}
diff --git a/app/assets/javascripts/raven/index.js b/app/assets/javascripts/raven/index.js
index 5325e495815..edc2293915f 100644
--- a/app/assets/javascripts/raven/index.js
+++ b/app/assets/javascripts/raven/index.js
@@ -6,6 +6,10 @@ const index = function index() {
currentUserId: gon.current_user_id,
whitelistUrls: [gon.gitlab_url],
isProduction: process.env.NODE_ENV,
+ release: gon.revision,
+ tags: {
+ revision: gon.revision,
+ },
});
return RavenConfig;
diff --git a/app/assets/javascripts/raven/raven_config.js b/app/assets/javascripts/raven/raven_config.js
index c7fe1cacf49..ae54fa5f1a9 100644
--- a/app/assets/javascripts/raven/raven_config.js
+++ b/app/assets/javascripts/raven/raven_config.js
@@ -1,4 +1,5 @@
import Raven from 'raven-js';
+import $ from 'jquery';
const IGNORE_ERRORS = [
// Random plugins/extensions
@@ -57,6 +58,8 @@ const RavenConfig = {
configure() {
Raven.config(this.options.sentryDsn, {
+ release: this.options.release,
+ tags: this.options.tags,
whitelistUrls: this.options.whitelistUrls,
environment: this.options.isProduction ? 'production' : 'development',
ignoreErrors: this.IGNORE_ERRORS,
@@ -72,7 +75,7 @@ const RavenConfig = {
},
bindRavenErrors() {
- window.$(document).on('ajaxError.raven', this.handleRavenErrors);
+ $(document).on('ajaxError.raven', this.handleRavenErrors);
},
handleRavenErrors(event, req, config, err) {
diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js
index b07b3a4d3a5..dace03554e8 100644
--- a/app/assets/javascripts/shortcuts_issuable.js
+++ b/app/assets/javascripts/shortcuts_issuable.js
@@ -38,7 +38,7 @@ import './shortcuts_navigation';
}
ShortcutsIssuable.prototype.replyWithSelectedText = function() {
- var quote, documentFragment, selected, separator;
+ var quote, documentFragment, el, selected, separator;
var replyField = $('.js-main-target-form #note_note');
documentFragment = window.gl.utils.getSelectedFragment();
@@ -47,10 +47,8 @@ import './shortcuts_navigation';
return;
}
- // If the documentFragment contains more than just Markdown, don't copy as GFM.
- if (documentFragment.querySelector('.md, .wiki')) return;
-
- selected = window.gl.CopyAsGFM.nodeToGFM(documentFragment);
+ el = window.gl.CopyAsGFM.transformGFMSelection(documentFragment.cloneNode(true));
+ selected = window.gl.CopyAsGFM.nodeToGFM(el);
if (selected.trim() === "") {
return;
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js
index 1488a66c695..da4abf0b68f 100644
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.js
@@ -69,10 +69,11 @@ export default {
<div>
<assignee-title
:number-of-assignees="store.assignees.length"
- :loading="loading"
+ :loading="loading || store.isFetching.assignees"
:editable="store.editable"
/>
<assignees
+ v-if="!store.isFetching.assignees"
class="value"
:root-path="store.rootPath"
:users="store.assignees"
diff --git a/app/assets/javascripts/sidebar/stores/sidebar_store.js b/app/assets/javascripts/sidebar/stores/sidebar_store.js
index 2d44c05bb8d..3356dd0191f 100644
--- a/app/assets/javascripts/sidebar/stores/sidebar_store.js
+++ b/app/assets/javascripts/sidebar/stores/sidebar_store.js
@@ -10,6 +10,9 @@ export default class SidebarStore {
this.humanTimeEstimate = '';
this.humanTimeSpent = '';
this.assignees = [];
+ this.isFetching = {
+ assignees: true,
+ };
SidebarStore.singleton = this;
}
@@ -18,6 +21,7 @@ export default class SidebarStore {
}
setAssigneeData(data) {
+ this.isFetching.assignees = false;
if (data.assignees) {
this.assignees = data.assignees;
}
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
index bacb26734c9..c44892dae3d 100644
--- a/app/assets/javascripts/single_file_diff.js
+++ b/app/assets/javascripts/single_file_diff.js
@@ -4,7 +4,7 @@
window.SingleFileDiff = (function() {
var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
- WRAPPER = '<div class="diff-content diff-wrap-lines"></div>';
+ WRAPPER = '<div class="diff-content"></div>';
LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
index f30c831fd99..fcd4fdaf09f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.js
@@ -213,7 +213,7 @@ export default {
v-if="shouldShowMergeOptionsDropdown"
:disabled="isMergeButtonDisabled"
type="button"
- class="btn btn-info dropdown-toggle"
+ class="btn btn-small btn-info dropdown-toggle"
data-toggle="dropdown">
<i
class="fa fa-caret-down"
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 fb78ea92da1..7c15abfff10 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
@@ -11,10 +11,6 @@ export default function deviseState(data) {
return 'conflicts';
} else if (data.work_in_progress) {
return 'workInProgress';
- } else if (this.mergeWhenPipelineSucceeds) {
- return this.mergeError ? 'autoMergeFailed' : 'mergeWhenPipelineSucceeds';
- } else if (!this.canMerge) {
- return 'notAllowedToMerge';
} else if (this.onlyAllowMergeIfPipelineSucceeds && this.isPipelineFailed) {
return 'pipelineFailed';
} else if (this.hasMergeableDiscussionsState) {
@@ -23,6 +19,10 @@ export default function deviseState(data) {
return 'pipelineBlocked';
} else if (this.hasSHAChanged) {
return 'shaMismatch';
+ } else if (this.mergeWhenPipelineSucceeds) {
+ return this.mergeError ? 'autoMergeFailed' : 'mergeWhenPipelineSucceeds';
+ } else if (!this.canMerge) {
+ return 'notAllowedToMerge';
} else if (this.canBeMerged) {
return 'readyToMerge';
}
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 4833fb325af..dc4b2195050 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
@@ -4,7 +4,7 @@ import { getStateKey } from '../dependencies';
export default class MergeRequestStore {
constructor(data) {
- this.startingSha = data.diff_head_sha;
+ this.sha = data.diff_head_sha;
this.setData(data);
}
@@ -16,7 +16,6 @@ export default class MergeRequestStore {
this.targetBranch = data.target_branch;
this.sourceBranch = data.source_branch;
this.mergeStatus = data.merge_status;
- this.sha = data.diff_head_sha;
this.commitMessage = data.merge_commit_message;
this.commitMessageWithDescription = data.merge_commit_message_with_description;
this.commitsCount = data.commits_count;
@@ -69,7 +68,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 !== this.startingSha;
+ this.hasSHAChanged = this.sha !== data.diff_head_sha;
this.canBeMerged = data.can_be_merged || false;
// Cherry-pick and Revert actions related
diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
new file mode 100644
index 00000000000..fd0dcd716d6
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
@@ -0,0 +1,122 @@
+<script>
+import ciIconBadge from './ci_badge_link.vue';
+import timeagoTooltip from './time_ago_tooltip.vue';
+import tooltipMixin from '../mixins/tooltip';
+import userAvatarLink from './user_avatar/user_avatar_link.vue';
+
+/**
+ * Renders header component for job and pipeline page based on UI mockups
+ *
+ * Used in:
+ * - job show page
+ * - pipeline show page
+ */
+export default {
+ props: {
+ status: {
+ type: Object,
+ required: true,
+ },
+ itemName: {
+ type: String,
+ required: true,
+ },
+ itemId: {
+ type: Number,
+ required: true,
+ },
+ time: {
+ type: String,
+ required: true,
+ },
+ user: {
+ type: Object,
+ required: true,
+ },
+ actions: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ },
+
+ mixins: [
+ tooltipMixin,
+ ],
+
+ components: {
+ ciIconBadge,
+ timeagoTooltip,
+ userAvatarLink,
+ },
+
+ computed: {
+ userAvatarAltText() {
+ return `${this.user.name}'s avatar`;
+ },
+ },
+
+ methods: {
+ onClickAction(action) {
+ this.$emit('postAction', action);
+ },
+ },
+};
+</script>
+<template>
+ <header class="page-content-header top-area">
+ <section class="header-main-content">
+
+ <ci-icon-badge :status="status" />
+
+ <strong>
+ {{itemName}} #{{itemId}}
+ </strong>
+
+ triggered
+
+ <timeago-tooltip :time="time" />
+
+ by
+
+ <user-avatar-link
+ :link-href="user.web_url"
+ :img-src="user.avatar_url"
+ :img-alt="userAvatarAltText"
+ :tooltip-text="user.name"
+ :img-size="24"
+ />
+
+ <a
+ :href="user.web_url"
+ :title="user.email"
+ class="js-user-link commit-committer-link"
+ ref="tooltip">
+ {{user.name}}
+ </a>
+ </section>
+
+ <section
+ class="header-action-button nav-controls"
+ v-if="actions.length">
+ <template
+ v-for="action in actions">
+ <a
+ v-if="action.type === 'link'"
+ :href="action.path"
+ :class="action.cssClass">
+ {{action.label}}
+ </a>
+
+ <button
+ v-else="action.type === 'button'"
+ @click="onClickAction(action)"
+ :class="action.cssClass"
+ type="button">
+ {{action.label}}
+ </button>
+
+ </template>
+ </section>
+ </header>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
new file mode 100644
index 00000000000..af2b4c6786e
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
@@ -0,0 +1,58 @@
+<script>
+import tooltipMixin from '../mixins/tooltip';
+import timeagoMixin from '../mixins/timeago';
+import '../../lib/utils/datetime_utility';
+
+/**
+ * Port of ruby helper time_ago_with_tooltip
+ */
+
+export default {
+ props: {
+ time: {
+ type: String,
+ required: true,
+ },
+
+ tooltipPlacement: {
+ type: String,
+ required: false,
+ default: 'top',
+ },
+
+ shortFormat: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+
+ cssClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+
+ mixins: [
+ tooltipMixin,
+ timeagoMixin,
+ ],
+
+ computed: {
+ timeagoCssClass() {
+ return this.shortFormat ? 'js-short-timeago' : 'js-timeago';
+ },
+ },
+};
+</script>
+<template>
+ <time
+ :class="[timeagoCssClass, cssClass]"
+ class="js-timeago js-timeago-render"
+ :title="tooltipTitle(time)"
+ :data-placement="tooltipPlacement"
+ data-container="body"
+ ref="tooltip">
+ {{timeFormated(time)}}
+ </time>
+</template>
diff --git a/app/assets/javascripts/vue_shared/mixins/timeago.js b/app/assets/javascripts/vue_shared/mixins/timeago.js
new file mode 100644
index 00000000000..20f63ab663c
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/mixins/timeago.js
@@ -0,0 +1,18 @@
+import '../../lib/utils/datetime_utility';
+
+/**
+ * Mixin with time ago methods used in some vue components
+ */
+export default {
+ methods: {
+ timeFormated(time) {
+ const timeago = gl.utils.getTimeago();
+
+ return timeago.format(time);
+ },
+
+ tooltipTitle(time) {
+ return gl.utils.formatDate(time);
+ },
+ },
+};
diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
index d5f87588c28..740930dce5b 100644
--- a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
+++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
@@ -4,7 +4,7 @@ import VueResource from 'vue-resource';
Vue.use(VueResource);
// Maintain a global counter for active requests
-// see: spec/support/wait_for_vue_resource.rb
+// see: spec/support/wait_for_requests.rb
Vue.http.interceptors.push((request, next) => {
window.activeVueResources = window.activeVueResources || 0;
window.activeVueResources += 1;
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index 9159927ed8b..75907c35b7e 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -10,7 +10,7 @@
top: 0;
margin-top: 3px;
padding: $gl-padding;
- z-index: 9;
+ z-index: 300;
width: 300px;
font-size: 14px;
background-color: $white-light;
@@ -108,8 +108,9 @@
}
.award-control {
- margin-right: 5px;
+ margin: 0 5px 6px 0;
outline: 0;
+ position: relative;
&.disabled {
cursor: default;
@@ -227,8 +228,8 @@
.award-control-icon-positive,
.award-control-icon-super-positive {
position: absolute;
- left: 11px;
- bottom: 7px;
+ left: 10px;
+ bottom: 6px;
opacity: 0;
@include transition(opacity, transform);
}
@@ -237,7 +238,3 @@
vertical-align: middle;
}
}
-
-.note-awards .award-control-icon-positive {
- left: 6px;
-}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index f8674b763c8..78f425057eb 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -66,10 +66,10 @@
&.video {
background: $file-image-bg;
text-align: center;
+ padding: 30px;
img,
video {
- padding: 20px;
max-width: 80%;
}
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 637731cc479..f0994e968c8 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -263,7 +263,9 @@
}
.filtered-search-input-dropdown-menu {
+ max-height: 215px;
max-width: 280px;
+ overflow: auto;
@media (max-width: $screen-xs-min) {
width: auto;
@@ -372,11 +374,6 @@
padding: 0;
}
-.filter-dropdown {
- max-height: 215px;
- overflow: auto;
-}
-
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
.issue-bulk-update-dropdown-toggle {
width: 100px;
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index eadb9409fee..25b4feca3c3 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -36,6 +36,10 @@
border-radius: 0;
}
}
+
+ &:empty {
+ margin: 0;
+ }
}
@media (max-width: $screen-sm-max) {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index ce8b27a1951..d8645afb7da 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -40,7 +40,17 @@ header {
}
&.with-horizontal-nav {
- border-color: transparent;
+ border-bottom: 0;
+
+ .navbar-border {
+ height: 1px;
+ position: absolute;
+ right: 0;
+ left: 0;
+ bottom: -1px;
+ background-color: $border-color;
+ opacity: 0;
+ }
}
.container-fluid {
@@ -114,16 +124,6 @@ header {
}
}
- .navbar-border {
- height: 1px;
- position: absolute;
- right: 0;
- left: 0;
- bottom: 0;
- background-color: $border-color;
- opacity: 0;
- }
-
.global-dropdown {
position: absolute;
left: -10px;
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 018f61ca3a8..5b62d7fa3a7 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -83,4 +83,8 @@
position: fixed;
top: $header-height;
}
+
+ &:not(.affix-top) {
+ min-height: 100%;
+ }
}
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index aa0c512a277..ddccfc96819 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -3,12 +3,6 @@
margin: 0;
padding: 0;
- .note-text {
- p:last-child {
- margin-bottom: 0;
- }
- }
-
.system-note {
.note-text {
color: $gl-text-color !important;
@@ -23,7 +17,6 @@
}
.timeline-entry {
- padding: $gl-padding $gl-btn-padding 0;
border-color: $white-normal;
color: $gl-text-color;
border-bottom: 1px solid $border-white-light;
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 0c3407f34f8..785b09e622f 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -21,6 +21,10 @@
margin-top: 0;
}
+ > :last-child {
+ margin-bottom: 0;
+ }
+
// Single code lines should wrap
code {
font-family: $monospace_font;
@@ -157,7 +161,7 @@
ul,
ol {
padding: 0;
- margin: 0 0 16px !important;
+ margin: 0 0 16px;
}
ul:dir(rtl),
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 17a4e8fd83e..4db77752c0c 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -247,7 +247,6 @@ $dark-diff-match-bg: rgba(255, 255, 255, 0.3);
$dark-diff-match-color: rgba(255, 255, 255, 0.1);
$file-mode-changed: #777;
$file-mode-changed: #777;
-$diff-image-bg: #ddd;
$diff-image-info-color: grey;
$diff-swipe-border: #999;
$diff-view-modes-color: grey;
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index f3de05aa5f6..3d9eff35583 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -4,11 +4,7 @@
color: $gl-text-color;
line-height: 34px;
- .author {
- color: $gl-text-color;
- }
-
- .identifier {
+ a {
color: $gl-text-color;
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index cfb1df4df84..58715c4c083 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -151,14 +151,10 @@
}
}
}
-
- .text-file.diff-wrap-lines table .line_holder td span {
- white-space: pre-wrap;
- }
}
.image {
- background: $diff-image-bg;
+ background: $file-image-bg;
text-align: center;
padding: 30px;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 9a63f758ce1..c2346f2f1c3 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -56,6 +56,10 @@
padding: 5px;
max-height: calc(100vh - 100px);
}
+
+ .emoji-block {
+ padding: 10px 0 4px;
+ }
}
.issuable-filter-count {
@@ -427,7 +431,7 @@
}
.detail-page-description {
- padding: 16px 0 0;
+ padding: 16px 0;
small {
color: $gray-darkest;
@@ -437,7 +441,7 @@
.edited-text {
color: $gray-darkest;
display: block;
- margin: 0 0 16px;
+ margin: 16px 0 0;
.author_link {
color: $gray-darkest;
diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss
index 8dbac76e30a..971d54e7472 100644
--- a/app/assets/stylesheets/pages/members.scss
+++ b/app/assets/stylesheets/pages/members.scss
@@ -184,4 +184,4 @@
}
}
}
-} \ No newline at end of file
+}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 1ac9d5af21d..183be86f650 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -209,8 +209,7 @@
}
}
- .mr-widget-heading,
- .mr-widget-body {
+ .mr-widget-heading {
.btn-default.btn-xs {
margin-left: 5px;
}
@@ -223,13 +222,17 @@
.btn-group .btn {
padding: 5px 10px;
+
+ &.dropdown-toggle {
+ padding: 5px 7px;
+ }
}
}
.mr-widget-body {
h4 {
- font-weight: 600;
- font-size: 16px;
+ font-weight: bold;
+ font-size: 15px;
margin: 5px 0;
color: $gl-text-color;
@@ -256,7 +259,6 @@
}
.bold {
- margin-left: 5px;
font-weight: bold;
font-size: 15px;
color: $gl-gray-light;
@@ -405,6 +407,12 @@
}
}
+.mr-state-widget .mr-widget-body {
+ .approve-btn {
+ margin-right: 5px;
+ }
+}
+
.mr_source_commit,
.mr_target_commit {
margin-bottom: 0;
@@ -547,7 +555,7 @@
p {
float: left;
- padding-left: 20px;
+ padding-left: 21px;
&::before {
top: 13px;
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 9db26f99a75..875e47cdff3 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -28,7 +28,7 @@
.note-edit-form {
.note-form-actions {
position: relative;
- margin: $gl-padding 0;
+ margin: $gl-padding 0 0;
}
.note-preview-holder {
@@ -124,10 +124,18 @@
}
.discussion-form {
- padding: $gl-padding-top $gl-padding;
+ padding: $gl-padding-top $gl-padding $gl-padding;
background-color: $white-light;
}
+.discussion-notes .disabled-comment {
+ padding: 6px 0;
+}
+
+.notes-form > li {
+ border: 0;
+}
+
.note-edit-form {
display: none;
font-size: 14px;
@@ -164,10 +172,6 @@
.discussion-body,
.diff-file {
- .notes .note {
- padding: 10px 15px;
- }
-
.discussion-reply-holder {
background-color: $white-light;
padding: 10px 16px;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 4b15fc2bd82..32d2e9ba4bd 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -14,19 +14,6 @@ ul.notes {
margin: 0;
padding: 0;
- .timeline-icon {
- float: left;
-
- svg {
- width: 16px;
- height: 16px;
- fill: $gray-darkest;
- position: absolute;
- left: 0;
- top: 16px;
- }
- }
-
.timeline-content {
margin-left: 55px;
@@ -43,7 +30,11 @@ ul.notes {
}
.discussion-body {
- padding-top: 15px;
+ padding-top: 8px;
+
+ .panel {
+ margin-bottom: 0;
+ }
}
.discussion {
@@ -52,20 +43,22 @@ ul.notes {
position: relative;
}
- .note {
+ > li {
+ padding: $gl-padding $gl-btn-padding;
display: block;
position: relative;
border-bottom: 1px solid $white-normal;
+ &:last-child {
+ // Override `.timeline > li:last-child { border-bottom: none; }`
+ border-bottom: 1px solid $white-normal;
+ }
+
&.being-posted {
pointer-events: none;
opacity: 0.5;
.dummy-avatar {
- display: inline-block;
- height: 40px;
- width: 40px;
- border-radius: 50%;
background-color: $kdb-border;
border: 1px solid darken($kdb-border, 25%);
}
@@ -78,11 +71,7 @@ ul.notes {
&.note-discussion {
&.timeline-entry {
- padding: 14px 10px;
- }
-
- .system-note {
- padding: 0;
+ padding: $gl-padding 10px;
}
}
@@ -125,7 +114,7 @@ ul.notes {
.note-awards {
.js-awards-block {
- margin-bottom: 16px;
+ margin-top: 16px;
}
}
@@ -160,14 +149,14 @@ ul.notes {
.system-note {
font-size: 14px;
- padding: 0;
+ padding-left: 0;
clear: both;
@media (min-width: $screen-sm-min) {
margin-left: 65px;
}
- .note-header {
+ .note-header-info {
padding-bottom: 0;
}
@@ -197,11 +186,22 @@ ul.notes {
}
}
- .timeline-content {
- padding: 14px 10px;
+ .timeline-icon {
+ float: left;
+
+ svg {
+ width: 16px;
+ height: 16px;
+ fill: $gray-darkest;
+ position: absolute;
+ left: 0;
+ top: 2px;
+ }
+ }
+ .timeline-content {
@media (min-width: $screen-sm-min) {
- margin-left: 20px;
+ margin-left: 30px;
}
}
@@ -377,7 +377,17 @@ ul.notes {
.note-header-info {
min-width: 0;
- padding-bottom: 5px;
+ padding-bottom: 8px;
+}
+
+.system-note .note-header-info {
+ padding-bottom: 0;
+}
+
+.note-header-author-name {
+ @media (max-width: $screen-xs-max) {
+ display: none;
+ }
}
.note-headline-light {
@@ -582,6 +592,22 @@ ul.notes {
}
}
+.discussion-body,
+.diff-file {
+ .notes .note {
+ padding-left: $gl-padding;
+ padding-right: $gl-padding;
+
+ &.system-note {
+ padding-left: 0;
+
+ @media (min-width: $screen-sm-min) {
+ margin-left: 70px;
+ }
+ }
+ }
+}
+
.diff-file {
.is-over {
.add-diff-note {
@@ -591,17 +617,11 @@ ul.notes {
}
.disabled-comment {
- margin-left: -$gl-padding-top;
- margin-right: -$gl-padding-top;
background-color: $gray-light;
border-radius: $border-radius-base;
border: 1px solid $border-gray-normal;
color: $note-disabled-comment-color;
- line-height: 200px;
-
- .disabled-comment-text {
- line-height: normal;
- }
+ padding: 90px 0;
a {
color: $gl-link-color;
@@ -651,7 +671,7 @@ ul.notes {
.line-resolve-all {
vertical-align: middle;
display: inline-block;
- padding: 6px 10px;
+ padding: 5px 10px 6px;
background-color: $gray-light;
border: 1px solid $border-color;
border-radius: $border-radius-default;
@@ -664,6 +684,10 @@ ul.notes {
.line-resolve-btn {
margin-right: 5px;
+
+ svg {
+ vertical-align: middle;
+ }
}
}
@@ -700,6 +724,10 @@ ul.notes {
}
}
+.line-resolve-text {
+ vertical-align: middle;
+}
+
.discussion-next-btn {
svg {
margin: 0;
@@ -717,9 +745,8 @@ ul.notes {
// Merge request notes in diffs
.diff-file {
// Diff is side by side
- .notes_content.parallel .note-header .note-headline-light {
+ .notes_content.parallel .note-header .note-header-author-name {
display: block;
- position: relative;
}
// Diff is inline
.notes_content .note-header .note-headline-light {
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index f0bf3d4c267..99745019d5a 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -384,10 +384,6 @@ a.deploy-project-label {
}
}
-.last-push-widget {
- margin-top: -1px;
-}
-
.fork-namespaces {
.row {
-webkit-flex-wrap: wrap;
diff --git a/app/controllers/admin/hook_logs_controller.rb b/app/controllers/admin/hook_logs_controller.rb
new file mode 100644
index 00000000000..aa069b89563
--- /dev/null
+++ b/app/controllers/admin/hook_logs_controller.rb
@@ -0,0 +1,29 @@
+class Admin::HookLogsController < Admin::ApplicationController
+ include HooksExecution
+
+ before_action :hook, only: [:show, :retry]
+ before_action :hook_log, only: [:show, :retry]
+
+ respond_to :html
+
+ def show
+ end
+
+ def retry
+ status, message = hook.execute(hook_log.request_data, hook_log.trigger)
+
+ set_hook_execution_notice(status, message)
+
+ redirect_to edit_admin_hook_path(@hook)
+ end
+
+ private
+
+ def hook
+ @hook ||= SystemHook.find(params[:hook_id])
+ end
+
+ def hook_log
+ @hook_log ||= hook.web_hook_logs.find(params[:id])
+ end
+end
diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index ccfe553c89e..b9251e140f8 100644
--- a/app/controllers/admin/hooks_controller.rb
+++ b/app/controllers/admin/hooks_controller.rb
@@ -1,5 +1,7 @@
class Admin::HooksController < Admin::ApplicationController
- before_action :hook, only: :edit
+ include HooksExecution
+
+ before_action :hook_logs, only: :edit
def index
@hooks = SystemHook.all
@@ -36,15 +38,9 @@ class Admin::HooksController < Admin::ApplicationController
end
def test
- data = {
- event_name: "project_create",
- name: "Ruby",
- path: "ruby",
- project_id: 1,
- owner_name: "Someone",
- owner_email: "example@gitlabhq.com"
- }
- hook.execute(data, 'system_hooks')
+ status, message = hook.execute(sample_hook_data, 'system_hooks')
+
+ set_hook_execution_notice(status, message)
redirect_back_or_default
end
@@ -55,6 +51,11 @@ class Admin::HooksController < Admin::ApplicationController
@hook ||= SystemHook.find(params[:id])
end
+ def hook_logs
+ @hook_logs ||=
+ Kaminari.paginate_array(hook.web_hook_logs.order(created_at: :desc)).page(params[:page])
+ end
+
def hook_params
params.require(:hook).permit(
:enable_ssl_verification,
@@ -65,4 +66,15 @@ class Admin::HooksController < Admin::ApplicationController
:url
)
end
+
+ def sample_hook_data
+ {
+ event_name: "project_create",
+ name: "Ruby",
+ path: "ruby",
+ project_id: 1,
+ owner_name: "Someone",
+ owner_email: "example@gitlabhq.com"
+ }
+ end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 8ce9150e4a9..47ce21d238b 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -11,6 +11,7 @@ class ApplicationController < ActionController::Base
include EnforcesTwoFactorAuthentication
before_action :authenticate_user_from_private_token!
+ before_action :authenticate_user_from_rss_token!
before_action :authenticate_user!
before_action :validate_user_service_ticket!
before_action :check_password_expiration
@@ -72,13 +73,20 @@ class ApplicationController < ActionController::Base
user = User.find_by_authentication_token(token) || User.find_by_personal_access_token(token)
- if user && can?(user, :log_in)
- # Notice we are passing store false, so the user is not
- # actually stored in the session and a token is needed
- # for every request. If you want the token to work as a
- # sign in token, you can simply remove store: false.
- sign_in user, store: false
- end
+ sessionless_sign_in(user)
+ end
+
+ # This filter handles authentication for atom request with an rss_token
+ def authenticate_user_from_rss_token!
+ return unless request.format.atom?
+
+ token = params[:rss_token].presence
+
+ return unless token.present?
+
+ user = User.find_by_rss_token(token)
+
+ sessionless_sign_in(user)
end
def log_exception(exception)
@@ -275,11 +283,17 @@ class ApplicationController < ActionController::Base
request.base_url
end
- def set_locale
- Gitlab::I18n.set_locale(current_user)
+ def set_locale(&block)
+ Gitlab::I18n.with_user_locale(current_user, &block)
+ end
- yield
- ensure
- Gitlab::I18n.reset_locale
+ def sessionless_sign_in(user)
+ if user && can?(user, :log_in)
+ # Notice we are passing store false, so the user is not
+ # actually stored in the session and a token is needed
+ # for every request. If you want the token to work as a
+ # sign in token, you can simply remove store: false.
+ sign_in user, store: false
+ end
end
end
diff --git a/app/controllers/concerns/diff_for_path.rb b/app/controllers/concerns/diff_for_path.rb
index 1efa9fe060f..d5388c4cd20 100644
--- a/app/controllers/concerns/diff_for_path.rb
+++ b/app/controllers/concerns/diff_for_path.rb
@@ -8,17 +8,6 @@ module DiffForPath
return render_404 unless diff_file
- diff_commit = commit_for_diff(diff_file)
- blob = diff_file.blob(diff_commit)
-
- locals = {
- diff_file: diff_file,
- diff_commit: diff_commit,
- diff_refs: diffs.diff_refs,
- blob: blob,
- project: project
- }
-
- render json: { html: view_to_html_string('projects/diffs/_content', locals) }
+ render json: { html: view_to_html_string('projects/diffs/_content', diff_file: diff_file) }
end
end
diff --git a/app/controllers/concerns/hooks_execution.rb b/app/controllers/concerns/hooks_execution.rb
new file mode 100644
index 00000000000..846cd60518f
--- /dev/null
+++ b/app/controllers/concerns/hooks_execution.rb
@@ -0,0 +1,15 @@
+module HooksExecution
+ extend ActiveSupport::Concern
+
+ private
+
+ def set_hook_execution_notice(status, message)
+ if status && status >= 200 && status < 400
+ flash[:notice] = "Hook executed successfully: HTTP #{status}"
+ elsif status
+ flash[:alert] = "Hook executed successfully but returned HTTP #{status} #{message}"
+ else
+ flash[:alert] = "Hook execution failed: #{message}"
+ end
+ end
+end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 5a1efcab1a3..3d49ea97591 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -8,7 +8,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = load_projects(params.merge(non_public: true)).page(params[:page])
respond_to do |format|
- format.html { @last_push = current_user.recent_push }
+ format.html
format.atom do
load_events
render layout: false
@@ -25,7 +25,6 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = load_projects(params.merge(starred: true)).
includes(:forked_from_project, :tags).page(params[:page])
- @last_push = current_user.recent_push
@groups = []
respond_to do |format|
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 79d420a32d3..6195121b931 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -9,8 +9,6 @@ class DashboardController < Dashboard::ApplicationController
respond_to :html
def activity
- @last_push = current_user.recent_push
-
respond_to do |format|
format.html
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 965ced4d372..3e921a1b1cb 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -165,7 +165,6 @@ class GroupsController < Groups::ApplicationController
def user_actions
if current_user
- @last_push = current_user.recent_push
@notification_setting = current_user.notification_settings_for(group)
end
end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 57e23cea00e..8cd1c47eb3f 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -40,6 +40,14 @@ class ProfilesController < Profiles::ApplicationController
redirect_to profile_account_path
end
+ def reset_rss_token
+ if current_user.reset_rss_token!
+ flash[:notice] = "RSS token was successfully reset"
+ end
+
+ redirect_to profile_account_path
+ end
+
def audit_log
@events = AuditEvent.where(entity_type: "User", entity_id: current_user.id).
order("created_at DESC").
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index 008d2f5815f..88dd600e5fe 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -51,13 +51,9 @@ class Projects::CompareController < Projects::ApplicationController
if @compare
@commits = @compare.commits
- @start_commit = @compare.start_commit
- @commit = @compare.commit
- @base_commit = @compare.base_commit
-
@diffs = @compare.diffs(diff_options)
- environment_params = @repository.branch_exists?(@head_ref) ? { ref: @head_ref } : { commit: @commit }
+ environment_params = @repository.branch_exists?(@head_ref) ? { ref: @head_ref } : { commit: @compare.commit }
@environment = EnvironmentsFinder.new(@project, current_user, environment_params).execute.last
@diff_notes_disabled = true
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index fd57afbd05f..efe83776834 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -31,6 +31,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
def folder
folder_environments = project.environments.where(environment_type: params[:id])
@environments = folder_environments.with_state(params[:scope] || :available)
+ .order(:name)
respond_to do |format|
format.html
diff --git a/app/controllers/projects/hook_logs_controller.rb b/app/controllers/projects/hook_logs_controller.rb
new file mode 100644
index 00000000000..354f0d6db3a
--- /dev/null
+++ b/app/controllers/projects/hook_logs_controller.rb
@@ -0,0 +1,33 @@
+class Projects::HookLogsController < Projects::ApplicationController
+ include HooksExecution
+
+ before_action :authorize_admin_project!
+
+ before_action :hook, only: [:show, :retry]
+ before_action :hook_log, only: [:show, :retry]
+
+ respond_to :html
+
+ layout 'project_settings'
+
+ def show
+ end
+
+ def retry
+ status, message = hook.execute(hook_log.request_data, hook_log.trigger)
+
+ set_hook_execution_notice(status, message)
+
+ redirect_to edit_namespace_project_hook_path(@project.namespace, @project, @hook)
+ end
+
+ private
+
+ def hook
+ @hook ||= @project.hooks.find(params[:hook_id])
+ end
+
+ def hook_log
+ @hook_log ||= hook.web_hook_logs.find(params[:id])
+ end
+end
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index 86d13a0d222..38bd82841dc 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -1,7 +1,9 @@
class Projects::HooksController < Projects::ApplicationController
+ include HooksExecution
+
# Authorize
before_action :authorize_admin_project!
- before_action :hook, only: :edit
+ before_action :hook_logs, only: :edit
respond_to :html
@@ -34,13 +36,7 @@ class Projects::HooksController < Projects::ApplicationController
if !@project.empty_repo?
status, message = TestHookService.new.execute(hook, current_user)
- if status && status >= 200 && status < 400
- flash[:notice] = "Hook executed successfully: HTTP #{status}"
- elsif status
- flash[:alert] = "Hook executed successfully but returned HTTP #{status} #{message}"
- else
- flash[:alert] = "Hook execution failed: #{message}"
- end
+ set_hook_execution_notice(status, message)
else
flash[:alert] = 'Hook execution failed. Ensure the project has commits.'
end
@@ -60,6 +56,11 @@ class Projects::HooksController < Projects::ApplicationController
@hook ||= @project.hooks.find(params[:id])
end
+ def hook_logs
+ @hook_logs ||=
+ Kaminari.paginate_array(hook.web_hook_logs.order(created_at: :desc)).page(params[:page])
+ end
+
def hook_params
params.require(:hook).permit(
:job_events,
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 0352065998b..314906b5f09 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -14,7 +14,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
]
before_action :validates_merge_request, only: [:show, :diffs, :commits, :pipelines]
before_action :define_show_vars, only: [:diffs, :commits, :conflicts, :conflict_for_path, :builds, :pipelines]
- before_action :define_commit_vars, only: [:diffs]
before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds, :conflicts, :conflict_for_path, :pipelines]
before_action :close_merge_request_without_source_project, only: [:show, :diffs, :commits, :builds, :pipelines]
before_action :check_if_can_be_merged, only: :show
@@ -130,8 +129,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@diff_notes_disabled = true
end
- define_commit_vars
-
render_diff_for_path(@diffs)
end
@@ -500,11 +497,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
end
- def define_commit_vars
- @commit = @merge_request.diff_head_commit
- @base_commit = @merge_request.diff_base_commit || @merge_request.likely_diff_base_commit
- end
-
def define_diff_vars
@merge_request_diff =
if params[:diff_id]
@@ -569,7 +561,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@source_project = merge_request.source_project
@commits = @merge_request.compare_commits.reverse
@commit = @merge_request.diff_head_commit
- @base_commit = @merge_request.diff_base_commit
@note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index 667f4870c7a..2a0b58fae7c 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -74,6 +74,6 @@ class Projects::RefsController < Projects::ApplicationController
private
def validate_ref_id
- return not_found! if params[:id].present? && params[:id] !~ Gitlab::Regex.git_reference_regex
+ return not_found! if params[:id].present? && params[:id] !~ Gitlab::PathRegex.git_reference_regex
end
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 544715d62ea..cc62e1fa99b 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -257,7 +257,7 @@ class ProjectsController < Projects::ApplicationController
#
# pages list order: repository readme, wiki home, issues list, customize workflow
def render_landing_page
- if @project.feature_available?(:repository, current_user)
+ if can?(current_user, :download_code, @project)
return render 'projects/no_repo' unless @project.repository_exists?
render 'projects/empty' if @project.empty_repo?
else
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index d59d51905a6..5b5cdebe919 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -15,16 +15,6 @@ module CommitsHelper
commit_person_link(commit, options.merge(source: :committer))
end
- def image_diff_class(diff)
- if diff.deleted_file
- "deleted"
- elsif diff.new_file
- "added"
- else
- nil
- end
- end
-
def commit_to_html(commit, ref, project)
render 'projects/commits/commit',
commit: commit,
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index 4a06ee653ee..4c4fbdd4d39 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -102,14 +102,14 @@ module DiffHelper
].join(' ').html_safe
end
- def commit_for_diff(diff_file)
- return diff_file.content_commit if diff_file.content_commit
+ def diff_file_blob_raw_path(diff_file)
+ namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.content_sha, diff_file.file_path))
+ end
- if diff_file.deleted_file
- @base_commit || @commit.parent || @commit
- else
- @commit
- end
+ def diff_file_old_blob_raw_path(diff_file)
+ sha = diff_file.old_content_sha
+ return unless sha
+ namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.old_content_sha, diff_file.old_path))
end
def diff_file_html_data(project, diff_file_path, diff_commit_id)
@@ -120,8 +120,8 @@ module DiffHelper
}
end
- def editable_diff?(diff)
- !diff.deleted_file && @merge_request && @merge_request.source_project
+ def editable_diff?(diff_file)
+ !diff_file.deleted_file? && @merge_request && @merge_request.source_project
end
private
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index e5b1e6e8bc7..4e6e6805920 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -69,13 +69,12 @@ module LabelsHelper
end
def render_colored_label(label, label_suffix = '', tooltip: true)
- label_color = label.color || Label::DEFAULT_COLOR
- text_color = text_color_for_bg(label_color)
+ text_color = text_color_for_bg(label.color)
# Intentionally not using content_tag here so that this method can be called
# by LabelReferenceFilter
span = %(<span class="label color-label #{"has-tooltip" if tooltip}" ) +
- %(style="background-color: #{label_color}; color: #{text_color}" ) +
+ %(style="background-color: #{label.color}; color: #{text_color}" ) +
%(title="#{escape_once(label.description)}" data-container="body">) +
%(#{escape_once(label.name)}#{label_suffix}</span>)
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index de959f13713..d36bb4ab074 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -49,7 +49,7 @@ module PreferencesHelper
user_view = current_user.project_view
- if @project.feature_available?(:repository, current_user)
+ if can?(current_user, :download_code, @project)
user_view
elsif user_view == "activity"
"activity"
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 98bbcfaaba5..7b7c03142c4 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -116,6 +116,7 @@ module ProjectsHelper
def last_push_event
return unless current_user
+ return current_user.recent_push unless @project
project_ids = [@project.id]
if fork = current_user.fork_of(@project)
diff --git a/app/helpers/rss_helper.rb b/app/helpers/rss_helper.rb
index ea5d2932ef4..9ac4df88dc3 100644
--- a/app/helpers/rss_helper.rb
+++ b/app/helpers/rss_helper.rb
@@ -1,5 +1,5 @@
module RssHelper
def rss_url_options
- { format: :atom, private_token: current_user.try(:private_token) }
+ { format: :atom, rss_token: current_user.try(:rss_token) }
end
end
diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb
index d889d141101..209bd56b78a 100644
--- a/app/helpers/system_note_helper.rb
+++ b/app/helpers/system_note_helper.rb
@@ -17,7 +17,8 @@ module SystemNoteHelper
'visible' => 'icon_eye',
'milestone' => 'icon_clock_o',
'discussion' => 'icon_comment_o',
- 'moved' => 'icon_arrow_circle_o_right'
+ 'moved' => 'icon_arrow_circle_o_right',
+ 'outdated' => 'icon_edit'
}.freeze
def icon_for_system_note(note)
diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb
index d2980db218a..654468bc7fe 100644
--- a/app/mailers/base_mailer.rb
+++ b/app/mailers/base_mailer.rb
@@ -1,4 +1,6 @@
class BaseMailer < ActionMailer::Base
+ around_action :render_with_default_locale
+
helper ApplicationHelper
helper MarkupHelper
@@ -14,6 +16,10 @@ class BaseMailer < ActionMailer::Base
private
+ def render_with_default_locale(&block)
+ Gitlab::I18n.with_default_locale(&block)
+ end
+
def default_sender_address
address = Mail::Address.new(Gitlab.config.gitlab.email_from)
address.display_name = Gitlab.config.gitlab.email_display_name
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index ffafc678968..fe63728ea23 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -89,6 +89,7 @@ class CommitStatus < ActiveRecord::Base
else
PipelineUpdateWorker.perform_async(pipeline.id)
end
+ ExpireJobCacheWorker.perform_async(commit_status.id)
end
end
end
diff --git a/app/models/concerns/discussion_on_diff.rb b/app/models/concerns/discussion_on_diff.rb
index a7bdf5587b2..eee1a36ac6b 100644
--- a/app/models/concerns/discussion_on_diff.rb
+++ b/app/models/concerns/discussion_on_diff.rb
@@ -47,4 +47,12 @@ module DiscussionOnDiff
prev_lines
end
+
+ def line_code_in_diffs(diff_refs)
+ if active?(diff_refs)
+ line_code
+ elsif diff_refs && created_at_diff?(diff_refs)
+ original_line_code
+ end
+ end
end
diff --git a/app/models/concerns/note_on_diff.rb b/app/models/concerns/note_on_diff.rb
index 6359f7596b1..f734952fa6c 100644
--- a/app/models/concerns/note_on_diff.rb
+++ b/app/models/concerns/note_on_diff.rb
@@ -33,14 +33,4 @@ module NoteOnDiff
def created_at_diff?(diff_refs)
false
end
-
- private
-
- def noteable_diff_refs
- if noteable.respond_to?(:diff_sha_refs)
- noteable.diff_sha_refs
- else
- noteable.diff_refs
- end
- end
end
diff --git a/app/models/diff_discussion.rb b/app/models/diff_discussion.rb
index 14ddd2fcc88..800574d8be0 100644
--- a/app/models/diff_discussion.rb
+++ b/app/models/diff_discussion.rb
@@ -19,21 +19,9 @@ class DiffDiscussion < Discussion
def merge_request_version_params
return unless for_merge_request?
+ return {} if active?
- if active?
- {}
- else
- diff_refs = position.diff_refs
-
- if diff = noteable.merge_request_diff_for(diff_refs)
- { diff_id: diff.id }
- elsif diff = noteable.merge_request_diff_for(diff_refs.head_sha)
- {
- diff_id: diff.id,
- start_sha: diff_refs.start_sha
- }
- end
- end
+ noteable.version_params_for(position.diff_refs)
end
def reply_attributes
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 76c59199afd..2a4cff37566 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -8,6 +8,7 @@ class DiffNote < Note
serialize :original_position, Gitlab::Diff::Position
serialize :position, Gitlab::Diff::Position
+ serialize :change_position, Gitlab::Diff::Position
validates :original_position, presence: true
validates :position, presence: true
@@ -25,7 +26,7 @@ class DiffNote < Note
DiffDiscussion
end
- %i(original_position position).each do |meth|
+ %i(original_position position change_position).each do |meth|
define_method "#{meth}=" do |new_position|
if new_position.is_a?(String)
new_position = JSON.parse(new_position) rescue nil
@@ -36,6 +37,8 @@ class DiffNote < Note
new_position = Gitlab::Diff::Position.new(new_position)
end
+ return if new_position == read_attribute(meth)
+
super(new_position)
end
end
@@ -45,7 +48,7 @@ class DiffNote < Note
end
def diff_line
- @diff_line ||= diff_file.line_for_position(self.original_position) if diff_file
+ @diff_line ||= diff_file&.line_for_position(self.original_position)
end
def for_line?(line)
@@ -60,7 +63,7 @@ class DiffNote < Note
return false unless supported?
return true if for_commit?
- diff_refs ||= noteable_diff_refs
+ diff_refs ||= noteable.diff_refs
self.position.diff_refs == diff_refs
end
@@ -96,7 +99,7 @@ class DiffNote < Note
self.project,
nil,
old_diff_refs: self.position.diff_refs,
- new_diff_refs: noteable_diff_refs,
+ new_diff_refs: noteable.diff_refs,
paths: self.position.paths
).execute(self)
end
diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb
index eef24052a06..40e43c27f91 100644
--- a/app/models/hooks/service_hook.rb
+++ b/app/models/hooks/service_hook.rb
@@ -2,6 +2,6 @@ class ServiceHook < WebHook
belongs_to :service
def execute(data)
- super(data, 'service_hook')
+ WebHookService.new(self, data, 'service_hook').execute
end
end
diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb
index c645805c6da..1584235ab00 100644
--- a/app/models/hooks/system_hook.rb
+++ b/app/models/hooks/system_hook.rb
@@ -3,8 +3,4 @@ class SystemHook < WebHook
default_value_for :push_events, false
default_value_for :repository_update_events, true
-
- def async_execute(data, hook_name)
- Sidekiq::Client.enqueue(SystemHookWorker, id, data, hook_name)
- end
end
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index a165fdc312f..7503f3739c3 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -1,6 +1,5 @@
class WebHook < ActiveRecord::Base
include Sortable
- include HTTParty
default_value_for :push_events, true
default_value_for :issues_events, false
@@ -13,52 +12,18 @@ class WebHook < ActiveRecord::Base
default_value_for :repository_update_events, false
default_value_for :enable_ssl_verification, true
+ has_many :web_hook_logs, dependent: :destroy
+
scope :push_hooks, -> { where(push_events: true) }
scope :tag_push_hooks, -> { where(tag_push_events: true) }
- # HTTParty timeout
- default_timeout Gitlab.config.gitlab.webhook_timeout
-
validates :url, presence: true, url: true
def execute(data, hook_name)
- parsed_url = URI.parse(url)
- if parsed_url.userinfo.blank?
- response = WebHook.post(url,
- body: data.to_json,
- headers: build_headers(hook_name),
- verify: enable_ssl_verification)
- else
- post_url = url.gsub("#{parsed_url.userinfo}@", '')
- auth = {
- username: CGI.unescape(parsed_url.user),
- password: CGI.unescape(parsed_url.password)
- }
- response = WebHook.post(post_url,
- body: data.to_json,
- headers: build_headers(hook_name),
- verify: enable_ssl_verification,
- basic_auth: auth)
- end
-
- [response.code, response.to_s]
- rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
- logger.error("WebHook Error => #{e}")
- [false, e.to_s]
+ WebHookService.new(self, data, hook_name).execute
end
def async_execute(data, hook_name)
- Sidekiq::Client.enqueue(ProjectWebHookWorker, id, data, hook_name)
- end
-
- private
-
- def build_headers(hook_name)
- headers = {
- 'Content-Type' => 'application/json',
- 'X-Gitlab-Event' => hook_name.singularize.titleize
- }
- headers['X-Gitlab-Token'] = token if token.present?
- headers
+ WebHookService.new(self, data, hook_name).async_execute
end
end
diff --git a/app/models/hooks/web_hook_log.rb b/app/models/hooks/web_hook_log.rb
new file mode 100644
index 00000000000..2738b229d84
--- /dev/null
+++ b/app/models/hooks/web_hook_log.rb
@@ -0,0 +1,13 @@
+class WebHookLog < ActiveRecord::Base
+ belongs_to :web_hook
+
+ serialize :request_headers, Hash
+ serialize :request_data, Hash
+ serialize :response_headers, Hash
+
+ validates :web_hook, presence: true
+
+ def success?
+ response_status =~ /^2/
+ end
+end
diff --git a/app/models/label.rb b/app/models/label.rb
index ddddb6bdf8f..074239702f8 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -133,6 +133,10 @@ class Label < ActiveRecord::Base
template
end
+ def color
+ super || DEFAULT_COLOR
+ end
+
def text_color
LabelsHelper.text_color_for_bg(self.color)
end
diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb
index d7c627432d2..ebf8fb92ab5 100644
--- a/app/models/legacy_diff_note.rb
+++ b/app/models/legacy_diff_note.rb
@@ -61,7 +61,7 @@ class LegacyDiffNote < Note
return true if for_commit?
return true unless diff_line
return false unless noteable
- return false if diff_refs && diff_refs != noteable_diff_refs
+ return false if diff_refs && diff_refs != noteable.diff_refs
noteable_diff = find_noteable_diff
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 9be00880438..356af776b8d 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -245,19 +245,6 @@ class MergeRequest < ActiveRecord::Base
end
end
- # MRs created before 8.4 don't store a MergeRequestDiff#base_commit_sha,
- # but we need to get a commit for the "View file @ ..." link by deleted files,
- # so we find the likely one if we can't get the actual one.
- # This will not be the actual base commit if the target branch was merged into
- # the source branch after the merge request was created, but it is good enough
- # for the specific purpose of linking to a commit.
- # It is not good enough for use in `Gitlab::Git::DiffRefs`, which needs the
- # true base commit, so we can't simply have `#diff_base_commit` fall back on
- # this method.
- def likely_diff_base_commit
- first_commit.try(:parent) || first_commit
- end
-
def diff_start_commit
if persisted?
merge_request_diff.start_commit
@@ -322,21 +309,14 @@ class MergeRequest < ActiveRecord::Base
end
def diff_refs
- return unless diff_start_commit || diff_base_commit
-
- Gitlab::Diff::DiffRefs.new(
- base_sha: diff_base_sha,
- start_sha: diff_start_sha,
- head_sha: diff_head_sha
- )
- end
-
- # Return diff_refs instance trying to not touch the git repository
- def diff_sha_refs
- if merge_request_diff && merge_request_diff.diff_refs_by_sha?
+ if persisted?
merge_request_diff.diff_refs
else
- diff_refs
+ Gitlab::Diff::DiffRefs.new(
+ base_sha: diff_base_sha,
+ start_sha: diff_start_sha,
+ head_sha: diff_head_sha
+ )
end
end
@@ -416,13 +396,24 @@ class MergeRequest < ActiveRecord::Base
@merge_request_diffs_by_diff_refs_or_sha[diff_refs_or_sha]
end
+ def version_params_for(diff_refs)
+ if diff = merge_request_diff_for(diff_refs)
+ { diff_id: diff.id }
+ elsif diff = merge_request_diff_for(diff_refs.head_sha)
+ {
+ diff_id: diff.id,
+ start_sha: diff_refs.start_sha
+ }
+ end
+ end
+
def reload_diff_if_branch_changed
if source_branch_changed? || target_branch_changed?
reload_diff
end
end
- def reload_diff
+ def reload_diff(current_user = nil)
return unless open?
old_diff_refs = self.diff_refs
@@ -432,7 +423,8 @@ class MergeRequest < ActiveRecord::Base
update_diff_notes_positions(
old_diff_refs: old_diff_refs,
- new_diff_refs: new_diff_refs
+ new_diff_refs: new_diff_refs,
+ current_user: current_user
)
end
@@ -858,10 +850,10 @@ class MergeRequest < ActiveRecord::Base
end
def has_complete_diff_refs?
- diff_sha_refs && diff_sha_refs.complete?
+ diff_refs && diff_refs.complete?
end
- def update_diff_notes_positions(old_diff_refs:, new_diff_refs:)
+ def update_diff_notes_positions(old_diff_refs:, new_diff_refs:, current_user: nil)
return unless has_complete_diff_refs?
return if new_diff_refs == old_diff_refs
@@ -875,7 +867,7 @@ class MergeRequest < ActiveRecord::Base
service = Notes::DiffPositionUpdateService.new(
self.project,
- nil,
+ current_user,
old_diff_refs: old_diff_refs,
new_diff_refs: new_diff_refs,
paths: paths
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index f0a3c30ea74..1bd61c1d465 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -150,6 +150,29 @@ class MergeRequestDiff < ActiveRecord::Base
)
end
+ # MRs created before 8.4 don't store their true diff refs (start and base),
+ # but we need to get a commit SHA for the "View file @ ..." link by a file,
+ # so we use an approximation of the diff refs if we can't get the actual one.
+ #
+ # These will not be the actual diff refs if the target branch was merged into
+ # the source branch after the merge request was created, but it is good enough
+ # for the specific purpose of linking to a commit.
+ #
+ # It is not good enough for highlighting diffs, so we can't simply pass
+ # these as `diff_refs.`
+ def fallback_diff_refs
+ real_refs = diff_refs
+ return real_refs if real_refs
+
+ likely_base_commit_sha = (first_commit&.parent || first_commit)&.sha
+
+ Gitlab::Diff::DiffRefs.new(
+ base_sha: likely_base_commit_sha,
+ start_sha: safe_start_commit_sha,
+ head_sha: head_commit_sha
+ )
+ end
+
def diff_refs_by_sha?
base_commit_sha? && head_commit_sha? && start_commit_sha?
end
@@ -175,12 +198,11 @@ class MergeRequestDiff < ActiveRecord::Base
self == merge_request.merge_request_diff
end
- def compare_with(sha, straight: true)
+ def compare_with(sha)
# When compare merge request versions we want diff A..B instead of A...B
# so we handle cases when user does squash and rebase of the commits between versions.
# For this reason we set straight to true by default.
- CompareService.new(project, head_commit_sha)
- .execute(project, sha, straight: straight)
+ CompareService.new(project, head_commit_sha).execute(project, sha, straight: true)
end
def commits_count
diff --git a/app/models/note.rb b/app/models/note.rb
index 46d0a4f159f..60257aac93b 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -124,13 +124,12 @@ class Note < ActiveRecord::Base
groups = {}
diff_notes.fresh.discussions.each do |discussion|
- if discussion.active?(diff_refs)
- discussions = groups[discussion.line_code] ||= []
- elsif diff_refs && discussion.created_at_diff?(diff_refs)
- discussions = groups[discussion.original_line_code] ||= []
- end
+ line_code = discussion.line_code_in_diffs(diff_refs)
- discussions << discussion if discussions
+ if line_code
+ discussions = groups[line_code] ||= []
+ discussions << discussion
+ end
end
groups
diff --git a/app/models/project.rb b/app/models/project.rb
index 65745fd6d37..29af57d7664 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -205,8 +205,8 @@ class Project < ActiveRecord::Base
presence: true,
dynamic_path: true,
length: { maximum: 255 },
- format: { with: Gitlab::Regex.project_path_regex,
- message: Gitlab::Regex.project_path_regex_message },
+ format: { with: Gitlab::PathRegex.project_path_format_regex,
+ message: Gitlab::PathRegex.project_path_format_message },
uniqueness: { scope: :namespace_id }
validates :namespace, presence: true
@@ -380,11 +380,9 @@ class Project < ActiveRecord::Base
end
def reference_pattern
- name_pattern = Gitlab::Regex::FULL_NAMESPACE_REGEX_STR
-
%r{
- ((?<namespace>#{name_pattern})\/)?
- (?<project>#{name_pattern})
+ ((?<namespace>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})\/)?
+ (?<project>#{Gitlab::PathRegex::PROJECT_PATH_FORMAT_REGEX})
}x
end
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index 50435b67eda..eddf308eae3 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -76,7 +76,7 @@ class IssueTrackerService < Service
message = "#{self.type} received response #{response.code} when attempting to connect to #{self.project_url}"
result = true
end
- rescue HTTParty::Error, Timeout::Error, SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED => error
+ rescue HTTParty::Error, Timeout::Error, SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, OpenSSL::SSL::SSLError => error
message = "#{self.type} had an error when trying to connect to #{self.project_url}: #{error.message}"
end
Rails.logger.info(message)
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index f388773efee..fe869623833 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -2,9 +2,10 @@ class JiraService < IssueTrackerService
include Gitlab::Routing.url_helpers
validates :url, url: true, presence: true, if: :activated?
+ validates :api_url, url: true, allow_blank: true
validates :project_key, presence: true, if: :activated?
- prop_accessor :username, :password, :url, :project_key,
+ prop_accessor :username, :password, :url, :api_url, :project_key,
:jira_issue_transition_id, :title, :description
before_update :reset_password
@@ -25,20 +26,18 @@ class JiraService < IssueTrackerService
super do
self.properties = {
title: issues_tracker['title'],
- url: issues_tracker['url']
+ url: issues_tracker['url'],
+ api_url: issues_tracker['api_url']
}
end
end
def reset_password
- # don't reset the password if a new one is provided
- if url_changed? && !password_touched?
- self.password = nil
- end
+ self.password = nil if reset_password?
end
def options
- url = URI.parse(self.url)
+ url = URI.parse(client_url)
{
username: self.username,
@@ -87,7 +86,8 @@ class JiraService < IssueTrackerService
def fields
[
- { type: 'text', name: 'url', title: 'URL', placeholder: 'https://jira.example.com' },
+ { type: 'text', name: 'url', title: 'Web URL', placeholder: 'https://jira.example.com' },
+ { type: 'text', name: 'api_url', title: 'JIRA API URL', placeholder: 'If different from Web URL' },
{ type: 'text', name: 'project_key', placeholder: 'Project Key' },
{ type: 'text', name: 'username', placeholder: '' },
{ type: 'password', name: 'password', placeholder: '' },
@@ -186,7 +186,7 @@ class JiraService < IssueTrackerService
end
def test_settings
- return unless url.present?
+ return unless client_url.present?
# Test settings by getting the project
jira_request { jira_project.present? }
end
@@ -236,13 +236,13 @@ class JiraService < IssueTrackerService
end
def send_message(issue, message, remote_link_props)
- return unless url.present?
+ return unless client_url.present?
jira_request do
if issue.comments.build.save!(body: message)
remote_link = issue.remotelink.build
remote_link.save!(remote_link_props)
- result_message = "#{self.class.name} SUCCESS: Successfully posted to #{url}."
+ result_message = "#{self.class.name} SUCCESS: Successfully posted to #{client_url}."
end
Rails.logger.info(result_message)
@@ -294,8 +294,21 @@ class JiraService < IssueTrackerService
def jira_request
yield
- rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, URI::InvalidURIError, JIRA::HTTPError => e
- Rails.logger.info "#{self.class.name} Send message ERROR: #{url} - #{e.message}"
+ rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, URI::InvalidURIError, JIRA::HTTPError, OpenSSL::SSL::SSLError => e
+ Rails.logger.info "#{self.class.name} Send message ERROR: #{client_url} - #{e.message}"
nil
end
+
+ def client_url
+ api_url.present? ? api_url : url
+ end
+
+ def reset_password?
+ # don't reset the password if a new one is provided
+ return false if password_touched?
+ return true if api_url_changed?
+ return false if api_url.present?
+
+ url_changed?
+ end
end
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index b2494a0be6e..8977a7cdafe 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -77,6 +77,14 @@ class KubernetesService < DeploymentService
]
end
+ def actual_namespace
+ if namespace.present?
+ namespace
+ else
+ default_namespace
+ end
+ end
+
# Check we can connect to the Kubernetes API
def test(*args)
kubeclient = build_kubeclient!
@@ -91,7 +99,7 @@ class KubernetesService < DeploymentService
variables = [
{ key: 'KUBE_URL', value: api_url, public: true },
{ key: 'KUBE_TOKEN', value: token, public: false },
- { key: 'KUBE_NAMESPACE', value: namespace_variable, public: true }
+ { key: 'KUBE_NAMESPACE', value: actual_namespace, public: true }
]
if ca_pem.present?
@@ -110,7 +118,7 @@ class KubernetesService < DeploymentService
with_reactive_cache do |data|
pods = data.fetch(:pods, nil)
filter_pods(pods, app: environment.slug).
- flat_map { |pod| terminals_for_pod(api_url, namespace, pod) }.
+ flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) }.
each { |terminal| add_terminal_auth(terminal, terminal_auth) }
end
end
@@ -124,7 +132,7 @@ class KubernetesService < DeploymentService
# Store as hashes, rather than as third-party types
pods = begin
- kubeclient.get_pods(namespace: namespace).as_json
+ kubeclient.get_pods(namespace: actual_namespace).as_json
rescue KubeException => err
raise err unless err.error_code == 404
[]
@@ -142,20 +150,12 @@ class KubernetesService < DeploymentService
default_namespace || TEMPLATE_PLACEHOLDER
end
- def namespace_variable
- if namespace.present?
- namespace
- else
- default_namespace
- end
- end
-
def default_namespace
"#{project.path}-#{project.id}" if project.present?
end
def build_kubeclient!(api_path: 'api', api_version: 'v1')
- raise "Incomplete settings" unless api_url && namespace && token
+ raise "Incomplete settings" unless api_url && actual_namespace && token
::Kubeclient::Client.new(
join_api_url(api_path),
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index b44f4fe000c..414c95f7705 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -2,6 +2,7 @@ class SystemNoteMetadata < ActiveRecord::Base
ICON_TYPES = %w[
commit description merge confidential visible label assignee cross_reference
title time_tracking branch milestone discussion task moved opened closed merged
+ outdated
].freeze
validates :note, presence: true
diff --git a/app/models/user.rb b/app/models/user.rb
index 837ab78228b..625ba90002b 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -15,6 +15,7 @@ class User < ActiveRecord::Base
add_authentication_token_field :authentication_token
add_authentication_token_field :incoming_email_token
+ add_authentication_token_field :rss_token
default_value_for :admin, false
default_value_for(:external) { current_application_settings.user_default_external }
@@ -367,7 +368,7 @@ class User < ActiveRecord::Base
def reference_pattern
%r{
#{Regexp.escape(reference_prefix)}
- (?<user>#{Gitlab::Regex::FULL_NAMESPACE_REGEX_STR})
+ (?<user>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})
}x
end
@@ -918,13 +919,13 @@ class User < ActiveRecord::Base
end
def assigned_open_merge_requests_count(force: false)
- Rails.cache.fetch(['users', id, 'assigned_open_merge_requests_count'], force: force) do
+ Rails.cache.fetch(['users', id, 'assigned_open_merge_requests_count'], force: force, expires_in: 20.minutes) do
MergeRequestsFinder.new(self, assignee_id: self.id, state: 'opened').execute.count
end
end
def assigned_open_issues_count(force: false)
- Rails.cache.fetch(['users', id, 'assigned_open_issues_count'], force: force) do
+ Rails.cache.fetch(['users', id, 'assigned_open_issues_count'], force: force, expires_in: 20.minutes) do
IssuesFinder.new(self, assignee_id: self.id, state: 'opened').execute.count
end
end
@@ -1004,6 +1005,13 @@ class User < ActiveRecord::Base
save
end
+ # each existing user needs to have an `rss_token`.
+ # we do this on read since migrating all existing users is not a feasible
+ # solution.
+ def rss_token
+ ensure_rss_token!
+ end
+
protected
# override, from Devise::Validatable
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
index f1030912c68..85c616ca576 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -28,6 +28,7 @@ module Issues
notification_service.close_issue(issue, current_user) if notifications
todo_service.close_issue(issue, current_user)
execute_hooks(issue, 'close')
+ invalidate_cache_counts(issue.assignees, issue)
end
issue
diff --git a/app/services/issues/reopen_service.rb b/app/services/issues/reopen_service.rb
index 40fbe354492..80ea6312768 100644
--- a/app/services/issues/reopen_service.rb
+++ b/app/services/issues/reopen_service.rb
@@ -8,6 +8,7 @@ module Issues
create_note(issue)
notification_service.reopen_issue(issue, current_user)
execute_hooks(issue, 'reopen')
+ invalidate_cache_counts(issue.assignees, issue)
end
issue
diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb
index f2053bda83a..2ffc989ed71 100644
--- a/app/services/merge_requests/close_service.rb
+++ b/app/services/merge_requests/close_service.rb
@@ -13,6 +13,7 @@ module MergeRequests
notification_service.close_mr(merge_request, current_user)
todo_service.close_merge_request(merge_request, current_user)
execute_hooks(merge_request, 'close')
+ invalidate_cache_counts(merge_request.assignees, merge_request)
end
merge_request
diff --git a/app/services/merge_requests/post_merge_service.rb b/app/services/merge_requests/post_merge_service.rb
index e8fb1b59752..f0d998731d7 100644
--- a/app/services/merge_requests/post_merge_service.rb
+++ b/app/services/merge_requests/post_merge_service.rb
@@ -13,6 +13,7 @@ module MergeRequests
create_note(merge_request)
notification_service.merge_mr(merge_request, current_user)
execute_hooks(merge_request, 'merge')
+ invalidate_cache_counts(merge_request.assignees, merge_request)
end
private
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 1131d6f4913..81d217929d5 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -66,12 +66,12 @@ module MergeRequests
filter_merge_requests(merge_requests).each do |merge_request|
if merge_request.source_branch == @branch_name || force_push?
- merge_request.reload_diff
+ merge_request.reload_diff(current_user)
else
mr_commit_ids = merge_request.commits_sha
push_commit_ids = @commits.map(&:id)
matches = mr_commit_ids & push_commit_ids
- merge_request.reload_diff if matches.any?
+ merge_request.reload_diff(current_user) if matches.any?
end
merge_request.mark_as_unchecked
diff --git a/app/services/merge_requests/reopen_service.rb b/app/services/merge_requests/reopen_service.rb
index fadcce5d9b6..f2fddf7f345 100644
--- a/app/services/merge_requests/reopen_service.rb
+++ b/app/services/merge_requests/reopen_service.rb
@@ -8,8 +8,9 @@ module MergeRequests
create_note(merge_request)
notification_service.reopen_mr(merge_request, current_user)
execute_hooks(merge_request, 'reopen')
- merge_request.reload_diff
+ merge_request.reload_diff(current_user)
merge_request.mark_as_unchecked
+ invalidate_cache_counts(merge_request.assignees, merge_request)
end
merge_request
diff --git a/app/services/notes/diff_position_update_service.rb b/app/services/notes/diff_position_update_service.rb
index 0cb731f5bc3..eff7b287269 100644
--- a/app/services/notes/diff_position_update_service.rb
+++ b/app/services/notes/diff_position_update_service.rb
@@ -1,26 +1,29 @@
module Notes
class DiffPositionUpdateService < BaseService
def execute(note)
- new_position = tracer.trace(note.position)
+ results = tracer.trace(note.position)
+ return unless results
- # Don't update the position if the type doesn't match, since that means
- # the diff line commented on was changed, and the comment is now outdated
- old_position = note.position
- if new_position &&
- new_position != old_position &&
- new_position.type == old_position.type
+ position = results[:position]
+ outdated = results[:outdated]
- note.position = new_position
- end
+ if outdated
+ note.change_position = position
- note
+ if note.persisted? && current_user
+ SystemNoteService.diff_discussion_outdated(note.to_discussion, project, current_user, position)
+ end
+ else
+ note.position = position
+ note.change_position = nil
+ end
end
private
def tracer
@tracer ||= Gitlab::Diff::PositionTracer.new(
- repository: project.repository,
+ project: project,
old_diff_refs: params[:old_diff_refs],
new_diff_refs: params[:new_diff_refs],
paths: params[:paths]
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index da6e6acd4a7..1c24b27a870 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -12,12 +12,13 @@ module Projects
TransferError = Class.new(StandardError)
def execute(new_namespace)
- if allowed_transfer?(current_user, project, new_namespace)
- transfer(project, new_namespace)
- else
- project.errors.add(:new_namespace, 'is invalid')
- false
+ if new_namespace.blank?
+ raise TransferError, 'Please select a new namespace for your project.'
end
+ unless allowed_transfer?(current_user, project, new_namespace)
+ raise TransferError, 'Transfer failed, please contact an admin.'
+ end
+ transfer(project, new_namespace)
rescue Projects::TransferService::TransferError => ex
project.reload
project.errors.add(:new_namespace, ex.message)
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 93bf1fb1615..0837c07e6aa 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -258,7 +258,7 @@ module SystemNoteService
create_note(NoteSummary.new(noteable, project, author, body, action: 'title'))
end
- def self.resolve_all_discussions(merge_request, project, author)
+ def resolve_all_discussions(merge_request, project, author)
body = "resolved all discussions"
create_note(NoteSummary.new(merge_request, project, author, body, action: 'discussion'))
@@ -274,6 +274,28 @@ module SystemNoteService
note
end
+ def diff_discussion_outdated(discussion, project, author, change_position)
+ merge_request = discussion.noteable
+ diff_refs = change_position.diff_refs
+ version_index = merge_request.merge_request_diffs.viewable.count
+
+ body = "changed this line in"
+ if version_params = merge_request.version_params_for(diff_refs)
+ line_code = change_position.line_code(project.repository)
+ url = url_helpers.diffs_namespace_project_merge_request_url(project.namespace, project, merge_request, version_params.merge(anchor: line_code))
+
+ body << " [version #{version_index} of the diff](#{url})"
+ else
+ body << " version #{version_index} of the diff"
+ end
+
+ note_attributes = discussion.reply_attributes.merge(project: project, author: author, note: body)
+ note = Note.create(note_attributes.merge(system: true))
+ note.system_note_metadata = SystemNoteMetadata.new(action: 'outdated')
+
+ note
+ end
+
# Called when the title of a Noteable is changed
#
# noteable - Noteable object that responds to `title`
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
new file mode 100644
index 00000000000..4241b912d5b
--- /dev/null
+++ b/app/services/web_hook_service.rb
@@ -0,0 +1,120 @@
+class WebHookService
+ class InternalErrorResponse
+ attr_reader :body, :headers, :code
+
+ def initialize
+ @headers = HTTParty::Response::Headers.new({})
+ @body = ''
+ @code = 'internal error'
+ end
+ end
+
+ include HTTParty
+
+ # HTTParty timeout
+ default_timeout Gitlab.config.gitlab.webhook_timeout
+
+ attr_accessor :hook, :data, :hook_name
+
+ def initialize(hook, data, hook_name)
+ @hook = hook
+ @data = data
+ @hook_name = hook_name
+ end
+
+ def execute
+ start_time = Time.now
+
+ response = if parsed_url.userinfo.blank?
+ make_request(hook.url)
+ else
+ make_request_with_auth
+ end
+
+ log_execution(
+ trigger: hook_name,
+ url: hook.url,
+ request_data: data,
+ response: response,
+ execution_duration: Time.now - start_time
+ )
+
+ [response.code, response.to_s]
+ rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
+ log_execution(
+ trigger: hook_name,
+ url: hook.url,
+ request_data: data,
+ response: InternalErrorResponse.new,
+ execution_duration: Time.now - start_time,
+ error_message: e.to_s
+ )
+
+ Rails.logger.error("WebHook Error => #{e}")
+
+ [nil, e.to_s]
+ end
+
+ def async_execute
+ Sidekiq::Client.enqueue(WebHookWorker, hook.id, data, hook_name)
+ end
+
+ private
+
+ def parsed_url
+ @parsed_url ||= URI.parse(hook.url)
+ end
+
+ def make_request(url, basic_auth = false)
+ self.class.post(url,
+ body: data.to_json,
+ headers: build_headers(hook_name),
+ verify: hook.enable_ssl_verification,
+ basic_auth: basic_auth)
+ end
+
+ def make_request_with_auth
+ post_url = hook.url.gsub("#{parsed_url.userinfo}@", '')
+ basic_auth = {
+ username: CGI.unescape(parsed_url.user),
+ password: CGI.unescape(parsed_url.password)
+ }
+ make_request(post_url, basic_auth)
+ end
+
+ def log_execution(trigger:, url:, request_data:, response:, execution_duration:, error_message: nil)
+ # logging for ServiceHook's is not available
+ return if hook.is_a?(ServiceHook)
+
+ WebHookLog.create(
+ web_hook: hook,
+ trigger: trigger,
+ url: url,
+ execution_duration: execution_duration,
+ request_headers: build_headers(hook_name),
+ request_data: request_data,
+ response_headers: format_response_headers(response),
+ response_body: response.body,
+ response_status: response.code,
+ internal_error_message: error_message
+ )
+ end
+
+ def build_headers(hook_name)
+ @headers ||= begin
+ {
+ 'Content-Type' => 'application/json',
+ 'X-Gitlab-Event' => hook_name.singularize.titleize
+ }.tap do |hash|
+ hash['X-Gitlab-Token'] = hook.token if hook.token.present?
+ end
+ end
+ end
+
+ # Make response headers more stylish
+ # Net::HTTPHeader has downcased hash with arrays: { 'content-type' => ['text/html; charset=utf-8'] }
+ # This method format response to capitalized hash with strings: { 'Content-Type' => 'text/html; charset=utf-8' }
+ def format_response_headers(response)
+ response.headers.each_capitalized.to_h
+ end
+end
diff --git a/app/validators/dynamic_path_validator.rb b/app/validators/dynamic_path_validator.rb
index d992b0c3725..6819886ebf4 100644
--- a/app/validators/dynamic_path_validator.rb
+++ b/app/validators/dynamic_path_validator.rb
@@ -3,212 +3,45 @@
# Custom validator for GitLab path values.
# These paths are assigned to `Namespace` (& `Group` as a subclass) & `Project`
#
-# Values are checked for formatting and exclusion from a list of reserved path
+# Values are checked for formatting and exclusion from a list of illegal path
# names.
class DynamicPathValidator < ActiveModel::EachValidator
- # All routes that appear on the top level must be listed here.
- # This will make sure that groups cannot be created with these names
- # as these routes would be masked by the paths already in place.
- #
- # Example:
- # /api/api-project
- #
- # the path `api` shouldn't be allowed because it would be masked by `api/*`
- #
- TOP_LEVEL_ROUTES = %w[
- -
- .well-known
- abuse_reports
- admin
- all
- api
- assets
- autocomplete
- ci
- dashboard
- explore
- files
- groups
- health_check
- help
- hooks
- import
- invites
- issues
- jwt
- koding
- member
- merge_requests
- new
- notes
- notification_settings
- oauth
- profile
- projects
- public
- repository
- robots.txt
- s
- search
- sent_notifications
- services
- snippets
- teams
- u
- unicorn_test
- unsubscribes
- uploads
- users
- ].freeze
-
- # This list should contain all words following `/*namespace_id/:project_id` in
- # routes that contain a second wildcard.
- #
- # Example:
- # /*namespace_id/:project_id/badges/*ref/build
- #
- # If `badges` was allowed as a project/group name, we would not be able to access the
- # `badges` route for those projects:
- #
- # Consider a namespace with path `foo/bar` and a project called `badges`.
- # The route to the build badge would then be `/foo/bar/badges/badges/master/build.svg`
- #
- # When accessing this path the route would be matched to the `badges` path
- # with the following params:
- # - namespace_id: `foo`
- # - project_id: `bar`
- # - ref: `badges/master`
- #
- # Failing to find the project, this would result in a 404.
- #
- # By rejecting `badges` the router can _count_ on the fact that `badges` will
- # be preceded by the `namespace/project`.
- WILDCARD_ROUTES = %w[
- badges
- blame
- blob
- builds
- commits
- create
- create_dir
- edit
- environments/folders
- files
- find_file
- gitlab-lfs/objects
- info/lfs/objects
- new
- preview
- raw
- refs
- tree
- update
- wikis
- ].freeze
-
- # These are all the paths that follow `/groups/*id/ or `/groups/*group_id`
- # We need to reject these because we have a `/groups/*id` page that is the same
- # as the `/*id`.
- #
- # If we would allow a subgroup to be created with the name `activity` then
- # this group would not be accessible through `/groups/parent/activity` since
- # this would map to the activity-page of it's parent.
- GROUP_ROUTES = %w[
- activity
- analytics
- audit_events
- avatar
- edit
- group_members
- hooks
- issues
- labels
- ldap
- ldap_group_links
- merge_requests
- milestones
- notification_setting
- pipeline_quota
- projects
- subgroups
- ].freeze
-
- CHILD_ROUTES = (WILDCARD_ROUTES | GROUP_ROUTES).freeze
-
- def self.without_reserved_wildcard_paths_regex
- @without_reserved_wildcard_paths_regex ||= regex_excluding_child_paths(WILDCARD_ROUTES)
- end
-
- def self.without_reserved_child_paths_regex
- @without_reserved_child_paths_regex ||= regex_excluding_child_paths(CHILD_ROUTES)
- end
-
- # This is used to validate a full path.
- # It doesn't match paths
- # - Starting with one of the top level words
- # - Containing one of the child level words in the middle of a path
- def self.regex_excluding_child_paths(child_routes)
- reserved_top_level_words = Regexp.union(TOP_LEVEL_ROUTES)
- not_starting_in_reserved_word = %r{\A/?(?!(#{reserved_top_level_words})(/|\z))}
-
- reserved_child_level_words = Regexp.union(child_routes)
- not_containing_reserved_child = %r{(?!\S+/(#{reserved_child_level_words})(/|\z))}
-
- %r{#{not_starting_in_reserved_word}
- #{not_containing_reserved_child}
- #{Gitlab::Regex.full_namespace_regex}}x
- end
-
- def self.valid?(path)
- path =~ Gitlab::Regex.full_namespace_regex && !full_path_reserved?(path)
- end
-
- def self.full_path_reserved?(path)
- path = path.to_s.downcase
- _project_part, namespace_parts = path.reverse.split('/', 2).map(&:reverse)
-
- wildcard_reserved?(path) || child_reserved?(namespace_parts)
- end
-
- def self.child_reserved?(path)
- return false unless path
-
- path !~ without_reserved_child_paths_regex
- end
+ class << self
+ def valid_user_path?(path)
+ "#{path}/" =~ Gitlab::PathRegex.root_namespace_path_regex
+ end
- def self.wildcard_reserved?(path)
- return false unless path
+ def valid_group_path?(path)
+ "#{path}/" =~ Gitlab::PathRegex.full_namespace_path_regex
+ end
- path !~ without_reserved_wildcard_paths_regex
+ def valid_project_path?(path)
+ "#{path}/" =~ Gitlab::PathRegex.full_project_path_regex
+ end
end
- delegate :full_path_reserved?,
- :child_reserved?,
- to: :class
-
- def path_reserved_for_record?(record, value)
+ def path_valid_for_record?(record, value)
full_path = record.respond_to?(:full_path) ? record.full_path : value
- # For group paths the entire path cannot contain a reserved child word
- # The path doesn't contain the last `_project_part` so we need to validate
- # if the entire path.
- # Example:
- # A *group* with full path `parent/activity` is reserved.
- # A *project* with full path `parent/activity` is allowed.
- if record.is_a? Group
- child_reserved?(full_path)
- else
- full_path_reserved?(full_path)
+ return true unless full_path
+
+ case record
+ when Project
+ self.class.valid_project_path?(full_path)
+ when Group
+ self.class.valid_group_path?(full_path)
+ else # User or non-Group Namespace
+ self.class.valid_user_path?(full_path)
end
end
def validate_each(record, attribute, value)
- unless value =~ Gitlab::Regex.namespace_regex
- record.errors.add(attribute, Gitlab::Regex.namespace_regex_message)
+ unless value =~ Gitlab::PathRegex.namespace_format_regex
+ record.errors.add(attribute, Gitlab::PathRegex.namespace_format_message)
return
end
- if path_reserved_for_record?(record, value)
+ unless path_valid_for_record?(record, value)
record.errors.add(attribute, "#{value} is a reserved name")
end
end
diff --git a/app/views/admin/hook_logs/_index.html.haml b/app/views/admin/hook_logs/_index.html.haml
new file mode 100644
index 00000000000..7dd9943190f
--- /dev/null
+++ b/app/views/admin/hook_logs/_index.html.haml
@@ -0,0 +1,37 @@
+.row.prepend-top-default.append-bottom-default
+ .col-lg-3
+ %h4.prepend-top-0
+ Recent Deliveries
+ %p When an event in GitLab triggers a webhook, you can use the request details to figure out if something went wrong.
+ .col-lg-9
+ - if hook_logs.any?
+ %table.table
+ %thead
+ %tr
+ %th Status
+ %th Trigger
+ %th URL
+ %th Elapsed time
+ %th Request time
+ %th
+ - hook_logs.each do |hook_log|
+ %tr
+ %td
+ = render partial: 'shared/hook_logs/status_label', locals: { hook_log: hook_log }
+ %td.hidden-xs
+ %span.label.label-gray.deploy-project-label
+ = hook_log.trigger.singularize.titleize
+ %td
+ = truncate(hook_log.url, length: 50)
+ %td.light
+ #{number_with_precision(hook_log.execution_duration, precision: 2)} ms
+ %td.light
+ = time_ago_with_tooltip(hook_log.created_at)
+ %td
+ = link_to 'View details', admin_hook_hook_log_path(hook, hook_log)
+
+ = paginate hook_logs, theme: 'gitlab'
+
+ - else
+ .settings-message.text-center
+ You don't have any webhooks deliveries
diff --git a/app/views/admin/hook_logs/show.html.haml b/app/views/admin/hook_logs/show.html.haml
new file mode 100644
index 00000000000..56127bacda2
--- /dev/null
+++ b/app/views/admin/hook_logs/show.html.haml
@@ -0,0 +1,10 @@
+- page_title 'Request details'
+%h3.page-title
+ Request details
+
+%hr
+
+= link_to 'Resend Request', retry_admin_hook_hook_log_path(@hook, @hook_log), class: "btn btn-default pull-right prepend-left-10"
+
+= render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log }
+
diff --git a/app/views/admin/hooks/edit.html.haml b/app/views/admin/hooks/edit.html.haml
index 0777f5e2629..0e35a1905bf 100644
--- a/app/views/admin/hooks/edit.html.haml
+++ b/app/views/admin/hooks/edit.html.haml
@@ -12,3 +12,9 @@
= render partial: 'form', locals: { form: f, hook: @hook }
.form-actions
= f.submit 'Save changes', class: 'btn btn-create'
+ = link_to 'Test hook', test_admin_hook_path(@hook), class: 'btn btn-default'
+ = link_to 'Remove', admin_hook_path(@hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' }
+
+%hr
+
+= render partial: 'admin/hook_logs/index', locals: { hook: @hook, hook_logs: @hook_logs }
diff --git a/app/views/admin/requests_profiles/index.html.haml b/app/views/admin/requests_profiles/index.html.haml
index ae918086a57..c7b63d9de98 100644
--- a/app/views/admin/requests_profiles/index.html.haml
+++ b/app/views/admin/requests_profiles/index.html.haml
@@ -20,7 +20,7 @@
%ul.content-list
- profiles.each do |profile|
%li
- = link_to profile.time.to_s(:long), admin_requests_profile_path(profile), data: {no_turbolink: true}
+ = link_to profile.time.to_s(:long), admin_requests_profile_path(profile)
- else
%p
No profiles found
diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml
index e1b270a08c2..a676eba2aee 100644
--- a/app/views/dashboard/_activities.html.haml
+++ b/app/views/dashboard/_activities.html.haml
@@ -1,6 +1,3 @@
-.hidden-xs
- = render "events/event_last_push", event: @last_push
-
.nav-block.activities
.controls
= link_to dashboard_projects_path(rss_url_options), class: 'btn rss-btn has-tooltip', title: 'Subscribe' do
diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml
index 190ad4b40a5..f893c3e1675 100644
--- a/app/views/dashboard/activity.html.haml
+++ b/app/views/dashboard/activity.html.haml
@@ -1,10 +1,16 @@
+- @no_container = true
+
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
- page_title "Activity"
- header_title "Activity", activity_dashboard_path
-= render 'dashboard/activity_head'
+.hidden-xs
+ = render "projects/last_push"
+
+%div{ class: container_class }
+ = render 'dashboard/activity_head'
-%section.activities
- = render 'activities'
+ %section.activities
+ = render 'activities'
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 596499230f9..3b2a555a143 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -1,19 +1,21 @@
+- @no_container = true
+
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
- page_title "Projects"
- header_title "Projects", dashboard_projects_path
-- unless show_user_callout?
- = render 'shared/user_callout'
+= render "projects/last_push"
-- if @projects.any? || params[:name]
- = render 'dashboard/projects_head'
+%div{ class: container_class }
+ - unless show_user_callout?
+ = render 'shared/user_callout'
-- if @last_push
- = render "events/event_last_push", event: @last_push
+ - if @projects.any? || params[:name]
+ = render 'dashboard/projects_head'
-- if @projects.any? || params[:name]
- = render 'projects'
-- else
- = render "zero_authorized_projects"
+ - if @projects.any? || params[:name]
+ = render 'projects'
+ - else
+ = render "zero_authorized_projects"
diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index 162ae153b1c..99efe9c9b86 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -1,13 +1,15 @@
+- @no_container = true
+
- page_title "Starred Projects"
- header_title "Projects", dashboard_projects_path
-= render 'dashboard/projects_head'
+= render "projects/last_push"
-- if @last_push
- = render "events/event_last_push", event: @last_push
+%div{ class: container_class }
+ = render 'dashboard/projects_head'
-- if @projects.any? || params[:filter_projects]
- = render 'projects'
-- else
- %h3 You don't have starred projects yet
- %p.slead Visit project page and press on star icon and it will appear on this page.
+ - if @projects.any? || params[:filter_projects]
+ = render 'projects'
+ - else
+ %h3 You don't have starred projects yet
+ %p.slead Visit project page and press on star icon and it will appear on this page.
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index a2f6a7ab1cb..d696577278d 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -8,7 +8,7 @@
= f.text_field :name, class: "form-control top", required: true, title: "This field is required."
.username.form-group
= f.label :username
- = f.text_field :username, class: "form-control middle", pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_JS, required: true, title: 'Please create a username with only alphanumeric characters.'
+ = f.text_field :username, class: "form-control middle", pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: 'Please create a username with only alphanumeric characters.'
%p.validation-error.hide Username is already taken.
%p.validation-success.hide Username is available.
%p.validation-pending.hide Checking username availability...
diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml
index c3f55ff821f..70042dee20f 100644
--- a/app/views/discussions/_diff_with_notes.html.haml
+++ b/app/views/discussions/_diff_with_notes.html.haml
@@ -3,7 +3,7 @@
.diff-file.file-holder
.js-file-title.file-title
- = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_file.content_commit, project: discussion.project, url: discussion_path(discussion), show_toggle: false
+ = render "projects/diffs/file_header", diff_file: diff_file, url: discussion_path(discussion), show_toggle: false
.diff-content.code.js-syntax-highlight
%table
diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml
index 74992e439f3..578e751ab47 100644
--- a/app/views/discussions/_discussion.html.haml
+++ b/app/views/discussions/_discussion.html.haml
@@ -32,10 +32,9 @@
- elsif discussion.diff_discussion?
on
= conditional_link_to url.present?, url do
- - if discussion.active?
- the diff
- - else
- an outdated diff
+ - unless discussion.active?
+ an old version of
+ the diff
= time_ago_with_tooltip(discussion.created_at, placement: "bottom", html_class: "note-created-ago")
= render "discussions/headline", discussion: discussion
diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml
index 7ba3f3f6c42..db5ab939948 100644
--- a/app/views/discussions/_notes.html.haml
+++ b/app/views/discussions/_notes.html.haml
@@ -1,10 +1,11 @@
.discussion-notes
%ul.notes{ data: { discussion_id: discussion.id } }
= render partial: "shared/notes/note", collection: discussion.notes, as: :note
- .flash-container
- - if current_user
- .discussion-reply-holder
+ .flash-container
+
+ .discussion-reply-holder
+ - if can_create_note?
- if discussion.potentially_resolvable?
- line_type = local_assigns.fetch(:line_type, nil)
@@ -19,3 +20,10 @@
= render "discussions/jump_to_next", discussion: discussion
- else
= link_to_reply_discussion(discussion)
+ - elsif !current_user
+ .disabled-comment.text-center
+ Please
+ = link_to "register", new_session_path(:user, redirect_to_referer: 'yes')
+ or
+ = link_to "sign in", new_session_path(:user, redirect_to_referer: 'yes')
+ to reply
diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml
deleted file mode 100644
index 1584695a62b..00000000000
--- a/app/views/events/_event_last_push.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-- if show_last_push_widget?(event)
- .row-content-block.clear-block.last-push-widget
- .event-last-push
- .event-last-push-text
- %span You pushed to
- = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name), title: h(event.project.name) do
- %strong= event.ref_name
- %span at
- %strong= link_to_project event.project
- #{time_ago_with_tooltip(event.created_at)}
-
- .pull-right
- = link_to new_mr_path_from_push_event(event), title: "New merge request", class: "btn btn-info btn-sm" do
- Create merge request
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index c0943100ae3..769ac655d0a 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -7,7 +7,7 @@
%span.pushed #{event.action_name} #{event.ref_type}
%strong
- commits_link = namespace_project_commits_path(project.namespace, project, event.ref_name)
- = link_to_if project.repository.branch_exists?(event.ref_name), event.ref_name, commits_link
+ = link_to_if project.repository.branch_exists?(event.ref_name), event.ref_name, commits_link, class: 'ref-name'
= render "events/event_scope", event: event
diff --git a/app/views/groups/_activities.html.haml b/app/views/groups/_activities.html.haml
index d7851c79990..fd6e7111f38 100644
--- a/app/views/groups/_activities.html.haml
+++ b/app/views/groups/_activities.html.haml
@@ -1,6 +1,3 @@
-.hidden-xs
- = render "events/event_last_push", event: @last_push
-
.nav-block
.controls
= link_to group_path(@group, rss_url_options), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do
diff --git a/app/views/groups/_head.html.haml b/app/views/groups/_head.html.haml
index 873504099d4..0f63774fb9b 100644
--- a/app/views/groups/_head.html.haml
+++ b/app/views/groups/_head.html.haml
@@ -12,3 +12,6 @@
= link_to activity_group_path(@group), title: 'Activity' do
%span
Activity
+
+.hidden-xs
+ = render "projects/last_push"
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 18997baa998..80a8ba4a755 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -6,7 +6,6 @@
= render 'groups/head'
= render 'groups/home_panel'
-
.groups-header{ class: container_class }
.top-area
= render 'groups/show_nav'
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index d068c895fa3..f6132464910 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -17,7 +17,7 @@
= link_to admin_broadcast_messages_path, title: 'Messages' do
%span
Messages
- = nav_link(controller: :hooks) do
+ = nav_link(controller: [:hooks, :hook_logs]) do
= link_to admin_hooks_path, title: 'Hooks' do
%span
System Hooks
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index 02eb7c8462c..546376aeed8 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -27,40 +27,38 @@
%h4 #{pluralize @message.diffs_count, "changed file"}:
%ul
- - @message.diffs.each do |diff|
+ - @message.diffs.each do |diff_file|
%li.file-stats
- %a{ href: "#{@message.target_url if @message.disable_diffs?}##{hexdigest(diff.file_path)}" }
- - if diff.deleted_file
+ %a{ href: "#{@message.target_url if @message.disable_diffs?}##{hexdigest(diff_file.file_path)}" }
+ - if diff_file.deleted_file?
%span.deleted-file
&minus;
- = diff.old_path
- - elsif diff.renamed_file
- = diff.old_path
+ = diff_file.old_path
+ - elsif diff_file.renamed_file?
+ = diff_file.old_path
&rarr;
- = diff.new_path
- - elsif diff.new_file
+ = diff_file.new_path
+ - elsif diff_file.new_file?
%span.new-file
&#43;
- = diff.new_path
+ = diff_file.new_path
- else
- = diff.new_path
+ = diff_file.new_path
- unless @message.disable_diffs?
- - diff_files = @message.diffs
-
- if @message.compare_timeout
%h5 The diff was not included because it is too large.
- else
%h4 Changes:
- - diff_files.each do |diff_file|
+ - @message.diffs.each do |diff_file|
- file_hash = hexdigest(diff_file.file_path)
%li{ id: file_hash }
%a{ href: @message.target_url + "##{file_hash}" }<
- - if diff_file.deleted_file
+ - if diff_file.deleted_file?
%strong<
= diff_file.old_path
deleted
- - elsif diff_file.renamed_file
+ - elsif diff_file.renamed_file?
%strong<
= diff_file.old_path
&rarr;
diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml
index 5ac23aa3997..895d8807e47 100644
--- a/app/views/notify/repository_push_email.text.haml
+++ b/app/views/notify/repository_push_email.text.haml
@@ -15,15 +15,15 @@
\
#{pluralize @message.diffs_count, "changed file"}:
\
- - @message.diffs.each do |diff|
- - if diff.deleted_file
- \- − #{diff.old_path}
- - elsif diff.renamed_file
- \- #{diff.old_path} → #{diff.new_path}
- - elsif diff.new_file
- \- + #{diff.new_path}
+ - @message.diffs.each do |diff_file|
+ - if diff_file.deleted_file?
+ \- − #{diff_file.old_path}
+ - elsif diff_file.renamed_file?
+ \- #{diff_file.old_path} → #{diff_file.new_path}
+ - elsif diff_file.new_file?
+ \- + #{diff_file.new_path}
- else
- \- #{diff.new_path}
+ \- #{diff_file.new_path}
- unless @message.disable_diffs?
- if @message.compare_timeout
\
@@ -36,9 +36,9 @@
- @message.diffs.each do |diff_file|
\
\=====================================
- - if diff_file.deleted_file
+ - if diff_file.deleted_file?
#{diff_file.old_path} deleted
- - elsif diff_file.renamed_file
+ - elsif diff_file.renamed_file?
#{diff_file.old_path} → #{diff_file.new_path}
- else
= diff_file.new_path
diff --git a/app/views/profiles/accounts/_reset_token.html.haml b/app/views/profiles/accounts/_reset_token.html.haml
new file mode 100644
index 00000000000..c31a4a8ecd4
--- /dev/null
+++ b/app/views/profiles/accounts/_reset_token.html.haml
@@ -0,0 +1,11 @@
+- name = label.parameterize
+- attribute = name.underscore
+
+.reset-action
+ %p.cgray
+ = label_tag name, label, class: "label-light"
+ = text_field_tag name, current_user.send(attribute), class: 'form-control', readonly: true, onclick: 'this.select()'
+ %p.help-block
+ = help_text
+ .prepend-top-default
+ = link_to button_label, [:reset, attribute, :profile], method: :put, data: { confirm: 'Are you sure?' }, class: 'btn btn-default private-token'
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 73f33e69d68..a319b18e507 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -8,35 +8,17 @@
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
- = incoming_email_token_enabled? ? "Private Tokens" : "Private Token"
+ Private Tokens
%p
- Keep
- = incoming_email_token_enabled? ? "these tokens" : "this token"
- secret, anyone with access to them can interact with GitLab as if they were you.
+ Keep these tokens secret, anyone with access to them can interact with
+ GitLab as if they were you.
.col-lg-9.private-tokens-reset
- .reset-action
- %p.cgray
- - if current_user.private_token
- = label_tag "private-token", "Private token", class: "label-light"
- = text_field_tag "private-token", current_user.private_token, class: "form-control", readonly: true, onclick: "this.select()"
- - else
- %span You don't have one yet. Click generate to fix it.
- %p.help-block
- Your private token is used to access the API and Atom feeds without username/password authentication.
- .prepend-top-default
- - if current_user.private_token
- = link_to 'Reset private token', reset_private_token_profile_path, method: :put, data: { confirm: "Are you sure?" }, class: "btn btn-default private-token"
- - else
- = f.submit 'Generate', class: "btn btn-default"
+ = render partial: 'reset_token', locals: { label: 'Private token', button_label: 'Reset private token', help_text: 'Your private token is used to access the API and Atom feeds without username/password authentication.' }
+
+ = render partial: 'reset_token', locals: { label: 'RSS token', button_label: 'Reset RSS token', help_text: 'Your RSS token is used to create urls for personalized RSS feeds.' }
+
- if incoming_email_token_enabled?
- .reset-action
- %p.cgray
- = label_tag "incoming-email-token", "Incoming Email Token", class: 'label-light'
- = text_field_tag "incoming-email-token", current_user.incoming_email_token, class: "form-control", readonly: true, onclick: "this.select()"
- %p.help-block
- Your incoming email token is used to create new issues by email, and is included in your project-specific email addresses.
- .prepend-top-default
- = link_to 'Reset incoming email token', reset_incoming_email_token_profile_path, method: :put, data: { confirm: "Are you sure?" }, class: "btn btn-default incoming-email-token"
+ = render partial: 'reset_token', locals: { label: 'Incoming email token', button_label: 'Reset incoming email token', help_text: 'Your incoming email token is used to create new issues by email, and is included in your project-specific email addresses.' }
%hr
.row.prepend-top-default
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 99690e6b98a..0ff19b3eab1 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -37,9 +37,9 @@
= f.select :dashboard, dashboard_choices, {}, class: 'form-control'
.form-group
= f.label :project_view, class: 'label-light' do
- Project view
+ Project home page content
= f.select :project_view, project_view_choices, {}, class: 'form-control'
.help-block
- Choose what content you want to see on a project's home page.
+ Choose what content you want to see on a project’s home page
.form-group
= f.submit 'Save changes', class: 'btn btn-save'
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
index f5bb7364d4a..10f581d751b 100644
--- a/app/views/projects/_activity.html.haml
+++ b/app/views/projects/_activity.html.haml
@@ -1,5 +1,3 @@
-- @no_container = true
-
%div{ class: container_class }
.nav-block.activity-filter-block.activities
.controls
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 0fd19780570..9a9fca78df3 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -24,7 +24,7 @@
= render 'projects/buttons/fork'
%span.hidden-xs
- - if @project.feature_available?(:repository, current_user)
+ - if can?(current_user, :download_code, @project)
.project-clone-holder
= render "shared/clone_panel"
diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml
index f8a6e98d280..e8b1940af2d 100644
--- a/app/views/projects/_last_push.html.haml
+++ b/app/views/projects/_last_push.html.haml
@@ -1,18 +1,18 @@
-- if event = last_push_event
- - if show_last_push_widget?(event)
- .row-content-block.top-block.hidden-xs.white
- %div{ class: container_class }
- .event-last-push
- .event-last-push-text
- %span You pushed to
- = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name, class: 'commit-sha') do
- %strong= event.ref_name
- - if @project && event.project != @project
- %span at
- %strong= link_to_project event.project
- = clipboard_button(text: event.ref_name, class: 'btn-clipboard btn-transparent', title: 'Copy branch to clipboard')
- #{time_ago_with_tooltip(event.created_at)}
+- event = last_push_event
+- if event && show_last_push_widget?(event)
+ .row-content-block.top-block.hidden-xs.white
+ .event-last-push
+ .event-last-push-text
+ %span You pushed to
+ %strong
+ = link_to event.ref_name, namespace_project_commits_path(event.project.namespace, event.project, event.ref_name), class: 'ref-name'
- .pull-right
- = link_to new_mr_path_from_push_event(event), title: "New merge request", class: "btn btn-info btn-sm" do
- Create merge request
+ - if event.project != @project
+ %span at
+ %strong= link_to_project event.project
+
+ #{time_ago_with_tooltip(event.created_at)}
+
+ .pull-right
+ = link_to new_mr_path_from_push_event(event), title: "New merge request", class: "btn btn-info btn-sm" do
+ Create merge request
diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml
index 27c8e3c7fca..ef8d8051cbf 100644
--- a/app/views/projects/activity.html.haml
+++ b/app/views/projects/activity.html.haml
@@ -1,3 +1,5 @@
+- @no_container = true
+
- page_title "Activity"
= render "projects/head"
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index 67f57b5e4b9..41f75a491a5 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -1,13 +1,14 @@
- @no_container = true
+
- page_title @blob.path, @ref
= render "projects/commits/head"
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('blob')
-%div{ class: container_class }
- = render 'projects/last_push'
+= render 'projects/last_push'
+%div{ class: container_class }
#tree-holder.tree-holder
= render 'blob', blob: @blob
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 304c512e1b5..869633e016d 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -61,7 +61,7 @@
= icon("trash-o")
- if branch.name != @repository.root_ref
- .divergence-graph{ title: "#{number_commits_ahead} commits ahead, #{number_commits_behind} commits behind #{@repository.root_ref}" }
+ .divergence-graph{ title: "#{number_commits_behind} commits behind #{@repository.root_ref}, #{number_commits_ahead} commits ahead" }
.graph-side
.bar.bar-behind{ style: "width: #{number_commits_behind * bar_graph_width_factor}%" }
%span.count.count-behind= number_commits_behind
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index e796920ac82..a190a8760ef 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -36,7 +36,7 @@
= icon('warning', class: 'text-warning has-tooltip', title: 'Job is stuck. Check runners.')
- if retried
- = icon('spinner', class: 'text-warning has-tooltip', title: 'Job was retried')
+ = icon('refresh', class: 'text-warning has-tooltip', title: 'Job was retried')
.label-container
- if job.tags.any?
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 6051ea2f1ce..3a1be3fa4b6 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -13,7 +13,7 @@
.block-connector
= render "projects/diffs/diffs", diffs: @diffs, environment: @environment
- = render "shared/notes/notes_with_form"
+ = render "shared/notes/notes_with_form", :autocomplete => true
- if can_collaborate_with_project?
- %w(revert cherry-pick).each do |type|
= render "projects/commit/change", type: type, commit: @commit, title: @commit.title
diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml
index c781e423c4d..c7e22a0b4ec 100644
--- a/app/views/projects/diffs/_content.html.haml
+++ b/app/views/projects/diffs/_content.html.haml
@@ -1,12 +1,12 @@
-.diff-content.diff-wrap-lines
- -# Skip all non non-supported blobs
- - return unless blob.respond_to?(:text?)
+- blob = diff_file.blob
+
+.diff-content
- if diff_file.too_large?
.nothing-here-block This diff could not be displayed because it is too large.
- elsif blob.too_large?
.nothing-here-block The file could not be displayed because it is too large.
- elsif blob.readable_text?
- - if !project.repository.diffable?(blob)
+ - if !diff_file.repository.diffable?(blob)
.nothing-here-block This diff was suppressed by a .gitattributes entry.
- elsif diff_file.collapsed?
- url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path, file_identifier: diff_file.file_identifier))
@@ -15,20 +15,13 @@
%a.click-to-expand
Click to expand it.
- elsif diff_file.diff_lines.length > 0
- - total_lines = 0
- - if blob.lines.any?
- - total_lines = blob.lines.last.chomp == '' ? blob.lines.size - 1 : blob.lines.size
- - if diff_view == :parallel
- = render "projects/diffs/parallel_view", diff_file: diff_file, total_lines: total_lines
- - else
- = render "projects/diffs/text_file", diff_file: diff_file, total_lines: total_lines
+ = render "projects/diffs/viewers/text", diff_file: diff_file
- else
- if diff_file.mode_changed?
.nothing-here-block File mode changed
- - elsif diff_file.renamed_file
+ - elsif diff_file.renamed_file?
.nothing-here-block File moved
- elsif blob.image?
- - old_blob = diff_file.old_blob(diff_file.old_content_commit || @base_commit)
- = render "projects/diffs/image", diff_file: diff_file, old_file: old_blob, file: blob
+ = render "projects/diffs/viewers/image", diff_file: diff_file
- else
.nothing-here-block No preview for this file type
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 71a1b9e6c05..4768438c29e 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -23,12 +23,4 @@
= render 'projects/diffs/warning', diff_files: diffs
.files{ data: { can_create_note: can_create_note } }
- - diff_files.each_with_index do |diff_file|
- - diff_commit = commit_for_diff(diff_file)
- - blob = diff_file.blob(diff_commit)
- - next unless blob
- - blob.load_all_data!(diffs.project.repository) unless blob.too_large?
- - file_hash = hexdigest(diff_file.file_path)
-
- = render 'projects/diffs/file', file_hash: file_hash, project: diffs.project,
- diff_file: diff_file, diff_commit: diff_commit, blob: blob, environment: environment
+ = render partial: 'projects/diffs/file', collection: diff_files, as: :diff_file, locals: { project: diffs.project, environment: environment }
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index f22b385fc0f..b5aea217384 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -1,10 +1,12 @@
- environment = local_assigns.fetch(:environment, nil)
-.diff-file.file-holder{ id: file_hash, data: diff_file_html_data(project, diff_file.file_path, diff_commit.id) }
+- file_hash = hexdigest(diff_file.file_path)
+.diff-file.file-holder{ id: file_hash, data: diff_file_html_data(project, diff_file.file_path, diff_file.content_sha) }
.js-file-title.file-title-flex-parent
.file-header-content
- = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_commit, project: project, url: "##{file_hash}"
+ = render "projects/diffs/file_header", diff_file: diff_file, url: "##{file_hash}"
- unless diff_file.submodule?
+ - blob = diff_file.blob
.file-actions.hidden-xs
- if blob.readable_text?
= link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: "Toggle comments for this file", disabled: @diff_notes_disabled do
@@ -15,9 +17,9 @@
= edit_blob_link(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path,
blob: blob, link_opts: link_opts)
- = view_file_button(diff_commit.id, diff_file.new_path, project)
- = view_on_environment_button(diff_commit.id, diff_file.new_path, environment) if environment
+ = view_file_button(diff_file.content_sha, diff_file.file_path, project)
+ = view_on_environment_button(diff_file.content_sha, diff_file.file_path, environment) if environment
= render 'projects/fork_suggestion'
- = render 'projects/diffs/content', diff_file: diff_file, diff_commit: diff_commit, blob: blob, project: project
+ = render 'projects/diffs/content', diff_file: diff_file
diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml
index 4e4fdb73ae3..73c316472e3 100644
--- a/app/views/projects/diffs/_file_header.html.haml
+++ b/app/views/projects/diffs/_file_header.html.haml
@@ -3,19 +3,20 @@
- if show_toggle
%i.fa.diff-toggle-caret.fa-fw
-- if defined?(blob) && blob && diff_file.submodule?
+- if diff_file.submodule?
+ - blob = diff_file.blob
%span
= icon('archive fw')
%strong.file-title-name
- = submodule_link(blob, diff_commit.id, project.repository)
+ = submodule_link(blob, diff_file.content_sha, diff_file.repository)
= copy_file_path_button(blob.path)
- else
= conditional_link_to url.present?, url do
= blob_icon diff_file.b_mode, diff_file.file_path
- - if diff_file.renamed_file
+ - if diff_file.renamed_file?
- old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
%strong.file-title-name.has-tooltip{ data: { title: diff_file.old_path, container: 'body' } }
= old_path
@@ -23,12 +24,13 @@
%strong.file-title-name.has-tooltip{ data: { title: diff_file.new_path, container: 'body' } }
= new_path
- else
- %strong.file-title-name.has-tooltip{ data: { title: diff_file.new_path, container: 'body' } }
- = diff_file.new_path
- - if diff_file.deleted_file
+ %strong.file-title-name.has-tooltip{ data: { title: diff_file.file_path, container: 'body' } }
+ = diff_file.file_path
+
+ - if diff_file.deleted_file?
deleted
- = copy_file_path_button(diff_file.new_path)
+ = copy_file_path_button(diff_file.file_path)
- if diff_file.mode_changed?
%small
diff --git a/app/views/projects/diffs/_image.html.haml b/app/views/projects/diffs/_image.html.haml
deleted file mode 100644
index ca10921c5e2..00000000000
--- a/app/views/projects/diffs/_image.html.haml
+++ /dev/null
@@ -1,69 +0,0 @@
-- diff = diff_file.diff
-- file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path))
-// diff_refs will be nil for orphaned commits (e.g. first commit in repo)
-- if diff_file.old_ref
- - old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path))
-
-- if diff.renamed_file || diff.new_file || diff.deleted_file
- .image
- %span.wrap
- .frame{ class: image_diff_class(diff) }
- %img{ src: diff.deleted_file ? old_file_raw_path : file_raw_path, alt: diff.new_path }
- %p.image-info= number_to_human_size(file.size)
-- else
- .image
- .two-up.view
- %span.wrap
- .frame.deleted
- %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path)) }
- %img{ src: old_file_raw_path, alt: diff.old_path }
- %p.image-info.hide
- %span.meta-filesize= number_to_human_size(old_file.size)
- |
- %b W:
- %span.meta-width
- |
- %b H:
- %span.meta-height
- %span.wrap
- .frame.added
- %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path)) }
- %img{ src: file_raw_path, alt: diff.new_path }
- %p.image-info.hide
- %span.meta-filesize= number_to_human_size(file.size)
- |
- %b W:
- %span.meta-width
- |
- %b H:
- %span.meta-height
-
- .swipe.view.hide
- .swipe-frame
- .frame.deleted
- %img{ src: old_file_raw_path, alt: diff.old_path }
- .swipe-wrap
- .frame.added
- %img{ src: file_raw_path, alt: diff.new_path }
- %span.swipe-bar
- %span.top-handle
- %span.bottom-handle
-
- .onion-skin.view.hide
- .onion-skin-frame
- .frame.deleted
- %img{ src: old_file_raw_path, alt: diff.old_path }
- .frame.added
- %img{ src: file_raw_path, alt: diff.new_path }
- .controls
- .transparent
- .drag-track
- .dragger{ :style => "left: 0px;" }
- .opaque
-
-
- .view-modes.hide
- %ul.view-modes-menu
- %li.two-up{ data: { mode: 'two-up' } } 2-up
- %li.swipe{ data: { mode: 'swipe' } } Swipe
- %li.onion-skin{ data: { mode: 'onion-skin' } } Onion skin
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index 45c95f7ab6a..8e5f4d2573d 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -49,7 +49,7 @@
- if discussions_left || discussions_right
= render "discussions/parallel_diff_discussion", discussions_left: discussions_left, discussions_right: discussions_right
- - if !diff_file.new_file && !diff_file.deleted_file && diff_file.diff_lines.any?
+ - if !diff_file.new_file? && !diff_file.deleted_file? && diff_file.diff_lines.any?
- last_line = diff_file.diff_lines.last
- if last_line.new_pos < total_lines
%tr.line_holder.parallel
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index fd4f3c8d3cc..e69c7f20d49 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -12,19 +12,19 @@
- diff_files.each do |diff_file|
- file_hash = hexdigest(diff_file.file_path)
%li
- - if diff_file.deleted_file
+ - if diff_file.deleted_file?
%span.deleted-file
%a{ href: "##{file_hash}" }
%i.fa.fa-minus
= diff_file.old_path
- - elsif diff_file.renamed_file
+ - elsif diff_file.renamed_file?
%span.renamed-file
%a{ href: "##{file_hash}" }
%i.fa.fa-minus
= diff_file.old_path
&rarr;
= diff_file.new_path
- - elsif diff_file.new_file
+ - elsif diff_file.new_file?
%span.new-file
%a{ href: "##{file_hash}" }
%i.fa.fa-plus
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index 5f3968b6709..e8a5e63e59e 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -3,13 +3,13 @@
.suppressed-container
%a.show-suppressed-diff.js-show-suppressed-diff Changes suppressed. Click to show.
-%table.text-file.code.js-syntax-highlight{ data: diff_view_data, class: too_big ? 'hide' : '' }
+%table.text-file.diff-wrap-lines.code.js-syntax-highlight{ data: diff_view_data, class: too_big ? 'hide' : '' }
= render partial: "projects/diffs/line",
collection: diff_file.highlighted_diff_lines,
as: :line,
locals: { diff_file: diff_file, discussions: @grouped_diff_discussions }
- - if !diff_file.new_file && !diff_file.deleted_file && diff_file.highlighted_diff_lines.any?
+ - if !diff_file.new_file? && !diff_file.deleted_file? && diff_file.highlighted_diff_lines.any?
- last_line = diff_file.highlighted_diff_lines.last
- if last_line.new_pos < total_lines
%tr.line_holder
diff --git a/app/views/projects/diffs/viewers/_image.html.haml b/app/views/projects/diffs/viewers/_image.html.haml
new file mode 100644
index 00000000000..ea75373581e
--- /dev/null
+++ b/app/views/projects/diffs/viewers/_image.html.haml
@@ -0,0 +1,68 @@
+- blob = diff_file.blob
+- old_blob = diff_file.old_blob
+- blob_raw_path = diff_file_blob_raw_path(diff_file)
+- old_blob_raw_path = diff_file_old_blob_raw_path(diff_file)
+
+- if diff_file.new_file? || diff_file.deleted_file?
+ .image
+ %span.wrap
+ .frame{ class: (diff_file.deleted_file? ? 'deleted' : 'added') }
+ %img{ src: blob_raw_path, alt: diff_file.file_path }
+ %p.image-info= number_to_human_size(blob.size)
+- else
+ .image
+ .two-up.view
+ %span.wrap
+ .frame.deleted
+ %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_content_sha, diff_file.old_path)) }
+ %img{ src: old_blob_raw_path, alt: diff_file.old_path }
+ %p.image-info.hide
+ %span.meta-filesize= number_to_human_size(old_blob.size)
+ |
+ %b W:
+ %span.meta-width
+ |
+ %b H:
+ %span.meta-height
+ %span.wrap
+ .frame.added
+ %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.content_sha, diff_file.new_path)) }
+ %img{ src: blob_raw_path, alt: diff_file.new_path }
+ %p.image-info.hide
+ %span.meta-filesize= number_to_human_size(blob.size)
+ |
+ %b W:
+ %span.meta-width
+ |
+ %b H:
+ %span.meta-height
+
+ .swipe.view.hide
+ .swipe-frame
+ .frame.deleted
+ %img{ src: old_blob_raw_path, alt: diff_file.old_path }
+ .swipe-wrap
+ .frame.added
+ %img{ src: blob_raw_path, alt: diff_file.new_path }
+ %span.swipe-bar
+ %span.top-handle
+ %span.bottom-handle
+
+ .onion-skin.view.hide
+ .onion-skin-frame
+ .frame.deleted
+ %img{ src: old_blob_raw_path, alt: diff_file.old_path }
+ .frame.added
+ %img{ src: blob_raw_path, alt: diff_file.new_path }
+ .controls
+ .transparent
+ .drag-track
+ .dragger{ :style => "left: 0px;" }
+ .opaque
+
+
+ .view-modes.hide
+ %ul.view-modes-menu
+ %li.two-up{ data: { mode: 'two-up' } } 2-up
+ %li.swipe{ data: { mode: 'swipe' } } Swipe
+ %li.onion-skin{ data: { mode: 'onion-skin' } } Onion skin
diff --git a/app/views/projects/diffs/viewers/_text.html.haml b/app/views/projects/diffs/viewers/_text.html.haml
new file mode 100644
index 00000000000..e4b89671724
--- /dev/null
+++ b/app/views/projects/diffs/viewers/_text.html.haml
@@ -0,0 +1,8 @@
+- blob = diff_file.blob
+- blob.load_all_data!(diff_file.repository)
+- total_lines = blob.lines.size
+- total_lines -= 1 if total_lines > 0 && blob.lines.last.blank?
+- if diff_view == :parallel
+ = render "projects/diffs/parallel_view", diff_file: diff_file, total_lines: total_lines
+- else
+ = render "projects/diffs/text_file", diff_file: diff_file, total_lines: total_lines
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index dd27e0866de..f5549d7f4cd 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -246,14 +246,16 @@
.row.prepend-top-default
.col-lg-3
%h4.prepend-top-0.danger-title
- Transfer project
+ Transfer project to new group
+ %p.append-bottom-0
+ Please select the group you want to transfer this project to in the dropdown to the right.
.col-lg-9
- = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_namespace_project_path(@project.namespace, @project), method: :put, remote: true) do |f|
+ = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_namespace_project_path(@project.namespace, @project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } ) do |f|
.form-group
= label_tag :new_namespace_id, nil, class: 'label-light' do
- %span Namespace
+ %span Select a new namespace
.form-group
- = select_tag :new_namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace', class: 'select2' }
+ = select_tag :new_namespace_id, namespaces_options(nil), include_blank: true, class: 'select2'
%ul
%li Be careful. Changing the project's namespace can have unintended side effects.
%li You can only transfer the project to namespaces you manage.
diff --git a/app/views/projects/hook_logs/_index.html.haml b/app/views/projects/hook_logs/_index.html.haml
new file mode 100644
index 00000000000..6962b223451
--- /dev/null
+++ b/app/views/projects/hook_logs/_index.html.haml
@@ -0,0 +1,37 @@
+.row.prepend-top-default.append-bottom-default
+ .col-lg-3
+ %h4.prepend-top-0
+ Recent Deliveries
+ %p When an event in GitLab triggers a webhook, you can use the request details to figure out if something went wrong.
+ .col-lg-9
+ - if hook_logs.any?
+ %table.table
+ %thead
+ %tr
+ %th Status
+ %th Trigger
+ %th URL
+ %th Elapsed time
+ %th Request time
+ %th
+ - hook_logs.each do |hook_log|
+ %tr
+ %td
+ = render partial: 'shared/hook_logs/status_label', locals: { hook_log: hook_log }
+ %td.hidden-xs
+ %span.label.label-gray.deploy-project-label
+ = hook_log.trigger.singularize.titleize
+ %td
+ = truncate(hook_log.url, length: 50)
+ %td.light
+ #{number_with_precision(hook_log.execution_duration, precision: 2)} ms
+ %td.light
+ = time_ago_with_tooltip(hook_log.created_at)
+ %td
+ = link_to 'View details', namespace_project_hook_hook_log_path(project.namespace, project, hook, hook_log)
+
+ = paginate hook_logs, theme: 'gitlab'
+
+ - else
+ .settings-message.text-center
+ You don't have any webhooks deliveries
diff --git a/app/views/projects/hook_logs/show.html.haml b/app/views/projects/hook_logs/show.html.haml
new file mode 100644
index 00000000000..2eabe92f8eb
--- /dev/null
+++ b/app/views/projects/hook_logs/show.html.haml
@@ -0,0 +1,11 @@
+= render 'projects/settings/head'
+
+.row.prepend-top-default.append-bottom-default
+ .col-lg-3
+ %h4.prepend-top-0
+ Request details
+ .col-lg-9
+
+ = link_to 'Resend Request', retry_namespace_project_hook_hook_log_path(@project.namespace, @project, @hook, @hook_log), class: "btn btn-default pull-right prepend-left-10"
+
+ = render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log }
diff --git a/app/views/projects/hooks/edit.html.haml b/app/views/projects/hooks/edit.html.haml
index 7998713be1f..fd382c1d63f 100644
--- a/app/views/projects/hooks/edit.html.haml
+++ b/app/views/projects/hooks/edit.html.haml
@@ -1,3 +1,4 @@
+- page_title 'Integrations'
= render 'projects/settings/head'
.row.prepend-top-default
@@ -10,5 +11,12 @@
.col-lg-9.append-bottom-default
= form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hook_path do |f|
= render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
+
= f.submit 'Save changes', class: 'btn btn-create'
+ = link_to 'Test hook', test_namespace_project_hook_path(@project.namespace, @project, @hook), class: 'btn btn-default'
+ = link_to 'Remove', namespace_project_hook_path(@project.namespace, @project, @hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' }
+
+%hr
+
+= render partial: 'projects/hook_logs/index', locals: { hook: @hook, hook_logs: @hook_logs, project: @project }
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index 4dfda54feb5..8b095f4ca10 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -1,7 +1,7 @@
- content_for :note_actions do
- if can?(current_user, :update_issue, @issue)
- = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, format: 'json'), data: {no_turbolink: true, original_text: "Reopen issue", alternative_text: "Comment & reopen issue"}, class: "btn btn-nr btn-reopen btn-comment js-note-target-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
- = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, format: 'json'), data: {no_turbolink: true, original_text: "Close issue", alternative_text: "Comment & close issue"}, class: "btn btn-nr btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+ = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, format: 'json'), data: {original_text: "Reopen issue", alternative_text: "Comment & reopen issue"}, class: "btn btn-nr btn-reopen btn-comment js-note-target-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, format: 'json'), data: {original_text: "Close issue", alternative_text: "Comment & close issue"}, class: "btn btn-nr btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
#notes
- = render 'shared/notes/notes_with_form'
+ = render 'shared/notes/notes_with_form', :autocomplete => true
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index b2401442620..67403c36d7f 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -2,6 +2,8 @@
- page_title "#{@issue.title} (#{@issue.to_reference})", "Issues"
- page_description @issue.description
- page_card_attributes @issue.card_attributes
+- can_update_issue = can?(current_user, :update_issue, @issue)
+- can_report_spam = @issue.submittable_as_spam_by?(current_user)
.clearfix.detail-page-header
.issuable-header
@@ -27,27 +29,29 @@
= icon('caret-down')
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
- %li
- = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link'
- - if can?(current_user, :update_issue, @issue)
+ - if can_update_issue
%li
- = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
%li
- = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+ = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
%li
- = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
- - if @issue.submittable_as_spam_by?(current_user)
+ = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ - if can_report_spam
%li
= link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
+ - if can_update_issue || can_report_spam
+ %li.divider
+ %li
+ = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link'
+ - if can_update_issue
+ = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit'
+ = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+ = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ - if can_report_spam
+ = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam'
= link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
New issue
- - if can?(current_user, :update_issue, @issue)
- = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
- = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
- - if @issue.submittable_as_spam_by?(current_user)
- = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam'
- = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit'
.issue-details.issuable-details
.detail-page-description.content-block
@@ -69,11 +73,11 @@
#related-branches{ data: { url: related_branches_namespace_project_issue_url(@project.namespace, @project, @issue) } }
// This element is filled in using JavaScript.
- .content-block.content-block-small
+ .content-block.emoji-block
.row
- .col-sm-6
+ .col-sm-8
= render 'award_emoji/awards_block', awardable: @issue, inline: true
- .col-sm-6.new-branch-col
+ .col-sm-4.new-branch-col
= render 'new_branch' unless @issue.confidential?
%section.issuable-discussion
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index 2e6420db212..b787edb3427 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -8,4 +8,4 @@
%button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { project_path: "#{project_path(@merge_request.project)}" } }
{{ buttonText }}
-#notes= render "shared/notes/notes_with_form"
+#notes= render "shared/notes/notes_with_form", :autocomplete => true
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 502220232a1..2cb3045f83e 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -5,12 +5,14 @@
- unless @project.default_issues_tracker?
= content_for :sub_nav do
= render "projects/merge_requests/head"
-= render 'projects/last_push'
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search'
+
+= render 'projects/last_push'
+
- if @project.merge_requests.exists?
%div{ class: container_class }
.top-area
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index e7c5bca6a37..d9428b8562e 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -13,7 +13,7 @@
= icon('angle-double-left')
.issuable-meta
- = issuable_meta(@merge_request, @project, "Merge Request")
+ = issuable_meta(@merge_request, @project, "Merge request")
- if can?(current_user, :update_merge_request, @merge_request)
.issuable-actions
diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml
index 37117bc64a3..0999b95c9c9 100644
--- a/app/views/projects/merge_requests/show/_versions.html.haml
+++ b/app/views/projects/merge_requests/show/_versions.html.haml
@@ -91,7 +91,7 @@
comparing two versions
- else
viewing an old version
- of this merge request.
+ of the diff.
.pull-right
= link_to 'Show latest version', diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-sm'
diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
index 2cd82e1b661..082a6bcbb2a 100644
--- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
+++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml
@@ -15,7 +15,7 @@
None
%td.next-run-cell
- if pipeline_schedule.active?
- = time_ago_with_tooltip(pipeline_schedule.next_run_at)
+ = time_ago_with_tooltip(pipeline_schedule.real_next_run)
- else
Inactive
%td
diff --git a/app/views/projects/settings/_head.html.haml b/app/views/projects/settings/_head.html.haml
index faed65d6588..00bd563999f 100644
--- a/app/views/projects/settings/_head.html.haml
+++ b/app/views/projects/settings/_head.html.haml
@@ -14,7 +14,7 @@
%span
Members
- if can_edit
- = nav_link(controller: [:integrations, :services, :hooks]) do
+ = nav_link(controller: [:integrations, :services, :hooks, :hook_logs]) do
= link_to project_settings_integrations_path(@project), title: 'Integrations' do
%span
Integrations
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index aab1c043e66..847f3c2f348 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -9,4 +9,4 @@
.row-content-block.top-block.content-component-block
= render 'award_emoji/awards_block', awardable: @snippet, inline: true
- #notes= render "shared/notes/notes_with_form"
+ #notes= render "shared/notes/notes_with_form", :autocomplete => true
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index b51955010ce..f7e410e27b8 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -4,6 +4,7 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
= render "projects/commits/head"
+
= render 'projects/last_push'
%div{ class: container_class }
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
index 90ae3f06a98..8d5b5129454 100644
--- a/app/views/shared/_group_form.html.haml
+++ b/app/views/shared/_group_form.html.haml
@@ -15,7 +15,7 @@
%strong= parent.full_path + '/'
= f.text_field :path, placeholder: 'open-source', class: 'form-control',
autofocus: local_assigns[:autofocus] || false, required: true,
- pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_JS,
+ pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS,
title: 'Please choose a group path with no special characters.',
"data-bind-in" => "#{'create_chat_team' if Gitlab.config.mattermost.enabled}"
- if parent
diff --git a/app/views/shared/hook_logs/_content.html.haml b/app/views/shared/hook_logs/_content.html.haml
new file mode 100644
index 00000000000..af6a499fadb
--- /dev/null
+++ b/app/views/shared/hook_logs/_content.html.haml
@@ -0,0 +1,44 @@
+%p
+ %strong Request URL:
+ POST
+ = hook_log.url
+ = render partial: 'shared/hook_logs/status_label', locals: { hook_log: hook_log }
+
+%p
+ %strong Trigger:
+ %td.hidden-xs
+ %span.label.label-gray.deploy-project-label
+ = hook_log.trigger.singularize.titleize
+%p
+ %strong Elapsed time:
+ #{number_with_precision(hook_log.execution_duration, precision: 2)} ms
+%p
+ %strong Request time:
+ = time_ago_with_tooltip(hook_log.created_at)
+
+%hr
+
+- if hook_log.internal_error_message.present?
+ .bs-callout.bs-callout-danger
+ = hook_log.internal_error_message
+
+%h5 Request headers:
+%pre
+ - hook_log.request_headers.each do |k,v|
+ <strong>#{k}:</strong> #{v}
+ %br
+
+%h5 Request body:
+%pre
+ :plain
+ #{JSON.pretty_generate(hook_log.request_data)}
+%h5 Response headers:
+%pre
+ - hook_log.response_headers.each do |k,v|
+ <strong>#{k}:</strong> #{v}
+ %br
+
+%h5 Response body:
+%pre
+ :plain
+ #{hook_log.response_body}
diff --git a/app/views/shared/hook_logs/_status_label.html.haml b/app/views/shared/hook_logs/_status_label.html.haml
new file mode 100644
index 00000000000..b4ea8e6f952
--- /dev/null
+++ b/app/views/shared/hook_logs/_status_label.html.haml
@@ -0,0 +1,3 @@
+- label_status = hook_log.success? ? 'label-success' : 'label-danger'
+%span{ class: "label #{label_status}" }
+ = hook_log.response_status
diff --git a/app/views/shared/icons/_icon_status_skipped.svg b/app/views/shared/icons/_icon_status_skipped.svg
index 1998dfef9ea..a9ba29c922c 100755
--- a/app/views/shared/icons/_icon_status_skipped.svg
+++ b/app/views/shared/icons/_icon_status_skipped.svg
@@ -1 +1 @@
-<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M0 7a7 7 0 1 1 14 0A7 7 0 0 1 0 7z"/><path d="M13 7A6 6 0 1 0 1 7a6 6 0 0 0 12 0z" fill="#FFF"/><path d="M7.69 7.7l-.905.905a.7.7 0 0 0 .99.99l1.85-1.85c.411-.412.411-1.078 0-1.49l-1.85-1.85a.7.7 0 0 0-.99.99l.905.905H4.48a.7.7 0 0 0 0 1.4h3.21z"/></g></svg>
+<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><path d="M7 14A7 7 0 1 1 7 0a7 7 0 0 1 0 14z"/><path d="M7 13A6 6 0 1 0 7 1a6 6 0 0 0 0 12z" fill="#FFF" fill-rule="nonzero"/><path d="M6.415 7.04L4.579 5.203a.295.295 0 0 1 .004-.416l.349-.349a.29.29 0 0 1 .416-.004l2.214 2.214a.289.289 0 0 1 .019.021l.132.133c.11.11.108.291 0 .398L5.341 9.573a.282.282 0 0 1-.398 0l-.331-.331a.285.285 0 0 1 0-.399L6.415 7.04zm2.54 0L7.119 5.203a.295.295 0 0 1 .004-.416l.349-.349a.29.29 0 0 1 .416-.004l2.214 2.214a.289.289 0 0 1 .019.021l.132.133c.11.11.108.291 0 .398L7.881 9.573a.282.282 0 0 1-.398 0l-.331-.331a.285.285 0 0 1 0-.399L8.955 7.04z"/></svg>
diff --git a/app/views/shared/icons/_icon_status_skipped_borderless.svg b/app/views/shared/icons/_icon_status_skipped_borderless.svg
index fb3e930b3cb..3c8a26d7f4d 100644
--- a/app/views/shared/icons/_icon_status_skipped_borderless.svg
+++ b/app/views/shared/icons/_icon_status_skipped_borderless.svg
@@ -1 +1 @@
-<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M12.0846,12.1 L10.6623,13.5223 C10.2454306,13.9539168 10.2513924,14.6399933 10.6756996,15.0643004 C11.1000067,15.4886076 11.7860832,15.4945694 12.2177,15.0777 L15.1261,12.1693 C15.7708612,11.5230891 15.7708612,10.4769109 15.1261,9.8307 L12.2177,6.9223 C11.7860832,6.50543057 11.1000067,6.51139239 10.6756996,6.93569957 C10.2513924,7.36000675 10.2454306,8.04608322 10.6623,8.4777 L12.0846,9.9 L7.04,9.9 C6.43248678,9.9 5.94,10.3924868 5.94,11 C5.94,11.6075132 6.43248678,12.1 7.04,12.1 L12.0846,12.1 L12.0846,12.1 Z" id="Shape"></path></svg>
+<svg width="22" height="22" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"><path d="M14.072 11.063l-2.82 2.82a.46.46 0 0 0-.001.652l.495.495a.457.457 0 0 0 .653-.001l3.7-3.7a.46.46 0 0 0 .001-.653l-.196-.196a.453.453 0 0 0-.03-.033l-3.479-3.479a.464.464 0 0 0-.654.007l-.548.548a.463.463 0 0 0-.007.654l2.886 2.886z"/><path d="M10.08 11.063l-2.819 2.82a.46.46 0 0 0-.002.652l.496.495a.457.457 0 0 0 .652-.001l3.7-3.7a.46.46 0 0 0 .002-.653l-.196-.196a.453.453 0 0 0-.03-.033l-3.48-3.479a.464.464 0 0 0-.653.007l-.548.548a.463.463 0 0 0-.007.654l2.886 2.886z"/></svg>
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index 93c7fa0c7d6..1cf662e29c4 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -9,7 +9,7 @@
- selected = local_assigns.fetch(:selected, nil)
- selected_toggle = local_assigns.fetch(:selected_toggle, nil)
- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by label")
-- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path), labels: labels_filter_path, default_label: "Labels"}
+- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), labels: labels_filter_path, default_label: "Labels"}
- dropdown_data.merge!(data_options)
- classes << 'js-extra-options' if extra_options
- classes << 'js-filter-submit' if filter_submit
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 80974bdb066..d36707dd042 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -45,7 +45,7 @@
{{hint}}
%span.js-filter-tag.dropdown-light-content
{{tag}}
- #js-dropdown-author.filtered-search-input-dropdown-menu.dropdown-menu{ data: { icon: 'pencil', hint: 'author', tag: '@author' } }
+ #js-dropdown-author.filtered-search-input-dropdown-menu.dropdown-menu
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
%button.btn.btn-link.dropdown-user
@@ -55,7 +55,7 @@
{{name}}
%span.dropdown-light-content
@{{username}}
- #js-dropdown-assignee.filtered-search-input-dropdown-menu.dropdown-menu{ data: { icon: 'user', hint: 'assignee', tag: '@assignee' } }
+ #js-dropdown-assignee.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } }
%button.btn.btn-link
@@ -70,7 +70,7 @@
{{name}}
%span.dropdown-light-content
@{{username}}
- #js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu{ data: { icon: 'clock-o', hint: 'milestone', tag: '%milestone' } }
+ #js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } }
%button.btn.btn-link
@@ -86,7 +86,7 @@
%li.filter-dropdown-item
%button.btn.btn-link.js-data-value
{{title}}
- #js-dropdown-label.filtered-search-input-dropdown-menu.dropdown-menu{ data: { icon: 'tag', hint: 'label', tag: '~label', type: 'array' } }
+ #js-dropdown-label.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } }
%button.btn.btn-link
diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml
index e9ce7b7ce9c..26567c08eb6 100644
--- a/app/views/shared/issuable/_sidebar_assignees.html.haml
+++ b/app/views/shared/issuable/_sidebar_assignees.html.haml
@@ -1,5 +1,8 @@
- if issuable.is_a?(Issue)
#js-vue-sidebar-assignees{ data: { field: "#{issuable.to_ability_name}[assignee_ids]" } }
+ .title.hide-collapsed
+ Assignee
+ = icon('spinner spin')
- else
.sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: (issuable.assignee.name if issuable.assignee) }
- if issuable.assignee
diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml
index a7bf610b9c7..1e34b7c1e76 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -18,7 +18,7 @@
.note-header
.note-header-info
%a{ href: user_path(note.author) }
- %span.hidden-xs
+ %span.note-header-author-name
= sanitize(note.author.name)
%span.note-headline-light
= note.author.to_reference
diff --git a/app/views/shared/notes/_notes_with_form.html.haml b/app/views/shared/notes/_notes_with_form.html.haml
index 05bb1970e21..5902798dfd0 100644
--- a/app/views/shared/notes/_notes_with_form.html.haml
+++ b/app/views/shared/notes/_notes_with_form.html.haml
@@ -3,24 +3,23 @@
= render 'shared/notes/edit_form', project: @project
-%ul.notes.notes-form.timeline
- %li.timeline-entry
- .flash-container.timeline-content
+- if can_create_note?
+ %ul.notes.notes-form.timeline
+ %li.timeline-entry
+ .flash-container.timeline-content
- - if can_create_note?
.timeline-icon.hidden-xs.hidden-sm
%a.author_link{ href: user_path(current_user) }
= image_tag avatar_icon(current_user), alt: current_user.to_reference, class: 'avatar s40'
.timeline-content.timeline-content-form
= render "shared/notes/form", view: diff_view
- - elsif !current_user
- .disabled-comment.text-center
- .disabled-comment-text.inline
- Please
- = link_to "register", new_session_path(:user, redirect_to_referer: 'yes')
- or
- = link_to "sign in", new_session_path(:user, redirect_to_referer: 'yes')
- to post a comment
+- elsif !current_user
+ .disabled-comment.text-center.prepend-top-default
+ Please
+ = link_to "register", new_session_path(:user, redirect_to_referer: 'yes')
+ or
+ = link_to "sign in", new_session_path(:user, redirect_to_referer: 'yes')
+ to comment
:javascript
- var notes = new Notes("#{notes_url}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}", false)
+ var notes = new Notes("#{notes_url}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}", #{autocomplete})
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index 51dbbc32cc9..216184eb839 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -9,4 +9,4 @@
.row-content-block.top-block.content-component-block
= render 'award_emoji/awards_block', awardable: @snippet, inline: true
- #notes= render "shared/notes/notes_with_form"
+ #notes= render "shared/notes/notes_with_form", :autocomplete => false
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 2b70d70e360..c587155bc4f 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -71,7 +71,7 @@
= @user.location
- unless @user.organization.blank?
.profile-link-holder.middle-dot-divider
- = icon('building')
+ = icon('briefcase')
= @user.organization
- if @user.bio.present?
diff --git a/app/workers/expire_job_cache_worker.rb b/app/workers/expire_job_cache_worker.rb
new file mode 100644
index 00000000000..08e281e7350
--- /dev/null
+++ b/app/workers/expire_job_cache_worker.rb
@@ -0,0 +1,35 @@
+class ExpireJobCacheWorker
+ include Sidekiq::Worker
+ include BuildQueue
+
+ def perform(job_id)
+ job = CommitStatus.joins(:pipeline, :project).find_by(id: job_id)
+ return unless job
+
+ pipeline = job.pipeline
+ project = job.project
+
+ Gitlab::EtagCaching::Store.new.tap do |store|
+ store.touch(project_pipeline_path(project, pipeline))
+ store.touch(project_job_path(project, job))
+ end
+ end
+
+ private
+
+ def project_pipeline_path(project, pipeline)
+ Gitlab::Routing.url_helpers.namespace_project_pipeline_path(
+ project.namespace,
+ project,
+ pipeline,
+ format: :json)
+ end
+
+ def project_job_path(project, job)
+ Gitlab::Routing.url_helpers.namespace_project_build_path(
+ project.namespace,
+ project,
+ job.id,
+ format: :json)
+ end
+end
diff --git a/app/workers/expire_pipeline_cache_worker.rb b/app/workers/expire_pipeline_cache_worker.rb
index 603e2f1aaea..d760f5b140f 100644
--- a/app/workers/expire_pipeline_cache_worker.rb
+++ b/app/workers/expire_pipeline_cache_worker.rb
@@ -10,6 +10,7 @@ class ExpirePipelineCacheWorker
store = Gitlab::EtagCaching::Store.new
store.touch(project_pipelines_path(project))
+ store.touch(project_pipeline_path(project, pipeline))
store.touch(commit_pipelines_path(project, pipeline.commit)) if pipeline.commit
store.touch(new_merge_request_pipelines_path(project))
each_pipelines_merge_request_path(project, pipeline) do |path|
@@ -28,6 +29,14 @@ class ExpirePipelineCacheWorker
format: :json)
end
+ def project_pipeline_path(project, pipeline)
+ Gitlab::Routing.url_helpers.namespace_project_pipeline_path(
+ project.namespace,
+ project,
+ pipeline,
+ format: :json)
+ end
+
def commit_pipelines_path(project, commit)
Gitlab::Routing.url_helpers.pipelines_namespace_project_commit_path(
project.namespace,
diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb
index d6ed0e253ad..fe6a49976e0 100644
--- a/app/workers/process_commit_worker.rb
+++ b/app/workers/process_commit_worker.rb
@@ -17,6 +17,7 @@ class ProcessCommitWorker
project = Project.find_by(id: project_id)
return unless project
+ return if commit_exists_in_upstream?(project, commit_hash)
user = User.find_by(id: user_id)
@@ -24,8 +25,6 @@ class ProcessCommitWorker
commit = build_commit(project, commit_hash)
- return unless commit.matches_cross_reference_regex?
-
author = commit.author || user
process_commit_message(project, commit, user, author, default)
@@ -76,4 +75,16 @@ class ProcessCommitWorker
Commit.from_hash(hash, project)
end
+
+ private
+
+ # Avoid reprocessing commits that already exist in the upstream
+ # when project is forked. This will also prevent duplicated system notes.
+ def commit_exists_in_upstream?(project, commit_hash)
+ return false unless project.forked?
+
+ upstream_project = project.forked_from_project
+ commit_id = commit_hash.with_indifferent_access[:id]
+ upstream_project.commit(commit_id).present?
+ end
end
diff --git a/app/workers/remove_old_web_hook_logs_worker.rb b/app/workers/remove_old_web_hook_logs_worker.rb
new file mode 100644
index 00000000000..555e1bb8691
--- /dev/null
+++ b/app/workers/remove_old_web_hook_logs_worker.rb
@@ -0,0 +1,10 @@
+class RemoveOldWebHookLogsWorker
+ include Sidekiq::Worker
+ include CronjobQueue
+
+ WEB_HOOK_LOG_LIFETIME = 2.days
+
+ def perform
+ WebHookLog.destroy_all(['created_at < ?', Time.now - WEB_HOOK_LOG_LIFETIME])
+ end
+end
diff --git a/app/workers/system_hook_worker.rb b/app/workers/system_hook_worker.rb
deleted file mode 100644
index 55d4e7d6dab..00000000000
--- a/app/workers/system_hook_worker.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-class SystemHookWorker
- include Sidekiq::Worker
- include DedicatedSidekiqQueue
-
- sidekiq_options retry: 4
-
- def perform(hook_id, data, hook_name)
- SystemHook.find(hook_id).execute(data, hook_name)
- end
-end
diff --git a/app/workers/project_web_hook_worker.rb b/app/workers/web_hook_worker.rb
index d973e662ff2..ad5ddf02a12 100644
--- a/app/workers/project_web_hook_worker.rb
+++ b/app/workers/web_hook_worker.rb
@@ -1,11 +1,13 @@
-class ProjectWebHookWorker
+class WebHookWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
sidekiq_options retry: 4
def perform(hook_id, data, hook_name)
+ hook = WebHook.find(hook_id)
data = data.with_indifferent_access
- WebHook.find(hook_id).execute(data, hook_name)
+
+ WebHookService.new(hook, data, hook_name).execute
end
end
diff --git a/changelogs/unreleased/12614-fix-long-message-from-mr.yml b/changelogs/unreleased/12614-fix-long-message-from-mr.yml
new file mode 100644
index 00000000000..30408ea4216
--- /dev/null
+++ b/changelogs/unreleased/12614-fix-long-message-from-mr.yml
@@ -0,0 +1,4 @@
+---
+title: Implement web hook logging
+merge_request: 11027
+author: Alexander Randa
diff --git a/changelogs/unreleased/17489-hide-code-from-guests.yml b/changelogs/unreleased/17489-hide-code-from-guests.yml
new file mode 100644
index 00000000000..eb6daffedfe
--- /dev/null
+++ b/changelogs/unreleased/17489-hide-code-from-guests.yml
@@ -0,0 +1,4 @@
+---
+title: Hide clone panel and file list when user is only a guest
+merge_request:
+author: James Clark
diff --git a/changelogs/unreleased/18927-reorder-issue-action-buttons.yml b/changelogs/unreleased/18927-reorder-issue-action-buttons.yml
new file mode 100644
index 00000000000..793d6582940
--- /dev/null
+++ b/changelogs/unreleased/18927-reorder-issue-action-buttons.yml
@@ -0,0 +1,4 @@
+---
+title: Reorder Issue action buttons in order of usability
+merge_request: 11642
+author:
diff --git a/changelogs/unreleased/29852-latex-formatting.yml b/changelogs/unreleased/29852-latex-formatting.yml
new file mode 100644
index 00000000000..e96cda1d6b3
--- /dev/null
+++ b/changelogs/unreleased/29852-latex-formatting.yml
@@ -0,0 +1,4 @@
+---
+title: Fix LaTeX formatting for AsciiDoc wiki
+merge_request: 11212
+author:
diff --git a/changelogs/unreleased/31448-jira-urls.yml b/changelogs/unreleased/31448-jira-urls.yml
new file mode 100644
index 00000000000..d0e39f61b55
--- /dev/null
+++ b/changelogs/unreleased/31448-jira-urls.yml
@@ -0,0 +1,4 @@
+---
+title: Add API URL to JIRA settings
+merge_request:
+author:
diff --git a/changelogs/unreleased/31556-ci-coverage-paralel-rspec.yml b/changelogs/unreleased/31556-ci-coverage-paralel-rspec.yml
new file mode 100644
index 00000000000..4137050a077
--- /dev/null
+++ b/changelogs/unreleased/31556-ci-coverage-paralel-rspec.yml
@@ -0,0 +1,4 @@
+---
+title: Fix the last coverage in trace log should be extracted
+merge_request: 11128
+author: dosuken123
diff --git a/changelogs/unreleased/32418-make-link-to-self-less-obvious.yml b/changelogs/unreleased/32418-make-link-to-self-less-obvious.yml
new file mode 100644
index 00000000000..aabe87dac0f
--- /dev/null
+++ b/changelogs/unreleased/32418-make-link-to-self-less-obvious.yml
@@ -0,0 +1,4 @@
+---
+title: Change links in issuable meta to black
+merge_request:
+author:
diff --git a/changelogs/unreleased/32682-skipped-ci-icon.yml b/changelogs/unreleased/32682-skipped-ci-icon.yml
new file mode 100644
index 00000000000..ad498b51900
--- /dev/null
+++ b/changelogs/unreleased/32682-skipped-ci-icon.yml
@@ -0,0 +1,4 @@
+---
+title: Adds new icon for CI skipped status
+merge_request:
+author:
diff --git a/changelogs/unreleased/32715-fix-note-padding.yml b/changelogs/unreleased/32715-fix-note-padding.yml
new file mode 100644
index 00000000000..867ed7eb171
--- /dev/null
+++ b/changelogs/unreleased/32715-fix-note-padding.yml
@@ -0,0 +1,4 @@
+---
+title: Make all notes use equal padding
+merge_request:
+author:
diff --git a/changelogs/unreleased/32799-remove-no_turbolink-attribute-from-haml.yml b/changelogs/unreleased/32799-remove-no_turbolink-attribute-from-haml.yml
new file mode 100644
index 00000000000..9c1c1fe77f2
--- /dev/null
+++ b/changelogs/unreleased/32799-remove-no_turbolink-attribute-from-haml.yml
@@ -0,0 +1,4 @@
+---
+title: Remove redundant data-turbolink attributes from links
+merge_request: 11672
+author: blackst0ne
diff --git a/changelogs/unreleased/32807-company-icon.yml b/changelogs/unreleased/32807-company-icon.yml
new file mode 100644
index 00000000000..718108d3733
--- /dev/null
+++ b/changelogs/unreleased/32807-company-icon.yml
@@ -0,0 +1,4 @@
+---
+title: Use briefcase icon for company in profile page
+merge_request:
+author:
diff --git a/changelogs/unreleased/32851-postgres-min-version.yml b/changelogs/unreleased/32851-postgres-min-version.yml
new file mode 100644
index 00000000000..139307d65c6
--- /dev/null
+++ b/changelogs/unreleased/32851-postgres-min-version.yml
@@ -0,0 +1,4 @@
+---
+title: Minimum postgresql version is now 9.2
+merge_request: 11677
+author:
diff --git a/changelogs/unreleased/add-unicode-trace-feature-test.yml b/changelogs/unreleased/add-unicode-trace-feature-test.yml
new file mode 100644
index 00000000000..90c6a9afefc
--- /dev/null
+++ b/changelogs/unreleased/add-unicode-trace-feature-test.yml
@@ -0,0 +1,4 @@
+---
+title: Add a feature test for Unicode trace
+merge_request: 10736
+author: dosuken123
diff --git a/changelogs/unreleased/ce-32623-browser-tooltip-commits-branch-list.yml b/changelogs/unreleased/ce-32623-browser-tooltip-commits-branch-list.yml
new file mode 100644
index 00000000000..93edafed699
--- /dev/null
+++ b/changelogs/unreleased/ce-32623-browser-tooltip-commits-branch-list.yml
@@ -0,0 +1,5 @@
+---
+title: Change order of commits ahead and behind on divergence graph for branch list
+ view
+merge_request:
+author:
diff --git a/changelogs/unreleased/ci-build-pipeline-header-vue.yml b/changelogs/unreleased/ci-build-pipeline-header-vue.yml
new file mode 100644
index 00000000000..2bbff2fdd16
--- /dev/null
+++ b/changelogs/unreleased/ci-build-pipeline-header-vue.yml
@@ -0,0 +1,4 @@
+---
+title: Creates CI Header component for Pipelines and Jobs details pages
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-consistent-last-push-event.yml b/changelogs/unreleased/dm-consistent-last-push-event.yml
new file mode 100644
index 00000000000..acc17cb4523
--- /dev/null
+++ b/changelogs/unreleased/dm-consistent-last-push-event.yml
@@ -0,0 +1,4 @@
+---
+title: Consistently display last push event widget
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-copy-as-gfm-without-empty-elements.yml b/changelogs/unreleased/dm-copy-as-gfm-without-empty-elements.yml
new file mode 100644
index 00000000000..45a61320ff2
--- /dev/null
+++ b/changelogs/unreleased/dm-copy-as-gfm-without-empty-elements.yml
@@ -0,0 +1,4 @@
+---
+title: Don't copy empty elements that were not selected on purpose as GFM
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-copy-gfm-when-parts-of-other-elements-are-selected.yml b/changelogs/unreleased/dm-copy-gfm-when-parts-of-other-elements-are-selected.yml
new file mode 100644
index 00000000000..ae916c30ff8
--- /dev/null
+++ b/changelogs/unreleased/dm-copy-gfm-when-parts-of-other-elements-are-selected.yml
@@ -0,0 +1,4 @@
+---
+title: Copy as GFM even when parts of other elements are selected
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-more-dependency-linkers.yml b/changelogs/unreleased/dm-more-dependency-linkers.yml
new file mode 100644
index 00000000000..12d45e71e85
--- /dev/null
+++ b/changelogs/unreleased/dm-more-dependency-linkers.yml
@@ -0,0 +1,4 @@
+---
+title: Autolink package names in more dependency files
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-outdated-system-note.yml b/changelogs/unreleased/dm-outdated-system-note.yml
new file mode 100644
index 00000000000..a1038a1051b
--- /dev/null
+++ b/changelogs/unreleased/dm-outdated-system-note.yml
@@ -0,0 +1,4 @@
+---
+title: Add system note with link to diff comparison when MR discussion becomes outdated
+merge_request:
+author:
diff --git a/changelogs/unreleased/dm-paste-code-inside-gfm-code.yml b/changelogs/unreleased/dm-paste-code-inside-gfm-code.yml
new file mode 100644
index 00000000000..d078ca449a5
--- /dev/null
+++ b/changelogs/unreleased/dm-paste-code-inside-gfm-code.yml
@@ -0,0 +1,4 @@
+---
+title: Don't wrap pasted code when it's already inside code tags
+merge_request:
+author:
diff --git a/changelogs/unreleased/feature-rss-scoped-token.yml b/changelogs/unreleased/feature-rss-scoped-token.yml
new file mode 100644
index 00000000000..740d8778be2
--- /dev/null
+++ b/changelogs/unreleased/feature-rss-scoped-token.yml
@@ -0,0 +1,4 @@
+---
+title: Expose atom links with an RSS token instead of using the private token
+merge_request: 11647
+author: Alexis Reigel
diff --git a/changelogs/unreleased/fix-counter-cache-for-acts-as-taggable.yml b/changelogs/unreleased/fix-counter-cache-for-acts-as-taggable.yml
new file mode 100644
index 00000000000..e40668546c0
--- /dev/null
+++ b/changelogs/unreleased/fix-counter-cache-for-acts-as-taggable.yml
@@ -0,0 +1,4 @@
+---
+title: Fix counter cache for acts as taggable
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-terminals-support-for-kubernetes-service.yml b/changelogs/unreleased/fix-terminals-support-for-kubernetes-service.yml
new file mode 100644
index 00000000000..fb91da9510c
--- /dev/null
+++ b/changelogs/unreleased/fix-terminals-support-for-kubernetes-service.yml
@@ -0,0 +1,4 @@
+---
+title: Fix terminals support for Kubernetes Service
+merge_request:
+author:
diff --git a/changelogs/unreleased/issuable-form-create-label-sub-groups.yml b/changelogs/unreleased/issuable-form-create-label-sub-groups.yml
new file mode 100644
index 00000000000..54b818d6d5e
--- /dev/null
+++ b/changelogs/unreleased/issuable-form-create-label-sub-groups.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed create new label form in issue form not working for sub-group projects
+merge_request:
+author:
diff --git a/changelogs/unreleased/issue_19262.yml b/changelogs/unreleased/issue_19262.yml
new file mode 100644
index 00000000000..7bcbc647fcb
--- /dev/null
+++ b/changelogs/unreleased/issue_19262.yml
@@ -0,0 +1,4 @@
+---
+title: Prevent commits from upstream repositories to be re-processed by forks
+merge_request:
+author:
diff --git a/changelogs/unreleased/mrchrisw-catch-openssl.yml b/changelogs/unreleased/mrchrisw-catch-openssl.yml
new file mode 100644
index 00000000000..a8b433fb0cd
--- /dev/null
+++ b/changelogs/unreleased/mrchrisw-catch-openssl.yml
@@ -0,0 +1,4 @@
+---
+title: Rescue OpenSSL::SSL::SSLError in JiraService & IssueTrackerService
+merge_request:
+author:
diff --git a/changelogs/unreleased/prevent-project-transfer.yml b/changelogs/unreleased/prevent-project-transfer.yml
new file mode 100644
index 00000000000..a5c74676aab
--- /dev/null
+++ b/changelogs/unreleased/prevent-project-transfer.yml
@@ -0,0 +1,4 @@
+---
+title: Prevent project transfers if a new group is not selected
+merge_request:
+author:
diff --git a/changelogs/unreleased/wait-for-ajax-handling-all-js-requests.yml b/changelogs/unreleased/wait-for-ajax-handling-all-js-requests.yml
new file mode 100644
index 00000000000..14aebe792c2
--- /dev/null
+++ b/changelogs/unreleased/wait-for-ajax-handling-all-js-requests.yml
@@ -0,0 +1,4 @@
+---
+title: Use wait_for_requests for both ajax and Vue requests
+merge_request:
+author:
diff --git a/changelogs/unreleased/zj-fix-pipeline-etag.yml b/changelogs/unreleased/zj-fix-pipeline-etag.yml
new file mode 100644
index 00000000000..03ebef8c575
--- /dev/null
+++ b/changelogs/unreleased/zj-fix-pipeline-etag.yml
@@ -0,0 +1,4 @@
+---
+title: Fix issue where real time pipelines were not cached
+merge_request: 11615
+author:
diff --git a/changelogs/unreleased/zj-sort-env-folders.yml b/changelogs/unreleased/zj-sort-env-folders.yml
new file mode 100644
index 00000000000..b3ca97aef94
--- /dev/null
+++ b/changelogs/unreleased/zj-sort-env-folders.yml
@@ -0,0 +1,4 @@
+---
+title: Sort folder for environments
+merge_request:
+author:
diff --git a/config/application.rb b/config/application.rb
index 95ba6774916..b0533759252 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -65,6 +65,7 @@ module Gitlab
hook
import_url
incoming_email_token
+ rss_token
key
otp_attempt
password
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 14d99c243fc..a727f7e2fa3 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -182,7 +182,7 @@ production: &base
cron: "0 * * * *"
# Execute scheduled triggers
pipeline_schedule_worker:
- cron: "0 */12 * * *"
+ cron: "19 * * * *"
# Remove expired build artifacts
expire_build_artifacts_worker:
cron: "50 * * * *"
diff --git a/config/initializers/acts_as_taggable.rb b/config/initializers/0_acts_as_taggable.rb
index c564c0cab11..54e9fcc31db 100644
--- a/config/initializers/acts_as_taggable.rb
+++ b/config/initializers/0_acts_as_taggable.rb
@@ -3,3 +3,7 @@ ActsAsTaggableOn.strict_case_match = true
# tags_counter enables caching count of tags which results in an update whenever a tag is added or removed
# since the count is not used anywhere its better performance wise to disable this cache
ActsAsTaggableOn.tags_counter = false
+
+# validate that counter cache is disabled
+raise "Counter cache is not disabled" if
+ ActsAsTaggableOn::Tagging.reflections["tag"].options[:counter_cache]
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 5a90830b5b3..4fb4baf631f 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -368,11 +368,14 @@ Settings.cron_jobs['gitlab_usage_ping_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['gitlab_usage_ping_worker']['cron'] ||= Settings.__send__(:cron_random_weekly_time)
Settings.cron_jobs['gitlab_usage_ping_worker']['job_class'] = 'GitlabUsagePingWorker'
-# Every day at 00:30
Settings.cron_jobs['schedule_update_user_activity_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['schedule_update_user_activity_worker']['cron'] ||= '30 0 * * *'
Settings.cron_jobs['schedule_update_user_activity_worker']['job_class'] = 'ScheduleUpdateUserActivityWorker'
+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'
+
#
# GitLab Shell
#
diff --git a/config/initializers/fast_gettext.rb b/config/initializers/fast_gettext.rb
index a69fe0c902e..eb589ecdb52 100644
--- a/config/initializers/fast_gettext.rb
+++ b/config/initializers/fast_gettext.rb
@@ -1,5 +1,6 @@
FastGettext.add_text_domain 'gitlab', path: File.join(Rails.root, 'locale'), type: :po
FastGettext.default_text_domain = 'gitlab'
FastGettext.default_available_locales = Gitlab::I18n.available_locales
+FastGettext.default_locale = :en
I18n.available_locales = Gitlab::I18n.available_locales
diff --git a/config/routes.rb b/config/routes.rb
index 2584981bb04..846054e6917 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,6 +1,5 @@
require 'sidekiq/web'
require 'sidekiq/cron/web'
-require 'constraints/group_url_constrainer'
Rails.application.routes.draw do
concern :access_requestable do
@@ -85,20 +84,6 @@ Rails.application.routes.draw do
root to: "root#index"
- # Since group show page is wildcard routing
- # we want all other routing to be checked before matching this one
- constraints(GroupUrlConstrainer.new) do
- scope(path: '*id',
- as: :group,
- constraints: { id: Gitlab::Regex.namespace_route_regex, format: /(html|json|atom)/ },
- controller: :groups) do
- get '/', action: :show
- patch '/', action: :update
- put '/', action: :update
- delete '/', action: :destroy
- end
- end
-
draw :test if Rails.env.test?
get '*unmatched_route', to: 'application#route_not_found'
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index 48993420ed9..c20581b1333 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -36,7 +36,7 @@ namespace :admin do
scope(path: 'groups/*id',
controller: :groups,
- constraints: { id: Gitlab::Regex.namespace_route_regex, format: /(html|json|atom)/ }) do
+ constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom)/ }) do
scope(as: :group) do
put :members_update
@@ -54,6 +54,12 @@ namespace :admin do
member do
get :test
end
+
+ resources :hook_logs, only: [:show] do
+ member do
+ get :retry
+ end
+ end
end
resources :broadcast_messages, only: [:index, :edit, :create, :update, :destroy] do
@@ -68,10 +74,12 @@ namespace :admin do
resources :projects, only: [:index]
- scope(path: 'projects/*namespace_id', as: :namespace) do
+ scope(path: 'projects/*namespace_id',
+ as: :namespace,
+ constraints: { namespace_id: Gitlab::PathRegex.full_namespace_route_regex }) do
resources(:projects,
path: '/',
- constraints: { id: Gitlab::Regex.project_route_regex },
+ constraints: { id: Gitlab::PathRegex.project_route_regex },
only: [:show]) do
member do
diff --git a/config/routes/git_http.rb b/config/routes/git_http.rb
index 42d874eeebc..a53c94326d4 100644
--- a/config/routes/git_http.rb
+++ b/config/routes/git_http.rb
@@ -1,5 +1,7 @@
-scope(path: '*namespace_id/:project_id', constraints: { format: nil }) do
- scope(constraints: { project_id: Gitlab::Regex.project_git_route_regex }, module: :projects) do
+scope(path: '*namespace_id/:project_id',
+ format: nil,
+ constraints: { namespace_id: Gitlab::PathRegex.full_namespace_route_regex }) do
+ scope(constraints: { project_id: Gitlab::PathRegex.project_git_route_regex }, module: :projects) do
# Git HTTP clients ('git clone' etc.)
scope(controller: :git_http) do
get '/info/refs', action: :info_refs
@@ -26,7 +28,7 @@ scope(path: '*namespace_id/:project_id', constraints: { format: nil }) do
end
# Redirect /group/project/info/refs to /group/project.git/info/refs
- scope(constraints: { project_id: Gitlab::Regex.project_route_regex }) do
+ scope(constraints: { project_id: Gitlab::PathRegex.project_route_regex }) do
# Allow /info/refs, /info/refs?service=git-upload-pack, and
# /info/refs?service=git-receive-pack, but nothing else.
#
diff --git a/config/routes/group.rb b/config/routes/group.rb
index 7b29e0e807c..11cdff55ed8 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -1,9 +1,11 @@
+require 'constraints/group_url_constrainer'
+
resources :groups, only: [:index, :new, :create]
scope(path: 'groups/*group_id',
module: :groups,
as: :group,
- constraints: { group_id: Gitlab::Regex.namespace_route_regex }) do
+ constraints: { group_id: Gitlab::PathRegex.full_namespace_route_regex }) do
resources :group_members, only: [:index, :create, :update, :destroy], concerns: :access_requestable do
post :resend_invite, on: :member
delete :leave, on: :collection
@@ -25,7 +27,7 @@ end
scope(path: 'groups/*id',
controller: :groups,
- constraints: { id: Gitlab::Regex.namespace_route_regex, format: /(html|json|atom)/ }) do
+ constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom)/ }) do
get :edit, as: :edit_group
get :issues, as: :issues_group
get :merge_requests, as: :merge_requests_group
@@ -34,3 +36,15 @@ scope(path: 'groups/*id',
get :subgroups, as: :subgroups_group
get '/', action: :show, as: :group_canonical
end
+
+constraints(GroupUrlConstrainer.new) do
+ scope(path: '*id',
+ as: :group,
+ constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom)/ },
+ controller: :groups) do
+ get '/', action: :show
+ patch '/', action: :update
+ put '/', action: :update
+ delete '/', action: :destroy
+ end
+end
diff --git a/config/routes/profile.rb b/config/routes/profile.rb
index 07c341999ea..3dc890e5785 100644
--- a/config/routes/profile.rb
+++ b/config/routes/profile.rb
@@ -5,6 +5,7 @@ resource :profile, only: [:show, :update] do
put :reset_private_token
put :reset_incoming_email_token
+ put :reset_rss_token
put :update_username
end
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 01b94f9f2b8..bec1f04d1f9 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -5,9 +5,24 @@ resources :projects, only: [:index, :new, :create]
draw :git_http
constraints(ProjectUrlConstrainer.new) do
- scope(path: '*namespace_id', as: :namespace) do
+ # If the route has a wildcard segment, the segment has a regex constraint,
+ # the segment is potentially followed by _another_ wildcard segment, and
+ # the `format` option is not set to false, we need to specify that
+ # regex constraint _outside_ of `constraints: {}`.
+ #
+ # Otherwise, Rails will overwrite the constraint with `/.+?/`,
+ # which breaks some of our wildcard routes like `/blob/*id`
+ # and `/tree/*id` that depend on the negative lookahead inside
+ # `Gitlab::PathRegex.full_namespace_route_regex`, which helps the router
+ # determine whether a certain path segment is part of `*namespace_id`,
+ # `:project_id`, or `*id`.
+ #
+ # See https://github.com/rails/rails/blob/v4.2.8/actionpack/lib/action_dispatch/routing/mapper.rb#L155
+ scope(path: '*namespace_id',
+ as: :namespace,
+ namespace_id: Gitlab::PathRegex.full_namespace_route_regex) do
scope(path: ':project_id',
- constraints: { project_id: Gitlab::Regex.project_route_regex },
+ constraints: { project_id: Gitlab::PathRegex.project_route_regex },
module: :projects,
as: :project) do
@@ -201,6 +216,12 @@ constraints(ProjectUrlConstrainer.new) do
member do
get :test
end
+
+ resources :hook_logs, only: [:show] do
+ member do
+ get :retry
+ end
+ end
end
resources :container_registry, only: [:index, :destroy],
@@ -314,7 +335,7 @@ constraints(ProjectUrlConstrainer.new) do
resources :runner_projects, only: [:create, :destroy]
resources :badges, only: [:index] do
collection do
- scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do
+ scope '*ref', constraints: { ref: Gitlab::PathRegex.git_reference_regex } do
constraints format: /svg/ do
get :build
get :coverage
@@ -337,7 +358,7 @@ constraints(ProjectUrlConstrainer.new) do
resources(:projects,
path: '/',
- constraints: { id: Gitlab::Regex.project_route_regex },
+ constraints: { id: Gitlab::PathRegex.project_route_regex },
only: [:edit, :show, :update, :destroy]) do
member do
put :transfer
diff --git a/config/routes/repository.rb b/config/routes/repository.rb
index 5cf37a06e97..11911636fa7 100644
--- a/config/routes/repository.rb
+++ b/config/routes/repository.rb
@@ -2,7 +2,7 @@
resource :repository, only: [:create] do
member do
- get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex }
+ get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex }
end
end
@@ -24,7 +24,7 @@ scope format: false do
member do
# tree viewer logs
- get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex }
+ get 'logs_tree', constraints: { id: Gitlab::PathRegex.git_reference_regex }
# Directories with leading dots erroneously get rejected if git
# ref regex used in constraints. Regex verification now done in controller.
get 'logs_tree/*path', action: :logs_tree, as: :logs_file, format: false, constraints: {
@@ -34,7 +34,7 @@ scope format: false do
end
end
- scope constraints: { id: Gitlab::Regex.git_reference_regex } do
+ scope constraints: { id: Gitlab::PathRegex.git_reference_regex } do
resources :network, only: [:show]
resources :graphs, only: [:show] do
diff --git a/config/routes/user.rb b/config/routes/user.rb
index b064a15e802..e682dcd6663 100644
--- a/config/routes/user.rb
+++ b/config/routes/user.rb
@@ -11,19 +11,7 @@ devise_scope :user do
get '/users/almost_there' => 'confirmations#almost_there'
end
-constraints(UserUrlConstrainer.new) do
- # Get all keys of user
- get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: Gitlab::Regex.namespace_route_regex }
-
- scope(path: ':username',
- as: :user,
- constraints: { username: Gitlab::Regex.namespace_route_regex },
- controller: :users) do
- get '/', action: :show
- end
-end
-
-scope(constraints: { username: Gitlab::Regex.namespace_route_regex }) do
+scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) do
scope(path: 'users/:username',
as: :user,
controller: :users) do
@@ -34,7 +22,7 @@ scope(constraints: { username: Gitlab::Regex.namespace_route_regex }) do
get :contributed, as: :contributed_projects
get :snippets
get :exists
- get '/', to: redirect('/%{username}')
+ get '/', to: redirect('/%{username}'), as: nil
end
# Compatibility with old routing
@@ -46,3 +34,15 @@ scope(constraints: { username: Gitlab::Regex.namespace_route_regex }) do
get '/u/:username/snippets', to: redirect('/users/%{username}/snippets')
get '/u/:username/contributed', to: redirect('/users/%{username}/contributed')
end
+
+constraints(UserUrlConstrainer.new) do
+ # Get all keys of user
+ get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }
+
+ scope(path: ':username',
+ as: :user,
+ constraints: { username: Gitlab::PathRegex.root_namespace_route_regex },
+ controller: :users) do
+ get '/', action: :show
+ end
+end
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 0ca1f565185..93df2d6f5ff 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -44,9 +44,8 @@
- [project_cache, 1]
- [project_destroy, 1]
- [project_export, 1]
- - [project_web_hook, 1]
+ - [web_hook, 1]
- [repository_check, 1]
- - [system_hook, 1]
- [git_garbage_collect, 1]
- [reactive_caching, 1]
- [cronjob, 1]
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 7bc225968de..ce140b75382 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -5,6 +5,7 @@ var path = require('path');
var webpack = require('webpack');
var StatsPlugin = require('stats-webpack-plugin');
var CompressionPlugin = require('compression-webpack-plugin');
+var NameAllModulesPlugin = require('name-all-modules-plugin');
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
@@ -69,7 +70,8 @@ var config = {
output: {
path: path.join(ROOT_PATH, 'public/assets/webpack'),
publicPath: '/assets/webpack/',
- filename: IS_PRODUCTION ? '[name].[chunkhash].bundle.js' : '[name].bundle.js'
+ filename: IS_PRODUCTION ? '[name].[chunkhash].bundle.js' : '[name].bundle.js',
+ chunkFilename: IS_PRODUCTION ? '[name].[chunkhash].chunk.js' : '[name].chunk.js',
},
devtool: 'cheap-module-source-map',
@@ -100,7 +102,7 @@ var config = {
loader: 'file-loader',
},
{
- test: /locale\/[a-z]+\/(.*)\.js$/,
+ test: /locale\/\w+\/(.*)\.js$/,
loader: 'exports-loader?locales',
},
]
@@ -126,10 +128,20 @@ var config = {
jQuery: 'jquery',
}),
- // use deterministic module ids in all environments
- IS_PRODUCTION ?
- new webpack.HashedModuleIdsPlugin() :
- new webpack.NamedModulesPlugin(),
+ // assign deterministic module ids
+ new webpack.NamedModulesPlugin(),
+ new NameAllModulesPlugin(),
+
+ // assign deterministic chunk ids
+ new webpack.NamedChunksPlugin((chunk) => {
+ if (chunk.name) {
+ return chunk.name;
+ }
+ return chunk.modules.map((m) => {
+ var chunkPath = m.request.split('!').pop();
+ return path.relative(m.context, chunkPath);
+ }).join('_');
+ }),
// create cacheable common library bundle for all vue chunks
new webpack.optimize.CommonsChunkPlugin({
diff --git a/db/migrate/20170317203554_index_routes_path_for_like.rb b/db/migrate/20170317203554_index_routes_path_for_like.rb
index 7ac09b2abe5..8d3609135d0 100644
--- a/db/migrate/20170317203554_index_routes_path_for_like.rb
+++ b/db/migrate/20170317203554_index_routes_path_for_like.rb
@@ -21,9 +21,8 @@ class IndexRoutesPathForLike < ActiveRecord::Migration
def down
return unless Gitlab::Database.postgresql?
+ return unless index_exists?(:routes, :path, name: INDEX_NAME)
- if index_exists?(:routes, :path, name: INDEX_NAME)
- execute("DROP INDEX CONCURRENTLY #{INDEX_NAME};")
- end
+ remove_concurrent_index_by_name(:routes, INDEX_NAME)
end
end
diff --git a/db/migrate/20170402231018_remove_index_for_users_current_sign_in_at.rb b/db/migrate/20170402231018_remove_index_for_users_current_sign_in_at.rb
index 9d4380ef960..84635fa39b9 100644
--- a/db/migrate/20170402231018_remove_index_for_users_current_sign_in_at.rb
+++ b/db/migrate/20170402231018_remove_index_for_users_current_sign_in_at.rb
@@ -11,13 +11,7 @@ class RemoveIndexForUsersCurrentSignInAt < ActiveRecord::Migration
disable_ddl_transaction!
def up
- if index_exists? :users, :current_sign_in_at
- if Gitlab::Database.postgresql?
- execute 'DROP INDEX CONCURRENTLY index_users_on_current_sign_in_at;'
- else
- remove_concurrent_index :users, :current_sign_in_at
- end
- end
+ remove_concurrent_index :users, :current_sign_in_at
end
def down
diff --git a/db/migrate/20170427103502_create_web_hook_logs.rb b/db/migrate/20170427103502_create_web_hook_logs.rb
new file mode 100644
index 00000000000..3643c52180c
--- /dev/null
+++ b/db/migrate/20170427103502_create_web_hook_logs.rb
@@ -0,0 +1,22 @@
+# rubocop:disable all
+class CreateWebHookLogs < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ create_table :web_hook_logs do |t|
+ t.references :web_hook, null: false, index: true, foreign_key: { on_delete: :cascade }
+
+ t.string :trigger
+ t.string :url
+ t.text :request_headers
+ t.text :request_data
+ t.text :response_headers
+ t.text :response_body
+ t.string :response_status
+ t.float :execution_duration
+ t.string :internal_error_message
+
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/db/migrate/20170503185032_index_redirect_routes_path_for_like.rb b/db/migrate/20170503185032_index_redirect_routes_path_for_like.rb
index 5b8b6c828be..8eb20faa03a 100644
--- a/db/migrate/20170503185032_index_redirect_routes_path_for_like.rb
+++ b/db/migrate/20170503185032_index_redirect_routes_path_for_like.rb
@@ -21,9 +21,8 @@ class IndexRedirectRoutesPathForLike < ActiveRecord::Migration
def down
return unless Gitlab::Database.postgresql?
+ return unless index_exists?(:redirect_routes, :path, name: INDEX_NAME)
- if index_exists?(:redirect_routes, :path, name: INDEX_NAME)
- execute("DROP INDEX CONCURRENTLY #{INDEX_NAME};")
- end
+ remove_concurrent_index_by_name(:redirect_routes, INDEX_NAME)
end
end
diff --git a/db/migrate/20170521184006_add_change_position_to_notes.rb b/db/migrate/20170521184006_add_change_position_to_notes.rb
new file mode 100644
index 00000000000..219ed1ade4c
--- /dev/null
+++ b/db/migrate/20170521184006_add_change_position_to_notes.rb
@@ -0,0 +1,13 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddChangePositionToNotes < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ add_column :notes, :change_position, :text
+ end
+end
diff --git a/db/migrate/20170523091700_add_rss_token_to_users.rb b/db/migrate/20170523091700_add_rss_token_to_users.rb
new file mode 100644
index 00000000000..06a85f6ac3d
--- /dev/null
+++ b/db/migrate/20170523091700_add_rss_token_to_users.rb
@@ -0,0 +1,19 @@
+class AddRssTokenToUsers < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column :users, :rss_token, :string
+
+ add_concurrent_index :users, :rss_token
+ end
+
+ def down
+ remove_concurrent_index :users, :rss_token if index_exists? :users, :rss_token
+
+ remove_column :users, :rss_token
+ end
+end
diff --git a/db/post_migrate/20170503004427_upate_retried_for_ci_build.rb b/db/post_migrate/20170503004427_update_retried_for_ci_build.rb
index 738e46b9207..705e11ed47d 100644
--- a/db/post_migrate/20170503004427_upate_retried_for_ci_build.rb
+++ b/db/post_migrate/20170503004427_update_retried_for_ci_build.rb
@@ -1,4 +1,4 @@
-class UpateRetriedForCiBuild < ActiveRecord::Migration
+class UpdateRetriedForCiBuild < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
@@ -54,13 +54,15 @@ class UpateRetriedForCiBuild < ActiveRecord::Migration
def with_temporary_partial_index
if Gitlab::Database.postgresql?
- execute 'CREATE INDEX CONCURRENTLY IF NOT EXISTS index_for_ci_builds_retried_migration ON ci_builds (id) WHERE retried IS NULL;'
+ unless index_exists?(:ci_builds, name: :index_for_ci_builds_retried_migration)
+ execute 'CREATE INDEX CONCURRENTLY index_for_ci_builds_retried_migration ON ci_builds (id) WHERE retried IS NULL;'
+ end
end
yield
- if Gitlab::Database.postgresql?
- execute 'DROP INDEX CONCURRENTLY IF EXISTS index_for_ci_builds_retried_migration'
+ if Gitlab::Database.postgresql? && index_exists?(:ci_builds, name: :index_for_ci_builds_retried_migration)
+ execute 'DROP INDEX CONCURRENTLY index_for_ci_builds_retried_migration'
end
end
end
diff --git a/db/schema.rb b/db/schema.rb
index d14126401c9..f6b513a5725 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170518231126) do
+ActiveRecord::Schema.define(version: 20170523091700) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -794,6 +794,7 @@ ActiveRecord::Schema.define(version: 20170518231126) do
t.string "discussion_id"
t.text "note_html"
t.integer "cached_markdown_version"
+ t.text "change_position"
end
add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree
@@ -1361,6 +1362,7 @@ ActiveRecord::Schema.define(version: 20170518231126) do
t.date "last_activity_on"
t.boolean "notified_of_own_activity"
t.string "preferred_language"
+ t.string "rss_token"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
@@ -1374,6 +1376,7 @@ ActiveRecord::Schema.define(version: 20170518231126) do
add_index "users", ["name"], name: "index_users_on_name", using: :btree
add_index "users", ["name"], name: "index_users_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
+ add_index "users", ["rss_token"], name: "index_users_on_rss_token", using: :btree
add_index "users", ["state"], name: "index_users_on_state", using: :btree
add_index "users", ["username"], name: "index_users_on_username", using: :btree
add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"}
@@ -1388,6 +1391,23 @@ ActiveRecord::Schema.define(version: 20170518231126) do
add_index "users_star_projects", ["project_id"], name: "index_users_star_projects_on_project_id", using: :btree
add_index "users_star_projects", ["user_id", "project_id"], name: "index_users_star_projects_on_user_id_and_project_id", unique: true, using: :btree
+ create_table "web_hook_logs", force: :cascade do |t|
+ t.integer "web_hook_id", null: false
+ t.string "trigger"
+ t.string "url"
+ t.text "request_headers"
+ t.text "request_data"
+ t.text "response_headers"
+ t.text "response_body"
+ t.string "response_status"
+ t.float "execution_duration"
+ t.string "internal_error_message"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ add_index "web_hook_logs", ["web_hook_id"], name: "index_web_hook_logs_on_web_hook_id", using: :btree
+
create_table "web_hooks", force: :cascade do |t|
t.string "url", limit: 2000
t.integer "project_id"
@@ -1451,4 +1471,5 @@ ActiveRecord::Schema.define(version: 20170518231126) do
add_foreign_key "timelogs", "merge_requests", name: "fk_timelogs_merge_requests_merge_request_id", on_delete: :cascade
add_foreign_key "trending_projects", "projects", on_delete: :cascade
add_foreign_key "u2f_registrations", "users"
+ add_foreign_key "web_hook_logs", "web_hooks", on_delete: :cascade
end
diff --git a/doc/README.md b/doc/README.md
index 7bab42bc135..9f12eed1471 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -42,6 +42,9 @@ Shortcuts to GitLab's most visited docs:
- [Create a group](gitlab-basics/create-group.md)
- [GitLab Subgroups](user/group/subgroups/index.md)
- [Search through GitLab](user/search/index.md): Search for issues, merge requests, projects, groups, todos, and issues in Issue Boards.
+- [Snippets](user/snippets.md): Snippets allow you to create little bits of code.
+- [Wikis](user/project/wiki/index.md): Enhance your repository documentation with built-in wikis.
+- [GitLab Pages](user/project/pages/index.md): Build, test, and deploy your static website with GitLab Pages.
### Repository
@@ -83,14 +86,6 @@ Manage files and branches from the UI (user interface):
- [Importing to GitLab](workflow/importing/README.md): Import your projects from GitHub, Bitbucket, GitLab.com, FogBugz and SVN into GitLab.
- [Migrating from SVN](workflow/importing/migrating_from_svn.md): Convert a SVN repository to Git and GitLab.
-## GitLab's superpowers
-
-Take a step ahead and dive into GitLab's advanced features.
-
-- [GitLab Pages](user/project/pages/index.md): Build, test, and deploy your static website with GitLab Pages.
-- [Snippets](user/snippets.md): Snippets allow you to create little bits of code.
-- [Wikis](user/project/wiki/index.md): Enhance your repository documentation with built-in wikis.
-
### Continuous Integration, Delivery, and Deployment
- [GitLab CI](ci/README.md): Explore the features and capabilities of Continuous Integration, Continuous Delivery, and Continuous Deployment with GitLab.
diff --git a/doc/development/i18n_guide.md b/doc/development/i18n_guide.md
index 44eca68aaca..735345bd126 100644
--- a/doc/development/i18n_guide.md
+++ b/doc/development/i18n_guide.md
@@ -7,6 +7,14 @@ For working with internationalization (i18n) we use
tool for this task and we have a lot of applications that will help us to work
with it.
+## Setting up GitLab Development Kit (GDK)
+
+In order to be able to work on the [GitLab Community Edition](https://gitlab.com/gitlab-org/gitlab-ce) project we must download and
+configure it through [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit), we can do it by following this [guide](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/set-up-gdk.md).
+
+Once we have the GitLab project ready we can start working on the
+translation of the project.
+
## Tools
We use a couple of gems:
@@ -211,9 +219,11 @@ Let's suppose you want to add translations for a new language, let's say French.
you just need to separate the region with an underscore (`_`). For example:
```sh
- bundle exec rake gettext:add_language[en_gb]
+ bundle exec rake gettext:add_language[en_GB]
```
+ Please note that you need to specify the region part in capitals.
+
1. Now that the language is added, a new directory has been created under the
path: `locale/fr/`. You can now start using your PO editor to edit the PO file
located in: `locale/fr/gitlab.edit.po`.
diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md
index da2dac23c6a..9a171d34671 100644
--- a/doc/install/database_mysql.md
+++ b/doc/install/database_mysql.md
@@ -281,5 +281,5 @@ GitLab database to `longtext` columns, which can persist values of up to 4GB
Details can be found in the [PostgreSQL][postgres-text-type] and
[MySQL][mysql-text-types] manuals.
-[postgres-text-type]: http://www.postgresql.org/docs/9.1/static/datatype-character.html
+[postgres-text-type]: http://www.postgresql.org/docs/9.2/static/datatype-character.html
[mysql-text-types]: http://dev.mysql.com/doc/refman/5.7/en/string-type-overview.html
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 5bba405f159..cda70b78c61 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -185,7 +185,8 @@ Create a `git` user for GitLab:
We recommend using a PostgreSQL database. For MySQL check the
[MySQL setup guide](database_mysql.md).
-> **Note**: because we need to make use of extensions you need at least pgsql 9.1.
+> **Note**: because we need to make use of extensions and concurrent index removal,
+you need at least PostgreSQL 9.2.
1. Install the database packages:
diff --git a/doc/install/kubernetes/gitlab_chart.md b/doc/install/kubernetes/gitlab_chart.md
index 2d7edbe16e4..b4ffd57afbb 100644
--- a/doc/install/kubernetes/gitlab_chart.md
+++ b/doc/install/kubernetes/gitlab_chart.md
@@ -1,4 +1,7 @@
# GitLab Helm Chart
+> Officially supported cloud providers are Google Container Service and Azure Container Service.
+
+> Officially supported schedulers are Kubernetes and Terraform.
The `gitlab` Helm chart deploys GitLab into your Kubernetes cluster.
@@ -14,7 +17,7 @@ This chart includes the following:
## Prerequisites
-- _At least_ 3 GB of RAM available on your cluster, in chunks of 1 GB
+- _At least_ 3 GB of RAM available on your cluster, in chunks of 1 GB. 41GB of storage and 2 CPU are also required.
- Kubernetes 1.4+ with Beta APIs enabled
- [Persistent Volume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) provisioner support in the underlying infrastructure
- The ability to point a DNS entry or URL at your GitLab install
@@ -203,9 +206,43 @@ its class in an annotation.
>**Note:**
The Ingress alone doesn't expose GitLab externally. You need to have a Ingress controller setup to do that.
-Setting up an Ingress controller can be as simple as installing the `nginx-ingress` helm chart. But be sure
+Setting up an Ingress controller can be done by installing the `nginx-ingress` helm chart. But be sure
to read the [documentation](https://github.com/kubernetes/charts/blob/master/stable/nginx-ingress/README.md)
+#### Preserving Source IPs
+
+If you are using the `LoadBalancer` serviceType you may run into issues where user IP addresses in the GitLab
+logs, and used in abuse throttling are not accurate. This is due to how Kubernetes uses source NATing on cluster nodes without endpoints.
+
+See the [Kubernetes documentation](https://kubernetes.io/docs/tutorials/services/source-ip/#source-ip-for-services-with-typeloadbalancer) for more information.
+
+To fix this you can add the following service annotation to your `values.yaml`
+
+```yaml
+## For minikube, set this to NodePort, elsewhere use LoadBalancer
+## ref: http://kubernetes.io/docs/user-guide/services/#publishing-services---service-types
+##
+serviceType: LoadBalancer
+
+## Optional annotations for gitlab service.
+serviceAnnotations:
+ service.beta.kubernetes.io/external-traffic: "OnlyLocal"
+```
+
+>**Note:**
+If you are using the ingress routing, you will likely also need to specify the annotation on the service for the ingress
+controller. For `nginx-ingress` you can check the
+[configuration documentation](https://github.com/kubernetes/charts/blob/master/stable/nginx-ingress/README.md#configuration)
+on how to add the annotation to the `controller.service.annotations` array.
+
+>**Note:**
+When using the `nginx-ingress` controller on Google Container Engine (GKE), and using the `external-traffic` annotation,
+you will need to additionally set the `controller.kind` to be DaemonSet. Otherwise only pods running on the same node
+as the nginx controller will be able to reach GitLab. This may result in pods within your cluster not being able to reach GitLab.
+See the [Kubernetes documentation](https://kubernetes.io/docs/tutorials/services/source-ip/#source-ip-for-services-with-typeloadbalancer) and
+[nginx-ingress configuration documentation](https://github.com/kubernetes/charts/blob/master/stable/nginx-ingress/README.md#configuration)
+for more information.
+
### External database
You can configure the GitLab Helm chart to connect to an external PostgreSQL
@@ -387,6 +424,7 @@ ingress:
```
## Installing GitLab using the Helm Chart
+> You may see a temporary error message `SchedulerPredicates failed due to PersistentVolumeClaim is not bound` while storage provisions. Once the storage provisions, the pods will automatically restart. This may take a couple minutes depending on your cloud provider. If the error persists, please review the [prerequisites](#prerequisites) to ensure you have enough RAM, CPU, and storage.
Once you [have configured](#configuration) GitLab in your `values.yml` file,
run the following:
diff --git a/doc/install/kubernetes/gitlab_runner_chart.md b/doc/install/kubernetes/gitlab_runner_chart.md
index dbd9ae3f70c..305b4593c73 100644
--- a/doc/install/kubernetes/gitlab_runner_chart.md
+++ b/doc/install/kubernetes/gitlab_runner_chart.md
@@ -1,4 +1,7 @@
# GitLab Runner Helm Chart
+> Officially supported cloud providers are Google Container Service and Azure Container Service.
+
+> Officially supported schedulers are Kubernetes and Terraform.
The `gitlab-runner` Helm chart deploys a GitLab Runner instance into your
Kubernetes cluster.
diff --git a/doc/install/kubernetes/index.md b/doc/install/kubernetes/index.md
index cae5837a12b..88c56a1d17c 100644
--- a/doc/install/kubernetes/index.md
+++ b/doc/install/kubernetes/index.md
@@ -1,5 +1,6 @@
-# Installing GitLab in Kubernetes
+# Installing GitLab on Kubernetes
> Officially supported cloud providers are Google Container Service and Azure Container Service.
+
> Officially supported schedulers are Kubernetes and Terraform.
The easiest method to deploy GitLab in [Kubernetes](https://kubernetes.io/) is
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 2e456557d77..5338ccb9d3a 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -138,7 +138,7 @@ installation (e.g. the number of users, projects, etc).
### PostgreSQL Requirements
-As of GitLab 9.0, PostgreSQL 9.2 or newer is required, and earlier versions are
+As of GitLab 9.3, PostgreSQL 9.2 or newer is required, and earlier versions are
not supported. We highly recommend users to use at least PostgreSQL 9.6 as this
is the PostgreSQL version used for development and testing.
diff --git a/doc/integration/github.md b/doc/integration/github.md
index de9aedbc596..b0d67db8b59 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -114,7 +114,8 @@ If everything goes well the user will be returned to GitLab and will be signed i
If you are attempting to import projects from GitHub Enterprise with a self-signed
certificate and the imports are failing, you will need to disable SSL verification.
-It should be disabled by adding `verify_ssl` to `false` to the provider configuration.
+It should be disabled by adding `verify_ssl` to `false` in the provider configuration
+and changing the global Git `sslVerify` option to `false` in the GitLab server.
For omnibus package:
@@ -131,6 +132,12 @@ For omnibus package:
]
```
+You will also need to disable Git SSL verification on the server hosting GitLab.
+
+```ruby
+omnibus_gitconfig['system'] = { "http" => ["sslVerify = false"] }
+```
+
For installation from source:
```
@@ -141,16 +148,14 @@ For installation from source:
args: { scope: 'user:email' } }
```
-
-For the changes to take effect, [reconfigure Gitlab] if you installed
-via Omnibus, or [restart GitLab] if you installed from source.
-
-You will also need to disable Git SSL verification on the server hosting GitLab with the following command:
+You will also need to disable Git SSL verification on the server hosting GitLab.
```
$ git config --global http.sslVerify false
```
-[reconfigure GitLab]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
-[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
+For the changes to take effect, [reconfigure Gitlab] if you installed
+via Omnibus, or [restart GitLab] if you installed from source.
+[reconfigure GitLab]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
+[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
diff --git a/doc/update/9.0-to-9.1.md b/doc/update/9.0-to-9.1.md
index 2b582d4eefd..2d597894517 100644
--- a/doc/update/9.0-to-9.1.md
+++ b/doc/update/9.0-to-9.1.md
@@ -104,7 +104,6 @@ cd /home/git/gitlab-shell
sudo -u git -H git fetch --all --tags
sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
-sudo -u git -H bin/compile
```
### 7. Update gitlab-workhorse
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index a4726673fc4..d5edf36f6b0 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -71,9 +71,9 @@ structure.
- You need to be an Owner of a group in order to be able to create
a subgroup. For more information check the [permissions table][permissions].
- For a list of words that are not allowed to be used as group names see the
- [`dynamic_path_validator.rb` file][reserved] under the `TOP_LEVEL_ROUTES`, `WILDCARD_ROUTES` and `GROUP_ROUTES` lists:
+ [`path_regex.rb` file][reserved] under the `TOP_LEVEL_ROUTES`, `PROJECT_WILDCARD_ROUTES` and `GROUP_ROUTES` lists:
- `TOP_LEVEL_ROUTES`: are names that are reserved as usernames or top level groups
- - `WILDCARD_ROUTES`: are names that are reserved for child groups or projects.
+ - `PROJECT_WILDCARD_ROUTES`: are names that are reserved for child groups or projects.
- `GROUP_ROUTES`: are names that are reserved for all groups or projects.
To create a subgroup:
@@ -163,4 +163,4 @@ Here's a list of what you can't do with subgroups:
[ce-2772]: https://gitlab.com/gitlab-org/gitlab-ce/issues/2772
[permissions]: ../../permissions.md#group
-[reserved]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/validators/dynamic_path_validator.rb
+[reserved]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/path_regex.rb
diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md
index e5038835027..f2ad42f21fd 100644
--- a/doc/user/profile/preferences.md
+++ b/doc/user/profile/preferences.md
@@ -50,15 +50,15 @@ You have 6 options here that you can use for your default dashboard view:
- Your groups
- Your [Todos]
-### Default project view
+### Project home page content
-The default project view settings allows you to choose what content you want to
-see on a project's landing page.
+The project home page content setting allows you to choose what content you want to
+see on a project’s home page.
You can choose between 2 options:
- Show the files and the readme (default)
-- Show the project's activity
+- Show the project’s activity
[rouge]: http://rouge.jneen.net/ "Rouge website"
[todos]: ../../workflow/todos.md
diff --git a/doc/user/project/integrations/img/webhook_logs.png b/doc/user/project/integrations/img/webhook_logs.png
new file mode 100755
index 00000000000..917068d9398
--- /dev/null
+++ b/doc/user/project/integrations/img/webhook_logs.png
Binary files differ
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index f611029afdc..a048260b033 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -97,7 +97,8 @@ in the table below.
| Field | Description |
| ----- | ----------- |
-| `URL` | The base URL to the JIRA project which is being linked to this GitLab project. E.g., `https://jira.example.com`. |
+| `Web URL` | The base URL to the JIRA instance web interface which is being linked to this GitLab project. E.g., `https://jira.example.com`. |
+| `JIRA API URL` | The base URL to the JIRA instance API. Web URL value will be used if not set. E.g., `https://jira-api.example.com`. |
| `Project key` | The short identifier for your JIRA project, all uppercase, e.g., `PROJ`. |
| `Username` | The user name created in [configuring JIRA step](#configuring-jira). |
| `Password` |The password of the user created in [configuring JIRA step](#configuring-jira). |
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 48d49c5d40c..d0bb1cd11a8 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -1017,6 +1017,22 @@ X-Gitlab-Event: Build Hook
}
```
+## Troubleshoot webhooks
+
+Gitlab stores each perform of the webhook.
+You can find records for last 2 days in "Recent Deliveries" section on the edit page of each webhook.
+
+![Recent deliveries](img/webhook_logs.png)
+
+In this section you can see HTTP status code (green for 200-299 codes, red for the others, `internal error` for failed deliveries ), triggered event, a time when the event was called, elapsed time of the request.
+
+If you need more information about execution, you can click `View details` link.
+On this page, you can see data that GitLab sends (request headers and body) and data that it received (response headers and body).
+
+From this page, you can repeat delivery with the same data by clicking `Resend Request` button.
+
+>**Note:** If URL or secret token of the webhook were updated, data will be delivered to the new address.
+
## Example webhook receiver
If you want to see GitLab's webhooks in action for testing purposes you can use
diff --git a/doc/user/project/milestones/img/progress.png b/doc/user/project/milestones/img/progress.png
new file mode 100644
index 00000000000..c85aecca729
--- /dev/null
+++ b/doc/user/project/milestones/img/progress.png
Binary files differ
diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md
index a43a42a8fe8..99233ed5ae2 100644
--- a/doc/user/project/milestones/index.md
+++ b/doc/user/project/milestones/index.md
@@ -44,3 +44,11 @@ special options available when filtering by milestone:
* **Started** - show issues or merge requests from any milestone with a start
date less than today. Note that this can return results from several
milestones in the same project.
+
+## Milestone progress statistics
+
+Milestone statistics can be viewed in the milestone sidebar. The milestone percentage statistic
+is calculated as; closed and merged merge requests plus all closed issues divided by
+total merge requests and issues.
+
+![Milestone statistics](img/progress.png)
diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md
index 3a6773909d6..d768b73286d 100644
--- a/doc/workflow/lfs/lfs_administration.md
+++ b/doc/workflow/lfs/lfs_administration.md
@@ -50,7 +50,7 @@ and [projects APIs](../../api/projects.md).
* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets)
is not supported
-* Currently, removing LFS objects from GitLab Git LFS storage is not supported
+* Support for removing unreferenced LFS objects was added in 8.14 onwards.
* LFS authentications via SSH was added with GitLab 8.12
* Only compatible with the GitLFS client versions 1.1.0 and up, or 1.0.2.
* The storage statistics currently count each LFS object multiple times for
diff --git a/features/project/hooks.feature b/features/project/hooks.feature
deleted file mode 100644
index 627738004c4..00000000000
--- a/features/project/hooks.feature
+++ /dev/null
@@ -1,37 +0,0 @@
-Feature: Project Hooks
- Background:
- Given I sign in as a user
- And I own project "Shop"
-
- Scenario: I should see hook list
- Given project has hook
- When I visit project hooks page
- Then I should see project hook
-
- Scenario: I add new hook
- Given I visit project hooks page
- When I submit new hook
- Then I should see newly created hook
-
- Scenario: I add new hook with SSL verification enabled
- Given I visit project hooks page
- When I submit new hook with SSL verification enabled
- Then I should see newly created hook with SSL verification enabled
-
- Scenario: I test hook
- Given project has hook
- And I visit project hooks page
- When I click test hook button
- Then hook should be triggered
-
- Scenario: I test a hook on empty project
- Given I own empty project with hook
- And I visit project hooks page
- When I click test hook button
- Then I should see hook error message
-
- Scenario: I test a hook on down URL
- Given project has hook
- And I visit project hooks page
- When I click test hook button with invalid URL
- Then I should see hook service down error message
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index bf09d7b7114..71c69a4fdea 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -22,7 +22,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
end
step 'I click "Create merge request" link' do
- click_link "Create merge request"
+ find_link("Create merge request", visible: false).trigger('click')
end
step 'I see prefilled new Merge Request page' do
diff --git a/features/steps/dashboard/event_filters.rb b/features/steps/dashboard/event_filters.rb
index ca3cd0ecc4e..a745254cc31 100644
--- a/features/steps/dashboard/event_filters.rb
+++ b/features/steps/dashboard/event_filters.rb
@@ -1,5 +1,5 @@
class Spinach::Features::EventFilters < Spinach::FeatureSteps
- include WaitForAjax
+ include WaitForRequests
include SharedAuthentication
include SharedPaths
include SharedProject
@@ -73,20 +73,20 @@ class Spinach::Features::EventFilters < Spinach::FeatureSteps
end
When 'I click "push" event filter' do
- wait_for_ajax
+ wait_for_requests
click_link("Push events")
- wait_for_ajax
+ wait_for_requests
end
When 'I click "team" event filter' do
- wait_for_ajax
+ wait_for_requests
click_link("Team")
- wait_for_ajax
+ wait_for_requests
end
When 'I click "merge" event filter' do
- wait_for_ajax
+ wait_for_requests
click_link("Merge events")
- wait_for_ajax
+ wait_for_requests
end
end
diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb
index 14c13c4818a..4a33babe3bd 100644
--- a/features/steps/dashboard/todos.rb
+++ b/features/steps/dashboard/todos.rb
@@ -3,7 +3,7 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
include SharedPaths
include SharedProject
include SharedUser
- include WaitForAjax
+ include WaitForRequests
step '"John Doe" is a developer of project "Shop"' do
project.team << [john_doe, :developer]
@@ -138,9 +138,9 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
end
step 'I should be directed to the corresponding page' do
- page.should have_css('.identifier', text: 'Merge Request !1')
+ page.should have_css('.identifier', text: 'Merge request !1')
# Merge request page loads and issues a number of Ajax requests
- wait_for_ajax
+ wait_for_requests
end
def should_see_todo(position, title, body, state: :pending)
diff --git a/features/steps/group/members.rb b/features/steps/group/members.rb
index b04a7015d4e..0ab1012660c 100644
--- a/features/steps/group/members.rb
+++ b/features/steps/group/members.rb
@@ -1,5 +1,5 @@
class Spinach::Features::GroupMembers < Spinach::FeatureSteps
- include WaitForAjax
+ include WaitForRequests
include SharedAuthentication
include SharedPaths
include SharedGroup
@@ -58,7 +58,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps
click_link 'Developer'
end
- wait_for_ajax
+ wait_for_requests
end
end
diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb
index 0b0983f0d06..0542b06c0ab 100644
--- a/features/steps/group/milestones.rb
+++ b/features/steps/group/milestones.rb
@@ -1,5 +1,5 @@
class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
- include WaitForAjax
+ include WaitForRequests
include SharedAuthentication
include SharedPaths
include SharedGroup
@@ -91,7 +91,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
end
step 'I should see the list of labels' do
- wait_for_ajax
+ wait_for_requests
page.within('#tab-labels') do
expect(page).to have_content 'bug'
diff --git a/features/steps/project/builds/artifacts.rb b/features/steps/project/builds/artifacts.rb
index 89132ff068f..4b72355b125 100644
--- a/features/steps/project/builds/artifacts.rb
+++ b/features/steps/project/builds/artifacts.rb
@@ -3,7 +3,7 @@ class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps
include SharedProject
include SharedBuilds
include RepoHelpers
- include WaitForAjax
+ include WaitForRequests
step 'I click artifacts download button' do
click_link 'Download'
@@ -79,7 +79,7 @@ class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps
step 'I click a link to file within build artifacts' do
page.within('.tree-table') { find_link('ci_artifacts.txt').click }
- wait_for_ajax
+ wait_for_requests
end
step 'I see a download link' do
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index 29055373a57..25514eb9ef2 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -4,8 +4,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
include SharedNote
include SharedPaths
include Select2Helper
- include WaitForVueResource
- include WaitForAjax
+ include WaitForRequests
step 'I am a member of project "Shop"' do
@project = ::Project.find_by(name: "Shop")
@@ -34,7 +33,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
expect(page).to have_content @merge_request.source_branch
expect(page).to have_content @merge_request.target_branch
- wait_for_vue_resource
+ wait_for_requests
end
step 'I fill out a "Merge Request On Forked Project" merge request' do
@@ -48,7 +47,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
first('.dropdown-target-project a', text: @project.path_with_namespace)
first('.js-source-branch').click
- wait_for_ajax
+ wait_for_requests
first('.dropdown-source-branch .dropdown-content a', text: 'fix').click
click_button "Compare branches and continue"
diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb
deleted file mode 100644
index 945d58a6458..00000000000
--- a/features/steps/project/hooks.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-require 'webmock'
-
-class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedPaths
- include RSpec::Matchers
- include RSpec::Mocks::ExampleMethods
- include WebMock::API
-
- step 'project has hook' do
- @hook = create(:project_hook, project: current_project)
- end
-
- step 'I own empty project with hook' do
- @project = create(:empty_project,
- name: 'Empty Project', namespace: @user.namespace)
- @hook = create(:project_hook, project: current_project)
- end
-
- step 'I should see project hook' do
- expect(page).to have_content @hook.url
- end
-
- step 'I submit new hook' do
- @url = 'http://example.org/1'
- fill_in "hook_url", with: @url
- expect { click_button "Add webhook" }.to change(ProjectHook, :count).by(1)
- end
-
- step 'I submit new hook with SSL verification enabled' do
- @url = 'http://example.org/2'
- fill_in "hook_url", with: @url
- check "hook_enable_ssl_verification"
- expect { click_button "Add webhook" }.to change(ProjectHook, :count).by(1)
- end
-
- step 'I should see newly created hook' do
- expect(current_path).to eq namespace_project_settings_integrations_path(current_project.namespace, current_project)
- expect(page).to have_content(@url)
- end
-
- step 'I should see newly created hook with SSL verification enabled' do
- expect(current_path).to eq namespace_project_settings_integrations_path(current_project.namespace, current_project)
- expect(page).to have_content(@url)
- expect(page).to have_content("SSL Verification: enabled")
- end
-
- step 'I click test hook button' do
- stub_request(:post, @hook.url).to_return(status: 200)
- click_link 'Test'
- end
-
- step 'I click test hook button with invalid URL' do
- stub_request(:post, @hook.url).to_raise(SocketError)
- click_link 'Test'
- end
-
- step 'hook should be triggered' do
- expect(current_path).to eq namespace_project_settings_integrations_path(current_project.namespace, current_project)
- expect(page).to have_selector '.flash-notice',
- text: 'Hook executed successfully: HTTP 200'
- end
-
- step 'I should see hook error message' do
- expect(page).to have_selector '.flash-alert',
- text: 'Hook execution failed. '\
- 'Ensure the project has commits.'
- end
-
- step 'I should see hook service down error message' do
- expect(page).to have_selector '.flash-alert',
- text: 'Hook execution failed: Exception from'
- end
-end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 8133760e619..54b6352c952 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -7,11 +7,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
include SharedMarkdown
include SharedDiffNote
include SharedUser
- include WaitForAjax
- include WaitForVueResource
+ include WaitForRequests
after do
- wait_for_ajax if javascript_test?
+ wait_for_requests if javascript_test?
end
step 'I click link "New Merge Request"' do
@@ -46,23 +45,23 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
page.within '.merge-request' do
expect(page).to have_content "Wiki Feature"
end
- wait_for_vue_resource
+ wait_for_requests
end
step 'I should see closed merge request "Bug NS-04"' do
expect(page).to have_content "Bug NS-04"
expect(page).to have_content "Closed by"
- wait_for_vue_resource
+ wait_for_requests
end
step 'I should see merge request "Bug NS-04"' do
expect(page).to have_content "Bug NS-04"
- wait_for_vue_resource
+ wait_for_requests
end
step 'I should see merge request "Feature NS-05"' do
expect(page).to have_content "Feature NS-05"
- wait_for_vue_resource
+ wait_for_requests
end
step 'I should not see "master" branch' do
@@ -99,7 +98,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I click button "Unsubscribe"' do
click_on "Unsubscribe"
- wait_for_ajax
+ wait_for_requests
end
step 'I click link "Close"' do
@@ -353,7 +352,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see a discussion by user "John Doe" has started on diff' do
# Trigger a refresh of notes
execute_script("$(document).trigger('visibilitychange');")
- wait_for_ajax
+ wait_for_requests
page.within(".notes .discussion") do
page.should have_content "#{user_exists("John Doe").name} #{user_exists("John Doe").to_reference} started a discussion"
page.should have_content sample_commit.line_code_path
@@ -363,12 +362,12 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see a badge of "1" next to the discussion link' do
expect_discussion_badge_to_have_counter("1")
- wait_for_vue_resource
+ wait_for_requests
end
step 'I should see a badge of "0" next to the discussion link' do
expect_discussion_badge_to_have_counter("0")
- wait_for_vue_resource
+ wait_for_requests
end
step 'I should see a discussion has started on commit diff' do
@@ -376,7 +375,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
page.should have_content "#{current_user.name} #{current_user.to_reference} started a discussion on commit"
page.should have_content sample_commit.line_code_path
page.should have_content "Line is wrong"
- wait_for_vue_resource
+ wait_for_requests
end
end
@@ -384,7 +383,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
page.within(".notes .discussion") do
page.should have_content "#{current_user.name} #{current_user.to_reference} started a discussion on commit"
page.should have_content "One comment to rule them all"
- wait_for_vue_resource
+ wait_for_requests
end
end
@@ -410,7 +409,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see merged request' do
page.within '.status-box' do
expect(page).to have_content "Merged"
- wait_for_vue_resource
+ wait_for_requests
end
end
@@ -422,7 +421,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
page.within '.status-box' do
expect(page).to have_content "Open"
end
- wait_for_vue_resource
+ wait_for_requests
end
step 'I click link "Hide inline discussion" of the third file' do
@@ -446,7 +445,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see a comment like "Line is wrong" in the third file' do
page.within '.files>div:nth-child(3) .note-body > .note-text' do
expect(page).to have_visible_content "Line is wrong"
- wait_for_vue_resource
+ wait_for_requests
end
end
@@ -470,7 +469,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
click_button "Comment"
end
- wait_for_ajax
+ wait_for_requests
page.within ".files>div:nth-child(2) .note-body > .note-text" do
expect(page).to have_content "Line is correct"
@@ -485,7 +484,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
click_button "Comment"
end
- wait_for_ajax
+ wait_for_requests
end
step 'I should still see a comment like "Line is correct" in the second file' do
@@ -514,7 +513,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see comments on the side-by-side diff page' do
page.within '.files>div:nth-child(2) .parallel .note-body > .note-text' do
expect(page).to have_visible_content "Line is correct"
- wait_for_vue_resource
+ wait_for_requests
end
end
@@ -538,7 +537,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see new target branch changes' do
expect(page).to have_content 'Request to merge fix into feature'
expect(page).to have_content 'changed target branch from merge-test to feature'
- wait_for_ajax
+ wait_for_requests
end
step 'I click on "Email Patches"' do
@@ -556,8 +555,14 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step '"Bug NS-05" has CI status' do
project = merge_request.source_project
project.enable_ci
- pipeline = create :ci_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch
- merge_request.update(head_pipeline: pipeline)
+
+ pipeline =
+ create(:ci_pipeline,
+ project: project,
+ sha: merge_request.diff_head_sha,
+ ref: merge_request.source_branch,
+ head_pipeline_of: merge_request)
+
create :ci_build, pipeline: pipeline
end
@@ -572,7 +577,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
expect(page).to have_content /([0-9]+ commits behind)/
end
- wait_for_vue_resource
+ wait_for_requests
end
step 'I should not see the diverged commits count' do
@@ -580,7 +585,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
expect(page).not_to have_content /([0-9]+ commit[s]? behind)/
end
- wait_for_vue_resource
+ wait_for_requests
end
def merge_request
@@ -597,7 +602,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
click_button "Comment"
end
- wait_for_ajax
+ wait_for_requests
page.within(".notes_holder", visible: true) do
expect(page).to have_content message
diff --git a/features/steps/project/merge_requests/acceptance.rb b/features/steps/project/merge_requests/acceptance.rb
index cdaa849308c..870dc862992 100644
--- a/features/steps/project/merge_requests/acceptance.rb
+++ b/features/steps/project/merge_requests/acceptance.rb
@@ -1,7 +1,7 @@
class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
include LoginHelpers
include GitlabRoutingHelper
- include WaitForVueResource
+ include WaitForRequests
step 'I am on the Merge Request detail page' do
visit merge_request_path(@merge_request)
@@ -28,7 +28,7 @@ class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
# Wait for View Resource requests to complete so they don't blow up if they are
# only handled after `DatabaseCleaner` has already run
- wait_for_vue_resource
+ wait_for_requests
end
step 'I should not see the Remove Source Branch button' do
@@ -36,7 +36,7 @@ class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
# Wait for View Resource requests to complete so they don't blow up if they are
# only handled after `DatabaseCleaner` has already run
- wait_for_vue_resource
+ wait_for_requests
end
step 'There is an open Merge Request' do
diff --git a/features/steps/project/merge_requests/revert.rb b/features/steps/project/merge_requests/revert.rb
index aa76d6f8c48..98d990f112f 100644
--- a/features/steps/project/merge_requests/revert.rb
+++ b/features/steps/project/merge_requests/revert.rb
@@ -1,7 +1,7 @@
class Spinach::Features::RevertMergeRequests < Spinach::FeatureSteps
include LoginHelpers
include GitlabRoutingHelper
- include WaitForVueResource
+ include WaitForRequests
step 'I click on the revert button' do
find("a[href='#modal-revert-commit']").click
@@ -16,7 +16,7 @@ class Spinach::Features::RevertMergeRequests < Spinach::FeatureSteps
step 'I should see the revert merge request notice' do
page.should have_content('The merge request has been successfully reverted.')
- wait_for_vue_resource
+ wait_for_requests
end
step 'I should not see the revert button' do
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 9c2196a8ef7..de32c9afcca 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -2,7 +2,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
- include WaitForAjax
+ include WaitForRequests
step 'change project settings' do
fill_in 'project_name_edit', with: 'NewName'
@@ -87,7 +87,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should see project "Shop" README' do
- wait_for_ajax
+ wait_for_requests
page.within('.readme-holder') do
expect(page).to have_content 'testme'
end
diff --git a/features/steps/project/project_milestone.rb b/features/steps/project/project_milestone.rb
index dc1190b7eea..a7d3352b8c4 100644
--- a/features/steps/project/project_milestone.rb
+++ b/features/steps/project/project_milestone.rb
@@ -2,7 +2,7 @@ class Spinach::Features::ProjectMilestone < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedPaths
- include WaitForAjax
+ include WaitForRequests
step 'milestone has issue "Bugfix1" with labels: "bug", "feature"' do
project = Project.find_by(name: "Shop")
@@ -35,7 +35,7 @@ class Spinach::Features::ProjectMilestone < Spinach::FeatureSteps
end
step 'I should see the labels "bug", "enhancement" and "feature"' do
- wait_for_ajax
+ wait_for_requests
page.within('#tab-issues') do
expect(page).to have_content 'bug'
diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb
index 3c0d987e403..66368a159ec 100644
--- a/features/steps/project/services.rb
+++ b/features/steps/project/services.rb
@@ -178,7 +178,8 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end
step 'I fill jira settings' do
- fill_in 'URL', with: 'http://jira.example'
+ fill_in 'Web URL', with: 'http://jira.example'
+ fill_in 'JIRA API URL', with: 'http://jira.example/api'
fill_in 'Username', with: 'gitlab'
fill_in 'Password', with: 'gitlab'
fill_in 'Project Key', with: 'GITLAB'
@@ -186,7 +187,8 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end
step 'I should see jira service settings saved' do
- expect(find_field('URL').value).to eq 'http://jira.example'
+ expect(find_field('Web URL').value).to eq 'http://jira.example'
+ expect(find_field('JIRA API URL').value).to eq 'http://jira.example/api'
expect(find_field('Username').value).to eq 'gitlab'
expect(find_field('Project Key').value).to eq 'GITLAB'
end
diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb
index 60febd20104..e3f5e9e3ef3 100644
--- a/features/steps/project/snippets.rb
+++ b/features/steps/project/snippets.rb
@@ -3,7 +3,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
include SharedProject
include SharedNote
include SharedPaths
- include WaitForAjax
+ include WaitForRequests
step 'project "Shop" have "Snippet one" snippet' do
create(:project_snippet,
@@ -59,7 +59,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
find('.ace_editor').native.send_keys 'Content of snippet three'
end
click_button "Create snippet"
- wait_for_ajax
+ wait_for_requests
end
step 'I should see snippet "Snippet three"' do
@@ -81,7 +81,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
fill_in "note_note", with: "Good snippet!"
click_button "Comment"
end
- wait_for_ajax
+ wait_for_requests
end
step 'I should see comment "Good snippet!"' do
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index ef09bddddd8..6efd4374b32 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -4,7 +4,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
include SharedProject
include SharedPaths
include RepoHelpers
- include WaitForAjax
+ include WaitForRequests
step "I don't have write access" do
@project = create(:project, :repository, name: "Other Project", path: "other-project")
@@ -37,12 +37,12 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I should see its content' do
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content old_gitignore_content
end
step 'I should see its new content' do
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content new_gitignore_content
end
diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb
index ada0ff20585..0fee158d590 100644
--- a/features/steps/project/source/markdown_render.rb
+++ b/features/steps/project/source/markdown_render.rb
@@ -5,7 +5,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedMarkdown
- include WaitForAjax
+ include WaitForRequests
step 'I own project "Delta"' do
@project = ::Project.find_by(name: "Delta")
@@ -35,7 +35,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'I should see correct document rendered' do
expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content "All API requests require authentication"
end
@@ -65,7 +65,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'I should see correct maintenance file rendered' do
expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/raketasks/maintenance.md")
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content "bundle exec rake gitlab:env:info RAILS_ENV=production"
end
@@ -97,7 +97,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'I see correct file rendered' do
expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content "Contents"
expect(page).to have_link "Users"
expect(page).to have_link "Rake tasks"
@@ -120,7 +120,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
When 'I visit markdown branch' do
visit namespace_project_tree_path(@project.namespace, @project, "markdown")
- wait_for_ajax
+ wait_for_requests
end
When 'I visit markdown branch "README.md" blob' do
@@ -143,7 +143,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'I see correct file rendered in markdown branch' do
expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content "Contents"
expect(page).to have_link "Users"
expect(page).to have_link "Rake tasks"
@@ -151,7 +151,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'I should see correct document rendered for markdown branch' do
expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md")
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content "All API requests require authentication"
end
@@ -169,7 +169,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
# Expected link contents
step 'The link with text "empty" should have url "tree/markdown"' do
- wait_for_ajax
+ wait_for_requests
find('a', text: /^empty$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown")
end
@@ -205,7 +205,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
end
step 'The link with text "ID" should have url "blob/markdown/README.mdID"' do
- wait_for_ajax
+ wait_for_requests
find('a', text: /^#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id'
end
@@ -300,12 +300,12 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps
step 'I should see the correct markdown' do
expect(current_path).to eq namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md")
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content "List users"
end
step 'Header "Application details" should have correct id and link' do
- wait_for_ajax
+ wait_for_requests
header_should_have_correct_id_and_link(2, 'Application details', 'application-details')
end
diff --git a/features/steps/shared/active_tab.rb b/features/steps/shared/active_tab.rb
index 8bae80a8707..af5db05e9e8 100644
--- a/features/steps/shared/active_tab.rb
+++ b/features/steps/shared/active_tab.rb
@@ -1,9 +1,9 @@
module SharedActiveTab
include Spinach::DSL
- include WaitForAjax
+ include WaitForRequests
after do
- wait_for_ajax if javascript_test?
+ wait_for_requests if javascript_test?
end
def ensure_active_main_tab(content)
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index 071aa2e3eff..36fc315599e 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -1,10 +1,10 @@
module SharedDiffNote
include Spinach::DSL
include RepoHelpers
- include WaitForAjax
+ include WaitForRequests
after do
- wait_for_ajax if javascript_test?
+ wait_for_requests if javascript_test?
end
step 'I cancel the diff comment' do
diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb
index 7d260025052..44eb8f321dd 100644
--- a/features/steps/shared/note.rb
+++ b/features/steps/shared/note.rb
@@ -1,9 +1,9 @@
module SharedNote
include Spinach::DSL
- include WaitForAjax
+ include WaitForRequests
after do
- wait_for_ajax if javascript_test?
+ wait_for_requests if javascript_test?
end
step 'I delete a comment' do
@@ -25,7 +25,7 @@ module SharedNote
click_button "Comment"
end
- wait_for_ajax
+ wait_for_requests
end
step 'I preview a comment text like "Bug fixed :smile:"' do
@@ -40,7 +40,7 @@ module SharedNote
click_button "Comment"
end
- wait_for_ajax
+ wait_for_requests
end
step 'I write a comment like ":+1: Nice"' do
@@ -127,7 +127,7 @@ module SharedNote
click_button "Comment"
end
- wait_for_ajax
+ wait_for_requests
end
step 'The comment with the header should not have an ID' do
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index bef3eac4d26..f0e751b820a 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -2,7 +2,7 @@ module SharedPaths
include Spinach::DSL
include RepoHelpers
include DashboardHelper
- include WaitForVueResource
+ include WaitForRequests
step 'I visit new project page' do
visit new_project_path
@@ -378,28 +378,28 @@ module SharedPaths
step 'I visit merge request page "Bug NS-04"' do
visit merge_request_path("Bug NS-04")
- wait_for_vue_resource
+ wait_for_requests
end
step 'I visit merge request page "Bug NS-05"' do
visit merge_request_path("Bug NS-05")
- wait_for_vue_resource
+ wait_for_requests
end
step 'I visit merge request page "Bug NS-07"' do
visit merge_request_path("Bug NS-07")
- wait_for_vue_resource
+ wait_for_requests
end
step 'I visit merge request page "Bug NS-08"' do
visit merge_request_path("Bug NS-08")
- wait_for_vue_resource
+ wait_for_requests
end
step 'I visit merge request page "Bug CO-01"' do
mr = MergeRequest.find_by(title: "Bug CO-01")
visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
- wait_for_vue_resource
+ wait_for_requests
end
step 'I visit project "Shop" merge requests page' do
diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb
index 0b3e942a4fd..a4fc77746ee 100644
--- a/features/steps/snippets/snippets.rb
+++ b/features/steps/snippets/snippets.rb
@@ -3,7 +3,7 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps
include SharedPaths
include SharedProject
include SharedSnippet
- include WaitForAjax
+ include WaitForRequests
step 'I click link "Personal snippet one"' do
click_link "Personal snippet one"
@@ -30,7 +30,7 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps
find('.ace_editor').native.send_keys 'Content of snippet three'
end
click_button "Create snippet"
- wait_for_ajax
+ wait_for_requests
end
step 'I submit new internal snippet' do
diff --git a/features/support/env.rb b/features/support/env.rb
index 23a1f702068..1690465d9b2 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -10,7 +10,7 @@ if ENV['CI']
Knapsack::Adapters::SpinachAdapter.bind
end
-%w(select2_helper test_env repo_helpers wait_for_ajax wait_for_requests sidekiq wait_for_vue_resource).each do |f|
+%w(select2_helper test_env repo_helpers wait_for_requests sidekiq).each do |f|
require Rails.root.join('spec', 'support', f)
end
@@ -33,7 +33,7 @@ end
Spinach.hooks.after_scenario do |scenario_data, step_definitions|
if scenario_data.tags.include?('javascript')
include WaitForRequests
- wait_for_requests_complete
+ block_and_wait_for_requests_complete
end
end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 52cd7cbe3db..ac113c5200d 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -45,9 +45,9 @@ module API
end
before { allow_access_with_scope :api }
- before { Gitlab::I18n.set_locale(current_user) }
+ before { Gitlab::I18n.locale = current_user&.preferred_language }
- after { Gitlab::I18n.reset_locale }
+ after { Gitlab::I18n.use_default_locale }
rescue_from Gitlab::Access::AccessDeniedError do
rack_response({ 'message' => '403 Forbidden' }.to_json, 403)
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 01cc8e8e1ca..cf4affe1758 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -252,7 +252,9 @@ module API
class RepoDiff < Grape::Entity
expose :old_path, :new_path, :a_mode, :b_mode, :diff
- expose :new_file, :renamed_file, :deleted_file
+ expose :new_file?, as: :new_file
+ expose :renamed_file?, as: :renamed_file
+ expose :deleted_file?, as: :deleted_file
end
class Milestone < ProjectEntity
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 96aaaf868ea..9ebd4841296 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -136,14 +136,15 @@ module API
post "/notify_post_receive" do
status 200
- return unless Gitlab::GitalyClient.enabled?
-
- begin
- repository = wiki? ? project.wiki.repository : project.repository
- Gitlab::GitalyClient::Notifications.new(repository.raw_repository).post_receive
- rescue GRPC::Unavailable => e
- render_api_error!(e, 500)
- end
+ # TODO: Re-enable when Gitaly is processing the post-receive notification
+ # return unless Gitlab::GitalyClient.enabled?
+ #
+ # begin
+ # repository = wiki? ? project.wiki.repository : project.repository
+ # Gitlab::GitalyClient::Notifications.new(repository.raw_repository).post_receive
+ # rescue GRPC::Unavailable => e
+ # render_api_error!(e, 500)
+ # end
end
end
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 8f16e532ecb..14d2bff9cb5 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -85,7 +85,7 @@ module API
optional :sha, type: String, desc: 'The commit sha of the archive to be downloaded'
optional :format, type: String, desc: 'The archive format'
end
- get ':id/repository/archive', requirements: { format: Gitlab::Regex.archive_formats_regex } do
+ get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do
begin
send_git_archive user_project.repository, ref: params[:sha], format: params[:format]
rescue
diff --git a/lib/api/services.rb b/lib/api/services.rb
index cb07df9e249..47bd9940f77 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -304,7 +304,13 @@ module API
required: true,
name: :url,
type: String,
- desc: 'The URL to the JIRA project which is being linked to this GitLab project, e.g., https://jira.example.com'
+ desc: 'The base URL to the JIRA instance web interface which is being linked to this GitLab project. E.g., https://jira.example.com'
+ },
+ {
+ required: false,
+ name: :api_url,
+ type: String,
+ desc: 'The base URL to the JIRA instance API. Web URL value will be used if not set. E.g., https://jira-api.example.com'
},
{
required: true,
diff --git a/lib/api/v3/repositories.rb b/lib/api/v3/repositories.rb
index e4d14bc8168..0eaa0de2eef 100644
--- a/lib/api/v3/repositories.rb
+++ b/lib/api/v3/repositories.rb
@@ -72,7 +72,7 @@ module API
optional :sha, type: String, desc: 'The commit sha of the archive to be downloaded'
optional :format, type: String, desc: 'The archive format'
end
- get ':id/repository/archive', requirements: { format: Gitlab::Regex.archive_formats_regex } do
+ get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do
begin
send_git_archive user_project.repository, ref: params[:sha], format: params[:format]
rescue
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 330cd963626..f755c99ea4a 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -84,7 +84,11 @@ module Backup
Dir.chdir(backup_path) do
backup_file_list.each do |file|
- next unless file =~ /(\d+)(?:_\d{4}_\d{2}_\d{2})?_gitlab_backup\.tar/
+ # For backward compatibility, there are 3 names the backups can have:
+ # - 1495527122_gitlab_backup.tar
+ # - 1495527068_2017_05_23_gitlab_backup.tar
+ # - 1495527097_2017_05_23_9.3.0-pre_gitlab_backup.tar
+ next unless file =~ /(\d+)(?:_\d{4}_\d{2}_\d{2}(_\d+\.\d+\.\d+.*)?)?_gitlab_backup\.tar$/
timestamp = $1.to_i
diff --git a/lib/banzai/filter/ascii_doc_post_processing_filter.rb b/lib/banzai/filter/ascii_doc_post_processing_filter.rb
new file mode 100644
index 00000000000..c9fcf057c5f
--- /dev/null
+++ b/lib/banzai/filter/ascii_doc_post_processing_filter.rb
@@ -0,0 +1,13 @@
+module Banzai
+ module Filter
+ class AsciiDocPostProcessingFilter < HTML::Pipeline::Filter
+ def call
+ doc.search('[data-math-style]').each do |node|
+ node.set_attribute('class', 'code math js-render-math')
+ end
+
+ doc
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index 522217deae4..2d6e8ffc90f 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -31,6 +31,10 @@ module Banzai
# Allow span elements
whitelist[:elements].push('span')
+ # Allow data-math-style attribute in order to support LaTeX formatting
+ whitelist[:attributes]['code'] = %w(data-math-style)
+ whitelist[:attributes]['pre'] = %w(data-math-style)
+
# Allow html5 details/summary elements
whitelist[:elements].push('details')
whitelist[:elements].push('summary')
diff --git a/lib/banzai/pipeline/ascii_doc_pipeline.rb b/lib/banzai/pipeline/ascii_doc_pipeline.rb
new file mode 100644
index 00000000000..1048b927cd3
--- /dev/null
+++ b/lib/banzai/pipeline/ascii_doc_pipeline.rb
@@ -0,0 +1,14 @@
+module Banzai
+ module Pipeline
+ class AsciiDocPipeline < BasePipeline
+ def self.filters
+ FilterArray[
+ Filter::SanitizationFilter,
+ Filter::ExternalLinkFilter,
+ Filter::PlantumlFilter,
+ Filter::AsciiDocPostProcessingFilter
+ ]
+ end
+ end
+ end
+end
diff --git a/lib/constraints/group_url_constrainer.rb b/lib/constraints/group_url_constrainer.rb
index 5f379756c11..6fc1d56d7a0 100644
--- a/lib/constraints/group_url_constrainer.rb
+++ b/lib/constraints/group_url_constrainer.rb
@@ -1,9 +1,9 @@
class GroupUrlConstrainer
def matches?(request)
- id = request.params[:id]
+ full_path = request.params[:group_id] || request.params[:id]
- return false unless DynamicPathValidator.valid?(id)
+ return false unless DynamicPathValidator.valid_group_path?(full_path)
- Group.find_by_full_path(id, follow_redirects: request.get?).present?
+ Group.find_by_full_path(full_path, follow_redirects: request.get?).present?
end
end
diff --git a/lib/constraints/project_url_constrainer.rb b/lib/constraints/project_url_constrainer.rb
index 6f542f63f98..4c0aee6c48f 100644
--- a/lib/constraints/project_url_constrainer.rb
+++ b/lib/constraints/project_url_constrainer.rb
@@ -2,9 +2,9 @@ class ProjectUrlConstrainer
def matches?(request)
namespace_path = request.params[:namespace_id]
project_path = request.params[:project_id] || request.params[:id]
- full_path = namespace_path + '/' + project_path
+ full_path = [namespace_path, project_path].join('/')
- return false unless DynamicPathValidator.valid?(full_path)
+ return false unless DynamicPathValidator.valid_project_path?(full_path)
Project.find_by_full_path(full_path, follow_redirects: request.get?).present?
end
diff --git a/lib/constraints/user_url_constrainer.rb b/lib/constraints/user_url_constrainer.rb
index 28159dc0dec..d16ae7f3f40 100644
--- a/lib/constraints/user_url_constrainer.rb
+++ b/lib/constraints/user_url_constrainer.rb
@@ -1,5 +1,9 @@
class UserUrlConstrainer
def matches?(request)
- User.find_by_full_path(request.params[:username], follow_redirects: request.get?).present?
+ full_path = request.params[:username]
+
+ return false unless DynamicPathValidator.valid_user_path?(full_path)
+
+ User.find_by_full_path(full_path, follow_redirects: request.get?).present?
end
end
diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb
index 96d38f6daa0..3d41ac76406 100644
--- a/lib/gitlab/asciidoc.rb
+++ b/lib/gitlab/asciidoc.rb
@@ -20,21 +20,20 @@ module Gitlab
backend: :gitlab_html5,
attributes: DEFAULT_ADOC_ATTRS }
- context[:pipeline] = :markup
+ context[:pipeline] = :ascii_doc
plantuml_setup
html = ::Asciidoctor.convert(input, asciidoc_opts)
html = Banzai.render(html, context)
-
html.html_safe
end
def self.plantuml_setup
Asciidoctor::PlantUml.configure do |conf|
- conf.url = ApplicationSetting.current.plantuml_url
- conf.svg_enable = ApplicationSetting.current.plantuml_enabled
- conf.png_enable = ApplicationSetting.current.plantuml_enabled
+ conf.url = current_application_settings.plantuml_url
+ conf.svg_enable = current_application_settings.plantuml_enabled
+ conf.png_enable = current_application_settings.plantuml_enabled
conf.txt_enable = false
end
end
@@ -47,13 +46,13 @@ module Gitlab
def stem(node)
return super unless node.style.to_sym == :latexmath
- %(<pre#{id_attribute(node)} class="code math js-render-math #{node.role}" data-math-style="display"><code>#{node.content}</code></pre>)
+ %(<pre#{id_attribute(node)} data-math-style="display"><code>#{node.content}</code></pre>)
end
def inline_quoted(node)
return super unless node.type.to_sym == :latexmath
- %(<code#{id_attribute(node)} class="code math js-render-math #{node.role}" data-math-style="inline">#{node.text}</code>)
+ %(<code#{id_attribute(node)} data-math-style="inline">#{node.text}</code>)
end
private
diff --git a/lib/gitlab/ci/trace/stream.rb b/lib/gitlab/ci/trace/stream.rb
index fa462cbe095..c4c0623df6c 100644
--- a/lib/gitlab/ci/trace/stream.rb
+++ b/lib/gitlab/ci/trace/stream.rb
@@ -73,7 +73,7 @@ module Gitlab
match = ""
- stream.each_line do |line|
+ reverse_line do |line|
matches = line.scan(regex)
next unless matches.is_a?(Array)
next if matches.empty?
@@ -86,34 +86,39 @@ module Gitlab
nil
rescue
# if bad regex or something goes wrong we dont want to interrupt transition
- # so we just silentrly ignore error for now
+ # so we just silently ignore error for now
end
private
- def read_last_lines(last_lines)
- chunks = []
- pos = lines = 0
- max = stream.size
-
- # We want an extra line to make sure fist line has full contents
- while lines <= last_lines && pos < max
- pos += BUFFER_SIZE
-
- buf =
- if pos <= max
- stream.seek(-pos, IO::SEEK_END)
- stream.read(BUFFER_SIZE)
- else # Reached the head, read only left
- stream.seek(0)
- stream.read(BUFFER_SIZE - (pos - max))
- end
-
- lines += buf.count("\n")
- chunks.unshift(buf)
+ def read_last_lines(limit)
+ to_enum(:reverse_line).first(limit).reverse.join
+ end
+
+ def reverse_line
+ stream.seek(0, IO::SEEK_END)
+ debris = ''
+
+ until (buf = read_backward(BUFFER_SIZE)).empty?
+ buf += debris
+ debris, *lines = buf.each_line.to_a
+ lines.reverse_each do |line|
+ yield(line.force_encoding('UTF-8'))
+ end
end
- chunks.join.lines.last(last_lines).join
+ yield(debris.force_encoding('UTF-8')) unless debris.empty?
+ end
+
+ def read_backward(length)
+ cur_offset = stream.tell
+ start = cur_offset - length
+ start = 0 if start < 0
+
+ stream.seek(start, IO::SEEK_SET)
+ stream.read(cur_offset - start).tap do
+ stream.seek(start, IO::SEEK_SET)
+ end
end
end
end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index e76c9abbe04..a412bb6dbd2 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -42,7 +42,7 @@ module Gitlab
'in the body of your migration class'
end
- if Database.postgresql?
+ if supports_drop_index_concurrently?
options = options.merge({ algorithm: :concurrently })
disable_statement_timeout
end
@@ -50,6 +50,39 @@ module Gitlab
remove_index(table_name, options.merge({ column: column_name }))
end
+ # Removes an existing index, concurrently when supported
+ #
+ # On PostgreSQL this method removes an index concurrently.
+ #
+ # Example:
+ #
+ # remove_concurrent_index :users, "index_X_by_Y"
+ #
+ # See Rails' `remove_index` for more info on the available arguments.
+ def remove_concurrent_index_by_name(table_name, index_name, options = {})
+ if transaction_open?
+ raise 'remove_concurrent_index_by_name can not be run inside a transaction, ' \
+ 'you can disable transactions by calling disable_ddl_transaction! ' \
+ 'in the body of your migration class'
+ end
+
+ if supports_drop_index_concurrently?
+ options = options.merge({ algorithm: :concurrently })
+ disable_statement_timeout
+ end
+
+ remove_index(table_name, options.merge({ name: index_name }))
+ end
+
+ # Only available on Postgresql >= 9.2
+ def supports_drop_index_concurrently?
+ return false unless Database.postgresql?
+
+ version = select_one("SELECT current_setting('server_version_num') AS v")['v'].to_i
+
+ version >= 90200
+ end
+
# Adds a foreign key with only minimal locking on the tables involved.
#
# This method only requires minimal locking when using PostgreSQL. When
diff --git a/lib/gitlab/dependency_linker.rb b/lib/gitlab/dependency_linker.rb
index c45ae8feb2c..3192bf6f667 100644
--- a/lib/gitlab/dependency_linker.rb
+++ b/lib/gitlab/dependency_linker.rb
@@ -1,7 +1,16 @@
module Gitlab
module DependencyLinker
LINKERS = [
- GemfileLinker
+ GemfileLinker,
+ GemspecLinker,
+ PackageJsonLinker,
+ ComposerJsonLinker,
+ PodfileLinker,
+ PodspecLinker,
+ PodspecJsonLinker,
+ CartfileLinker,
+ GodepsJsonLinker,
+ RequirementsTxtLinker
].freeze
def self.linker(blob_name)
diff --git a/lib/gitlab/dependency_linker/base_linker.rb b/lib/gitlab/dependency_linker/base_linker.rb
index 5f4027e7e81..7bbd154eb03 100644
--- a/lib/gitlab/dependency_linker/base_linker.rb
+++ b/lib/gitlab/dependency_linker/base_linker.rb
@@ -1,6 +1,9 @@
module Gitlab
module DependencyLinker
class BaseLinker
+ URL_REGEX = %r{https?://[^'" ]+}.freeze
+ REPO_REGEX = %r{[^/'" ]+/[^/'" ]+}.freeze
+
class_attribute :file_type
def self.support?(blob_name)
@@ -26,59 +29,20 @@ module Gitlab
private
- def package_url(name)
- raise NotImplementedError
- end
-
def link_dependencies
raise NotImplementedError
end
- def package_link(name, url = package_url(name))
- return name unless url
-
- %{<a href="#{ERB::Util.html_escape_once(url)}" rel="noopener noreferrer" target="_blank">#{ERB::Util.html_escape_once(name)}</a>}
+ def license_url(name)
+ Licensee::License.find(name)&.url
end
- # Links package names in a method call or assignment string argument.
- #
- # Example:
- # link_method_call("gem")
- # # Will link `package` in `gem "package"`, `gem("package")` and `gem = "package"`
- #
- # link_method_call("gem", "specific_package")
- # # Will link `specific_package` in `gem "specific_package"`
- #
- # link_method_call("github", /[^\/]+\/[^\/]+/)
- # # Will link `user/repo` in `github "user/repo"`, but not `github "package"`
- #
- # link_method_call(%w[add_dependency add_development_dependency])
- # # Will link `spec.add_dependency "package"` and `spec.add_development_dependency "package"`
- #
- # link_method_call("name")
- # # Will link `package` in `self.name = "package"`
- def link_method_call(method_names, value = nil, &url_proc)
- value =
- case value
- when String
- Regexp.escape(value)
- when nil
- /[^'"]+/
- else
- value
- end
-
- method_names = Array(method_names).map { |name| Regexp.escape(name) }
-
- regex = %r{
- #{Regexp.union(method_names)} # Method name
- \s* # Whitespace
- [(=]? # Opening brace or equals sign
- \s* # Whitespace
- ['"](?<name>#{value})['"] # Package name in quotes
- }x
+ def github_url(name)
+ "https://github.com/#{name}"
+ end
- link_regex(regex, &url_proc)
+ def link_tag(name, url)
+ %{<a href="#{ERB::Util.html_escape_once(url)}" rel="nofollow noreferrer noopener" target="_blank">#{ERB::Util.html_escape_once(name)}</a>}
end
# Links package names based on regex.
@@ -86,13 +50,13 @@ module Gitlab
# Example:
# link_regex(/(github:|:github =>)\s*['"](?<name>[^'"]+)['"]/)
# # Will link `user/repo` in `github: "user/repo"` or `:github => "user/repo"`
- def link_regex(regex)
+ def link_regex(regex, &url_proc)
highlighted_lines.map!.with_index do |rich_line, i|
marker = StringRegexMarker.new(plain_lines[i], rich_line.html_safe)
marker.mark(regex, group: :name) do |text, left:, right:|
- url = block_given? ? yield(text) : package_url(text)
- package_link(text, url)
+ url = yield(text)
+ url ? link_tag(text, url) : text
end
end
end
@@ -104,6 +68,19 @@ module Gitlab
def highlighted_lines
@highlighted_lines ||= highlighted_text.lines
end
+
+ def regexp_for_value(value, default: /[^'" ]+/)
+ case value
+ when Array
+ Regexp.union(value.map { |v| regexp_for_value(v, default: default) })
+ when String
+ Regexp.escape(value)
+ when Regexp
+ value
+ else
+ default
+ end
+ end
end
end
end
diff --git a/lib/gitlab/dependency_linker/cartfile_linker.rb b/lib/gitlab/dependency_linker/cartfile_linker.rb
new file mode 100644
index 00000000000..4f69f2c4ab2
--- /dev/null
+++ b/lib/gitlab/dependency_linker/cartfile_linker.rb
@@ -0,0 +1,14 @@
+module Gitlab
+ module DependencyLinker
+ class CartfileLinker < MethodLinker
+ self.file_type = :cartfile
+
+ private
+
+ def link_dependencies
+ link_method_call('github', REPO_REGEX, &method(:github_url))
+ link_method_call(%w[github git binary], URL_REGEX, &:itself)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/dependency_linker/cocoapods.rb b/lib/gitlab/dependency_linker/cocoapods.rb
new file mode 100644
index 00000000000..2fbde7da1b4
--- /dev/null
+++ b/lib/gitlab/dependency_linker/cocoapods.rb
@@ -0,0 +1,10 @@
+module Gitlab
+ module DependencyLinker
+ module Cocoapods
+ def package_url(name)
+ package = name.split("/", 2).first
+ "https://cocoapods.org/pods/#{package}"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/dependency_linker/composer_json_linker.rb b/lib/gitlab/dependency_linker/composer_json_linker.rb
new file mode 100644
index 00000000000..0245bf4077a
--- /dev/null
+++ b/lib/gitlab/dependency_linker/composer_json_linker.rb
@@ -0,0 +1,18 @@
+module Gitlab
+ module DependencyLinker
+ class ComposerJsonLinker < PackageJsonLinker
+ self.file_type = :composer_json
+
+ private
+
+ def link_packages
+ link_packages_at_key("require", &method(:package_url))
+ link_packages_at_key("require-dev", &method(:package_url))
+ end
+
+ def package_url(name)
+ "https://packagist.org/packages/#{name}" if name =~ %r{\A#{REPO_REGEX}\z}
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/dependency_linker/gemfile_linker.rb b/lib/gitlab/dependency_linker/gemfile_linker.rb
index 9b82e126528..d034ea67387 100644
--- a/lib/gitlab/dependency_linker/gemfile_linker.rb
+++ b/lib/gitlab/dependency_linker/gemfile_linker.rb
@@ -1,28 +1,31 @@
module Gitlab
module DependencyLinker
- class GemfileLinker < BaseLinker
+ class GemfileLinker < MethodLinker
self.file_type = :gemfile
private
def link_dependencies
- # Link `gem "package_name"` to https://rubygems.org/gems/package_name
- link_method_call("gem")
+ link_urls
+ link_packages
+ end
+ def link_urls
# Link `github: "user/repo"` to https://github.com/user/repo
- link_regex(/(github:|:github\s*=>)\s*['"](?<name>[^'"]+)['"]/) do |name|
- "https://github.com/#{name}"
- end
+ link_regex(/(github:|:github\s*=>)\s*['"](?<name>[^'"]+)['"]/, &method(:github_url))
# Link `git: "https://gitlab.example.com/user/repo"` to https://gitlab.example.com/user/repo
- link_regex(%r{(git:|:git\s*=>)\s*['"](?<name>https?://[^'"]+)['"]}) { |url| url }
+ link_regex(%r{(git:|:git\s*=>)\s*['"](?<name>#{URL_REGEX})['"]}, &:itself)
# Link `source "https://rubygems.org"` to https://rubygems.org
- link_method_call("source", %r{https?://[^'"]+}) { |url| url }
+ link_method_call('source', URL_REGEX, &:itself)
end
- def package_url(name)
- "https://rubygems.org/gems/#{name}"
+ def link_packages
+ # Link `gem "package_name"` to https://rubygems.org/gems/package_name
+ link_method_call('gem') do |name|
+ "https://rubygems.org/gems/#{name}"
+ end
end
end
end
diff --git a/lib/gitlab/dependency_linker/gemspec_linker.rb b/lib/gitlab/dependency_linker/gemspec_linker.rb
new file mode 100644
index 00000000000..f1783ee2ab4
--- /dev/null
+++ b/lib/gitlab/dependency_linker/gemspec_linker.rb
@@ -0,0 +1,18 @@
+module Gitlab
+ module DependencyLinker
+ class GemspecLinker < MethodLinker
+ self.file_type = :gemspec
+
+ private
+
+ def link_dependencies
+ link_method_call('homepage', URL_REGEX, &:itself)
+ link_method_call('license', &method(:license_url))
+
+ link_method_call(%w[name add_dependency add_runtime_dependency add_development_dependency]) do |name|
+ "https://rubygems.org/gems/#{name}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/dependency_linker/godeps_json_linker.rb b/lib/gitlab/dependency_linker/godeps_json_linker.rb
new file mode 100644
index 00000000000..fe091baee6d
--- /dev/null
+++ b/lib/gitlab/dependency_linker/godeps_json_linker.rb
@@ -0,0 +1,26 @@
+module Gitlab
+ module DependencyLinker
+ class GodepsJsonLinker < JsonLinker
+ NESTED_REPO_REGEX = %r{([^/]+/)+[^/]+?}.freeze
+
+ self.file_type = :godeps_json
+
+ private
+
+ def link_dependencies
+ link_json('ImportPath') do |path|
+ case path
+ when %r{\A(?<repo>gitlab\.com/#{NESTED_REPO_REGEX})\.git/(?<path>.+)\z},
+ %r{\A(?<repo>git(lab|hub)\.com/#{REPO_REGEX})/(?<path>.+)\z}
+
+ "https://#{$~[:repo]}/tree/master/#{$~[:path]}"
+ when /\Agolang\.org/
+ "https://godoc.org/#{path}"
+ else
+ "https://#{path}"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/dependency_linker/json_linker.rb b/lib/gitlab/dependency_linker/json_linker.rb
new file mode 100644
index 00000000000..a8ef25233d8
--- /dev/null
+++ b/lib/gitlab/dependency_linker/json_linker.rb
@@ -0,0 +1,44 @@
+module Gitlab
+ module DependencyLinker
+ class JsonLinker < BaseLinker
+ def link
+ return highlighted_text unless json
+
+ super
+ end
+
+ private
+
+ # Links package names in a JSON key or values.
+ #
+ # Example:
+ # link_json('name')
+ # # Will link `package` in `"name": "package"`
+ #
+ # link_json('name', 'specific_package')
+ # # Will link `specific_package` in `"name": "specific_package"`
+ #
+ # link_json('name', /[^\/]+\/[^\/]+/)
+ # # Will link `user/repo` in `"name": "user/repo"`, but not `"name": "package"`
+ #
+ # link_json('specific_package', '1.0.1', link: :key)
+ # # Will link `specific_package` in `"specific_package": "1.0.1"`
+ def link_json(key, value = nil, link: :value, &url_proc)
+ key = regexp_for_value(key, default: /[^" ]+/)
+ value = regexp_for_value(value, default: /[^" ]+/)
+
+ if link == :value
+ value = /(?<name>#{value})/
+ else
+ key = /(?<name>#{key})/
+ end
+
+ link_regex(/"#{key}":\s*"#{value}"/, &url_proc)
+ end
+
+ def json
+ @json ||= JSON.parse(plain_text) rescue nil
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/dependency_linker/method_linker.rb b/lib/gitlab/dependency_linker/method_linker.rb
new file mode 100644
index 00000000000..0ffa2a83c93
--- /dev/null
+++ b/lib/gitlab/dependency_linker/method_linker.rb
@@ -0,0 +1,39 @@
+module Gitlab
+ module DependencyLinker
+ class MethodLinker < BaseLinker
+ private
+
+ # Links package names in a method call or assignment string argument.
+ #
+ # Example:
+ # link_method_call('gem')
+ # # Will link `package` in `gem "package"`, `gem("package")` and `gem = "package"`
+ #
+ # link_method_call('gem', 'specific_package')
+ # # Will link `specific_package` in `gem "specific_package"`
+ #
+ # link_method_call('github', /[^\/"]+\/[^\/"]+/)
+ # # Will link `user/repo` in `github "user/repo"`, but not `github "package"`
+ #
+ # link_method_call(%w[add_dependency add_development_dependency])
+ # # Will link `spec.add_dependency "package"` and `spec.add_development_dependency "package"`
+ #
+ # link_method_call('name')
+ # # Will link `package` in `self.name = "package"`
+ def link_method_call(method_name, value = nil, &url_proc)
+ method_name = regexp_for_value(method_name)
+ value = regexp_for_value(value)
+
+ regex = %r{
+ #{method_name} # Method name
+ \s* # Whitespace
+ [(=]? # Opening brace or equals sign
+ \s* # Whitespace
+ ['"](?<name>#{value})['"] # Package name in quotes
+ }x
+
+ link_regex(regex, &url_proc)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/dependency_linker/package_json_linker.rb b/lib/gitlab/dependency_linker/package_json_linker.rb
new file mode 100644
index 00000000000..330c95f0880
--- /dev/null
+++ b/lib/gitlab/dependency_linker/package_json_linker.rb
@@ -0,0 +1,44 @@
+module Gitlab
+ module DependencyLinker
+ class PackageJsonLinker < JsonLinker
+ self.file_type = :package_json
+
+ private
+
+ def link_dependencies
+ link_json('name', json["name"], &method(:package_url))
+ link_json('license', &method(:license_url))
+ link_json(%w[homepage url], URL_REGEX, &:itself)
+
+ link_packages
+ end
+
+ def link_packages
+ link_packages_at_key("dependencies", &method(:package_url))
+ link_packages_at_key("devDependencies", &method(:package_url))
+ end
+
+ def link_packages_at_key(key, &url_proc)
+ dependencies = json[key]
+ return unless dependencies
+
+ dependencies.each do |name, version|
+ link_json(name, version, link: :key, &url_proc)
+
+ link_json(name) do |value|
+ case value
+ when /\A#{URL_REGEX}\z/
+ value
+ when /\A#{REPO_REGEX}\z/
+ github_url(value)
+ end
+ end
+ end
+ end
+
+ def package_url(name)
+ "https://npmjs.com/package/#{name}"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/dependency_linker/podfile_linker.rb b/lib/gitlab/dependency_linker/podfile_linker.rb
new file mode 100644
index 00000000000..60ad166ea17
--- /dev/null
+++ b/lib/gitlab/dependency_linker/podfile_linker.rb
@@ -0,0 +1,15 @@
+module Gitlab
+ module DependencyLinker
+ class PodfileLinker < GemfileLinker
+ include Cocoapods
+
+ self.file_type = :podfile
+
+ private
+
+ def link_packages
+ link_method_call('pod', &method(:package_url))
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/dependency_linker/podspec_json_linker.rb b/lib/gitlab/dependency_linker/podspec_json_linker.rb
new file mode 100644
index 00000000000..d82237ed3f1
--- /dev/null
+++ b/lib/gitlab/dependency_linker/podspec_json_linker.rb
@@ -0,0 +1,32 @@
+module Gitlab
+ module DependencyLinker
+ class PodspecJsonLinker < JsonLinker
+ include Cocoapods
+
+ self.file_type = :podspec_json
+
+ private
+
+ def link_dependencies
+ link_json('name', json["name"], &method(:package_url))
+ link_json('license', &method(:license_url))
+ link_json(%w[homepage git], URL_REGEX, &:itself)
+
+ link_packages_at_key("dependencies", &method(:package_url))
+
+ json["subspecs"]&.each do |subspec|
+ link_packages_at_key("dependencies", subspec, &method(:package_url))
+ end
+ end
+
+ def link_packages_at_key(key, root = json, &url_proc)
+ dependencies = root[key]
+ return unless dependencies
+
+ dependencies.each do |name, _|
+ link_regex(/"(?<name>#{Regexp.escape(name)})":\s*\[/, &url_proc)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/dependency_linker/podspec_linker.rb b/lib/gitlab/dependency_linker/podspec_linker.rb
new file mode 100644
index 00000000000..a52c7a02439
--- /dev/null
+++ b/lib/gitlab/dependency_linker/podspec_linker.rb
@@ -0,0 +1,24 @@
+module Gitlab
+ module DependencyLinker
+ class PodspecLinker < MethodLinker
+ include Cocoapods
+
+ STRING_REGEX = /['"](?<name>[^'"]+)['"]/.freeze
+
+ self.file_type = :podspec
+
+ private
+
+ def link_dependencies
+ link_method_call('homepage', URL_REGEX, &:itself)
+
+ link_regex(%r{(git:|:git\s*=>)\s*['"](?<name>#{URL_REGEX})['"]}, &:itself)
+
+ link_method_call('license', &method(:license_url))
+ link_regex(/license\s*=\s*\{\s*(type:|:type\s*=>)\s*#{STRING_REGEX}/, &method(:license_url))
+
+ link_method_call(%w[name dependency], &method(:package_url))
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/dependency_linker/requirements_txt_linker.rb b/lib/gitlab/dependency_linker/requirements_txt_linker.rb
new file mode 100644
index 00000000000..2e197e5cd94
--- /dev/null
+++ b/lib/gitlab/dependency_linker/requirements_txt_linker.rb
@@ -0,0 +1,17 @@
+module Gitlab
+ module DependencyLinker
+ class RequirementsTxtLinker < BaseLinker
+ self.file_type = :requirements_txt
+
+ private
+
+ def link_dependencies
+ link_regex(/^(?<name>(?![a-z+]+:)[^#.-][^ ><=;\[]+)/) do |name|
+ "https://pypi.python.org/pypi/#{name}"
+ end
+
+ link_regex(%r{^(?<name>https?://[^ ]+)}, &:itself)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index c6bf25b5874..2aef7fdaa35 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -1,16 +1,17 @@
module Gitlab
module Diff
class File
- attr_reader :diff, :repository, :diff_refs
+ attr_reader :diff, :repository, :diff_refs, :fallback_diff_refs
- delegate :new_file, :deleted_file, :renamed_file,
- :old_path, :new_path, :a_mode, :b_mode,
+ delegate :new_file?, :deleted_file?, :renamed_file?,
+ :old_path, :new_path, :a_mode, :b_mode, :mode_changed?,
:submodule?, :too_large?, :collapsed?, to: :diff, prefix: false
- def initialize(diff, repository:, diff_refs: nil)
+ def initialize(diff, repository:, diff_refs: nil, fallback_diff_refs: nil)
@diff = diff
@repository = repository
@diff_refs = diff_refs
+ @fallback_diff_refs = fallback_diff_refs
end
def position(line)
@@ -49,24 +50,60 @@ module Gitlab
line_code(line) if line
end
+ def old_sha
+ diff_refs&.base_sha
+ end
+
+ def new_sha
+ diff_refs&.head_sha
+ end
+
+ def content_sha
+ return old_content_sha if deleted_file?
+ return @content_sha if defined?(@content_sha)
+
+ refs = diff_refs || fallback_diff_refs
+ @content_sha = refs&.head_sha
+ end
+
def content_commit
- return unless diff_refs
+ return @content_commit if defined?(@content_commit)
+
+ sha = content_sha
+ @content_commit = repository.commit(sha) if sha
+ end
+
+ def old_content_sha
+ return if new_file?
+ return @old_content_sha if defined?(@old_content_sha)
- repository.commit(deleted_file ? old_ref : new_ref)
+ refs = diff_refs || fallback_diff_refs
+ @old_content_sha = refs&.base_sha
end
def old_content_commit
- return unless diff_refs
+ return @old_content_commit if defined?(@old_content_commit)
- repository.commit(old_ref)
+ sha = old_content_sha
+ @old_content_commit = repository.commit(sha) if sha
end
- def old_ref
- diff_refs.try(:base_sha)
+ def blob
+ return @blob if defined?(@blob)
+
+ sha = content_sha
+ return @blob = nil unless sha
+
+ repository.blob_at(sha, file_path)
end
- def new_ref
- diff_refs.try(:head_sha)
+ def old_blob
+ return @old_blob if defined?(@old_blob)
+
+ sha = old_content_sha
+ return @old_blob = nil unless sha
+
+ @old_blob = repository.blob_at(sha, old_path)
end
attr_writer :highlighted_diff_lines
@@ -85,10 +122,6 @@ module Gitlab
@parallel_diff_lines ||= Gitlab::Diff::ParallelDiff.new(self).parallelize
end
- def mode_changed?
- a_mode && b_mode && a_mode != b_mode
- end
-
def raw_diff
diff.diff.to_s
end
@@ -117,20 +150,8 @@ module Gitlab
diff_lines.count(&:removed?)
end
- def old_blob(commit = old_content_commit)
- return unless commit
-
- repository.blob_at(commit.id, old_path)
- end
-
- def blob(commit = content_commit)
- return unless commit
-
- repository.blob_at(commit.id, file_path)
- end
-
def file_identifier
- "#{file_path}-#{new_file}-#{deleted_file}-#{renamed_file}"
+ "#{file_path}-#{new_file?}-#{deleted_file?}-#{renamed_file?}"
end
end
end
diff --git a/lib/gitlab/diff/file_collection/base.rb b/lib/gitlab/diff/file_collection/base.rb
index 2b9fc65b985..79836a2fbab 100644
--- a/lib/gitlab/diff/file_collection/base.rb
+++ b/lib/gitlab/diff/file_collection/base.rb
@@ -2,7 +2,7 @@ module Gitlab
module Diff
module FileCollection
class Base
- attr_reader :project, :diff_options, :diff_view, :diff_refs
+ attr_reader :project, :diff_options, :diff_refs, :fallback_diff_refs
delegate :count, :size, :real_size, to: :diff_files
@@ -10,24 +10,33 @@ module Gitlab
::Commit.max_diff_options.merge(ignore_whitespace_change: false, no_collapse: false)
end
- def initialize(diffable, project:, diff_options: nil, diff_refs: nil)
+ def initialize(diffable, project:, diff_options: nil, diff_refs: nil, fallback_diff_refs: nil)
diff_options = self.class.default_options.merge(diff_options || {})
- @diffable = diffable
- @diffs = diffable.raw_diffs(diff_options)
- @project = project
+ @diffable = diffable
+ @diffs = diffable.raw_diffs(diff_options)
+ @project = project
@diff_options = diff_options
- @diff_refs = diff_refs
+ @diff_refs = diff_refs
+ @fallback_diff_refs = fallback_diff_refs
end
def diff_files
@diff_files ||= @diffs.decorate! { |diff| decorate_diff!(diff) }
end
+ def diff_file_with_old_path(old_path)
+ diff_files.find { |diff_file| diff_file.old_path == old_path }
+ end
+
+ def diff_file_with_new_path(new_path)
+ diff_files.find { |diff_file| diff_file.new_path == new_path }
+ end
+
private
def decorate_diff!(diff)
- Gitlab::Diff::File.new(diff, repository: project.repository, diff_refs: diff_refs)
+ Gitlab::Diff::File.new(diff, repository: project.repository, diff_refs: diff_refs, fallback_diff_refs: fallback_diff_refs)
end
end
end
diff --git a/lib/gitlab/diff/file_collection/merge_request_diff.rb b/lib/gitlab/diff/file_collection/merge_request_diff.rb
index 0bd226ef050..9a58b500a2c 100644
--- a/lib/gitlab/diff/file_collection/merge_request_diff.rb
+++ b/lib/gitlab/diff/file_collection/merge_request_diff.rb
@@ -8,7 +8,8 @@ module Gitlab
super(merge_request_diff,
project: merge_request_diff.project,
diff_options: diff_options,
- diff_refs: merge_request_diff.diff_refs)
+ diff_refs: merge_request_diff.diff_refs,
+ fallback_diff_refs: merge_request_diff.fallback_diff_refs)
end
def diff_files
diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb
index 7db896522a9..ed2f541977a 100644
--- a/lib/gitlab/diff/highlight.rb
+++ b/lib/gitlab/diff/highlight.rb
@@ -3,7 +3,7 @@ module Gitlab
class Highlight
attr_reader :diff_file, :diff_lines, :raw_lines, :repository
- delegate :old_path, :new_path, :old_ref, :new_ref, to: :diff_file, prefix: :diff
+ delegate :old_path, :new_path, :old_sha, :new_sha, to: :diff_file, prefix: :diff
def initialize(diff_lines, repository: nil)
@repository = repository
@@ -61,12 +61,12 @@ module Gitlab
def old_lines
return unless diff_file
- @old_lines ||= Gitlab::Highlight.highlight_lines(self.repository, diff_old_ref, diff_old_path)
+ @old_lines ||= Gitlab::Highlight.highlight_lines(self.repository, diff_old_sha, diff_old_path)
end
def new_lines
return unless diff_file
- @new_lines ||= Gitlab::Highlight.highlight_lines(self.repository, diff_new_ref, diff_new_path)
+ @new_lines ||= Gitlab::Highlight.highlight_lines(self.repository, diff_new_sha, diff_new_path)
end
end
end
diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb
index fc728123c97..4d96778a2b2 100644
--- a/lib/gitlab/diff/position.rb
+++ b/lib/gitlab/diff/position.rb
@@ -12,20 +12,26 @@ module Gitlab
attr_reader :head_sha
def initialize(attrs = {})
+ if diff_file = attrs[:diff_file]
+ attrs[:diff_refs] = diff_file.diff_refs
+ attrs[:old_path] = diff_file.old_path
+ attrs[:new_path] = diff_file.new_path
+ end
+
+ if diff_refs = attrs[:diff_refs]
+ attrs[:base_sha] = diff_refs.base_sha
+ attrs[:start_sha] = diff_refs.start_sha
+ attrs[:head_sha] = diff_refs.head_sha
+ end
+
@old_path = attrs[:old_path]
@new_path = attrs[:new_path]
+ @base_sha = attrs[:base_sha]
+ @start_sha = attrs[:start_sha]
+ @head_sha = attrs[:head_sha]
+
@old_line = attrs[:old_line]
@new_line = attrs[:new_line]
-
- if attrs[:diff_refs]
- @base_sha = attrs[:diff_refs].base_sha
- @start_sha = attrs[:diff_refs].start_sha
- @head_sha = attrs[:diff_refs].head_sha
- else
- @base_sha = attrs[:base_sha]
- @start_sha = attrs[:start_sha]
- @head_sha = attrs[:head_sha]
- end
end
# `Gitlab::Diff::Position` objects are stored as serialized attributes in
@@ -129,11 +135,11 @@ module Gitlab
end
def diff_line(repository)
- @diff_line ||= diff_file(repository).line_for_position(self)
+ @diff_line ||= diff_file(repository)&.line_for_position(self)
end
def line_code(repository)
- @line_code ||= diff_file(repository).line_code_for_position(self)
+ @line_code ||= diff_file(repository)&.line_code_for_position(self)
end
private
diff --git a/lib/gitlab/diff/position_tracer.rb b/lib/gitlab/diff/position_tracer.rb
index e89ff238ec7..dcabb5f7fe5 100644
--- a/lib/gitlab/diff/position_tracer.rb
+++ b/lib/gitlab/diff/position_tracer.rb
@@ -3,21 +3,21 @@
module Gitlab
module Diff
class PositionTracer
- attr_accessor :repository
+ attr_accessor :project
attr_accessor :old_diff_refs
attr_accessor :new_diff_refs
attr_accessor :paths
- def initialize(repository:, old_diff_refs:, new_diff_refs:, paths: nil)
- @repository = repository
+ def initialize(project:, old_diff_refs:, new_diff_refs:, paths: nil)
+ @project = project
@old_diff_refs = old_diff_refs
@new_diff_refs = new_diff_refs
@paths = paths
end
- def trace(old_position)
+ def trace(ab_position)
return unless old_diff_refs&.complete? && new_diff_refs&.complete?
- return unless old_position.diff_refs == old_diff_refs
+ return unless ab_position.diff_refs == old_diff_refs
# Suppose we have an MR with source branch `feature` and target branch `master`.
# When the MR was created, the head of `master` was commit A, and the
@@ -44,14 +44,16 @@ module Gitlab
#
# For diff notes for diff A->B, the position looks like this:
# Position
- # base_sha - ID of commit A
+ # start_sha - ID of commit A
# head_sha - ID of commit B
+ # base_sha - ID of base commit of A and B
# old_path - path as of A (nil if file was newly created)
# new_path - path as of B (nil if file was deleted)
# old_line - line number as of A (nil if file was newly created)
# new_line - line number as of B (nil if file was deleted)
#
- # We can easily update `base_sha` and `head_sha` to hold the IDs of commits C and D,
+ # We can easily update `start_sha` and `head_sha` to hold the IDs of
+ # commits C and D, and can trivially determine `base_sha` based on those,
# but need to find the paths and line numbers as of C and D.
#
# If the file was unchanged or newly created in A->B, the path as of D can be found
@@ -68,107 +70,161 @@ module Gitlab
# by generating diff A->C ("base to base"), finding the diff file with
# `diff_file.old_path == position.old_path`, and taking `diff_file.new_path`.
# The path as of D can be found by taking diff C->D, finding the diff file
- # with that same `old_path` and taking `diff_file.new_path`.
+ # with `old_path` set to that `diff_file.new_path` and taking `diff_file.new_path`.
# The line number as of C can be found by using the LineMapper on diff A->C
# and providing the line number as of A.
# The line number as of D can be found by using the LineMapper on diff C->D
# and providing the line number as of C.
- results = nil
- results ||= trace_added_line(old_position) if old_position.added? || old_position.unchanged?
- results ||= trace_removed_line(old_position) if old_position.removed? || old_position.unchanged?
-
- return unless results
-
- file_diff, old_line, new_line = results
-
- new_position = Position.new(
- old_path: file_diff.old_path,
- new_path: file_diff.new_path,
- head_sha: new_diff_refs.head_sha,
- start_sha: new_diff_refs.start_sha,
- base_sha: new_diff_refs.base_sha,
- old_line: old_line,
- new_line: new_line
- )
-
- # If a position is found, but is not actually contained in the diff, for example
- # because it was an unchanged line in the context of a change that was undone,
- # we cannot return this as a successful trace.
- return unless new_position.diff_line(repository)
-
- new_position
+ if ab_position.added?
+ trace_added_line(ab_position)
+ elsif ab_position.removed?
+ trace_removed_line(ab_position)
+ else # unchanged
+ trace_unchanged_line(ab_position)
+ end
end
private
- def trace_added_line(old_position)
- file_path = old_position.new_path
-
- return unless diff_head_to_head
-
- file_head_to_head = diff_head_to_head.find { |diff_file| diff_file.old_path == file_path }
-
- file_path = file_head_to_head.new_path if file_head_to_head
-
- new_line = LineMapper.new(file_head_to_head).old_to_new(old_position.new_line)
-
- return unless new_line
-
- file_diff = new_diffs.find { |diff_file| diff_file.new_path == file_path }
- return unless file_diff
-
- old_line = LineMapper.new(file_diff).new_to_old(new_line)
-
- [file_diff, old_line, new_line]
+ def trace_added_line(ab_position)
+ b_path = ab_position.new_path
+ b_line = ab_position.new_line
+
+ bd_diff = bd_diffs.diff_file_with_old_path(b_path)
+
+ d_path = bd_diff&.new_path || b_path
+ d_line = LineMapper.new(bd_diff).old_to_new(b_line)
+
+ if d_line
+ cd_diff = cd_diffs.diff_file_with_new_path(d_path)
+
+ c_path = cd_diff&.old_path || d_path
+ c_line = LineMapper.new(cd_diff).new_to_old(d_line)
+
+ if c_line
+ # If the line is still in D but also in C, it has turned from an
+ # added line into an unchanged one.
+ new_position = position(cd_diff, c_line, d_line)
+ if valid_position?(new_position)
+ # If the line is still in the MR, we don't treat this as outdated.
+ { position: new_position, outdated: false }
+ else
+ # If the line is no longer in the MR, we unfortunately cannot show
+ # the current state on the CD diff, so we treat it as outdated.
+ ac_diff = ac_diffs.diff_file_with_new_path(c_path)
+
+ { position: position(ac_diff, nil, c_line), outdated: true }
+ end
+ else
+ # If the line is still in D and not in C, it is still added.
+ { position: position(cd_diff, nil, d_line), outdated: false }
+ end
+ else
+ # If the line is no longer in D, it has been removed from the MR.
+ { position: position(bd_diff, b_line, nil), outdated: true }
+ end
end
- def trace_removed_line(old_position)
- file_path = old_position.old_path
+ def trace_removed_line(ab_position)
+ a_path = ab_position.old_path
+ a_line = ab_position.old_line
- return unless diff_base_to_base
+ ac_diff = ac_diffs.diff_file_with_old_path(a_path)
- file_base_to_base = diff_base_to_base.find { |diff_file| diff_file.old_path == file_path }
+ c_path = ac_diff&.new_path || a_path
+ c_line = LineMapper.new(ac_diff).old_to_new(a_line)
- file_path = file_base_to_base.old_path if file_base_to_base
+ if c_line
+ cd_diff = cd_diffs.diff_file_with_old_path(c_path)
- old_line = LineMapper.new(file_base_to_base).old_to_new(old_position.old_line)
+ d_path = cd_diff&.new_path || c_path
+ d_line = LineMapper.new(cd_diff).old_to_new(c_line)
- return unless old_line
+ if d_line
+ # If the line is still in C but also in D, it has turned from a
+ # removed line into an unchanged one.
+ bd_diff = bd_diffs.diff_file_with_new_path(d_path)
- file_diff = new_diffs.find { |diff_file| diff_file.old_path == file_path }
- return unless file_diff
-
- new_line = LineMapper.new(file_diff).old_to_new(old_line)
+ { position: position(bd_diff, nil, d_line), outdated: true }
+ else
+ # If the line is still in C and not in D, it is still removed.
+ { position: position(cd_diff, c_line, nil), outdated: false }
+ end
+ else
+ # If the line is no longer in C, it has been removed outside of the MR.
+ { position: position(ac_diff, a_line, nil), outdated: true }
+ end
+ end
- [file_diff, old_line, new_line]
+ def trace_unchanged_line(ab_position)
+ a_path = ab_position.old_path
+ a_line = ab_position.old_line
+ b_path = ab_position.new_path
+ b_line = ab_position.new_line
+
+ ac_diff = ac_diffs.diff_file_with_old_path(a_path)
+
+ c_path = ac_diff&.new_path || a_path
+ c_line = LineMapper.new(ac_diff).old_to_new(a_line)
+
+ bd_diff = bd_diffs.diff_file_with_old_path(b_path)
+
+ d_line = LineMapper.new(bd_diff).old_to_new(b_line)
+
+ cd_diff = cd_diffs.diff_file_with_old_path(c_path)
+
+ if c_line && d_line
+ # If the line is still in C and D, it is still unchanged.
+ new_position = position(cd_diff, c_line, d_line)
+ if valid_position?(new_position)
+ # If the line is still in the MR, we don't treat this as outdated.
+ { position: new_position, outdated: false }
+ else
+ # If the line is no longer in the MR, we unfortunately cannot show
+ # the current state on the CD diff or any change on the BD diff,
+ # so we treat it as outdated.
+ { position: nil, outdated: true }
+ end
+ elsif d_line # && !c_line
+ # If the line is still in D but no longer in C, it has turned from
+ # an unchanged line into an added one.
+ # We don't treat this as outdated since the line is still in the MR.
+ { position: position(cd_diff, nil, d_line), outdated: false }
+ else # !d_line && (c_line || !c_line)
+ # If the line is no longer in D, it has turned from an unchanged line
+ # into a removed one.
+ { position: position(bd_diff, b_line, nil), outdated: true }
+ end
end
- def diff_base_to_base
- @diff_base_to_base ||= diff_files(old_diff_refs.base_sha || old_diff_refs.start_sha, new_diff_refs.base_sha || new_diff_refs.start_sha)
+ def ac_diffs
+ @ac_diffs ||= compare(
+ old_diff_refs.base_sha || old_diff_refs.start_sha,
+ new_diff_refs.base_sha || new_diff_refs.start_sha,
+ straight: true
+ )
end
- def diff_head_to_head
- @diff_head_to_head ||= diff_files(old_diff_refs.head_sha, new_diff_refs.head_sha)
+ def bd_diffs
+ @bd_diffs ||= compare(old_diff_refs.head_sha, new_diff_refs.head_sha, straight: true)
end
- def new_diffs
- @new_diffs ||= diff_files(new_diff_refs.start_sha, new_diff_refs.head_sha, use_base: true)
+ def cd_diffs
+ @cd_diffs ||= compare(new_diff_refs.start_sha, new_diff_refs.head_sha)
end
- def diff_files(start_sha, head_sha, use_base: false)
- base_sha = self.repository.merge_base(start_sha, head_sha) || start_sha
+ def compare(start_sha, head_sha, straight: false)
+ compare = CompareService.new(project, head_sha).execute(project, start_sha, straight: straight)
+ compare.diffs(paths: paths)
+ end
- diffs = self.repository.raw_repository.diff(
- use_base ? base_sha : start_sha,
- head_sha,
- {},
- *paths
- )
+ def position(diff_file, old_line, new_line)
+ Position.new(diff_file: diff_file, old_line: old_line, new_line: new_line)
+ end
- diffs.decorate! do |diff|
- Gitlab::Diff::File.new(diff, repository: self.repository)
- end
+ def valid_position?(position)
+ !!position.diff_line(project.repository)
end
end
end
diff --git a/lib/gitlab/etag_caching/router.rb b/lib/gitlab/etag_caching/router.rb
index ba31041d0c1..cc285162b44 100644
--- a/lib/gitlab/etag_caching/router.rb
+++ b/lib/gitlab/etag_caching/router.rb
@@ -10,7 +10,7 @@ module Gitlab
# - Ending in `issues/id`/realtime_changes` for the `issue_title` route
USED_IN_ROUTES = %w[noteable issue notes issues realtime_changes
commit pipelines merge_requests new].freeze
- RESERVED_WORDS = DynamicPathValidator::WILDCARD_ROUTES - USED_IN_ROUTES
+ RESERVED_WORDS = Gitlab::PathRegex::ILLEGAL_PROJECT_PATH_WORDS - USED_IN_ROUTES
RESERVED_WORDS_REGEX = Regexp.union(*RESERVED_WORDS)
ROUTES = [
Gitlab::EtagCaching::Router::Route.new(
@@ -38,7 +38,7 @@ module Gitlab
'project_pipelines'
),
Gitlab::EtagCaching::Router::Route.new(
- %r(^(?!.*(#{RESERVED_WORDS})).*/pipelines/\d+\.json\z),
+ %r(^(?!.*(#{RESERVED_WORDS_REGEX})).*/pipelines/\d+\.json\z),
'project_pipeline'
)
].freeze
diff --git a/lib/gitlab/git/diff.rb b/lib/gitlab/git/diff.rb
index 31d1b66b4f7..deade337354 100644
--- a/lib/gitlab/git/diff.rb
+++ b/lib/gitlab/git/diff.rb
@@ -11,6 +11,10 @@ module Gitlab
# Stats properties
attr_accessor :new_file, :renamed_file, :deleted_file
+ alias_method :new_file?, :new_file
+ alias_method :deleted_file?, :deleted_file
+ alias_method :renamed_file?, :renamed_file
+
attr_accessor :too_large
# The maximum size of a diff to display.
@@ -208,6 +212,10 @@ module Gitlab
hash
end
+ def mode_changed?
+ a_mode && b_mode && a_mode != b_mode
+ end
+
def submodule?
a_mode == '160000' || b_mode == '160000'
end
diff --git a/lib/gitlab/git/diff_collection.rb b/lib/gitlab/git/diff_collection.rb
index bcbad8ec829..898a5ae15f2 100644
--- a/lib/gitlab/git/diff_collection.rb
+++ b/lib/gitlab/git/diff_collection.rb
@@ -19,22 +19,19 @@ module Gitlab
@line_count = 0
@byte_count = 0
@overflow = false
+ @empty = true
@array = Array.new
end
def each(&block)
- if @populated
- # @iterator.each is slower than just iterating the array in place
- @array.each(&block)
- else
- Gitlab::GitalyClient.migrate(:commit_raw_diffs) do
- each_patch(&block)
- end
+ Gitlab::GitalyClient.migrate(:commit_raw_diffs) do
+ each_patch(&block)
end
end
def empty?
- !@iterator.any?
+ any? # Make sure the iterator has been exercised
+ @empty
end
def overflow?
@@ -60,17 +57,17 @@ module Gitlab
collection = each_with_index do |element, i|
@array[i] = yield(element)
end
- @populated = true
collection
end
+ alias_method :to_ary, :to_a
+
private
def populate!
return if @populated
each { nil } # force a loop through all diffs
- @populated = true
nil
end
@@ -79,15 +76,17 @@ module Gitlab
end
def each_patch
- @iterator.each_with_index do |raw, i|
- # First yield cached Diff instances from @array
- if @array[i]
- yield @array[i]
- next
- end
+ i = 0
+ @array.each do |diff|
+ yield diff
+ i += 1
+ end
+
+ return if @overflow
+ return if @iterator.nil?
- # We have exhausted @array, time to create new Diff instances or stop.
- break if @overflow
+ @iterator.each do |raw|
+ @empty = false
if !@all_diffs && i >= @max_files
@overflow = true
@@ -113,7 +112,13 @@ module Gitlab
end
yield @array[i] = diff
+ i += 1
end
+
+ @populated = true
+
+ # Allow iterator to be garbage-collected. It cannot be reused anyway.
+ @iterator = nil
end
end
end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 6200bd460ea..21f2e6b6970 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -12,6 +12,7 @@ module Gitlab
gon.katex_js_url = ActionController::Base.helpers.asset_path('katex.js')
gon.sentry_dsn = current_application_settings.clientside_sentry_dsn if current_application_settings.clientside_sentry_enabled
gon.gitlab_url = Gitlab.config.gitlab.url
+ gon.revision = Gitlab::REVISION
if current_user
gon.current_user_id = current_user.id
diff --git a/lib/gitlab/health_checks/fs_shards_check.rb b/lib/gitlab/health_checks/fs_shards_check.rb
index df962d203b7..e78b7f22e03 100644
--- a/lib/gitlab/health_checks/fs_shards_check.rb
+++ b/lib/gitlab/health_checks/fs_shards_check.rb
@@ -2,6 +2,9 @@ module Gitlab
module HealthChecks
class FsShardsCheck
extend BaseAbstractCheck
+ RANDOM_STRING = SecureRandom.hex(1000).freeze
+ COMMAND_TIMEOUT = '1'.freeze
+ TIMEOUT_EXECUTABLE = 'timeout'.freeze
class << self
def readiness
@@ -41,8 +44,6 @@ module Gitlab
private
- RANDOM_STRING = SecureRandom.hex(1000).freeze
-
def operation_metrics(ok_metric, latency_metric, operation, **labels)
with_timing operation do |result, elapsed|
[
@@ -63,8 +64,8 @@ module Gitlab
@storage_paths ||= Gitlab.config.repositories.storages
end
- def with_timeout(args)
- %w{timeout 1}.concat(args)
+ def exec_with_timeout(cmd_args, *args, &block)
+ Gitlab::Popen.popen([TIMEOUT_EXECUTABLE, COMMAND_TIMEOUT].concat(cmd_args), *args, &block)
end
def tmp_file_path(storage_name)
@@ -78,7 +79,7 @@ module Gitlab
def storage_stat_test(storage_name)
stat_path = File.join(path(storage_name), '.')
begin
- _, status = Gitlab::Popen.popen(with_timeout(%W{ stat #{stat_path} }))
+ _, status = exec_with_timeout(%W{ stat #{stat_path} })
status == 0
rescue Errno::ENOENT
File.exist?(stat_path) && File::Stat.new(stat_path).readable?
@@ -86,7 +87,7 @@ module Gitlab
end
def storage_write_test(tmp_path)
- _, status = Gitlab::Popen.popen(with_timeout(%W{ tee #{tmp_path} })) do |stdin|
+ _, status = exec_with_timeout(%W{ tee #{tmp_path} }) do |stdin|
stdin.write(RANDOM_STRING)
end
status == 0
@@ -96,7 +97,7 @@ module Gitlab
end
def storage_read_test(tmp_path)
- _, status = Gitlab::Popen.popen(with_timeout(%W{ diff #{tmp_path} - })) do |stdin|
+ _, status = exec_with_timeout(%W{ diff #{tmp_path} - }) do |stdin|
stdin.write(RANDOM_STRING)
end
status == 0
@@ -106,7 +107,7 @@ module Gitlab
end
def delete_test_file(tmp_path)
- _, status = Gitlab::Popen.popen(with_timeout(%W{ rm -f #{tmp_path} }))
+ _, status = exec_with_timeout(%W{ rm -f #{tmp_path} })
status == 0
rescue Errno::ENOENT
File.delete(tmp_path) rescue Errno::ENOENT
diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb
index 3411516319f..5ab3eeb3aff 100644
--- a/lib/gitlab/i18n.rb
+++ b/lib/gitlab/i18n.rb
@@ -12,15 +12,36 @@ module Gitlab
AVAILABLE_LANGUAGES.keys
end
- def set_locale(current_user)
- requested_locale = current_user&.preferred_language || ::I18n.default_locale
- locale = FastGettext.set_locale(requested_locale)
- ::I18n.locale = locale
+ def locale
+ FastGettext.locale
end
- def reset_locale
+ def locale=(locale_string)
+ requested_locale = locale_string || ::I18n.default_locale
+ new_locale = FastGettext.set_locale(requested_locale)
+ ::I18n.locale = new_locale
+ end
+
+ def use_default_locale
FastGettext.set_locale(::I18n.default_locale)
::I18n.locale = ::I18n.default_locale
end
+
+ def with_locale(locale_string)
+ original_locale = locale
+
+ self.locale = locale_string
+ yield
+ ensure
+ self.locale = original_locale
+ end
+
+ def with_user_locale(user, &block)
+ with_locale(user&.preferred_language, &block)
+ end
+
+ def with_default_locale(&block)
+ with_locale(::I18n.default_locale, &block)
+ end
end
end
diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb
new file mode 100644
index 00000000000..1c0abc9f7cf
--- /dev/null
+++ b/lib/gitlab/path_regex.rb
@@ -0,0 +1,264 @@
+module Gitlab
+ module PathRegex
+ extend self
+
+ # All routes that appear on the top level must be listed here.
+ # This will make sure that groups cannot be created with these names
+ # as these routes would be masked by the paths already in place.
+ #
+ # Example:
+ # /api/api-project
+ #
+ # the path `api` shouldn't be allowed because it would be masked by `api/*`
+ #
+ TOP_LEVEL_ROUTES = %w[
+ -
+ .well-known
+ abuse_reports
+ admin
+ all
+ api
+ assets
+ autocomplete
+ ci
+ dashboard
+ explore
+ files
+ groups
+ health_check
+ help
+ hooks
+ import
+ invites
+ issues
+ jwt
+ koding
+ member
+ merge_requests
+ new
+ notes
+ notification_settings
+ oauth
+ profile
+ projects
+ public
+ repository
+ robots.txt
+ s
+ search
+ sent_notifications
+ services
+ snippets
+ teams
+ u
+ unicorn_test
+ unsubscribes
+ uploads
+ users
+ ].freeze
+
+ # This list should contain all words following `/*namespace_id/:project_id` in
+ # routes that contain a second wildcard.
+ #
+ # Example:
+ # /*namespace_id/:project_id/badges/*ref/build
+ #
+ # If `badges` was allowed as a project/group name, we would not be able to access the
+ # `badges` route for those projects:
+ #
+ # Consider a namespace with path `foo/bar` and a project called `badges`.
+ # The route to the build badge would then be `/foo/bar/badges/badges/master/build.svg`
+ #
+ # When accessing this path the route would be matched to the `badges` path
+ # with the following params:
+ # - namespace_id: `foo`
+ # - project_id: `bar`
+ # - ref: `badges/master`
+ #
+ # Failing to find the project, this would result in a 404.
+ #
+ # By rejecting `badges` the router can _count_ on the fact that `badges` will
+ # be preceded by the `namespace/project`.
+ PROJECT_WILDCARD_ROUTES = %w[
+ badges
+ blame
+ blob
+ builds
+ commits
+ create
+ create_dir
+ edit
+ environments/folders
+ files
+ find_file
+ gitlab-lfs/objects
+ info/lfs/objects
+ new
+ preview
+ raw
+ refs
+ tree
+ update
+ wikis
+ ].freeze
+
+ # These are all the paths that follow `/groups/*id/ or `/groups/*group_id`
+ # We need to reject these because we have a `/groups/*id` page that is the same
+ # as the `/*id`.
+ #
+ # If we would allow a subgroup to be created with the name `activity` then
+ # this group would not be accessible through `/groups/parent/activity` since
+ # this would map to the activity-page of its parent.
+ GROUP_ROUTES = %w[
+ activity
+ analytics
+ audit_events
+ avatar
+ edit
+ group_members
+ hooks
+ issues
+ labels
+ ldap
+ ldap_group_links
+ merge_requests
+ milestones
+ notification_setting
+ pipeline_quota
+ projects
+ subgroups
+ ].freeze
+
+ ILLEGAL_PROJECT_PATH_WORDS = PROJECT_WILDCARD_ROUTES
+ ILLEGAL_GROUP_PATH_WORDS = (PROJECT_WILDCARD_ROUTES | GROUP_ROUTES).freeze
+
+ # The namespace regex is used in JavaScript to validate usernames in the "Register" form. However, Javascript
+ # does not support the negative lookbehind assertion (?<!) that disallows usernames ending in `.git` and `.atom`.
+ # Since this is a non-trivial problem to solve in Javascript (heavily complicate the regex, modify view code to
+ # allow non-regex validations, etc), `NAMESPACE_FORMAT_REGEX_JS` serves as a Javascript-compatible version of
+ # `NAMESPACE_FORMAT_REGEX`, with the negative lookbehind assertion removed. This means that the client-side validation
+ # will pass for usernames ending in `.atom` and `.git`, but will be caught by the server-side validation.
+ PATH_REGEX_STR = '[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*'.freeze
+ NAMESPACE_FORMAT_REGEX_JS = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze
+
+ NO_SUFFIX_REGEX = /(?<!\.git|\.atom)/.freeze
+ NAMESPACE_FORMAT_REGEX = /(?:#{NAMESPACE_FORMAT_REGEX_JS})#{NO_SUFFIX_REGEX}/.freeze
+ PROJECT_PATH_FORMAT_REGEX = /(?:#{PATH_REGEX_STR})#{NO_SUFFIX_REGEX}/.freeze
+ FULL_NAMESPACE_FORMAT_REGEX = %r{(#{NAMESPACE_FORMAT_REGEX}/)*#{NAMESPACE_FORMAT_REGEX}}.freeze
+
+ def root_namespace_route_regex
+ @root_namespace_route_regex ||= begin
+ illegal_words = Regexp.new(Regexp.union(TOP_LEVEL_ROUTES).source, Regexp::IGNORECASE)
+
+ single_line_regexp %r{
+ (?!(#{illegal_words})/)
+ #{NAMESPACE_FORMAT_REGEX}
+ }x
+ end
+ end
+
+ def full_namespace_route_regex
+ @full_namespace_route_regex ||= begin
+ illegal_words = Regexp.new(Regexp.union(ILLEGAL_GROUP_PATH_WORDS).source, Regexp::IGNORECASE)
+
+ single_line_regexp %r{
+ #{root_namespace_route_regex}
+ (?:
+ /
+ (?!#{illegal_words}/)
+ #{NAMESPACE_FORMAT_REGEX}
+ )*
+ }x
+ end
+ end
+
+ def project_route_regex
+ @project_route_regex ||= begin
+ illegal_words = Regexp.new(Regexp.union(ILLEGAL_PROJECT_PATH_WORDS).source, Regexp::IGNORECASE)
+
+ single_line_regexp %r{
+ (?!(#{illegal_words})/)
+ #{PROJECT_PATH_FORMAT_REGEX}
+ }x
+ end
+ end
+
+ def project_git_route_regex
+ @project_git_route_regex ||= /#{project_route_regex}\.git/.freeze
+ end
+
+ def root_namespace_path_regex
+ @root_namespace_path_regex ||= %r{\A#{root_namespace_route_regex}/\z}
+ end
+
+ def full_namespace_path_regex
+ @full_namespace_path_regex ||= %r{\A#{full_namespace_route_regex}/\z}
+ end
+
+ def project_path_regex
+ @project_path_regex ||= %r{\A#{project_route_regex}/\z}
+ end
+
+ def full_project_path_regex
+ @full_project_path_regex ||= %r{\A#{full_namespace_route_regex}/#{project_route_regex}/\z}
+ end
+
+ def full_namespace_format_regex
+ @namespace_format_regex ||= /A#{FULL_NAMESPACE_FORMAT_REGEX}\z/.freeze
+ end
+
+ def namespace_format_regex
+ @namespace_format_regex ||= /\A#{NAMESPACE_FORMAT_REGEX}\z/.freeze
+ end
+
+ def namespace_format_message
+ "can contain only letters, digits, '_', '-' and '.'. " \
+ "Cannot start with '-' or end in '.', '.git' or '.atom'." \
+ end
+
+ def project_path_format_regex
+ @project_path_format_regex ||= /\A#{PROJECT_PATH_FORMAT_REGEX}\z/.freeze
+ end
+
+ def project_path_format_message
+ "can contain only letters, digits, '_', '-' and '.'. " \
+ "Cannot start with '-', end in '.git' or end in '.atom'" \
+ end
+
+ def archive_formats_regex
+ # |zip|tar| tar.gz | tar.bz2 |
+ @archive_formats_regex ||= /(zip|tar|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)/.freeze
+ end
+
+ def git_reference_regex
+ # Valid git ref regex, see:
+ # https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
+
+ @git_reference_regex ||= single_line_regexp %r{
+ (?!
+ (?# doesn't begins with)
+ \/| (?# rule #6)
+ (?# doesn't contain)
+ .*(?:
+ [\/.]\.| (?# rule #1,3)
+ \/\/| (?# rule #6)
+ @\{| (?# rule #8)
+ \\ (?# rule #9)
+ )
+ )
+ [^\000-\040\177~^:?*\[]+ (?# rule #4-5)
+ (?# doesn't end with)
+ (?<!\.lock) (?# rule #1)
+ (?<![\/.]) (?# rule #6-7)
+ }x
+ end
+
+ private
+
+ def single_line_regexp(regex)
+ # Turns a multiline extended regexp into a single line one,
+ # beacuse `rake routes` breaks on multiline regexes.
+ Regexp.new(regex.source.gsub(/\(\?#.+?\)/, '').gsub(/\s*/, ''), regex.options ^ Regexp::EXTENDED).freeze
+ end
+ end
+end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index b7fef5dd068..e4d2a992470 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -2,39 +2,6 @@ module Gitlab
module Regex
extend self
- # The namespace regex is used in Javascript to validate usernames in the "Register" form. However, Javascript
- # does not support the negative lookbehind assertion (?<!) that disallows usernames ending in `.git` and `.atom`.
- # Since this is a non-trivial problem to solve in Javascript (heavily complicate the regex, modify view code to
- # allow non-regex validatiions, etc), `NAMESPACE_REGEX_STR_JS` serves as a Javascript-compatible version of
- # `NAMESPACE_REGEX_STR`, with the negative lookbehind assertion removed. This means that the client-side validation
- # will pass for usernames ending in `.atom` and `.git`, but will be caught by the server-side validation.
- PATH_REGEX_STR = '[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*'.freeze
- NAMESPACE_REGEX_STR_JS = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze
- NO_SUFFIX_REGEX_STR = '(?<!\.git|\.atom)'.freeze
- NAMESPACE_REGEX_STR = "(?:#{NAMESPACE_REGEX_STR_JS})#{NO_SUFFIX_REGEX_STR}".freeze
- PROJECT_REGEX_STR = "(?:#{PATH_REGEX_STR})#{NO_SUFFIX_REGEX_STR}".freeze
-
- # Same as NAMESPACE_REGEX_STR but allows `/` in the path.
- # So `group/subgroup` will match this regex but not NAMESPACE_REGEX_STR
- FULL_NAMESPACE_REGEX_STR = "(?:#{NAMESPACE_REGEX_STR}/)*#{NAMESPACE_REGEX_STR}".freeze
-
- def namespace_regex
- @namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze
- end
-
- def full_namespace_regex
- @full_namespace_regex ||= %r{\A#{FULL_NAMESPACE_REGEX_STR}\z}
- end
-
- def namespace_route_regex
- @namespace_route_regex ||= /#{NAMESPACE_REGEX_STR}/.freeze
- end
-
- def namespace_regex_message
- "can contain only letters, digits, '_', '-' and '.'. " \
- "Cannot start with '-' or end in '.', '.git' or '.atom'." \
- end
-
def namespace_name_regex
@namespace_name_regex ||= /\A[\p{Alnum}\p{Pd}_\. ]*\z/.freeze
end
@@ -52,23 +19,6 @@ module Gitlab
"It must start with letter, digit, emoji or '_'."
end
- def project_path_regex
- @project_path_regex ||= /\A#{PROJECT_REGEX_STR}\z/.freeze
- end
-
- def project_route_regex
- @project_route_regex ||= /#{PROJECT_REGEX_STR}/.freeze
- end
-
- def project_git_route_regex
- @project_route_git_regex ||= /#{PATH_REGEX_STR}\.git/.freeze
- end
-
- def project_path_regex_message
- "can contain only letters, digits, '_', '-' and '.'. " \
- "Cannot start with '-', end in '.git' or end in '.atom'" \
- end
-
def file_name_regex
@file_name_regex ||= /\A[[[:alnum:]]_\-\.\@\+]*\z/.freeze
end
@@ -77,36 +27,8 @@ module Gitlab
"can contain only letters, digits, '_', '-', '@', '+' and '.'."
end
- def archive_formats_regex
- # |zip|tar| tar.gz | tar.bz2 |
- @archive_formats_regex ||= /(zip|tar|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)/.freeze
- end
-
- def git_reference_regex
- # Valid git ref regex, see:
- # https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
-
- @git_reference_regex ||= %r{
- (?!
- (?# doesn't begins with)
- \/| (?# rule #6)
- (?# doesn't contain)
- .*(?:
- [\/.]\.| (?# rule #1,3)
- \/\/| (?# rule #6)
- @\{| (?# rule #8)
- \\ (?# rule #9)
- )
- )
- [^\000-\040\177~^:?*\[]+ (?# rule #4-5)
- (?# doesn't end with)
- (?<!\.lock) (?# rule #1)
- (?<![\/.]) (?# rule #6-7)
- }x.freeze
- end
-
def container_registry_reference_regex
- git_reference_regex
+ Gitlab::PathRegex.git_reference_regex
end
##
diff --git a/lib/tasks/tokens.rake b/lib/tasks/tokens.rake
index 95735f43802..ad1818ff1fa 100644
--- a/lib/tasks/tokens.rake
+++ b/lib/tasks/tokens.rake
@@ -11,6 +11,11 @@ namespace :tokens do
reset_all_users_token(:reset_incoming_email_token!)
end
+ desc "Reset all GitLab RSS tokens"
+ task reset_all_rss: :environment do
+ reset_all_users_token(:reset_rss_token!)
+ end
+
def reset_all_users_token(reset_token_method)
TmpUser.find_in_batches do |batch|
puts "Processing batch starting with user ID: #{batch.first.id}"
@@ -35,4 +40,9 @@ class TmpUser < ActiveRecord::Base
write_new_token(:incoming_email_token)
save!(validate: false)
end
+
+ def reset_rss_token!
+ write_new_token(:rss_token)
+ save!(validate: false)
+ end
end
diff --git a/package.json b/package.json
index 800327d8a08..8e280e77d67 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,7 @@
"jszip-utils": "^0.0.2",
"marked": "^0.3.6",
"mousetrap": "^1.4.6",
+ "name-all-modules-plugin": "^1.0.1",
"pdfjs-dist": "^1.8.252",
"pikaday": "^1.5.1",
"prismjs": "^1.6.0",
@@ -57,8 +58,8 @@
"vue-loader": "^11.3.4",
"vue-resource": "^0.9.3",
"vue-template-compiler": "^2.2.6",
- "webpack": "^2.3.3",
- "webpack-bundle-analyzer": "^2.3.0"
+ "webpack": "^2.6.1",
+ "webpack-bundle-analyzer": "^2.8.2"
},
"devDependencies": {
"babel-plugin-istanbul": "^4.0.0",
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index d40aae04fc3..3f99e2ff596 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -99,6 +99,42 @@ describe ApplicationController do
end
end
+ describe '#authenticate_user_from_rss_token' do
+ describe "authenticating a user from an RSS token" do
+ controller(described_class) do
+ def index
+ render text: 'authenticated'
+ end
+ end
+
+ context "when the 'rss_token' param is populated with the RSS token" do
+ context 'when the request format is atom' do
+ it "logs the user in" do
+ get :index, rss_token: user.rss_token, format: :atom
+ expect(response).to have_http_status 200
+ expect(response.body).to eq 'authenticated'
+ end
+ end
+
+ context 'when the request format is not atom' do
+ it "doesn't log the user in" do
+ get :index, rss_token: user.rss_token
+ expect(response.status).not_to have_http_status 200
+ expect(response.body).not_to eq 'authenticated'
+ end
+ end
+ end
+
+ context "when the 'rss_token' param is populated with an invalid RSS token" do
+ it "doesn't log the user" do
+ get :index, rss_token: "token"
+ expect(response.status).not_to eq 200
+ expect(response.body).not_to eq 'authenticated'
+ end
+ end
+ end
+ end
+
describe '#route_not_found' do
it 'renders 404 if authenticated' do
allow(controller).to receive(:current_user).and_return(user)
diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb
index 010e3180ea4..0be7bc6a045 100644
--- a/spec/controllers/import/bitbucket_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_controller_spec.rb
@@ -133,9 +133,13 @@ describe Import::BitbucketController do
end
context "when a namespace with the Bitbucket user's username already exists" do
- let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) }
+ let!(:existing_namespace) { create(:group, name: other_username) }
context "when the namespace is owned by the GitLab user" do
+ before do
+ existing_namespace.add_owner(user)
+ end
+
it "takes the existing namespace" do
expect(Gitlab::BitbucketImport::ProjectCreator).
to receive(:new).with(bitbucket_repo, bitbucket_repo.name, existing_namespace, user, access_params).
@@ -146,11 +150,6 @@ describe Import::BitbucketController do
end
context "when the namespace is not owned by the GitLab user" do
- before do
- existing_namespace.owner = create(:user)
- existing_namespace.save
- end
-
it "doesn't create a project" do
expect(Gitlab::BitbucketImport::ProjectCreator).
not_to receive(:new)
@@ -202,10 +201,14 @@ describe Import::BitbucketController do
end
context 'user has chosen an existing nested namespace and name for the project' do
- let(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
- let(:nested_namespace) { create(:namespace, name: 'bar', parent: parent_namespace, owner: user) }
+ let(:parent_namespace) { create(:group, name: 'foo', owner: user) }
+ let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
let(:test_name) { 'test_name' }
+ before do
+ nested_namespace.add_owner(user)
+ end
+
it 'takes the selected namespace and name' do
expect(Gitlab::BitbucketImport::ProjectCreator).
to receive(:new).with(bitbucket_repo, test_name, nested_namespace, user, access_params).
@@ -248,7 +251,7 @@ describe Import::BitbucketController do
context 'user has chosen existent and non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
- let!(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
+ let!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
it 'takes the selected namespace and name' do
expect(Gitlab::BitbucketImport::ProjectCreator).
diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb
index 2dbb89219d0..3afd09063d7 100644
--- a/spec/controllers/import/gitlab_controller_spec.rb
+++ b/spec/controllers/import/gitlab_controller_spec.rb
@@ -108,9 +108,13 @@ describe Import::GitlabController do
end
context "when a namespace with the GitLab.com user's username already exists" do
- let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) }
+ let!(:existing_namespace) { create(:group, name: other_username) }
context "when the namespace is owned by the GitLab server user" do
+ before do
+ existing_namespace.add_owner(user)
+ end
+
it "takes the existing namespace" do
expect(Gitlab::GitlabImport::ProjectCreator).
to receive(:new).with(gitlab_repo, existing_namespace, user, access_params).
@@ -121,11 +125,6 @@ describe Import::GitlabController do
end
context "when the namespace is not owned by the GitLab server user" do
- before do
- existing_namespace.owner = create(:user)
- existing_namespace.save
- end
-
it "doesn't create a project" do
expect(Gitlab::GitlabImport::ProjectCreator).
not_to receive(:new)
@@ -174,10 +173,14 @@ describe Import::GitlabController do
end
end
end
-
+
context 'user has chosen an existing nested namespace for the project' do
- let(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
- let(:nested_namespace) { create(:namespace, name: 'bar', parent: parent_namespace, owner: user) }
+ let(:parent_namespace) { create(:group, name: 'foo', owner: user) }
+ let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
+
+ before do
+ nested_namespace.add_owner(user)
+ end
it 'takes the selected namespace and name' do
expect(Gitlab::GitlabImport::ProjectCreator).
@@ -221,7 +224,7 @@ describe Import::GitlabController do
context 'user has chosen existent and non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
- let!(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
+ let!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
it 'takes the selected namespace and name' do
expect(Gitlab::GitlabImport::ProjectCreator).
diff --git a/spec/controllers/projects/builds_controller_spec.rb b/spec/controllers/projects/builds_controller_spec.rb
index 3ce23c17cdc..f41503fd34e 100644
--- a/spec/controllers/projects/builds_controller_spec.rb
+++ b/spec/controllers/projects/builds_controller_spec.rb
@@ -144,6 +144,8 @@ describe Projects::BuildsController do
it 'returns a trace' do
expect(response).to have_http_status(:ok)
+ expect(json_response['id']).to eq build.id
+ expect(json_response['status']).to eq build.status
expect(json_response['html']).to eq('BUILD TRACE')
end
end
@@ -153,10 +155,23 @@ describe Projects::BuildsController do
it 'returns no traces' do
expect(response).to have_http_status(:ok)
+ expect(json_response['id']).to eq build.id
+ expect(json_response['status']).to eq build.status
expect(json_response['html']).to be_nil
end
end
+ context 'when build has a trace with ANSI sequence and Unicode' do
+ let(:build) { create(:ci_build, :unicode_trace, pipeline: pipeline) }
+
+ it 'returns a trace with Unicode' do
+ expect(response).to have_http_status(:ok)
+ expect(json_response['id']).to eq build.id
+ expect(json_response['status']).to eq build.status
+ expect(json_response['html']).to include("ヾ(´༎ຶД༎ຶ`)ノ")
+ end
+ end
+
def get_trace
get :trace, namespace_id: project.namespace,
project_id: project,
@@ -185,48 +200,6 @@ describe Projects::BuildsController do
end
end
- describe 'GET trace.json' do
- let(:pipeline) { create(:ci_pipeline, project: project) }
- let(:build) { create(:ci_build, pipeline: pipeline) }
- let(:user) { create(:user) }
-
- context 'when user is logged in as developer' do
- before do
- project.add_developer(user)
- sign_in(user)
-
- get_trace
- end
-
- it 'traces build log' do
- expect(response).to have_http_status(:ok)
- expect(json_response['id']).to eq build.id
- expect(json_response['status']).to eq build.status
- end
- end
-
- context 'when user is logged in as non member' do
- before do
- sign_in(user)
-
- get_trace
- end
-
- it 'traces build log' do
- expect(response).to have_http_status(:ok)
- expect(json_response['id']).to eq build.id
- expect(json_response['status']).to eq build.status
- end
- end
-
- def get_trace
- get :trace, namespace_id: project.namespace,
- project_id: project,
- id: build.id,
- format: :json
- end
- end
-
describe 'POST retry' do
before do
project.add_developer(user)
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index c0f8c36a018..20f99b209eb 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -1,25 +1,25 @@
require 'spec_helper'
describe Projects::EnvironmentsController do
- let(:user) { create(:user) }
- let(:project) { create(:empty_project) }
+ set(:user) { create(:user) }
+ set(:project) { create(:empty_project) }
- let(:environment) do
+ set(:environment) do
create(:environment, name: 'production', project: project)
end
before do
- project.team << [user, :master]
+ project.add_master(user)
sign_in(user)
end
describe 'GET index' do
- context 'when standardrequest has been made' do
+ context 'when a request for the HTML is made' do
it 'responds with status code 200' do
get :index, environment_params
- expect(response).to be_ok
+ expect(response).to have_http_status(:ok)
end
end
@@ -84,6 +84,9 @@ describe Projects::EnvironmentsController do
create(:environment, project: project,
name: 'staging-1.0/review',
state: :available)
+ create(:environment, project: project,
+ name: 'staging-1.0/zzz',
+ state: :available)
end
context 'when using default format' do
@@ -98,7 +101,7 @@ describe Projects::EnvironmentsController do
end
context 'when using JSON format' do
- it 'responds with JSON' do
+ it 'sorts the subfolders lexicographically' do
get :folder, namespace_id: project.namespace,
project_id: project,
id: 'staging-1.0',
@@ -108,6 +111,8 @@ describe Projects::EnvironmentsController do
expect(response).not_to render_template 'folder'
expect(json_response['environments'][0])
.to include('name' => 'staging-1.0/review')
+ expect(json_response['environments'][1])
+ .to include('name' => 'staging-1.0/zzz')
end
end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index f0dc6df15ee..587a5820c6f 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -127,7 +127,7 @@ describe Projects::MergeRequestsController do
recorded = ActiveRecord::QueryRecorder.new { go(format: :json) }
- expect(recorded.count).to be_within(1).of(51)
+ expect(recorded.count).to be_within(5).of(50)
expect(recorded.cached_count).to eq(0)
end
end
@@ -357,8 +357,7 @@ describe Projects::MergeRequestsController do
end
before do
- pipeline = create(:ci_empty_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch)
- merge_request.update(head_pipeline: pipeline)
+ create(:ci_empty_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch, head_pipeline_of: merge_request)
end
it 'returns :merge_when_pipeline_succeeds' do
@@ -1173,13 +1172,13 @@ describe Projects::MergeRequestsController do
let!(:pipeline) do
create(:ci_pipeline, project: merge_request.source_project,
ref: merge_request.source_branch,
- sha: merge_request.diff_head_sha)
+ sha: merge_request.diff_head_sha,
+ head_pipeline_of: merge_request)
end
let(:status) { pipeline.detailed_status(double('user')) }
before do
- merge_request.update(head_pipeline: pipeline)
get_pipeline_status
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index a8be6768a47..4f6fc6691be 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -226,6 +226,50 @@ describe ProjectsController do
end
end
+ describe '#transfer' do
+ render_views
+
+ let(:project) { create(:project) }
+ let(:admin) { create(:admin) }
+ let(:new_namespace) { create(:namespace) }
+
+ it 'updates namespace' do
+ sign_in(admin)
+
+ put :transfer,
+ namespace_id: project.namespace.path,
+ new_namespace_id: new_namespace.id,
+ id: project.path,
+ format: :js
+
+ project.reload
+
+ expect(project.namespace).to eq(new_namespace)
+ expect(response).to have_http_status(200)
+ end
+
+ context 'when new namespace is empty' do
+ it 'project namespace is not changed' do
+ controller.instance_variable_set(:@project, project)
+ sign_in(admin)
+
+ old_namespace = project.namespace
+
+ put :transfer,
+ namespace_id: old_namespace.path,
+ new_namespace_id: nil,
+ id: project.path,
+ format: :js
+
+ project.reload
+
+ expect(project.namespace).to eq(old_namespace)
+ expect(response).to have_http_status(200)
+ expect(flash[:alert]).to eq 'Please select a new namespace for your project.'
+ end
+ end
+ end
+
describe "#destroy" do
let(:admin) { create(:admin) }
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 78ddd8d5584..f5e99fdf00b 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -128,6 +128,16 @@ FactoryGirl.define do
end
end
+ trait :unicode_trace do
+ after(:create) do |build, evaluator|
+ trace = File.binread(
+ File.expand_path(
+ Rails.root.join('spec/fixtures/trace/ansi-sequence-and-unicode')))
+
+ build.trace.set(trace)
+ end
+ end
+
trait :erased do
erased_at Time.now
erased_by factory: :user
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index 561fbc8e247..361c5b9a49e 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -20,6 +20,15 @@ FactoryGirl.define do
end
end
+ # Persist merge request head_pipeline_id
+ # on pipeline factories to avoid circular references
+ transient { head_pipeline_of nil }
+
+ after(:create) do |pipeline, evaluator|
+ merge_request = evaluator.head_pipeline_of
+ merge_request&.update(head_pipeline: pipeline)
+ end
+
factory :ci_pipeline do
transient { config nil }
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index 28ddd0da753..3fad4d2d658 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -20,7 +20,6 @@ FactoryGirl.define do
project factory: :empty_project
active true
properties({
- namespace: 'somepath',
api_url: 'https://kubernetes.example.com',
token: 'a' * 40
})
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index 33fa80772ff..e60fe713bc3 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -8,6 +8,10 @@ FactoryGirl.define do
confirmation_token { nil }
can_create_group true
+ before(:create) do |user|
+ user.ensure_rss_token
+ end
+
trait :admin do
admin true
end
diff --git a/spec/factories/web_hook_log.rb b/spec/factories/web_hook_log.rb
new file mode 100644
index 00000000000..230b3f6b26e
--- /dev/null
+++ b/spec/factories/web_hook_log.rb
@@ -0,0 +1,14 @@
+FactoryGirl.define do
+ factory :web_hook_log do
+ web_hook factory: :project_hook
+ trigger 'push_hooks'
+ url { generate(:url) }
+ request_headers {}
+ request_data {}
+ response_headers {}
+ response_body ''
+ response_status '200'
+ execution_duration 2.0
+ internal_error_message nil
+ end
+end
diff --git a/spec/features/admin/admin_hook_logs_spec.rb b/spec/features/admin/admin_hook_logs_spec.rb
new file mode 100644
index 00000000000..5b67f4de6ac
--- /dev/null
+++ b/spec/features/admin/admin_hook_logs_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+feature 'Admin::HookLogs', feature: true do
+ let(:project) { create(:project) }
+ let(:system_hook) { create(:system_hook) }
+ let(:hook_log) { create(:web_hook_log, web_hook: system_hook, internal_error_message: 'some error') }
+
+ before do
+ login_as :admin
+ end
+
+ scenario 'show list of hook logs' do
+ hook_log
+ visit edit_admin_hook_path(system_hook)
+
+ expect(page).to have_content('Recent Deliveries')
+ expect(page).to have_content(hook_log.url)
+ end
+
+ scenario 'show hook log details' do
+ hook_log
+ visit edit_admin_hook_path(system_hook)
+ click_link 'View details'
+
+ expect(page).to have_content("POST #{hook_log.url}")
+ expect(page).to have_content(hook_log.internal_error_message)
+ expect(page).to have_content('Resend Request')
+ end
+
+ scenario 'retry hook log' do
+ WebMock.stub_request(:post, system_hook.url)
+
+ hook_log
+ visit edit_admin_hook_path(system_hook)
+ click_link 'View details'
+ click_link 'Resend Request'
+
+ expect(current_path).to eq(edit_admin_hook_path(system_hook))
+ end
+end
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index c5f24d412d7..80f7ec43c06 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -58,10 +58,19 @@ describe 'Admin::Hooks', feature: true do
end
describe 'Remove existing hook' do
- it 'remove existing hook' do
- visit admin_hooks_path
+ context 'removes existing hook' do
+ it 'from hooks list page' do
+ visit admin_hooks_path
+
+ expect { click_link 'Remove' }.to change(SystemHook, :count).by(-1)
+ end
- expect { click_link 'Remove' }.to change(SystemHook, :count).by(-1)
+ it 'from hook edit page' do
+ visit admin_hooks_path
+ click_link 'Edit'
+
+ expect { click_link 'Remove' }.to change(SystemHook, :count).by(-1)
+ end
end
end
diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb
index fa3d9ee25c0..a9251db13e5 100644
--- a/spec/features/admin/admin_labels_spec.rb
+++ b/spec/features/admin/admin_labels_spec.rb
@@ -34,11 +34,11 @@ RSpec.describe 'admin issues labels' do
page.within '.labels' do
page.all('.btn-remove').each do |remove|
remove.click
- wait_for_ajax
+ wait_for_requests
end
end
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content("There are no labels yet")
expect(page).not_to have_content('bug')
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index c5b1ef1295c..12cf59f42b0 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -277,7 +277,7 @@ describe "Admin::Users", feature: true do
page.within(first('.group_member')) do
find('.btn-remove').click
end
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_selector('.group_member')
end
diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb
index 9ea325ab41b..711c8a710f3 100644
--- a/spec/features/atom/dashboard_issues_spec.rb
+++ b/spec/features/atom/dashboard_issues_spec.rb
@@ -20,13 +20,20 @@ describe "Dashboard Issues Feed", feature: true do
expect(body).to have_selector('title', text: "#{user.name} issues")
end
+ it "renders atom feed via RSS token" do
+ visit issues_dashboard_path(:atom, rss_token: user.rss_token)
+
+ expect(response_headers['Content-Type']).to have_content('application/atom+xml')
+ expect(body).to have_selector('title', text: "#{user.name} issues")
+ end
+
it "renders atom feed with url parameters" do
- visit issues_dashboard_path(:atom, private_token: user.private_token, state: 'opened', assignee_id: user.id)
+ visit issues_dashboard_path(:atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
params = CGI.parse(URI.parse(link[:href]).query)
- expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s])
end
@@ -35,7 +42,7 @@ describe "Dashboard Issues Feed", feature: true do
let!(:issue2) { create(:issue, author: user, assignees: [assignee], project: project2, description: 'test desc') }
it "renders issue fields" do
- visit issues_dashboard_path(:atom, private_token: user.private_token)
+ visit issues_dashboard_path(:atom, rss_token: user.rss_token)
entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue2.title}')]")
@@ -58,7 +65,7 @@ describe "Dashboard Issues Feed", feature: true do
end
it "renders issue label and milestone info" do
- visit issues_dashboard_path(:atom, private_token: user.private_token)
+ visit issues_dashboard_path(:atom, rss_token: user.rss_token)
entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue1.title}')]")
diff --git a/spec/features/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb
index 746df36bb25..1df058b023c 100644
--- a/spec/features/atom/dashboard_spec.rb
+++ b/spec/features/atom/dashboard_spec.rb
@@ -11,6 +11,13 @@ describe "Dashboard Feed", feature: true do
end
end
+ context "projects atom feed via RSS token" do
+ it "renders projects atom feed" do
+ visit dashboard_projects_path(:atom, rss_token: user.rss_token)
+ expect(body).to have_selector('feed title')
+ end
+ end
+
context 'feed content' do
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project, author: user, description: '') }
@@ -20,7 +27,7 @@ describe "Dashboard Feed", feature: true do
project.team << [user, :master]
issue_event(issue, user)
note_event(note, user)
- visit dashboard_projects_path(:atom, private_token: user.private_token)
+ visit dashboard_projects_path(:atom, rss_token: user.rss_token)
end
it "has issue opened event" do
diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb
index 4f6754ad541..a61231ea254 100644
--- a/spec/features/atom/issues_spec.rb
+++ b/spec/features/atom/issues_spec.rb
@@ -43,25 +43,40 @@ describe 'Issues Feed', feature: true do
end
end
+ context 'when authenticated via RSS token' do
+ it 'renders atom feed' do
+ visit namespace_project_issues_path(project.namespace, project, :atom,
+ rss_token: user.rss_token)
+
+ expect(response_headers['Content-Type']).
+ to have_content('application/atom+xml')
+ expect(body).to have_selector('title', text: "#{project.name} issues")
+ expect(body).to have_selector('author email', text: issue.author_public_email)
+ expect(body).to have_selector('assignees assignee email', text: issue.assignees.first.public_email)
+ expect(body).to have_selector('assignee email', text: issue.assignees.first.public_email)
+ expect(body).to have_selector('entry summary', text: issue.title)
+ end
+ end
+
it "renders atom feed with url parameters for project issues" do
visit namespace_project_issues_path(project.namespace, project,
- :atom, private_token: user.private_token, state: 'opened', assignee_id: user.id)
+ :atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
params = CGI.parse(URI.parse(link[:href]).query)
- expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s])
end
it "renders atom feed with url parameters for group issues" do
- visit issues_group_path(group, :atom, private_token: user.private_token, state: 'opened', assignee_id: user.id)
+ visit issues_group_path(group, :atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
params = CGI.parse(URI.parse(link[:href]).query)
- expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s])
end
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index 7a2987e815d..fae5aaa52bd 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -11,6 +11,13 @@ describe "User Feed", feature: true do
end
end
+ context 'user atom feed via RSS token' do
+ it "renders user atom feed" do
+ visit user_path(user, :atom, rss_token: user.rss_token)
+ expect(body).to have_selector('feed title')
+ end
+ end
+
context 'feed content' do
let(:project) { create(:project) }
let(:issue) do
@@ -40,7 +47,7 @@ describe "User Feed", feature: true do
issue_event(issue, user)
note_event(note, user)
merge_request_event(merge_request, user)
- visit user_path(user, :atom, private_token: user.private_token)
+ visit user_path(user, :atom, rss_token: user.rss_token)
end
it 'has issue opened event' do
diff --git a/spec/features/auto_deploy_spec.rb b/spec/features/auto_deploy_spec.rb
index 6c7423e4922..1cf7396bbac 100644
--- a/spec/features/auto_deploy_spec.rb
+++ b/spec/features/auto_deploy_spec.rb
@@ -46,7 +46,7 @@ describe 'Auto deploy' do
within '.gitlab-ci-yml-selector' do
click_on 'OpenShift'
end
- wait_for_ajax
+ wait_for_requests
click_button 'Commit changes'
expect(page).to have_content('New Merge Request From auto-deploy into master')
diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb
index 505e0b5c355..32ac265814f 100644
--- a/spec/features/boards/add_issues_modal_spec.rb
+++ b/spec/features/boards/add_issues_modal_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
describe 'Issue Boards add issue modal', :feature, :js do
- include WaitForVueResource
-
let(:project) { create(:empty_project, :public) }
let(:board) { create(:board, project: project) }
let(:user) { create(:user) }
@@ -19,13 +17,13 @@ describe 'Issue Boards add issue modal', :feature, :js do
login_as(user)
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
end
it 'resets filtered search state' do
visit namespace_project_board_path(project.namespace, project, board, search: 'testing')
- wait_for_vue_resource
+ wait_for_requests
click_button('Add issues')
@@ -74,7 +72,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
before do
click_button('Add issues')
- wait_for_vue_resource
+ wait_for_requests
end
it 'loads issues' do
@@ -107,7 +105,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
click_button('Add issues')
- wait_for_vue_resource
+ wait_for_requests
page.within('.add-issues-modal') do
expect(find('.add-issues-footer')).not_to have_button(planning.title)
@@ -122,7 +120,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
find('.form-control').native.send_keys(issue.title)
find('.form-control').native.send_keys(:enter)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.card', count: 1)
end
@@ -133,7 +131,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
find('.form-control').native.send_keys('testing search')
find('.form-control').native.send_keys(:enter)
- wait_for_vue_resource
+ wait_for_requests
expect(page).not_to have_selector('.card')
expect(page).not_to have_content("You haven't added any issues to your project yet")
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 18585488e26..ba27db23ced 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -1,7 +1,6 @@
require 'rails_helper'
describe 'Issue Boards', feature: true, js: true do
- include WaitForVueResource
include DragTo
let(:project) { create(:empty_project, :public) }
@@ -19,7 +18,7 @@ describe 'Issue Boards', feature: true, js: true do
context 'no lists' do
before do
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.board', count: 2)
end
@@ -46,7 +45,7 @@ describe 'Issue Boards', feature: true, js: true do
page.within(find('.board-blank-state')) do
click_button('Add default lists')
end
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.board', count: 3)
@@ -84,7 +83,7 @@ describe 'Issue Boards', feature: true, js: true do
before do
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.board', count: 3)
expect(find('.board:nth-child(1)')).to have_selector('.card')
@@ -117,7 +116,7 @@ describe 'Issue Boards', feature: true, js: true do
find('.filtered-search').set(issue8.title)
find('.filtered-search').native.send_keys(:enter)
- wait_for_vue_resource
+ wait_for_requests
expect(find('.board:nth-child(1)')).to have_selector('.card', count: 0)
expect(find('.board:nth-child(2)')).to have_selector('.card', count: 0)
@@ -128,7 +127,7 @@ describe 'Issue Boards', feature: true, js: true do
find('.filtered-search').set(issue5.title)
find('.filtered-search').native.send_keys(:enter)
- wait_for_vue_resource
+ wait_for_requests
expect(find('.board:nth-child(1)')).to have_selector('.card', count: 1)
expect(find('.board:nth-child(2)')).to have_selector('.card', count: 0)
@@ -140,20 +139,20 @@ describe 'Issue Boards', feature: true, js: true do
find('.board-delete').click
end
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.board', count: 2)
end
it 'removes checkmark in new list dropdown after deleting' do
click_button 'Add list'
- wait_for_ajax
+ wait_for_requests
page.within(find('.board:nth-child(1)')) do
find('.board-delete').click
end
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.board', count: 2)
end
@@ -164,7 +163,7 @@ describe 'Issue Boards', feature: true, js: true do
end
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
page.within(find('.board', match: :first)) do
expect(page.find('.board-header')).to have_content('58')
@@ -172,13 +171,13 @@ describe 'Issue Boards', feature: true, js: true do
expect(page).to have_content('Showing 20 of 58 issues')
evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.card', count: 40)
expect(page).to have_content('Showing 40 of 58 issues')
evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.card', count: 58)
expect(page).to have_content('Showing all issues')
@@ -188,7 +187,7 @@ describe 'Issue Boards', feature: true, js: true do
context 'closed' do
it 'shows list of closed issues' do
wait_for_board_cards(3, 1)
- wait_for_ajax
+ wait_for_requests
end
it 'moves issue to closed' do
@@ -272,7 +271,7 @@ describe 'Issue Boards', feature: true, js: true do
context 'new list' do
it 'shows all labels in new list dropdown' do
click_button 'Add list'
- wait_for_ajax
+ wait_for_requests
page.within('.dropdown-menu-issues-board-new') do
expect(page).to have_content(planning.title)
@@ -283,52 +282,52 @@ describe 'Issue Boards', feature: true, js: true do
it 'creates new list for label' do
click_button 'Add list'
- wait_for_ajax
+ wait_for_requests
page.within('.dropdown-menu-issues-board-new') do
click_link testing.title
end
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.board', count: 4)
end
it 'creates new list for Backlog label' do
click_button 'Add list'
- wait_for_ajax
+ wait_for_requests
page.within('.dropdown-menu-issues-board-new') do
click_link backlog.title
end
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.board', count: 4)
end
it 'creates new list for Closed label' do
click_button 'Add list'
- wait_for_ajax
+ wait_for_requests
page.within('.dropdown-menu-issues-board-new') do
click_link closed.title
end
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.board', count: 4)
end
it 'keeps dropdown open after adding new list' do
click_button 'Add list'
- wait_for_ajax
+ wait_for_requests
page.within('.dropdown-menu-issues-board-new') do
click_link closed.title
end
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_css('#js-add-list.open')
end
@@ -336,7 +335,7 @@ describe 'Issue Boards', feature: true, js: true do
it 'creates new list from a new label' do
click_button 'Add list'
- wait_for_ajax
+ wait_for_requests
click_link 'Create new label'
@@ -346,8 +345,8 @@ describe 'Issue Boards', feature: true, js: true do
click_button 'Create'
- wait_for_ajax
- wait_for_vue_resource
+ wait_for_requests
+ wait_for_requests
expect(page).to have_selector('.board', count: 4)
end
@@ -360,7 +359,7 @@ describe 'Issue Boards', feature: true, js: true do
click_filter_link(user2.username)
submit_filter
- wait_for_vue_resource
+ wait_for_requests
wait_for_board_cards(1, 1)
wait_for_empty_boards((2..3))
end
@@ -370,7 +369,7 @@ describe 'Issue Boards', feature: true, js: true do
click_filter_link(user.username)
submit_filter
- wait_for_vue_resource
+ wait_for_requests
wait_for_board_cards(1, 1)
wait_for_empty_boards((2..3))
@@ -381,7 +380,7 @@ describe 'Issue Boards', feature: true, js: true do
click_filter_link(milestone.title)
submit_filter
- wait_for_vue_resource
+ wait_for_requests
wait_for_board_cards(1, 1)
wait_for_board_cards(2, 0)
wait_for_board_cards(3, 0)
@@ -392,7 +391,7 @@ describe 'Issue Boards', feature: true, js: true do
click_filter_link(testing.title)
submit_filter
- wait_for_vue_resource
+ wait_for_requests
wait_for_board_cards(1, 1)
wait_for_empty_boards((2..3))
end
@@ -407,7 +406,7 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_board_cards(1, 1)
wait_for_empty_boards((2..3))
- wait_for_vue_resource
+ wait_for_requests
page.within(find('.board', match: :first)) do
expect(page.find('.board-header')).to have_content('1')
@@ -442,7 +441,7 @@ describe 'Issue Boards', feature: true, js: true do
click_filter_link(testing.title)
submit_filter
- wait_for_vue_resource
+ wait_for_requests
page.within(find('.board', match: :first)) do
expect(page.find('.board-header')).to have_content('51')
@@ -470,7 +469,7 @@ describe 'Issue Boards', feature: true, js: true do
submit_filter
- wait_for_vue_resource
+ wait_for_requests
wait_for_board_cards(1, 1)
wait_for_empty_boards((2..3))
@@ -481,14 +480,14 @@ describe 'Issue Boards', feature: true, js: true do
expect(page).to have_selector('.card', count: 8)
expect(find('.card', match: :first)).to have_content(bug.title)
click_button(bug.title)
- wait_for_vue_resource
+ wait_for_requests
end
page.within('.tokens-container') do
expect(page).to have_content(bug.title)
end
- wait_for_vue_resource
+ wait_for_requests
wait_for_board_cards(1, 1)
wait_for_empty_boards((2..3))
@@ -500,12 +499,12 @@ describe 'Issue Boards', feature: true, js: true do
click_button(bug.title)
end
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.card', count: 1)
end
- wait_for_vue_resource
+ wait_for_requests
end
end
end
@@ -513,7 +512,7 @@ describe 'Issue Boards', feature: true, js: true do
context 'keyboard shortcuts' do
before do
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
end
it 'allows user to use keyboard shortcuts' do
@@ -526,7 +525,7 @@ describe 'Issue Boards', feature: true, js: true do
before do
logout
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
end
it 'displays lists' do
@@ -550,7 +549,7 @@ describe 'Issue Boards', feature: true, js: true do
logout
login_as(user_guest)
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
end
it 'does not show create new list' do
diff --git a/spec/features/boards/issue_ordering_spec.rb b/spec/features/boards/issue_ordering_spec.rb
index bfa2a72a256..6c40cb2c9eb 100644
--- a/spec/features/boards/issue_ordering_spec.rb
+++ b/spec/features/boards/issue_ordering_spec.rb
@@ -1,7 +1,6 @@
require 'rails_helper'
describe 'Issue Boards', :feature, :js do
- include WaitForVueResource
include DragTo
let(:project) { create(:empty_project, :public) }
@@ -24,7 +23,7 @@ describe 'Issue Boards', :feature, :js do
before do
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.board', count: 2)
end
@@ -38,7 +37,7 @@ describe 'Issue Boards', :feature, :js do
it 'moves un-ordered issue to top of list' do
drag(from_index: 3, to_index: 0)
- wait_for_vue_resource
+ wait_for_requests
page.within(first('.board')) do
expect(first('.card')).to have_content(issue4.title)
@@ -49,7 +48,7 @@ describe 'Issue Boards', :feature, :js do
context 'ordering in list' do
before do
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.board', count: 2)
end
@@ -57,7 +56,7 @@ describe 'Issue Boards', :feature, :js do
it 'moves from middle to top' do
drag(from_index: 1, to_index: 0)
- wait_for_vue_resource
+ wait_for_requests
expect(first('.card')).to have_content(issue2.title)
end
@@ -65,7 +64,7 @@ describe 'Issue Boards', :feature, :js do
it 'moves from middle to bottom' do
drag(from_index: 1, to_index: 2)
- wait_for_vue_resource
+ wait_for_requests
expect(all('.card').last).to have_content(issue2.title)
end
@@ -73,7 +72,7 @@ describe 'Issue Boards', :feature, :js do
it 'moves from top to bottom' do
drag(from_index: 0, to_index: 2)
- wait_for_vue_resource
+ wait_for_requests
expect(all('.card').last).to have_content(issue3.title)
end
@@ -81,7 +80,7 @@ describe 'Issue Boards', :feature, :js do
it 'moves from bottom to top' do
drag(from_index: 2, to_index: 0)
- wait_for_vue_resource
+ wait_for_requests
expect(first('.card')).to have_content(issue1.title)
end
@@ -89,7 +88,7 @@ describe 'Issue Boards', :feature, :js do
it 'moves from top to middle' do
drag(from_index: 0, to_index: 1)
- wait_for_vue_resource
+ wait_for_requests
expect(first('.card')).to have_content(issue2.title)
end
@@ -97,7 +96,7 @@ describe 'Issue Boards', :feature, :js do
it 'moves from bottom to middle' do
drag(from_index: 2, to_index: 1)
- wait_for_vue_resource
+ wait_for_requests
expect(all('.card').last).to have_content(issue2.title)
end
@@ -112,7 +111,7 @@ describe 'Issue Boards', :feature, :js do
before do
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.board', count: 3)
end
@@ -120,7 +119,7 @@ describe 'Issue Boards', :feature, :js do
it 'moves to top of another list' do
drag(list_from_index: 0, list_to_index: 1)
- wait_for_vue_resource
+ wait_for_requests
expect(first('.board')).to have_selector('.card', count: 2)
expect(all('.board')[1]).to have_selector('.card', count: 4)
@@ -133,7 +132,7 @@ describe 'Issue Boards', :feature, :js do
it 'moves to bottom of another list' do
drag(list_from_index: 0, list_to_index: 1, to_index: 2)
- wait_for_vue_resource
+ wait_for_requests
expect(first('.board')).to have_selector('.card', count: 2)
expect(all('.board')[1]).to have_selector('.card', count: 4)
@@ -146,7 +145,7 @@ describe 'Issue Boards', :feature, :js do
it 'moves to index of another list' do
drag(list_from_index: 0, list_to_index: 1, to_index: 1)
- wait_for_vue_resource
+ wait_for_requests
expect(first('.board')).to have_selector('.card', count: 2)
expect(all('.board')[1]).to have_selector('.card', count: 4)
diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb
index a9cc6c49f8e..c2167ba12cd 100644
--- a/spec/features/boards/keyboard_shortcut_spec.rb
+++ b/spec/features/boards/keyboard_shortcut_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
describe 'Issue Boards shortcut', feature: true, js: true do
- include WaitForVueResource
-
let(:project) { create(:empty_project) }
before do
@@ -17,6 +15,6 @@ describe 'Issue Boards shortcut', feature: true, js: true do
find('body').native.send_keys('gb')
expect(page).to have_selector('.boards-list')
- wait_for_vue_resource
+ wait_for_requests
end
end
diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb
index e1367c675e5..ce132bfd979 100644
--- a/spec/features/boards/modal_filter_spec.rb
+++ b/spec/features/boards/modal_filter_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
describe 'Issue Boards add issue modal filtering', :feature, :js do
- include WaitForVueResource
-
let(:project) { create(:empty_project, :public) }
let(:board) { create(:board, project: project) }
let(:planning) { create(:label, project: project, name: 'Planning') }
@@ -24,7 +22,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
find('.form-control').native.send_keys('testing empty state')
find('.form-control').native.send_keys(:enter)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_content('There are no issues to show.')
end
@@ -38,7 +36,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
submit_filter
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.card', count: 0)
@@ -48,7 +46,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
click_button('Add issues')
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.card', count: 1)
end
@@ -62,13 +60,13 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
submit_filter
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.card', count: 0)
find('.clear-search').click
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.card', count: 1)
end
@@ -89,7 +87,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
submit_filter
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.js-visual-token', text: user2.username)
expect(page).to have_selector('.card', count: 1)
@@ -112,7 +110,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
submit_filter
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.js-visual-token', text: 'none')
expect(page).to have_selector('.card', count: 1)
@@ -125,7 +123,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
submit_filter
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.js-visual-token', text: user2.username)
expect(page).to have_selector('.card', count: 1)
@@ -147,7 +145,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
submit_filter
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.js-visual-token', text: 'upcoming')
expect(page).to have_selector('.card', count: 0)
@@ -160,7 +158,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
submit_filter
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.js-visual-token', text: milestone.name)
expect(page).to have_selector('.card', count: 1)
@@ -182,7 +180,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
submit_filter
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.js-visual-token', text: 'none')
expect(page).to have_selector('.card', count: 1)
@@ -195,7 +193,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
submit_filter
page.within('.add-issues-modal') do
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.js-visual-token', text: label.title)
expect(page).to have_selector('.card', count: 1)
@@ -205,7 +203,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
def visit_board
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
click_button('Add issues')
end
diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb
index f04a1a89e96..0e98f994018 100644
--- a/spec/features/boards/new_issue_spec.rb
+++ b/spec/features/boards/new_issue_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
describe 'Issue Boards new issue', feature: true, js: true do
- include WaitForVueResource
-
let(:project) { create(:empty_project, :public) }
let(:board) { create(:board, project: project) }
let!(:list) { create(:list, board: board, position: 0) }
@@ -15,7 +13,7 @@ describe 'Issue Boards new issue', feature: true, js: true do
login_as(user)
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.board', count: 2)
end
@@ -60,7 +58,7 @@ describe 'Issue Boards new issue', feature: true, js: true do
click_button 'Submit issue'
end
- wait_for_vue_resource
+ wait_for_requests
page.within(first('.board .board-issue-count')) do
expect(page).to have_content('1')
@@ -77,7 +75,7 @@ describe 'Issue Boards new issue', feature: true, js: true do
click_button 'Submit issue'
end
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.issue-boards-sidebar')
end
@@ -86,7 +84,7 @@ describe 'Issue Boards new issue', feature: true, js: true do
context 'unauthorized user' do
before do
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
end
it 'does not display new issue button' do
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 4667be49fe6..34f4d765117 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
describe 'Issue Boards', feature: true, js: true do
- include WaitForVueResource
-
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:project) { create(:empty_project, :public) }
@@ -25,7 +23,7 @@ describe 'Issue Boards', feature: true, js: true do
login_as(user)
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
end
after do
@@ -74,7 +72,7 @@ describe 'Issue Boards', feature: true, js: true do
click_button 'Remove from board'
end
- wait_for_vue_resource
+ wait_for_requests
page.within(first('.board')) do
expect(page).to have_selector('.card', count: 1)
@@ -88,12 +86,12 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.assignee') do
click_link 'Edit'
- wait_for_ajax
+ wait_for_requests
page.within('.dropdown-menu-user') do
click_link user.name
- wait_for_vue_resource
+ wait_for_requests
end
expect(page).to have_content(user.name)
@@ -109,13 +107,13 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.assignee') do
click_link 'Edit'
- wait_for_ajax
+ wait_for_requests
page.within('.dropdown-menu-user') do
click_link 'Unassigned'
end
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_content('No assignee')
end
@@ -131,7 +129,7 @@ describe 'Issue Boards', feature: true, js: true do
click_button 'assign yourself'
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_content(user.name)
end
@@ -145,12 +143,12 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.assignee') do
click_link 'Edit'
- wait_for_ajax
+ wait_for_requests
page.within('.dropdown-menu-user') do
click_link user.name
- wait_for_vue_resource
+ wait_for_requests
end
expect(page).to have_content(user.name)
@@ -162,7 +160,7 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.assignee') do
click_link 'Edit'
-
+
expect(find('.dropdown-menu')).to have_selector('.is-active')
end
end
@@ -175,11 +173,11 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.milestone') do
click_link 'Edit'
- wait_for_ajax
+ wait_for_requests
click_link milestone.title
- wait_for_vue_resource
+ wait_for_requests
page.within('.value') do
expect(page).to have_content(milestone.title)
@@ -193,11 +191,11 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.milestone') do
click_link 'Edit'
- wait_for_ajax
+ wait_for_requests
click_link "No Milestone"
- wait_for_vue_resource
+ wait_for_requests
page.within('.value') do
expect(page).not_to have_content(milestone.title)
@@ -215,7 +213,7 @@ describe 'Issue Boards', feature: true, js: true do
click_button Date.today.day
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_content(Date.today.to_s(:medium))
end
@@ -229,11 +227,11 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.labels') do
click_link 'Edit'
- wait_for_ajax
+ wait_for_requests
click_link bug.title
- wait_for_vue_resource
+ wait_for_requests
find('.dropdown-menu-close-icon').click
@@ -253,12 +251,12 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.labels') do
click_link 'Edit'
- wait_for_ajax
+ wait_for_requests
click_link bug.title
click_link regression.title
- wait_for_vue_resource
+ wait_for_requests
find('.dropdown-menu-close-icon').click
@@ -280,11 +278,11 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.labels') do
click_link 'Edit'
- wait_for_ajax
+ wait_for_requests
click_link stretch.title
- wait_for_vue_resource
+ wait_for_requests
find('.dropdown-menu-close-icon').click
@@ -305,7 +303,7 @@ describe 'Issue Boards', feature: true, js: true do
page.within('.subscription') do
click_button 'Subscribe'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content("Unsubscribe")
end
end
diff --git a/spec/features/boards/sub_group_project_spec.rb b/spec/features/boards/sub_group_project_spec.rb
index 6cd7fddd288..4cd05010a93 100644
--- a/spec/features/boards/sub_group_project_spec.rb
+++ b/spec/features/boards/sub_group_project_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
describe 'Sub-group project issue boards', :feature, :js do
- include WaitForVueResource
-
let(:group) { create(:group) }
let(:nested_group_1) { create(:group, parent: group) }
let(:project) { create(:empty_project, group: nested_group_1) }
@@ -18,7 +16,7 @@ describe 'Sub-group project issue boards', :feature, :js do
login_as(user)
visit namespace_project_board_path(project.namespace, project, board)
- wait_for_vue_resource
+ wait_for_requests
end
it 'creates new label from sidebar' do
@@ -35,7 +33,7 @@ describe 'Sub-group project issue boards', :feature, :js do
click_button 'Create'
- wait_for_ajax
+ wait_for_requests
end
page.within '.labels' do
diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb
index 496faf87a16..1b6d8439f92 100644
--- a/spec/features/calendar_spec.rb
+++ b/spec/features/calendar_spec.rb
@@ -74,7 +74,7 @@ feature 'Contributions Calendar', :feature, :js do
describe 'calendar day selection' do
before do
visit user.username
- wait_for_ajax
+ wait_for_requests
end
it 'displays calendar' do
@@ -86,7 +86,7 @@ feature 'Contributions Calendar', :feature, :js do
before do
cells[0].click
- wait_for_ajax
+ wait_for_requests
@first_day_activities = selected_day_activities
end
@@ -97,7 +97,7 @@ feature 'Contributions Calendar', :feature, :js do
describe 'select another calendar day' do
before do
cells[1].click
- wait_for_ajax
+ wait_for_requests
end
it 'displays different calendar day activities' do
@@ -108,7 +108,7 @@ feature 'Contributions Calendar', :feature, :js do
describe 'deselect calendar day' do
before do
cells[0].click
- wait_for_ajax
+ wait_for_requests
end
it 'hides calendar day activities' do
@@ -122,7 +122,7 @@ feature 'Contributions Calendar', :feature, :js do
shared_context 'visit user page' do
before do
visit user.username
- wait_for_ajax
+ wait_for_requests
end
end
diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb
index be615519a09..740f60c05cc 100644
--- a/spec/features/copy_as_gfm_spec.rb
+++ b/spec/features/copy_as_gfm_spec.rb
@@ -51,7 +51,6 @@ describe 'Copy as GFM', feature: true, js: true do
To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/).
-
- Manage Git repositories with fine grained access controls that keep your code secure
- Perform code reviews and enhance collaboration with merge requests
@@ -66,6 +65,38 @@ describe 'Copy as GFM', feature: true, js: true do
GFM
)
+ aggregate_failures('an accidentally selected empty element') do
+ gfm = '# Heading1'
+
+ html = <<-HTML.strip_heredoc
+ <h1>Heading1</h1>
+
+ <h2></h2>
+ HTML
+
+ output_gfm = html_to_gfm(html)
+ expect(output_gfm.strip).to eq(gfm.strip)
+ end
+
+ aggregate_failures('an accidentally selected other element') do
+ gfm = 'Test comment with **Markdown!**'
+
+ html = <<-HTML.strip_heredoc
+ <li class="note">
+ <div class="md">
+ <p>
+ Test comment with <strong>Markdown!</strong>
+ </p>
+ </div>
+ </li>
+
+ <li class="note"></li>
+ HTML
+
+ output_gfm = html_to_gfm(html)
+ expect(output_gfm.strip).to eq(gfm.strip)
+ end
+
verify(
'InlineDiffFilter',
@@ -352,7 +383,6 @@ describe 'Copy as GFM', feature: true, js: true do
<<-GFM.strip_heredoc,
- Nested
-
- Lists
GFM
@@ -375,7 +405,6 @@ describe 'Copy as GFM', feature: true, js: true do
<<-GFM.strip_heredoc,
1. Nested
-
1. Numbered lists
GFM
@@ -479,7 +508,7 @@ describe 'Copy as GFM', feature: true, js: true do
context 'from a blob' do
before do
visit namespace_project_blob_path(project.namespace, project, File.join('master', 'files/ruby/popen.rb'))
- wait_for_ajax
+ wait_for_requests
end
context 'selecting one word of text' do
@@ -521,7 +550,7 @@ describe 'Copy as GFM', feature: true, js: true do
context 'from a GFM code block' do
before do
visit namespace_project_blob_path(project.namespace, project, File.join('markdown', 'doc/api/users.md'))
- wait_for_ajax
+ wait_for_requests
end
context 'selecting one word of text' do
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index cbeb73d9cae..b416bbd3c79 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -7,7 +7,7 @@ feature 'Cycle Analytics', feature: true, js: true do
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
let(:milestone) { create(:milestone, project: project) }
let(:mr) { create_merge_request_closing_issue(issue, commit_message: "References #{issue.to_reference}") }
- let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha) }
+ let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) }
context 'as an allowed user' do
context 'when project is new' do
@@ -17,7 +17,7 @@ feature 'Cycle Analytics', feature: true, js: true do
login_as(user)
visit namespace_project_cycle_analytics_path(project.namespace, project)
- wait_for_ajax
+ wait_for_requests
end
it 'shows introductory message' do
@@ -33,7 +33,6 @@ feature 'Cycle Analytics', feature: true, js: true do
context "when there's cycle analytics data" do
before do
allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue])
- mr.update(head_pipeline: pipeline)
project.add_master(user)
create_cycle
@@ -73,7 +72,7 @@ feature 'Cycle Analytics', feature: true, js: true do
project.team << [user, :master]
login_as(user)
visit namespace_project_cycle_analytics_path(project.namespace, project)
- wait_for_ajax
+ wait_for_requests
end
it 'shows the content in Spanish' do
@@ -96,7 +95,7 @@ feature 'Cycle Analytics', feature: true, js: true do
login_as(guest)
visit namespace_project_cycle_analytics_path(project.namespace, project)
- wait_for_ajax
+ wait_for_requests
end
it 'needs permissions to see restricted stages' do
@@ -140,6 +139,6 @@ feature 'Cycle Analytics', feature: true, js: true do
def click_stage(stage_name)
find('.stage-nav li', text: stage_name).click
- wait_for_ajax
+ wait_for_requests
end
end
diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb
index c977f266296..0764044260e 100644
--- a/spec/features/dashboard/activity_spec.rb
+++ b/spec/features/dashboard/activity_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe 'Dashboard Activity', feature: true do
login_as(create :user)
visit activity_dashboard_path
end
-
- it_behaves_like "it has an RSS button with current_user's private token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+
+ it_behaves_like "it has an RSS button with current_user's RSS token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end
diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb
index 0e9e3f78be2..1793e323588 100644
--- a/spec/features/dashboard/datetime_on_tooltips_spec.rb
+++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb
@@ -15,7 +15,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do
login_as user
visit user_path(user)
- wait_for_ajax()
+ wait_for_requests()
page.find('.js-timeago').hover
end
@@ -32,7 +32,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do
login_as user
visit user_snippets_path(user)
- wait_for_ajax()
+ wait_for_requests()
page.find('.js-timeago.snippet-created-ago').hover
end
diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb
index 52b4d82e856..b0e2953dda2 100644
--- a/spec/features/dashboard/groups_list_spec.rb
+++ b/spec/features/dashboard/groups_list_spec.rb
@@ -23,7 +23,7 @@ describe 'Dashboard Groups page', js: true, feature: true do
it 'filters groups' do
fill_in 'filter_groups', with: group.name
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content(group.full_name)
expect(page).not_to have_content(nested_group.full_name)
@@ -32,10 +32,10 @@ describe 'Dashboard Groups page', js: true, feature: true do
it 'resets search when user cleans the input' do
fill_in 'filter_groups', with: group.name
- wait_for_ajax
+ wait_for_requests
fill_in 'filter_groups', with: ""
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content(group.full_name)
expect(page).to have_content(nested_group.full_name)
diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb
index 7a132dba1e9..2346a9ec2ed 100644
--- a/spec/features/dashboard/issues_spec.rb
+++ b/spec/features/dashboard/issues_spec.rb
@@ -62,6 +62,6 @@ RSpec.describe 'Dashboard Issues', feature: true do
expect(page).to have_content(other_issue.title)
end
- it_behaves_like "it has an RSS button with current_user's private token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+ it_behaves_like "it has an RSS button with current_user's RSS token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end
diff --git a/spec/features/dashboard/milestone_filter_spec.rb b/spec/features/dashboard/milestone_filter_spec.rb
index d60a002a8d7..b5b92c36895 100644
--- a/spec/features/dashboard/milestone_filter_spec.rb
+++ b/spec/features/dashboard/milestone_filter_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
describe 'Dashboard > milestone filter', :feature, :js do
- include WaitForAjax
-
let(:user) { create(:user) }
let(:project) { create(:project, name: 'test', namespace: user.namespace) }
let(:milestone) { create(:milestone, title: "v1.0", project: project) }
@@ -28,14 +26,14 @@ describe 'Dashboard > milestone filter', :feature, :js do
before do
find(milestone_select).click
- wait_for_ajax
+ wait_for_requests
page.within('.dropdown-content') do
click_link 'v1.0'
end
find(milestone_select).click
- wait_for_ajax
+ wait_for_requests
end
it 'shows issues with Milestone v1.0' do
@@ -48,9 +46,9 @@ describe 'Dashboard > milestone filter', :feature, :js do
# open & close dropdown
find('.dropdown-menu-close').click
-
+
expect(find('.milestone-filter')).not_to have_selector('.dropdown.open')
-
+
find(milestone_select).click
expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1)
diff --git a/spec/features/dashboard/project_member_activity_index_spec.rb b/spec/features/dashboard/project_member_activity_index_spec.rb
index 16c214ae060..cdf919af9b5 100644
--- a/spec/features/dashboard/project_member_activity_index_spec.rb
+++ b/spec/features/dashboard/project_member_activity_index_spec.rb
@@ -11,7 +11,7 @@ feature 'Project member activity', feature: true, js: true do
def visit_activities_and_wait_with_event(event_type)
Event.create(project: project, author_id: user.id, action: event_type)
visit activity_namespace_project_path(project.namespace, project)
- wait_for_ajax
+ wait_for_requests
end
subject { page.find(".event-title").text }
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index f1789fc9d43..01351548a99 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -31,5 +31,5 @@ RSpec.describe 'Dashboard Projects', feature: true do
end
end
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end
diff --git a/spec/features/dashboard_issues_spec.rb b/spec/features/dashboard_issues_spec.rb
index ad60fb2c74f..1c53f6dff06 100644
--- a/spec/features/dashboard_issues_spec.rb
+++ b/spec/features/dashboard_issues_spec.rb
@@ -53,10 +53,10 @@ describe "Dashboard Issues filtering", feature: true, js: true do
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
- expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('milestone_title' => [''])
expect(params).to include('assignee_id' => [user.id.to_s])
- expect(auto_discovery_params).to include('private_token' => [user.private_token])
+ expect(auto_discovery_params).to include('rss_token' => [user.rss_token])
expect(auto_discovery_params).to include('milestone_title' => [''])
expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
end
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
index 76c77e0bc5f..0cb75538311 100644
--- a/spec/features/expand_collapse_diffs_spec.rb
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -36,7 +36,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
visit namespace_project_commit_path(project.namespace, project, project.commit(branch), anchor: "#{large_diff[:id]}_0_1")
execute_script('window.location.reload()')
- wait_for_ajax
+ wait_for_requests
expect(large_diff).to have_selector('.code')
expect(large_diff).not_to have_selector('.nothing-here-block')
@@ -50,7 +50,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
visit namespace_project_commit_path(project.namespace, project, project.commit(branch), anchor: large_diff[:id])
execute_script('window.location.reload()')
- wait_for_ajax
+ wait_for_requests
expect(large_diff).to have_selector('.code')
expect(large_diff).not_to have_selector('.nothing-here-block')
@@ -94,7 +94,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
context 'expanding a diff for a renamed file' do
before do
large_diff_renamed.find('.click-to-expand').click
- wait_for_ajax
+ wait_for_requests
end
it 'shows the old content' do
@@ -116,7 +116,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
find('.js-file-title', match: :first)
# Click `large_diff.md` title
all('.diff-toggle-caret')[1].click
- wait_for_ajax
+ wait_for_requests
end
it 'makes a request to get the content' do
@@ -139,7 +139,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
large_diff.find('.add-diff-note').click
large_diff.find('.note-textarea').send_keys comment_text
large_diff.find_button('Comment').click
- wait_for_ajax
+ wait_for_requests
end
it 'adds the comment' do
@@ -160,7 +160,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
find('.js-file-title', match: :first)
# Click `large_diff.md` title
all('.diff-toggle-caret')[1].click
- wait_for_ajax
+ wait_for_requests
end
it 'shows the diff content' do
@@ -216,7 +216,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
expect(page).to have_no_content('No longer a symlink')
find('.click-to-expand').click
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content('No longer a symlink')
end
@@ -273,7 +273,7 @@ feature 'Expand and collapse diffs', js: true, feature: true do
expect(page).to have_content('too_large_image.jpg')
find('.note-textarea')
- wait_for_ajax
+ wait_for_requests
execute_script('window.ajaxUris = []; $(document).ajaxSend(function(event, xhr, settings) { ajaxUris.push(settings.url) });')
end
diff --git a/spec/features/explore/groups_list_spec.rb b/spec/features/explore/groups_list_spec.rb
index 9828cb179a7..d4284ed099b 100644
--- a/spec/features/explore/groups_list_spec.rb
+++ b/spec/features/explore/groups_list_spec.rb
@@ -23,7 +23,7 @@ describe 'Explore Groups page', :js, :feature do
it 'filters groups' do
fill_in 'filter_groups', with: group.name
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content(group.full_name)
expect(page).not_to have_content(public_group.full_name)
@@ -32,10 +32,10 @@ describe 'Explore Groups page', :js, :feature do
it 'resets search when user cleans the input' do
fill_in 'filter_groups', with: group.name
- wait_for_ajax
+ wait_for_requests
fill_in 'filter_groups', with: ""
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content(group.full_name)
expect(page).to have_content(public_group.full_name)
diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb
index 005a029a393..55092412340 100644
--- a/spec/features/gitlab_flavored_markdown_spec.rb
+++ b/spec/features/gitlab_flavored_markdown_spec.rb
@@ -49,8 +49,6 @@ describe "GitLab Flavored Markdown", feature: true do
end
describe "for issues", feature: true, js: true do
- include WaitForVueResource
-
before do
@other_issue = create(:issue,
author: @user,
diff --git a/spec/features/groups/activity_spec.rb b/spec/features/groups/activity_spec.rb
index 3b481cba424..81f9c103e95 100644
--- a/spec/features/groups/activity_spec.rb
+++ b/spec/features/groups/activity_spec.rb
@@ -11,8 +11,8 @@ feature 'Group activity page', feature: true do
visit path
end
- it_behaves_like "it has an RSS button with current_user's private token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+ it_behaves_like "it has an RSS button with current_user's RSS token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end
context 'when signed out' do
@@ -20,7 +20,7 @@ feature 'Group activity page', feature: true do
visit path
end
- it_behaves_like "it has an RSS button without a private token"
- it_behaves_like "an autodiscoverable RSS feed without a private token"
+ it_behaves_like "it has an RSS button without an RSS token"
+ it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end
end
diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb
index 45f57845c74..d6b88542ef7 100644
--- a/spec/features/groups/issues_spec.rb
+++ b/spec/features/groups/issues_spec.rb
@@ -12,15 +12,15 @@ feature 'Group issues page', feature: true do
context 'when signed in' do
let(:user) { user_in_group }
- it_behaves_like "it has an RSS button with current_user's private token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+ it_behaves_like "it has an RSS button with current_user's RSS token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end
context 'when signed out' do
let(:user) { nil }
- it_behaves_like "it has an RSS button without a private token"
- it_behaves_like "an autodiscoverable RSS feed without a private token"
+ it_behaves_like "it has an RSS button without an RSS token"
+ it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end
end
@@ -33,7 +33,7 @@ feature 'Group issues page', feature: true do
it 'filters by only group users' do
click_button('Assignee')
- wait_for_ajax
+ wait_for_requests
expect(find('.dropdown-menu-assignee')).to have_link(user.name)
expect(find('.dropdown-menu-assignee')).not_to have_link(user2.name)
diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb
index fb39693e8ca..d3c49c37374 100644
--- a/spec/features/groups/show_spec.rb
+++ b/spec/features/groups/show_spec.rb
@@ -11,7 +11,7 @@ feature 'Group show page', feature: true do
visit path
end
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end
context 'when signed out' do
@@ -19,6 +19,6 @@ feature 'Group show page', feature: true do
visit path
end
- it_behaves_like "an autodiscoverable RSS feed without a private token"
+ it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end
end
diff --git a/spec/features/issues/award_emoji_spec.rb b/spec/features/issues/award_emoji_spec.rb
index 853632614c4..81ae54c7a10 100644
--- a/spec/features/issues/award_emoji_spec.rb
+++ b/spec/features/issues/award_emoji_spec.rb
@@ -1,8 +1,6 @@
require 'rails_helper'
describe 'Awards Emoji', feature: true do
- include WaitForVueResource
-
let!(:project) { create(:project, :public) }
let!(:user) { create(:user) }
let(:issue) do
@@ -22,7 +20,7 @@ describe 'Awards Emoji', feature: true do
# The `heart_tip` emoji is not valid anymore so we need to skip validation
issue.award_emoji.build(user: user, name: 'heart_tip').save!(validate: false)
visit namespace_project_issue_path(project.namespace, project, issue)
- wait_for_vue_resource
+ wait_for_requests
end
# Regression test: https://gitlab.com/gitlab-org/gitlab-ce/issues/29529
@@ -36,19 +34,19 @@ describe 'Awards Emoji', feature: true do
before do
visit namespace_project_issue_path(project.namespace, project, issue)
- wait_for_vue_resource
+ wait_for_requests
end
it 'increments the thumbsdown emoji', js: true do
find('[data-name="thumbsdown"]').click
- wait_for_ajax
+ wait_for_requests
expect(thumbsdown_emoji).to have_text("1")
end
context 'click the thumbsup emoji' do
it 'increments the thumbsup emoji', js: true do
find('[data-name="thumbsup"]').click
- wait_for_ajax
+ wait_for_requests
expect(thumbsup_emoji).to have_text("1")
end
@@ -60,7 +58,7 @@ describe 'Awards Emoji', feature: true do
context 'click the thumbsdown emoji' do
it 'increments the thumbsdown emoji', js: true do
find('[data-name="thumbsdown"]').click
- wait_for_ajax
+ wait_for_requests
expect(thumbsdown_emoji).to have_text("1")
end
@@ -113,7 +111,7 @@ describe 'Awards Emoji', feature: true do
click_button 'Comment'
end
- wait_for_ajax
+ wait_for_requests
end
def thumbsup_emoji
@@ -143,6 +141,6 @@ describe 'Awards Emoji', feature: true do
find('[data-name="smiley"]').click
end
- wait_for_ajax
+ wait_for_requests
end
end
diff --git a/spec/features/issues/award_spec.rb b/spec/features/issues/award_spec.rb
index 08e3f99e29f..fcf22dd5033 100644
--- a/spec/features/issues/award_spec.rb
+++ b/spec/features/issues/award_spec.rb
@@ -6,12 +6,10 @@ feature 'Issue awards', js: true, feature: true do
let(:issue) { create(:issue, project: project) }
describe 'logged in' do
- include WaitForVueResource
-
before do
login_as(user)
visit namespace_project_issue_path(project.namespace, project, issue)
- wait_for_vue_resource
+ wait_for_requests
end
it 'adds award to issue' do
@@ -41,11 +39,9 @@ feature 'Issue awards', js: true, feature: true do
end
describe 'logged out' do
- include WaitForVueResource
-
before do
visit namespace_project_issue_path(project.namespace, project, issue)
- wait_for_vue_resource
+ wait_for_requests
end
it 'does not see award menu button' do
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index 1de50d6d77e..0a6f645b27e 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -306,7 +306,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
page.within('.issues_bulk_update') do
click_button 'Labels'
- wait_for_ajax
+ wait_for_requests
expect(find('.dropdown-menu-labels li', text: 'bug')).to have_css('.is-active')
expect(find('.dropdown-menu-labels li', text: 'feature')).to have_css('.is-indeterminate')
@@ -349,7 +349,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
def open_milestone_dropdown(items = [])
page.within('.issues_bulk_update') do
click_button 'Milestone'
- wait_for_ajax
+ wait_for_requests
items.map do |item|
click_link item
end
@@ -359,7 +359,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
def open_labels_dropdown(items = [], unmark = false)
page.within('.issues_bulk_update') do
click_button 'Labels'
- wait_for_ajax
+ wait_for_requests
items.map do |item|
click_link item
end
@@ -392,6 +392,6 @@ feature 'Issues > Labels bulk assignment', feature: true do
def update_issues
click_button 'Update issues'
- wait_for_ajax
+ wait_for_requests
end
end
diff --git a/spec/features/issues/create_branch_merge_request_spec.rb b/spec/features/issues/create_branch_merge_request_spec.rb
index 44c19275ae5..1d7d8d291b2 100644
--- a/spec/features/issues/create_branch_merge_request_spec.rb
+++ b/spec/features/issues/create_branch_merge_request_spec.rb
@@ -16,7 +16,7 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js:
select_dropdown_option('create-mr')
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content("created branch 1-cherry-coloured-funk")
expect(page).to have_content("mentioned in merge request !1")
@@ -32,7 +32,7 @@ feature 'Create Branch/Merge Request Dropdown on issue page', feature: true, js:
select_dropdown_option('create-branch')
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.dropdown-toggle-text ', text: '1-cherry-coloured-funk')
expect(current_path).to eq namespace_project_tree_path(project.namespace, project, '1-cherry-coloured-funk')
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
index 0579d6c80ab..b29177bed06 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -16,7 +16,7 @@ describe 'Dropdown author', js: true, feature: true do
end
sleep 0.5
- wait_for_ajax
+ wait_for_requests
end
def dropdown_author_size
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index a8f4e2d7e10..7958ad7e24f 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -761,7 +761,7 @@ describe 'Filter issues', js: true, feature: true do
sort_toggle.click
find('.filtered-search-wrapper .dropdown-menu li a', text: 'Oldest updated').click
- wait_for_ajax
+ wait_for_requests
expect(find('.issues-list .issue:first-of-type .issue-title-text a')).to have_content(old_issue.title)
end
@@ -778,17 +778,17 @@ describe 'Filter issues', js: true, feature: true do
it 'open state' do
find('.issues-state-filters a', text: 'Closed').click
- wait_for_ajax
+ wait_for_requests
find('.issues-state-filters a', text: 'Open').click
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.issues-list .issue', count: 4)
end
it 'closed state' do
find('.issues-state-filters a', text: 'Closed').click
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.issues-list .issue', count: 1)
expect(find('.issues-list .issue:first-of-type .issue-title-text a')).to have_content(closed_issue.title)
@@ -796,7 +796,7 @@ describe 'Filter issues', js: true, feature: true do
it 'all state' do
find('.issues-state-filters a', text: 'All').click
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.issues-list .issue', count: 5)
end
@@ -810,10 +810,10 @@ describe 'Filter issues', js: true, feature: true do
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
- expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('milestone_title' => [milestone.title])
expect(params).to include('assignee_id' => [user.id.to_s])
- expect(auto_discovery_params).to include('private_token' => [user.private_token])
+ expect(auto_discovery_params).to include('rss_token' => [user.rss_token])
expect(auto_discovery_params).to include('milestone_title' => [milestone.title])
expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
end
@@ -825,10 +825,10 @@ describe 'Filter issues', js: true, feature: true do
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
- expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('milestone_title' => [milestone.title])
expect(params).to include('assignee_id' => [user.id.to_s])
- expect(auto_discovery_params).to include('private_token' => [user.private_token])
+ expect(auto_discovery_params).to include('rss_token' => [user.rss_token])
expect(auto_discovery_params).to include('milestone_title' => [milestone.title])
expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
end
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index 5c0907e26df..009b3bc8bf6 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -3,7 +3,6 @@ require 'rails_helper'
describe 'New/edit issue', :feature, :js do
include GitlabRoutingHelper
include ActionView::Helpers::JavaScriptHelper
- include WaitForAjax
let!(:project) { create(:project) }
let!(:user) { create(:user)}
@@ -28,7 +27,7 @@ describe 'New/edit issue', :feature, :js do
before do
click_button 'Unassigned'
- wait_for_ajax
+ wait_for_requests
end
it 'unselects other assignees when unassigned is selected' do
@@ -69,7 +68,7 @@ describe 'New/edit issue', :feature, :js do
expect(find('a', text: 'Assign to me')).to be_visible
click_button 'Unassigned'
- wait_for_ajax
+ wait_for_requests
page.within '.dropdown-menu-user' do
click_link user2.name
@@ -155,7 +154,7 @@ describe 'New/edit issue', :feature, :js do
it 'correctly updates the selected user when changing assignee' do
click_button 'Unassigned'
- wait_for_ajax
+ wait_for_requests
page.within '.dropdown-menu-user' do
click_link user.name
@@ -220,6 +219,37 @@ describe 'New/edit issue', :feature, :js do
end
end
+ describe 'sub-group project' do
+ let(:group) { create(:group) }
+ let(:nested_group_1) { create(:group, parent: group) }
+ let(:sub_group_project) { create(:empty_project, group: nested_group_1) }
+
+ before do
+ sub_group_project.add_master(user)
+
+ visit new_namespace_project_issue_path(sub_group_project.namespace, sub_group_project)
+ end
+
+ it 'creates new label from dropdown' do
+ click_button 'Labels'
+
+ click_link 'Create new label'
+
+ page.within '.dropdown-new-label' do
+ fill_in 'new_label_name', with: 'test label'
+ first('.suggest-colors-dropdown a').click
+
+ click_button 'Create'
+
+ wait_for_requests
+ end
+
+ page.within '.dropdown-menu-labels' do
+ expect(page).to have_link 'test label'
+ end
+ end
+ end
+
def before_for_selector(selector)
js = <<-JS.strip_heredoc
(function(selector) {
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index ad29911248f..350473437a8 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -11,7 +11,7 @@ feature 'GFM autocomplete', feature: true, js: true do
login_as(user)
visit namespace_project_issue_path(project.namespace, project, issue)
- wait_for_ajax
+ wait_for_requests
end
it 'opens autocomplete menu when field starts with text' do
@@ -40,7 +40,7 @@ feature 'GFM autocomplete', feature: true, js: true do
expect(page).to have_selector('.atwho-container')
- wait_for_ajax
+ wait_for_requests
expect(find('#at-view-58')).not_to have_selector('.cur:first-of-type')
end
@@ -80,7 +80,7 @@ feature 'GFM autocomplete', feature: true, js: true do
expect(page).to have_selector('.atwho-container')
- wait_for_ajax
+ wait_for_requests
expect(find('#at-view-64')).to have_selector('.cur:first-of-type')
end
@@ -93,7 +93,7 @@ feature 'GFM autocomplete', feature: true, js: true do
expect(page).to have_selector('.atwho-container')
- wait_for_ajax
+ wait_for_requests
expect(find('#at-view-64')).to have_content(user.name)
end
@@ -106,7 +106,7 @@ feature 'GFM autocomplete', feature: true, js: true do
expect(page).to have_selector('.atwho-container')
- wait_for_ajax
+ wait_for_requests
expect(find('#at-view-58')).to have_selector('.cur:first-of-type')
end
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 0de0f93089a..99ad8013023 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -23,7 +23,7 @@ feature 'Issue Sidebar', feature: true do
find('.block.assignee .edit-link').click
- wait_for_ajax
+ wait_for_requests
end
it 'shows author in assignee dropdown' do
@@ -37,7 +37,7 @@ feature 'Issue Sidebar', feature: true do
find('.dropdown-input-field').native.send_keys user2.name
sleep 1 # Required to wait for end of input delay
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content(user2.name)
end
@@ -48,7 +48,7 @@ feature 'Issue Sidebar', feature: true do
click_button 'assign yourself'
- wait_for_ajax
+ wait_for_requests
find('.block.assignee .edit-link').click
diff --git a/spec/features/issues/notes_on_issues_spec.rb b/spec/features/issues/notes_on_issues_spec.rb
index a4035324d2b..15c817cabac 100644
--- a/spec/features/issues/notes_on_issues_spec.rb
+++ b/spec/features/issues/notes_on_issues_spec.rb
@@ -15,7 +15,7 @@ describe 'Create notes on issues', :js, :feature do
fill_in 'note[note]', with: note_text
click_button 'Comment'
- wait_for_ajax
+ wait_for_requests
end
it 'creates a note with reference and cross references the issue' do
diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb
index b250fa2ed3c..0911f1db9ba 100644
--- a/spec/features/issues/update_issues_spec.rb
+++ b/spec/features/issues/update_issues_spec.rb
@@ -108,11 +108,11 @@ feature 'Multiple issue updating from issues#index', feature: true do
def click_update_assignee_button
find('.js-update-assignee').click
- wait_for_ajax
+ wait_for_requests
end
def click_update_issues_button
find('.update_selected_issues').click
- wait_for_ajax
+ wait_for_requests
end
end
diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb
index 4cd6c1171ac..d14c319707c 100644
--- a/spec/features/issues/user_uses_slash_commands_spec.rb
+++ b/spec/features/issues/user_uses_slash_commands_spec.rb
@@ -18,7 +18,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do
end
after do
- wait_for_ajax
+ wait_for_requests
end
describe 'adding a due date from note' do
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 06ed2dbac64..eecc565d2bd 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -377,7 +377,7 @@ describe 'Issues', feature: true do
previous_token = find('input#issue_email').value
find('.incoming-email-token-reset').trigger('click')
- wait_for_ajax
+ wait_for_requests
expect(page).to have_no_field('issue_email', with: previous_token)
new_token = project1.new_issue_address(@user.reload)
@@ -423,7 +423,7 @@ describe 'Issues', feature: true do
expect(page).to have_content 'No assignee'
end
- # wait_for_ajax does not work with vue-resource at the moment
+ # wait_for_requests does not work with vue-resource at the moment
sleep 1
expect(issue.reload.assignees).to be_empty
@@ -661,7 +661,7 @@ describe 'Issues', feature: true do
click_button date.day
end
- wait_for_ajax
+ wait_for_requests
expect(find('.value').text).to have_content date.strftime('%b %-d, %Y')
end
@@ -677,7 +677,7 @@ describe 'Issues', feature: true do
click_button date.day
end
- wait_for_ajax
+ wait_for_requests
expect(page).to have_no_content 'No due date'
@@ -689,8 +689,6 @@ describe 'Issues', feature: true do
end
describe 'title issue#show', js: true do
- include WaitForVueResource
-
it 'updates the title', js: true do
issue = create(:issue, author: @user, assignees: [@user], project: project, title: 'new title')
@@ -700,7 +698,7 @@ describe 'Issues', feature: true do
issue.update(title: "updated title")
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_text("updated title")
end
end
diff --git a/spec/features/merge_requests/closes_issues_spec.rb b/spec/features/merge_requests/closes_issues_spec.rb
index ee0880a1e2f..e627618042a 100644
--- a/spec/features/merge_requests/closes_issues_spec.rb
+++ b/spec/features/merge_requests/closes_issues_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
feature 'Merge Request closing issues message', feature: true, js: true do
- include WaitForAjax
-
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:issue_1) { create(:issue, project: project)}
@@ -25,7 +23,7 @@ feature 'Merge Request closing issues message', feature: true, js: true do
login_as user
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
- wait_for_ajax
+ wait_for_requests
end
context 'not closing or mentioning any issue' do
diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb
index 04b7593ce68..7f669565085 100644
--- a/spec/features/merge_requests/conflicts_spec.rb
+++ b/spec/features/merge_requests/conflicts_spec.rb
@@ -23,13 +23,13 @@ feature 'Merge request conflict resolution', js: true, feature: true do
end
click_button 'Commit conflict resolution'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content('All merge conflicts were resolved')
merge_request.reload_diff
click_on 'Changes'
- wait_for_ajax
+ wait_for_requests
within find('.diff-file', text: 'files/ruby/popen.rb') do
expect(page).to have_selector('.line_content.new', text: "vars = { 'PWD' => path }")
@@ -53,23 +53,23 @@ feature 'Merge request conflict resolution', js: true, feature: true do
within find('.files-wrapper .diff-file', text: 'files/ruby/popen.rb') do
click_button 'Edit inline'
- wait_for_ajax
+ wait_for_requests
execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("One morning");')
end
within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do
click_button 'Edit inline'
- wait_for_ajax
+ wait_for_requests
execute_script('ace.edit($(".files-wrapper .diff-file pre")[1]).setValue("Gregor Samsa woke from troubled dreams");')
end
click_button 'Commit conflict resolution'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content('All merge conflicts were resolved')
merge_request.reload_diff
click_on 'Changes'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content('One morning')
expect(page).to have_content('Gregor Samsa woke from troubled dreams')
@@ -126,21 +126,21 @@ feature 'Merge request conflict resolution', js: true, feature: true do
it 'conflicts are resolved in Edit inline mode' do
within find('.files-wrapper .diff-file', text: 'files/markdown/ruby-style-guide.md') do
- wait_for_ajax
+ wait_for_requests
execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("Gregor Samsa woke from troubled dreams");')
end
click_button 'Commit conflict resolution'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content('All merge conflicts were resolved')
merge_request.reload_diff
click_on 'Changes'
- wait_for_ajax
+ wait_for_requests
click_link 'Expand all'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content('Gregor Samsa woke from troubled dreams')
end
@@ -171,7 +171,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do
it 'shows an error if the conflicts page is visited directly' do
visit current_url + '/conflicts'
- wait_for_ajax
+ wait_for_requests
expect(find('#conflicts')).to have_content('Please try to resolve them locally.')
end
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb
index f1b3e7f158c..82987c768d1 100644
--- a/spec/features/merge_requests/create_new_mr_spec.rb
+++ b/spec/features/merge_requests/create_new_mr_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
feature 'Create New Merge Request', feature: true, js: true do
- include WaitForVueResource
-
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
@@ -146,7 +144,7 @@ feature 'Create New Merge Request', feature: true, js: true do
page.within('.merge-request') do
click_link 'Pipelines'
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_content "##{pipeline.id}"
end
diff --git a/spec/features/merge_requests/deleted_source_branch_spec.rb b/spec/features/merge_requests/deleted_source_branch_spec.rb
index 01e5e4f3a05..1723fb7d365 100644
--- a/spec/features/merge_requests/deleted_source_branch_spec.rb
+++ b/spec/features/merge_requests/deleted_source_branch_spec.rb
@@ -32,7 +32,7 @@ describe 'Deleted source branch', feature: true, js: true do
end
click_on 'Changes'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.diffs.tab-pane .nothing-here-block')
expect(page).to have_content('Source branch does not exist.')
diff --git a/spec/features/merge_requests/diff_notes_avatars_spec.rb b/spec/features/merge_requests/diff_notes_avatars_spec.rb
index ccf047d3efa..854e2d1758f 100644
--- a/spec/features/merge_requests/diff_notes_avatars_spec.rb
+++ b/spec/features/merge_requests/diff_notes_avatars_spec.rb
@@ -60,7 +60,7 @@ feature 'Diff note avatars', feature: true, js: true do
click_button 'Comment'
- wait_for_ajax
+ wait_for_requests
end
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
@@ -76,7 +76,7 @@ feature 'Diff note avatars', feature: true, js: true do
before do
visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: view)
- wait_for_ajax
+ wait_for_requests
end
it 'shows note avatar' do
@@ -114,7 +114,7 @@ feature 'Diff note avatars', feature: true, js: true do
find('.js-note-delete').click
end
- wait_for_ajax
+ wait_for_requests
page.within find("[id='#{position.line_code(project.repository)}']") do
expect(page).not_to have_selector('img.js-diff-comment-avatar')
@@ -129,7 +129,7 @@ feature 'Diff note avatars', feature: true, js: true do
click_button 'Comment'
- wait_for_ajax
+ wait_for_requests
end
page.within find("[id='#{position.line_code(project.repository)}']") do
@@ -148,7 +148,7 @@ feature 'Diff note avatars', feature: true, js: true do
find('.js-comment-button').trigger 'click'
- wait_for_ajax
+ wait_for_requests
end
end
@@ -166,7 +166,7 @@ feature 'Diff note avatars', feature: true, js: true do
visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, view: view)
- wait_for_ajax
+ wait_for_requests
end
it 'shows extra comment count' do
diff --git a/spec/features/merge_requests/discussion_spec.rb b/spec/features/merge_requests/discussion_spec.rb
index f59d0faa274..1a09cc54c2e 100644
--- a/spec/features/merge_requests/discussion_spec.rb
+++ b/spec/features/merge_requests/discussion_spec.rb
@@ -43,7 +43,7 @@ feature 'Merge Request Discussions', feature: true do
it 'shows a link to the outdated diff' do
within(".discussion[data-discussion-id='#{outdated_discussion.id}']") do
path = diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: old_merge_request_diff.id, anchor: outdated_discussion.line_code)
- expect(page).to have_link('an outdated diff', href: path)
+ expect(page).to have_link('an old version of the diff', href: path)
end
end
end
diff --git a/spec/features/merge_requests/filter_merge_requests_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb
index 2da60e9f4ad..1e26b3d601e 100644
--- a/spec/features/merge_requests/filter_merge_requests_spec.rb
+++ b/spec/features/merge_requests/filter_merge_requests_spec.rb
@@ -289,7 +289,7 @@ describe 'Filter merge requests', feature: true do
page.within '.dropdown-menu-sort' do
click_link 'Oldest created'
end
- wait_for_ajax
+ wait_for_requests
page.within '.mr-list' do
expect(page).to have_content('Frontend')
diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb
index f8518f450dc..00ef1ffdddc 100644
--- a/spec/features/merge_requests/form_spec.rb
+++ b/spec/features/merge_requests/form_spec.rb
@@ -90,7 +90,7 @@ describe 'New/edit merge request', feature: true, js: true do
page.within '.issuable-meta' do
merge_request = MergeRequest.find_by(source_branch: 'fix')
- expect(page).to have_text("Merge Request #{merge_request.to_reference}")
+ expect(page).to have_text("Merge request #{merge_request.to_reference}")
# compare paths because the host differ in test
expect(find_link(merge_request.to_reference)[:href])
.to end_with(merge_request_path(merge_request))
diff --git a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
index c102722d6db..c1d4d508e57 100644
--- a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
+++ b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
@@ -39,7 +39,7 @@ feature 'Merge immediately', :feature, :js do
expect(find('.accept-merge-request.btn-info')).to have_content('Merge in progress')
- wait_for_vue_resource
+ wait_for_requests
end
end
end
diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
index e1ba1df18a4..09f889d4dd6 100644
--- a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
@@ -14,12 +14,12 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
let(:pipeline) do
create(:ci_pipeline, project: project,
sha: merge_request.diff_head_sha,
- ref: merge_request.source_branch)
+ ref: merge_request.source_branch,
+ head_pipeline_of: merge_request)
end
before do
project.add_master(user)
- merge_request.update(head_pipeline_id: pipeline.id)
end
context 'when there is active pipeline for merge request' do
diff --git a/spec/features/merge_requests/mini_pipeline_graph_spec.rb b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
index 5b2798af32f..51e7467c14c 100644
--- a/spec/features/merge_requests/mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
@@ -56,7 +56,7 @@ feature 'Mini Pipeline Graph', :js, :feature do
before do
toggle.click
- wait_for_ajax
+ wait_for_requests
end
it 'should open when toggle is clicked' do
diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
index cdda0542c51..b1dc81a606a 100644
--- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
+++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
feature 'Only allow merge requests to be merged if the pipeline succeeds', feature: true, js: true do
- include WaitForVueResource
-
let(:merge_request) { create(:merge_request_with_diffs) }
let(:project) { merge_request.target_project }
@@ -16,7 +14,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
it 'allows MR to be merged' do
visit_merge_request(merge_request)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_button 'Merge'
end
@@ -28,11 +26,9 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
project: project,
sha: merge_request.diff_head_sha,
ref: merge_request.source_branch,
- status: status)
+ status: status, head_pipeline_of: merge_request)
end
- before { merge_request.update(head_pipeline: pipeline) }
-
context 'when merge requests can only be merged if the pipeline succeeds' do
before do
project.update_attribute(:only_allow_merge_if_pipeline_succeeds, true)
@@ -44,7 +40,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
it 'does not allow to merge immediately' do
visit_merge_request(merge_request)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_button 'Merge when pipeline succeeds'
expect(page).not_to have_button 'Select merge moment'
@@ -57,7 +53,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
it 'does not allow MR to be merged' do
visit_merge_request(merge_request)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_css('button[disabled="disabled"]', text: 'Merge')
expect(page).to have_content('Please retry the job or push a new commit to fix the failure.')
@@ -70,7 +66,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
it 'does not allow MR to be merged' do
visit_merge_request(merge_request)
- wait_for_vue_resource
+ wait_for_requests
expect(page).not_to have_button 'Merge'
expect(page).to have_content('Please retry the job or push a new commit to fix the failure.')
@@ -83,7 +79,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
it 'allows MR to be merged' do
visit_merge_request(merge_request)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_button 'Merge'
end
@@ -95,7 +91,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
it 'allows MR to be merged' do
visit_merge_request(merge_request)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_button 'Merge'
end
@@ -113,7 +109,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
it 'allows MR to be merged immediately' do
visit_merge_request(merge_request)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_button 'Merge when pipeline succeeds'
@@ -128,7 +124,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
it 'allows MR to be merged' do
visit_merge_request(merge_request)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_button 'Merge'
end
@@ -140,7 +136,7 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
it 'allows MR to be merged' do
visit_merge_request(merge_request)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_button 'Merge'
end
diff --git a/spec/features/merge_requests/pipelines_spec.rb b/spec/features/merge_requests/pipelines_spec.rb
index 99e283ac181..4c76004cb93 100644
--- a/spec/features/merge_requests/pipelines_spec.rb
+++ b/spec/features/merge_requests/pipelines_spec.rb
@@ -26,7 +26,7 @@ feature 'Pipelines for Merge Requests', feature: true, js: true do
page.within('.merge-request-tabs') do
click_link('Pipelines')
end
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.pipeline-actions')
end
diff --git a/spec/features/merge_requests/update_merge_requests_spec.rb b/spec/features/merge_requests/update_merge_requests_spec.rb
index 9ecc998785b..4ef59a8aeb8 100644
--- a/spec/features/merge_requests/update_merge_requests_spec.rb
+++ b/spec/features/merge_requests/update_merge_requests_spec.rb
@@ -107,7 +107,7 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
def change_assignee(text)
find('#check_all_issues').click
find('.js-update-assignee').click
- wait_for_ajax
+ wait_for_requests
page.within '.dropdown-menu-user' do
click_link text
@@ -125,6 +125,6 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t
def click_update_merge_requests_button
find('.update_selected_issues').click
- wait_for_ajax
+ wait_for_requests
end
end
diff --git a/spec/features/merge_requests/user_posts_diff_notes_spec.rb b/spec/features/merge_requests/user_posts_diff_notes_spec.rb
index 7756202e3f5..14bc549c9f9 100644
--- a/spec/features/merge_requests/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_requests/user_posts_diff_notes_spec.rb
@@ -73,7 +73,7 @@ feature 'Merge requests > User posts diff notes', :js do
context 'with an unfolded line' do
before(:each) do
find('.js-unfold', match: :first).click
- wait_for_ajax
+ wait_for_requests
end
# The first `.js-unfold` unfolds upwards, therefore the first
@@ -122,7 +122,7 @@ feature 'Merge requests > User posts diff notes', :js do
context 'with an unfolded line' do
before(:each) do
find('.js-unfold', match: :first).click
- wait_for_ajax
+ wait_for_requests
end
# The first `.js-unfold` unfolds upwards, therefore the first
@@ -213,7 +213,7 @@ feature 'Merge requests > User posts diff notes', :js do
write_comment_on_line(line_holder, diff_side)
click_button 'Comment'
- wait_for_ajax
+ wait_for_requests
assert_comment_persistence(line_holder, asset_form_reset: asset_form_reset)
end
diff --git a/spec/features/merge_requests/user_posts_notes_spec.rb b/spec/features/merge_requests/user_posts_notes_spec.rb
index 7fc0e2ce6ec..06de072257a 100644
--- a/spec/features/merge_requests/user_posts_notes_spec.rb
+++ b/spec/features/merge_requests/user_posts_notes_spec.rb
@@ -98,7 +98,7 @@ describe 'Merge requests > User posts notes', :js do
find('.btn-save').click
end
- wait_for_ajax
+ wait_for_requests
find('.note').hover
find('.js-note-edit').click
@@ -139,7 +139,7 @@ describe 'Merge requests > User posts notes', :js do
find('.js-note-attachment-delete').click
is_expected.not_to have_css('.note-attachment')
is_expected.not_to have_css('.current-note-edit-form')
- wait_for_ajax
+ wait_for_requests
end
end
end
diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
index f0ad57eb92f..0e64a3e1a4b 100644
--- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb
+++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
@@ -21,7 +21,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
end
after do
- wait_for_ajax
+ wait_for_requests
end
describe 'toggling the WIP prefix in the title from note' do
@@ -160,7 +160,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
it 'changes target branch from a note' do
write_note("message start \n/target_branch merge-test\n message end.")
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_content('/target_branch')
expect(page).to have_content('message start')
expect(page).to have_content('message end.')
diff --git a/spec/features/merge_requests/versions_spec.rb b/spec/features/merge_requests/versions_spec.rb
index 2b5b803946c..aad522ee26e 100644
--- a/spec/features/merge_requests/versions_spec.rb
+++ b/spec/features/merge_requests/versions_spec.rb
@@ -75,7 +75,7 @@ feature 'Merge Request versions', js: true, feature: true do
find(".js-comment-button").click
end
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content("Typo, please fix")
end
@@ -124,9 +124,11 @@ feature 'Merge Request versions', js: true, feature: true do
diff_refs: merge_request_diff3.compare_with(merge_request_diff1.head_commit_sha).diff_refs
)
outdated_diff_note = create(:diff_note_on_merge_request, project: project, noteable: merge_request, position: position)
+ outdated_diff_note.position = outdated_diff_note.original_position
+ outdated_diff_note.save!
visit current_url
- wait_for_ajax
+ wait_for_requests
expect(page).to have_css(".diffs .notes[data-discussion-id='#{outdated_diff_note.discussion_id}']")
end
@@ -144,7 +146,7 @@ feature 'Merge Request versions', js: true, feature: true do
find(".js-comment-button").click
end
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content("Typo, please fix")
end
diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb
index 8370499f6ed..118ecd9cba5 100644
--- a/spec/features/merge_requests/widget_deployments_spec.rb
+++ b/spec/features/merge_requests/widget_deployments_spec.rb
@@ -18,7 +18,7 @@ feature 'Widget Deployments Header', feature: true, js: true do
end
scenario 'displays that the environment is deployed' do
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content("Deployed to #{environment.name}")
expect(find('.js-deploy-time')['data-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium))
@@ -34,7 +34,7 @@ feature 'Widget Deployments Header', feature: true, js: true do
end
background do
- wait_for_ajax
+ wait_for_requests
end
scenario 'does show stop button' do
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb
index 016c641b3c5..4f3a5119915 100644
--- a/spec/features/merge_requests/widget_spec.rb
+++ b/spec/features/merge_requests/widget_spec.rb
@@ -27,7 +27,7 @@ describe 'Merge request', :feature, :js do
it 'shows widget status after creating new merge request' do
click_button 'Submit merge request'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.accept-merge-request')
expect(find('.accept-merge-request')['disabled']).not_to be(true)
@@ -48,7 +48,7 @@ describe 'Merge request', :feature, :js do
end
it 'shows environments link' do
- wait_for_ajax
+ wait_for_requests
page.within('.mr-widget-heading') do
expect(page).to have_content("Deployed to #{environment.name}")
@@ -58,7 +58,7 @@ describe 'Merge request', :feature, :js do
it 'shows green accept merge request button' do
# Wait for the `ci_status` and `merge_check` requests
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.accept-merge-request')
expect(find('.accept-merge-request')['disabled']).not_to be(true)
end
@@ -76,7 +76,7 @@ describe 'Merge request', :feature, :js do
it 'has danger button while waiting for external CI status' do
# Wait for the `ci_status` and `merge_check` requests
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.accept-merge-request.btn-danger')
end
end
@@ -88,32 +88,29 @@ describe 'Merge request', :feature, :js do
sha: merge_request.diff_head_sha,
ref: merge_request.source_branch,
status: 'failed',
- statuses: [commit_status])
+ statuses: [commit_status],
+ head_pipeline_of: merge_request)
create(:ci_build, :pending, pipeline: pipeline)
- merge_request.update(head_pipeline: pipeline)
-
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
end
it 'has danger button when not succeeded' do
# Wait for the `ci_status` and `merge_check` requests
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.accept-merge-request.btn-danger')
end
end
context 'when merge request is in the blocked pipeline state' do
before do
- pipeline = create(
+ create(
:ci_pipeline,
project: project,
sha: merge_request.diff_head_sha,
ref: merge_request.source_branch,
- status: :manual
- )
-
- merge_request.update(head_pipeline: pipeline)
+ status: :manual,
+ head_pipeline_of: merge_request)
visit namespace_project_merge_request_path(project.namespace,
project,
@@ -135,17 +132,16 @@ describe 'Merge request', :feature, :js do
sha: merge_request.diff_head_sha,
ref: merge_request.source_branch,
status: 'pending',
- statuses: [commit_status])
+ statuses: [commit_status],
+ head_pipeline_of: merge_request)
create(:ci_build, :pending, pipeline: pipeline)
- merge_request.update(head_pipeline: pipeline)
-
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
end
it 'has info button when MWBS button' do
# Wait for the `ci_status` and `merge_check` requests
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.accept-merge-request.btn-info')
end
end
@@ -163,7 +159,7 @@ describe 'Merge request', :feature, :js do
it 'shows information about the merge error' do
# Wait for the `ci_status` and `merge_check` requests
- wait_for_ajax
+ wait_for_requests
page.within('.mr-widget-body') do
expect(page).to have_content('Something went wrong')
@@ -184,7 +180,7 @@ describe 'Merge request', :feature, :js do
it 'shows information about the merge error' do
# Wait for the `ci_status` and `merge_check` requests
- wait_for_ajax
+ wait_for_requests
page.within('.mr-widget-body') do
expect(page).to have_content('Something went wrong')
diff --git a/spec/features/milestones/milestones_spec.rb b/spec/features/milestones/milestones_spec.rb
index 9eec3d7f270..b3dfd6d0e81 100644
--- a/spec/features/milestones/milestones_spec.rb
+++ b/spec/features/milestones/milestones_spec.rb
@@ -78,7 +78,7 @@ describe 'Milestone draggable', feature: true, js: true do
scroll_into_view('.milestone-content')
drag_to(selector: '.issues-sortable-list', list_to_index: 1)
- wait_for_ajax
+ wait_for_requests
end
def create_and_drag_merge_request(params = {})
@@ -87,12 +87,12 @@ describe 'Milestone draggable', feature: true, js: true do
visit namespace_project_milestone_path(project.namespace, project, milestone)
page.find("a[href='#tab-merge-requests']").click
- wait_for_ajax
+ wait_for_requests
scroll_into_view('.milestone-content')
drag_to(selector: '.merge_requests-sortable-list', list_to_index: 1)
- wait_for_ajax
+ wait_for_requests
end
def scroll_into_view(selector)
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index e63feb14b7e..7df628fd7a0 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -47,6 +47,21 @@ describe 'Profile account page', feature: true do
end
end
+ describe 'when I reset RSS token' do
+ before do
+ visit profile_account_path
+ end
+
+ it 'resets RSS token' do
+ previous_token = find("#rss-token").value
+
+ click_link('Reset RSS token')
+
+ expect(page).to have_content 'RSS token was successfully reset'
+ expect(find('#rss-token').value).not_to eq(previous_token)
+ end
+ end
+
describe 'when I reset incoming email token' do
before do
allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true)
diff --git a/spec/features/projects/activity/rss_spec.rb b/spec/features/projects/activity/rss_spec.rb
index b47c6d431eb..3c1de5c09b2 100644
--- a/spec/features/projects/activity/rss_spec.rb
+++ b/spec/features/projects/activity/rss_spec.rb
@@ -16,7 +16,7 @@ feature 'Project Activity RSS' do
visit path
end
- it_behaves_like "it has an RSS button with current_user's private token"
+ it_behaves_like "it has an RSS button with current_user's RSS token"
end
context 'when signed out' do
@@ -24,6 +24,6 @@ feature 'Project Activity RSS' do
visit path
end
- it_behaves_like "it has an RSS button without a private token"
+ it_behaves_like "it has an RSS button without an RSS token"
end
end
diff --git a/spec/features/projects/artifacts/file_spec.rb b/spec/features/projects/artifacts/file_spec.rb
index 74308a7e8dd..25db908d917 100644
--- a/spec/features/projects/artifacts/file_spec.rb
+++ b/spec/features/projects/artifacts/file_spec.rb
@@ -13,7 +13,7 @@ feature 'Artifact file', :js, feature: true do
before do
visit_file('other_artifacts_0.1.2/doc_sample.txt')
- wait_for_ajax
+ wait_for_requests
end
it 'displays an error' do
@@ -37,7 +37,7 @@ feature 'Artifact file', :js, feature: true do
before do
visit_file('rails_sample.jpg')
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob' do
diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb
index fc242082278..82cfbfda157 100644
--- a/spec/features/projects/blobs/blob_show_spec.rb
+++ b/spec/features/projects/blobs/blob_show_spec.rb
@@ -6,7 +6,7 @@ feature 'File blob', :js, feature: true do
def visit_blob(path, fragment = nil)
visit namespace_project_blob_path(project.namespace, project, File.join('master', path), anchor: fragment)
- wait_for_ajax
+ wait_for_requests
end
context 'Ruby file' do
@@ -61,7 +61,7 @@ feature 'File blob', :js, feature: true do
before do
find('.js-blob-viewer-switch-btn[data-viewer=simple]').click
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the simple viewer' do
@@ -82,7 +82,7 @@ feature 'File blob', :js, feature: true do
before do
find('.js-blob-viewer-switch-btn[data-viewer=rich]').click
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the rich viewer' do
@@ -170,7 +170,7 @@ feature 'File blob', :js, feature: true do
before do
find('.js-blob-viewer-switcher .js-blob-viewer-switch-btn[data-viewer=simple]').click
- wait_for_ajax
+ wait_for_requests
end
it 'displays an error' do
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index cc5b1a7e734..1a38997450d 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -18,7 +18,7 @@ feature 'Editing file blob', feature: true, js: true do
end
def edit_and_commit
- wait_for_ajax
+ wait_for_requests
find('.js-edit-blob').click
execute_script('ace.edit("editor").setValue("class NextFeature\nend\n")')
click_button 'Commit changes'
diff --git a/spec/features/projects/blobs/user_create_spec.rb b/spec/features/projects/blobs/user_create_spec.rb
index d805450e095..4b6c55f5f44 100644
--- a/spec/features/projects/blobs/user_create_spec.rb
+++ b/spec/features/projects/blobs/user_create_spec.rb
@@ -15,7 +15,7 @@ feature 'New blob creation', feature: true, js: true do
end
def edit_file
- wait_for_ajax
+ wait_for_requests
fill_in 'file_name', with: 'feature.rb'
execute_script("ace.edit('editor').setValue('#{content}')")
end
diff --git a/spec/features/projects/commit/cherry_pick_spec.rb b/spec/features/projects/commit/cherry_pick_spec.rb
index fa67d390c47..bc7ca0ddd38 100644
--- a/spec/features/projects/commit/cherry_pick_spec.rb
+++ b/spec/features/projects/commit/cherry_pick_spec.rb
@@ -72,11 +72,11 @@ describe 'Cherry-pick Commits' do
click_button 'master'
end
- wait_for_ajax
+ wait_for_requests
page.within('#modal-cherry-pick-commit .dropdown-menu') do
find('.dropdown-input input').set('feature')
- wait_for_ajax
+ wait_for_requests
click_link "feature"
end
diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
index 98c0f2c63b0..f2de195eb7f 100644
--- a/spec/features/projects/commit/mini_pipeline_graph_spec.rb
+++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
@@ -32,7 +32,7 @@ feature 'Mini Pipeline Graph in Commit View', :js, :feature do
it 'should show the builds list when stage is clicked' do
first('.mini-pipeline-graph-dropdown-toggle').click
- wait_for_ajax
+ wait_for_requests
page.within '.js-builds-dropdown-list' do
expect(page).to have_selector('.ci-status-icon-running')
diff --git a/spec/features/projects/commit/rss_spec.rb b/spec/features/projects/commit/rss_spec.rb
index 6e0e1916f87..03b6d560c96 100644
--- a/spec/features/projects/commit/rss_spec.rb
+++ b/spec/features/projects/commit/rss_spec.rb
@@ -12,8 +12,8 @@ feature 'Project Commits RSS' do
visit path
end
- it_behaves_like "it has an RSS button with current_user's private token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+ it_behaves_like "it has an RSS button with current_user's RSS token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end
context 'when signed out' do
@@ -21,7 +21,7 @@ feature 'Project Commits RSS' do
visit path
end
- it_behaves_like "it has an RSS button without a private token"
- it_behaves_like "an autodiscoverable RSS feed without a private token"
+ it_behaves_like "it has an RSS button without an RSS token"
+ it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end
end
diff --git a/spec/features/projects/compare_spec.rb b/spec/features/projects/compare_spec.rb
index b2a3b111c9e..4162f2579d1 100644
--- a/spec/features/projects/compare_spec.rb
+++ b/spec/features/projects/compare_spec.rb
@@ -52,8 +52,12 @@ describe "Compare", js: true do
def select_using_dropdown(dropdown_type, selection)
dropdown = find(".js-compare-#{dropdown_type}-dropdown")
dropdown.find(".compare-dropdown-toggle").click
+ # find input before using to wait for the inputs visiblity
+ dropdown.find('.dropdown-menu')
dropdown.fill_in("Filter by Git revision", with: selection)
- wait_for_ajax
- dropdown.find_all("a[data-ref=\"#{selection}\"]", visible: true).last.click
+ wait_for_requests
+ # find before all to wait for the items visiblity
+ dropdown.find("a[data-ref=\"#{selection}\"]", match: :first)
+ dropdown.all("a[data-ref=\"#{selection}\"]").last.click
end
end
diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb
index 4533a6fb144..c49648f54bd 100644
--- a/spec/features/projects/features_visibility_spec.rb
+++ b/spec/features/projects/features_visibility_spec.rb
@@ -21,17 +21,17 @@ describe 'Edit Project Settings', feature: true do
select 'Disabled', from: "project_project_feature_attributes_#{tool_name}_access_level"
click_button 'Save changes'
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_selector(".shortcuts-#{shortcut_name}")
select 'Everyone with access', from: "project_project_feature_attributes_#{tool_name}_access_level"
click_button 'Save changes'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector(".shortcuts-#{shortcut_name}")
select 'Only team members', from: "project_project_feature_attributes_#{tool_name}_access_level"
click_button 'Save changes'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector(".shortcuts-#{shortcut_name}")
sleep 0.1
@@ -169,7 +169,7 @@ describe 'Edit Project Settings', feature: true do
select "Disabled", from: "project_project_feature_attributes_wiki_access_level"
click_button "Save changes"
- wait_for_ajax
+ wait_for_requests
visit namespace_project_path(project.namespace, project)
@@ -182,7 +182,7 @@ describe 'Edit Project Settings', feature: true do
select "Disabled", from: "project_project_feature_attributes_wiki_access_level"
click_button "Save changes"
- wait_for_ajax
+ wait_for_requests
visit activity_namespace_project_path(project.namespace, project)
@@ -223,7 +223,7 @@ describe 'Edit Project Settings', feature: true do
def save_changes_and_check_activity_tab
click_button "Save changes"
- wait_for_ajax
+ wait_for_requests
visit activity_namespace_project_path(project.namespace, project)
diff --git a/spec/features/projects/files/browse_files_spec.rb b/spec/features/projects/files/browse_files_spec.rb
index 4166aec1956..c0a9327249c 100644
--- a/spec/features/projects/files/browse_files_spec.rb
+++ b/spec/features/projects/files/browse_files_spec.rb
@@ -24,7 +24,7 @@ feature 'user browses project', feature: true, js: true do
click_link 'files'
click_link 'lfs'
click_link 'lfs_object.iso'
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_content 'Download (1.5 MB)'
expect(page).to have_content 'version https://git-lfs.github.com/spec/v1'
@@ -36,7 +36,7 @@ feature 'user browses project', feature: true, js: true do
last_commit = project.repository.last_commit_for_path(project.default_branch, 'files')
click_link 'files'
- wait_for_ajax
+ wait_for_requests
page.within('.blob-commit-info') do
expect(page).to have_content last_commit.short_id
diff --git a/spec/features/projects/files/dockerfile_dropdown_spec.rb b/spec/features/projects/files/dockerfile_dropdown_spec.rb
index 548131c7cd4..93909e91d05 100644
--- a/spec/features/projects/files/dockerfile_dropdown_spec.rb
+++ b/spec/features/projects/files/dockerfile_dropdown_spec.rb
@@ -19,14 +19,14 @@ feature 'User wants to add a Dockerfile file', feature: true do
scenario 'user can pick a Dockerfile file from the dropdown', js: true do
find('.js-dockerfile-selector').click
- wait_for_ajax
+ wait_for_requests
within '.dockerfile-selector' do
find('.dropdown-input-field').set('HTTPd')
find('.dropdown-content li', text: 'HTTPd').click
end
- wait_for_ajax
+ wait_for_requests
expect(page).to have_css('.dockerfile-selector .dropdown-toggle-text', text: 'HTTPd')
expect(page).to have_content('COPY ./ /usr/local/apache2/htdocs/')
diff --git a/spec/features/projects/files/find_file_keyboard_spec.rb b/spec/features/projects/files/find_file_keyboard_spec.rb
index e7a6749d8ac..ee42bcaec4b 100644
--- a/spec/features/projects/files/find_file_keyboard_spec.rb
+++ b/spec/features/projects/files/find_file_keyboard_spec.rb
@@ -10,7 +10,7 @@ feature 'Find file keyboard shortcuts', feature: true, js: true do
visit namespace_project_find_file_path(project.namespace, project, project.repository.root_ref)
- wait_for_ajax
+ wait_for_requests
end
it 'opens file when pressing enter key' do
diff --git a/spec/features/projects/files/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb
index e59428f8b24..e9f49453121 100644
--- a/spec/features/projects/files/gitignore_dropdown_spec.rb
+++ b/spec/features/projects/files/gitignore_dropdown_spec.rb
@@ -15,12 +15,12 @@ feature 'User wants to add a .gitignore file', feature: true do
scenario 'user can pick a .gitignore file from the dropdown', js: true do
find('.js-gitignore-selector').click
- wait_for_ajax
+ wait_for_requests
within '.gitignore-selector' do
find('.dropdown-input-field').set('rails')
find('.dropdown-content li', text: 'Rails').click
end
- wait_for_ajax
+ wait_for_requests
expect(page).to have_css('.gitignore-selector .dropdown-toggle-text', text: 'Rails')
expect(page).to have_content('/.bundle')
diff --git a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
index 85b66b93fba..031b89d0499 100644
--- a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
+++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
@@ -15,12 +15,12 @@ feature 'User wants to add a .gitlab-ci.yml file', feature: true do
scenario 'user can pick a template from the dropdown', js: true do
find('.js-gitlab-ci-yml-selector').click
- wait_for_ajax
+ wait_for_requests
within '.gitlab-ci-yml-selector' do
find('.dropdown-input-field').set('Jekyll')
find('.dropdown-content li', text: 'Jekyll').click
end
- wait_for_ajax
+ wait_for_requests
expect(page).to have_css('.gitlab-ci-yml-selector .dropdown-toggle-text', text: 'Jekyll')
expect(page).to have_content('This file is a template, and might need editing before it works on your project')
diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
index 249830921ac..8d410cc3f2e 100644
--- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb
+++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
@@ -63,7 +63,7 @@ feature 'project owner creates a license file', feature: true, js: true do
page.within('.js-license-selector-wrap') do
click_button 'Apply a license template'
click_link template
- wait_for_ajax
+ wait_for_requests
end
end
end
diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
index 70a41886985..8e197bccabf 100644
--- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
+++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
@@ -40,7 +40,7 @@ feature 'project owner sees a link to create a license file in empty project', f
page.within('.js-license-selector-wrap') do
click_button 'Apply a license template'
click_link template
- wait_for_ajax
+ wait_for_requests
end
end
end
diff --git a/spec/features/projects/files/undo_template_spec.rb b/spec/features/projects/files/undo_template_spec.rb
index cd3af0b7d29..de10eec0557 100644
--- a/spec/features/projects/files/undo_template_spec.rb
+++ b/spec/features/projects/files/undo_template_spec.rb
@@ -57,7 +57,7 @@ end
def select_file_template(template_selector_selector, template_name)
find(template_selector_selector).click
find('.dropdown-content li', text: template_name).click
- wait_for_ajax
+ wait_for_requests
end
def select_file_template_type(template_type)
diff --git a/spec/features/projects/guest_navigation_menu_spec.rb b/spec/features/projects/guest_navigation_menu_spec.rb
index 726469daba4..b91c3eff478 100644
--- a/spec/features/projects/guest_navigation_menu_spec.rb
+++ b/spec/features/projects/guest_navigation_menu_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Guest navigation menu" do
+describe 'Guest navigation menu' do
let(:project) { create(:empty_project, :private, public_builds: false) }
let(:guest) { create(:user) }
@@ -10,10 +10,10 @@ describe "Guest navigation menu" do
login_as(guest)
end
- it "shows allowed tabs only" do
+ it 'shows allowed tabs only' do
visit namespace_project_path(project.namespace, project)
- within(".nav-links") do
+ within('.layout-nav') do
expect(page).to have_content 'Project'
expect(page).to have_content 'Issues'
expect(page).to have_content 'Wiki'
@@ -23,4 +23,60 @@ describe "Guest navigation menu" do
expect(page).not_to have_content 'Merge Requests'
end
end
+
+ it 'does not show fork button' do
+ visit namespace_project_path(project.namespace, project)
+
+ within('.count-buttons') do
+ expect(page).not_to have_link 'Fork'
+ end
+ end
+
+ it 'does not show clone path' do
+ visit namespace_project_path(project.namespace, project)
+
+ within('.project-repo-buttons') do
+ expect(page).not_to have_selector '.project-clone-holder'
+ end
+ end
+
+ describe 'project landing page' do
+ before do
+ project.project_feature.update!(
+ issues_access_level: ProjectFeature::DISABLED,
+ wiki_access_level: ProjectFeature::DISABLED
+ )
+ end
+
+ it 'does not show the project file list landing page' do
+ visit namespace_project_path(project.namespace, project)
+
+ expect(page).not_to have_selector '.project-stats'
+ expect(page).not_to have_selector '.project-last-commit'
+ expect(page).not_to have_selector '.project-show-files'
+ expect(page).to have_selector '.project-show-customize_workflow'
+ end
+
+ it 'shows the customize workflow when issues and wiki are disabled' do
+ visit namespace_project_path(project.namespace, project)
+
+ expect(page).to have_selector '.project-show-customize_workflow'
+ end
+
+ it 'shows the wiki when enabled' do
+ project.project_feature.update!(wiki_access_level: ProjectFeature::PRIVATE)
+
+ visit namespace_project_path(project.namespace, project)
+
+ expect(page).to have_selector '.project-show-wiki'
+ end
+
+ it 'shows the issues when enabled' do
+ project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
+
+ visit namespace_project_path(project.namespace, project)
+
+ expect(page).to have_selector '.issues-list'
+ end
+ end
end
diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb
index fa5e30075e3..3076c863dcb 100644
--- a/spec/features/projects/issuable_templates_spec.rb
+++ b/spec/features/projects/issuable_templates_spec.rb
@@ -34,14 +34,14 @@ feature 'issuable templates', feature: true, js: true do
scenario 'user selects "bug" template' do
select_template 'bug'
- wait_for_ajax
+ wait_for_requests
assert_template
save_changes
end
scenario 'user selects "bug" template and then "no template"' do
select_template 'bug'
- wait_for_ajax
+ wait_for_requests
select_option 'No template'
assert_template('')
save_changes('')
@@ -49,7 +49,7 @@ feature 'issuable templates', feature: true, js: true do
scenario 'user selects "bug" template, edits description and then selects "reset template"' do
select_template 'bug'
- wait_for_ajax
+ wait_for_requests
find_field('issue_description').send_keys(description_addition)
assert_template(template_content + description_addition)
select_option 'Reset template'
@@ -61,7 +61,7 @@ feature 'issuable templates', feature: true, js: true do
start_height = page.evaluate_script('$(".markdown-area").outerHeight()')
select_template 'test'
- wait_for_ajax
+ wait_for_requests
end_height = page.evaluate_script('$(".markdown-area").outerHeight()')
@@ -88,7 +88,7 @@ feature 'issuable templates', feature: true, js: true do
scenario 'user selects "bug" template' do
select_template 'bug'
- wait_for_ajax
+ wait_for_requests
assert_template("#{template_content}")
save_changes
end
@@ -111,7 +111,7 @@ feature 'issuable templates', feature: true, js: true do
scenario 'user selects "feature-proposal" template' do
select_template 'feature-proposal'
- wait_for_ajax
+ wait_for_requests
assert_template
save_changes
end
@@ -143,7 +143,7 @@ feature 'issuable templates', feature: true, js: true do
context 'template exists in target project' do
scenario 'user selects template' do
select_template 'feature-proposal'
- wait_for_ajax
+ wait_for_requests
assert_template
save_changes
end
diff --git a/spec/features/projects/issues/rss_spec.rb b/spec/features/projects/issues/rss_spec.rb
index 71429f00095..f6852192aef 100644
--- a/spec/features/projects/issues/rss_spec.rb
+++ b/spec/features/projects/issues/rss_spec.rb
@@ -16,8 +16,8 @@ feature 'Project Issues RSS' do
visit path
end
- it_behaves_like "it has an RSS button with current_user's private token"
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+ it_behaves_like "it has an RSS button with current_user's RSS token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end
context 'when signed out' do
@@ -25,7 +25,7 @@ feature 'Project Issues RSS' do
visit path
end
- it_behaves_like "it has an RSS button without a private token"
- it_behaves_like "an autodiscoverable RSS feed without a private token"
+ it_behaves_like "it has an RSS button without an RSS token"
+ it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end
end
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index 836f81fb16d..34fafe072a3 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -24,7 +24,7 @@ feature 'Prioritize labels', feature: true do
page.within('.other-labels') do
all('.js-toggle-priority')[1].click
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_content('feature')
end
@@ -43,7 +43,7 @@ feature 'Prioritize labels', feature: true do
expect(page).to have_content('feature')
first('.js-toggle-priority').click
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_content('bug')
end
@@ -59,7 +59,7 @@ feature 'Prioritize labels', feature: true do
page.within('.other-labels') do
first('.js-toggle-priority').click
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_content('bug')
end
@@ -78,7 +78,7 @@ feature 'Prioritize labels', feature: true do
expect(page).to have_content('bug')
first('.js-toggle-priority').click
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_content('bug')
end
@@ -107,7 +107,7 @@ feature 'Prioritize labels', feature: true do
end
refresh
- wait_for_ajax
+ wait_for_requests
page.within('.prioritized-labels') do
expect(first('li')).to have_content('feature')
diff --git a/spec/features/projects/main/rss_spec.rb b/spec/features/projects/main/rss_spec.rb
index b1a3af612a1..53966229a2a 100644
--- a/spec/features/projects/main/rss_spec.rb
+++ b/spec/features/projects/main/rss_spec.rb
@@ -12,7 +12,7 @@ feature 'Project RSS' do
visit path
end
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end
context 'when signed out' do
@@ -20,6 +20,6 @@ feature 'Project RSS' do
visit path
end
- it_behaves_like "an autodiscoverable RSS feed without a private token"
+ it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end
end
diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb
index ab2b089db2e..3d253f01484 100644
--- a/spec/features/projects/members/group_links_spec.rb
+++ b/spec/features/projects/members/group_links_spec.rb
@@ -20,7 +20,7 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t
click_link 'Guest'
end
- wait_for_ajax
+ wait_for_requests
visit namespace_project_settings_members_path(project.namespace, project)
@@ -31,7 +31,7 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t
tomorrow = Date.today + 3
fill_in "member_expires_at_#{group.id}", with: tomorrow.strftime("%F")
- wait_for_ajax
+ wait_for_requests
page.within(find('li.group_member')) do
expect(page).to have_content('Expires in')
@@ -42,7 +42,7 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t
page.within(first('.group_member')) do
find('.btn-remove').click
end
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_selector('.group_member')
end
diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
index 19d14ad9af4..1e6f15d8258 100644
--- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
+++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
@@ -38,7 +38,7 @@ feature 'Projects > Members > Master adds member with expiration date', feature:
page.within "#project_member_#{new_member.project_members.first.id}" do
find('.js-access-expiration-date').set date.to_s(:medium)
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content('Expires in 3 days')
end
end
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index 1211b17b3d8..f40e1bc4930 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -2,10 +2,9 @@ require 'spec_helper'
feature 'Pipeline Schedules', :feature do
include PipelineSchedulesHelper
- include WaitForAjax
let!(:project) { create(:project) }
- let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) }
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project ) }
let!(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule) }
let(:scope) { nil }
let!(:user) { create(:user) }
@@ -32,6 +31,7 @@ feature 'Pipeline Schedules', :feature do
it 'displays the required information description' do
page.within('.pipeline-schedule-table-row') do
expect(page).to have_content('pipeline schedule')
+ expect(page).to have_content(pipeline_schedule.real_next_run.strftime('%b %d, %Y'))
expect(page).to have_link('master')
expect(page).to have_link("##{pipeline.id}")
end
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 5f82cf2f5e5..a97a92aa64f 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
describe 'Pipelines', :feature, :js do
- include WaitForVueResource
-
let(:project) { create(:empty_project) }
context 'when user is logged in' do
@@ -54,7 +52,7 @@ describe 'Pipelines', :feature, :js do
context 'header tabs' do
before do
visit namespace_project_pipelines_path(project.namespace, project)
- wait_for_vue_resource
+ wait_for_requests
end
it 'shows a tab for All pipelines and count' do
@@ -106,7 +104,7 @@ describe 'Pipelines', :feature, :js do
context 'when canceling' do
before do
find('.js-pipelines-cancel-button').click
- wait_for_vue_resource
+ wait_for_requests
end
it 'indicated that pipelines was canceled' do
@@ -136,7 +134,7 @@ describe 'Pipelines', :feature, :js do
context 'when retrying' do
before do
find('.js-pipelines-retry-button').click
- wait_for_vue_resource
+ wait_for_requests
end
it 'shows running pipeline that is not retryable' do
@@ -356,14 +354,14 @@ describe 'Pipelines', :feature, :js do
it 'should render pagination' do
visit namespace_project_pipelines_path(project.namespace, project)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.gl-pagination')
end
it 'should render second page of pipelines' do
visit namespace_project_pipelines_path(project.namespace, project, page: '2')
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('.gl-pagination .page', count: 2)
end
@@ -392,7 +390,7 @@ describe 'Pipelines', :feature, :js do
create(:generic_commit_status, pipeline: pipeline, stage: 'external', name: 'jenkins', stage_idx: 3)
visit namespace_project_pipeline_path(project.namespace, project, pipeline)
- wait_for_vue_resource
+ wait_for_requests
end
it 'shows a graph with grouped stages' do
@@ -507,6 +505,6 @@ describe 'Pipelines', :feature, :js do
def visit_project_pipelines(**query)
visit namespace_project_pipelines_path(project.namespace, project, query)
- wait_for_vue_resource
+ wait_for_requests
end
end
diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb
index 881ad7910dd..04414490571 100644
--- a/spec/features/projects/ref_switcher_spec.rb
+++ b/spec/features/projects/ref_switcher_spec.rb
@@ -12,12 +12,12 @@ feature 'Ref switcher', feature: true, js: true do
it 'allow user to change ref by enter key' do
click_button 'master'
- wait_for_ajax
+ wait_for_requests
page.within '.project-refs-form' do
input = find('input[type="search"]')
input.set 'binary'
- wait_for_ajax
+ wait_for_requests
expect(find('.dropdown-content ul')).to have_selector('li', count: 6)
@@ -31,7 +31,7 @@ feature 'Ref switcher', feature: true, js: true do
it "user selects ref with special characters" do
click_button 'master'
- wait_for_ajax
+ wait_for_requests
page.within '.project-refs-form' do
page.fill_in 'Search branches and tags', with: "'test'"
diff --git a/spec/features/projects/settings/integration_settings_spec.rb b/spec/features/projects/settings/integration_settings_spec.rb
index d3232f0cc16..fbaea14a2be 100644
--- a/spec/features/projects/settings/integration_settings_spec.rb
+++ b/spec/features/projects/settings/integration_settings_spec.rb
@@ -85,11 +85,55 @@ feature 'Integration settings', feature: true do
expect(current_path).to eq(integrations_path)
end
- scenario 'remove existing webhook' do
- hook
- visit integrations_path
+ context 'remove existing webhook' do
+ scenario 'from webhooks list page' do
+ hook
+ visit integrations_path
+
+ expect { click_link 'Remove' }.to change(ProjectHook, :count).by(-1)
+ end
+
+ scenario 'from webhook edit page' do
+ hook
+ visit integrations_path
+ click_link 'Edit'
+
+ expect { click_link 'Remove' }.to change(ProjectHook, :count).by(-1)
+ end
+ end
+ end
+
+ context 'Webhook logs' do
+ let(:hook) { create(:project_hook, project: project) }
+ let(:hook_log) { create(:web_hook_log, web_hook: hook, internal_error_message: 'some error') }
+
+ scenario 'show list of hook logs' do
+ hook_log
+ visit edit_namespace_project_hook_path(project.namespace, project, hook)
+
+ expect(page).to have_content('Recent Deliveries')
+ expect(page).to have_content(hook_log.url)
+ end
+
+ scenario 'show hook log details' do
+ hook_log
+ visit edit_namespace_project_hook_path(project.namespace, project, hook)
+ click_link 'View details'
+
+ expect(page).to have_content("POST #{hook_log.url}")
+ expect(page).to have_content(hook_log.internal_error_message)
+ expect(page).to have_content('Resend Request')
+ end
+
+ scenario 'retry hook log' do
+ WebMock.stub_request(:post, hook.url)
+
+ hook_log
+ visit edit_namespace_project_hook_path(project.namespace, project, hook)
+ click_link 'View details'
+ click_link 'Resend Request'
- expect { click_link 'Remove' }.to change(ProjectHook, :count).by(-1)
+ expect(current_path).to eq(edit_namespace_project_hook_path(project.namespace, project, hook))
end
end
end
diff --git a/spec/features/projects/snippets/show_spec.rb b/spec/features/projects/snippets/show_spec.rb
index cedf3778c7e..b844e60e5d5 100644
--- a/spec/features/projects/snippets/show_spec.rb
+++ b/spec/features/projects/snippets/show_spec.rb
@@ -17,7 +17,7 @@ feature 'Project snippet', :js, feature: true do
before do
visit namespace_project_snippet_path(project.namespace, project, snippet)
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob' do
@@ -48,7 +48,7 @@ feature 'Project snippet', :js, feature: true do
before do
visit namespace_project_snippet_path(project.namespace, project, snippet)
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the rich viewer' do
@@ -78,7 +78,7 @@ feature 'Project snippet', :js, feature: true do
before do
find('.js-blob-viewer-switch-btn[data-viewer=simple]').click
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the simple viewer' do
@@ -99,7 +99,7 @@ feature 'Project snippet', :js, feature: true do
before do
find('.js-blob-viewer-switch-btn[data-viewer=rich]').click
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the rich viewer' do
@@ -120,7 +120,7 @@ feature 'Project snippet', :js, feature: true do
before do
visit namespace_project_snippet_path(project.namespace, project, snippet, anchor: 'L1')
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the simple viewer' do
diff --git a/spec/features/projects/sub_group_issuables_spec.rb b/spec/features/projects/sub_group_issuables_spec.rb
new file mode 100644
index 00000000000..cf21b208f65
--- /dev/null
+++ b/spec/features/projects/sub_group_issuables_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe 'Subgroup Issuables', :feature, :js do
+ let!(:group) { create(:group, name: 'group') }
+ let!(:subgroup) { create(:group, parent: group, name: 'subgroup') }
+ let!(:project) { create(:empty_project, namespace: subgroup, name: 'project') }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+ login_as user
+ end
+
+ it 'shows the full subgroup title when issues index page is empty' do
+ visit namespace_project_issues_path(project.namespace.to_param, project.to_param)
+
+ expect_to_have_full_subgroup_title
+ end
+
+ it 'shows the full subgroup title when merge requests index page is empty' do
+ visit namespace_project_merge_requests_path(project.namespace.to_param, project.to_param)
+
+ expect_to_have_full_subgroup_title
+ end
+
+ def expect_to_have_full_subgroup_title
+ title = find('.title-container')
+
+ expect(title).not_to have_selector '.initializing'
+ expect(title).to have_content 'group / subgroup / project'
+ end
+end
diff --git a/spec/features/projects/tree/rss_spec.rb b/spec/features/projects/tree/rss_spec.rb
index 9ac51997d65..9bf59c4139c 100644
--- a/spec/features/projects/tree/rss_spec.rb
+++ b/spec/features/projects/tree/rss_spec.rb
@@ -12,7 +12,7 @@ feature 'Project Tree RSS' do
visit path
end
- it_behaves_like "an autodiscoverable RSS feed with current_user's private token"
+ it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end
context 'when signed out' do
@@ -20,6 +20,6 @@ feature 'Project Tree RSS' do
visit path
end
- it_behaves_like "an autodiscoverable RSS feed without a private token"
+ it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end
end
diff --git a/spec/features/projects/view_on_env_spec.rb b/spec/features/projects/view_on_env_spec.rb
index b7a41ca54e6..640f1376548 100644
--- a/spec/features/projects/view_on_env_spec.rb
+++ b/spec/features/projects/view_on_env_spec.rb
@@ -54,7 +54,7 @@ describe 'View on environment', js: true do
visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
- wait_for_ajax
+ wait_for_requests
end
it 'has a "View on env" button' do
@@ -70,7 +70,7 @@ describe 'View on environment', js: true do
visit namespace_project_compare_path(project.namespace, project, from: 'master', to: branch_name)
- wait_for_ajax
+ wait_for_requests
end
it 'has a "View on env" button' do
@@ -84,7 +84,7 @@ describe 'View on environment', js: true do
visit namespace_project_compare_path(project.namespace, project, from: 'master', to: sha)
- wait_for_ajax
+ wait_for_requests
end
it 'has a "View on env" button' do
@@ -98,7 +98,7 @@ describe 'View on environment', js: true do
visit namespace_project_blob_path(project.namespace, project, File.join(branch_name, file_path))
- wait_for_ajax
+ wait_for_requests
end
it 'has a "View on env" button' do
@@ -112,7 +112,7 @@ describe 'View on environment', js: true do
visit namespace_project_blob_path(project.namespace, project, File.join(sha, file_path))
- wait_for_ajax
+ wait_for_requests
end
it 'has a "View on env" button' do
@@ -126,7 +126,7 @@ describe 'View on environment', js: true do
visit namespace_project_commit_path(project.namespace, project, sha)
- wait_for_ajax
+ wait_for_requests
end
it 'has a "View on env" button' do
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
index 5c502ce4fb5..8912d575878 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -28,6 +28,40 @@ feature 'Projects > Wiki > User creates wiki page', js: true, feature: true do
expect(page).to have_content("Last edited by #{user.name}")
expect(page).to have_content('My awesome wiki!')
end
+
+ scenario 'creates ASCII wiki with LaTeX blocks' do
+ stub_application_setting(plantuml_url: 'http://localhost', plantuml_enabled: true)
+
+ ascii_content = <<~MD
+ :stem: latexmath
+
+ [stem]
+ ++++
+ \sqrt{4} = 2
+ ++++
+
+ another part
+
+ [latexmath]
+ ++++
+ \beta_x \gamma
+ ++++
+
+ stem:[2+2] is 4
+ MD
+
+ find('#wiki_format option[value=asciidoc]').select_option
+ fill_in :wiki_content, with: ascii_content
+
+ page.within '.wiki-form' do
+ click_button 'Create page'
+ end
+
+ page.within '.wiki' do
+ expect(page).to have_selector('.katex', count: 3)
+ expect(page).to have_content('2+2 is 4')
+ end
+ end
end
context 'when wiki is not empty' do
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index 2fda7758407..7834807b1f1 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -28,7 +28,7 @@ describe "Search", feature: true do
it 'shows group name after filtering' do
find('.js-search-group-dropdown').trigger('click')
- wait_for_ajax
+ wait_for_requests
page.within '.search-holder' do
click_link group.name
@@ -39,7 +39,7 @@ describe "Search", feature: true do
it 'filters by group projects after filtering by group' do
find('.js-search-group-dropdown').trigger('click')
- wait_for_ajax
+ wait_for_requests
page.within '.search-holder' do
click_link group.name
@@ -49,7 +49,7 @@ describe "Search", feature: true do
page.within('.project-filter') do
find('.js-search-project-dropdown').trigger('click')
- wait_for_ajax
+ wait_for_requests
expect(page).to have_link(group_project.name_with_namespace)
end
@@ -58,7 +58,7 @@ describe "Search", feature: true do
it 'shows project name after filtering' do
page.within('.project-filter') do
find('.js-search-project-dropdown').trigger('click')
- wait_for_ajax
+ wait_for_requests
click_link project.name_with_namespace
end
diff --git a/spec/features/snippets/create_snippet_spec.rb b/spec/features/snippets/create_snippet_spec.rb
index 9409c323288..31a2d4ae984 100644
--- a/spec/features/snippets/create_snippet_spec.rb
+++ b/spec/features/snippets/create_snippet_spec.rb
@@ -13,7 +13,7 @@ feature 'Create Snippet', :js, feature: true do
end
click_button 'Create snippet'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content('My Snippet Title')
expect(page).to have_content('Hello World!')
@@ -27,7 +27,7 @@ feature 'Create Snippet', :js, feature: true do
end
click_button 'Create snippet'
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content('My Snippet Title')
expect(page).to have_content('snippet+file+name')
diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb
index 698eb46573f..f7afc174019 100644
--- a/spec/features/snippets/notes_on_personal_snippets_spec.rb
+++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb
@@ -93,7 +93,7 @@ describe 'Comments on personal snippets', :js, feature: true do
click_on 'Remove comment'
end
- wait_for_ajax
+ wait_for_requests
expect(page).not_to have_selector("#notes-list li#note_#{snippet_notes[0].id}")
end
diff --git a/spec/features/snippets/public_snippets_spec.rb b/spec/features/snippets/public_snippets_spec.rb
index 2df483818c3..afd945a8555 100644
--- a/spec/features/snippets/public_snippets_spec.rb
+++ b/spec/features/snippets/public_snippets_spec.rb
@@ -5,7 +5,7 @@ feature 'Public Snippets', :js, feature: true do
public_snippet = create(:personal_snippet, :public)
visit snippet_path(public_snippet)
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content(public_snippet.content)
end
diff --git a/spec/features/snippets/show_spec.rb b/spec/features/snippets/show_spec.rb
index e36cf547f80..95fc1d2bb62 100644
--- a/spec/features/snippets/show_spec.rb
+++ b/spec/features/snippets/show_spec.rb
@@ -11,7 +11,7 @@ feature 'Snippet', :js, feature: true do
before do
visit snippet_path(snippet)
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob' do
@@ -42,7 +42,7 @@ feature 'Snippet', :js, feature: true do
before do
visit snippet_path(snippet)
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the rich viewer' do
@@ -72,7 +72,7 @@ feature 'Snippet', :js, feature: true do
before do
find('.js-blob-viewer-switch-btn[data-viewer=simple]').click
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the simple viewer' do
@@ -93,7 +93,7 @@ feature 'Snippet', :js, feature: true do
before do
find('.js-blob-viewer-switch-btn[data-viewer=rich]').click
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the rich viewer' do
@@ -114,7 +114,7 @@ feature 'Snippet', :js, feature: true do
before do
visit snippet_path(snippet, anchor: 'L1')
- wait_for_ajax
+ wait_for_requests
end
it 'displays the blob using the simple viewer' do
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index 8bd13caf2b0..563e65d3cc5 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -64,13 +64,11 @@ feature 'Task Lists', feature: true do
describe 'for Issues', feature: true do
describe 'multiple tasks', js: true do
- include WaitForVueResource
-
let!(:issue) { create(:issue, description: markdown, author: user, project: project) }
it 'renders' do
visit_issue(project, issue)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 6)
@@ -79,7 +77,7 @@ feature 'Task Lists', feature: true do
it 'contains the required selectors' do
visit_issue(project, issue)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox")
expect(page).to have_selector('a.btn-close')
@@ -87,14 +85,14 @@ feature 'Task Lists', feature: true do
it 'is only editable by author' do
visit_issue(project, issue)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox")
logout(:user)
login_as(user2)
visit current_path
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox")
end
@@ -106,13 +104,11 @@ feature 'Task Lists', feature: true do
end
describe 'single incomplete task', js: true do
- include WaitForVueResource
-
let!(:issue) { create(:issue, description: singleIncompleteMarkdown, author: user, project: project) }
it 'renders' do
visit_issue(project, issue)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 1)
@@ -127,12 +123,11 @@ feature 'Task Lists', feature: true do
end
describe 'single complete task', js: true do
- include WaitForVueResource
let!(:issue) { create(:issue, description: singleCompleteMarkdown, author: user, project: project) }
it 'renders' do
visit_issue(project, issue)
- wait_for_vue_resource
+ wait_for_requests
expect(page).to have_selector('ul.task-list', count: 1)
expect(page).to have_selector('li.task-list-item', count: 1)
diff --git a/spec/features/todos/todos_filtering_spec.rb b/spec/features/todos/todos_filtering_spec.rb
index f32e70c2c3f..bbfa4e08379 100644
--- a/spec/features/todos/todos_filtering_spec.rb
+++ b/spec/features/todos/todos_filtering_spec.rb
@@ -28,7 +28,7 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
click_link project_1.name_with_namespace
end
- wait_for_ajax
+ wait_for_requests
expect(page).to have_content project_1.name_with_namespace
expect(page).not_to have_content project_2.name_with_namespace
@@ -43,7 +43,7 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
click_link user_1.name
end
- wait_for_ajax
+ wait_for_requests
expect(find('.todos-list')).to have_content 'merge request'
expect(find('.todos-list')).not_to have_content 'issue'
@@ -90,7 +90,7 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
click_link 'Issue'
end
- wait_for_ajax
+ wait_for_requests
expect(find('.todos-list')).to have_content issue.to_reference
expect(find('.todos-list')).not_to have_content merge_request.to_reference
@@ -132,7 +132,7 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
click_link name
end
- wait_for_ajax
+ wait_for_requests
end
def expect_to_see_action(action_name)
diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb
index 55b3e3d9424..bb4b2aed0e3 100644
--- a/spec/features/todos/todos_spec.rb
+++ b/spec/features/todos/todos_spec.rb
@@ -64,7 +64,7 @@ describe 'Dashboard Todos', feature: true do
before do
within first('.todo') do
click_link 'Done'
- wait_for_ajax
+ wait_for_requests
click_link 'Undo'
end
end
@@ -309,9 +309,9 @@ describe 'Dashboard Todos', feature: true do
def mark_all_and_undo
find('.js-todos-mark-all').trigger('click')
- wait_for_ajax
+ wait_for_requests
find('.js-todos-undo-all').trigger('click')
- wait_for_ajax
+ wait_for_requests
end
end
end
diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb
index 544d2dcb87f..2fed8067042 100644
--- a/spec/features/u2f_spec.rb
+++ b/spec/features/u2f_spec.rb
@@ -6,7 +6,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
def manage_two_factor_authentication
click_on 'Manage two-factor authentication'
expect(page).to have_content("Setup new U2F device")
- wait_for_ajax
+ wait_for_requests
end
def register_u2f_device(u2f_device = nil, name: 'My device')
diff --git a/spec/features/uploads/user_uploads_file_to_note_spec.rb b/spec/features/uploads/user_uploads_file_to_note_spec.rb
index 8f03024ea06..9332d3b88d2 100644
--- a/spec/features/uploads/user_uploads_file_to_note_spec.rb
+++ b/spec/features/uploads/user_uploads_file_to_note_spec.rb
@@ -64,7 +64,7 @@ feature 'User uploads file to note', feature: true do
context 'uploading is complete' do
it 'shows "Attach a file" button on uploading complete', js: true do
dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')])
- wait_for_ajax
+ wait_for_requests
expect(page).to have_button('Attach a file')
expect(page).not_to have_selector('.uploading-progress-container', visible: true)
@@ -73,7 +73,7 @@ feature 'User uploads file to note', feature: true do
scenario 'they see the attached file', js: true do
dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')])
click_button 'Comment'
- wait_for_ajax
+ wait_for_requests
expect(find('a.no-attachment-icon img[alt="dk"]')['src'])
.to match(%r{/#{project.full_path}/uploads/\h{32}/dk\.png$})
diff --git a/spec/features/users/projects_spec.rb b/spec/features/users/projects_spec.rb
index 373b64808f8..67ce4b44464 100644
--- a/spec/features/users/projects_spec.rb
+++ b/spec/features/users/projects_spec.rb
@@ -16,7 +16,7 @@ describe 'Projects tab on a user profile', :feature, :js do
click_link('Personal projects')
end
- wait_for_ajax
+ wait_for_requests
end
it 'paginates results' do
diff --git a/spec/features/users/rss_spec.rb b/spec/features/users/rss_spec.rb
index 14564abb16d..dbd5f66b55e 100644
--- a/spec/features/users/rss_spec.rb
+++ b/spec/features/users/rss_spec.rb
@@ -9,7 +9,7 @@ feature 'User RSS' do
visit path
end
- it_behaves_like "it has an RSS button with current_user's private token"
+ it_behaves_like "it has an RSS button with current_user's RSS token"
end
context 'when signed out' do
@@ -17,6 +17,6 @@ feature 'User RSS' do
visit path
end
- it_behaves_like "it has an RSS button without a private token"
+ it_behaves_like "it has an RSS button without an RSS token"
end
end
diff --git a/spec/features/users/snippets_spec.rb b/spec/features/users/snippets_spec.rb
index 4efbd672322..2e388115633 100644
--- a/spec/features/users/snippets_spec.rb
+++ b/spec/features/users/snippets_spec.rb
@@ -11,7 +11,7 @@ describe 'Snippets tab on a user profile', feature: true, js: true do
allow(Snippet).to receive(:default_per_page).and_return(1)
visit user_path(user)
page.within('.user-profile-nav') { click_link 'Snippets' }
- wait_for_ajax
+ wait_for_requests
end
it_behaves_like 'paginated snippets', remote: true
@@ -27,7 +27,7 @@ describe 'Snippets tab on a user profile', feature: true, js: true do
login_as(:user)
visit user_path(user)
page.within('.user-profile-nav') { click_link 'Snippets' }
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.snippet-row', count: 2)
@@ -38,7 +38,7 @@ describe 'Snippets tab on a user profile', feature: true, js: true do
it 'contains only public snippets of a user when a user is not logged in' do
visit user_path(user)
page.within('.user-profile-nav') { click_link 'Snippets' }
- wait_for_ajax
+ wait_for_requests
expect(page).to have_selector('.snippet-row', count: 1)
expect(page).to have_content(public_snippet.title)
diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb
index c43feadc808..fbe078bd136 100644
--- a/spec/features/users_spec.rb
+++ b/spec/features/users_spec.rb
@@ -78,25 +78,25 @@ feature 'Users', feature: true, js: true do
scenario 'doesn\'t show an error border if the username is available' do
fill_in username_input, with: 'new-user'
- wait_for_ajax
+ wait_for_requests
expect(find('.username')).not_to have_css '.gl-field-error-outline'
end
scenario 'does not show an error border if the username contains dots (.)' do
fill_in username_input, with: 'new.user.username'
- wait_for_ajax
+ wait_for_requests
expect(find('.username')).not_to have_css '.gl-field-error-outline'
end
scenario 'shows an error border if the username already exists' do
fill_in username_input, with: user.username
- wait_for_ajax
+ wait_for_requests
expect(find('.username')).to have_css '.gl-field-error-outline'
end
scenario 'shows an error border if the username contains special characters' do
fill_in username_input, with: 'new$user!username'
- wait_for_ajax
+ wait_for_requests
expect(find('.username')).to have_css '.gl-field-error-outline'
end
end
diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb
index 099146678ae..355a4845afb 100644
--- a/spec/helpers/notes_helper_spec.rb
+++ b/spec/helpers/notes_helper_spec.rb
@@ -92,7 +92,13 @@ describe NotesHelper do
)
end
- let(:discussion) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, position: position).to_discussion }
+ let(:diff_note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, position: position) }
+ let(:discussion) { diff_note.to_discussion }
+
+ before do
+ diff_note.position = diff_note.original_position
+ diff_note.save!
+ end
it 'returns the diff version comparison path with the line code' do
expect(helper.discussion_path(discussion)).to eq(diffs_namespace_project_merge_request_path(project.namespace, project, merge_request, diff_id: merge_request_diff3, start_sha: merge_request_diff1.head_commit_sha, anchor: discussion.line_code))
diff --git a/spec/helpers/rss_helper_spec.rb b/spec/helpers/rss_helper_spec.rb
index f3f174f3d14..269e1057e8d 100644
--- a/spec/helpers/rss_helper_spec.rb
+++ b/spec/helpers/rss_helper_spec.rb
@@ -3,17 +3,17 @@ require 'spec_helper'
describe RssHelper do
describe '#rss_url_options' do
context 'when signed in' do
- it "includes the current_user's private_token" do
+ it "includes the current_user's rss_token" do
current_user = create(:user)
allow(helper).to receive(:current_user).and_return(current_user)
- expect(helper.rss_url_options).to include private_token: current_user.private_token
+ expect(helper.rss_url_options).to include rss_token: current_user.rss_token
end
end
context 'when signed out' do
- it "does not have a private_token" do
+ it "does not have an rss_token" do
allow(helper).to receive(:current_user).and_return(nil)
- expect(helper.rss_url_options[:private_token]).to be_nil
+ expect(helper.rss_url_options[:rss_token]).to be_nil
end
end
end
diff --git a/spec/javascripts/copy_as_gfm_spec.js b/spec/javascripts/copy_as_gfm_spec.js
new file mode 100644
index 00000000000..ded450749d3
--- /dev/null
+++ b/spec/javascripts/copy_as_gfm_spec.js
@@ -0,0 +1,49 @@
+import '~/copy_as_gfm';
+
+(() => {
+ describe('gl.CopyAsGFM', () => {
+ describe('gl.CopyAsGFM.pasteGFM', () => {
+ function callPasteGFM() {
+ const e = {
+ originalEvent: {
+ clipboardData: {
+ getData(mimeType) {
+ // When GFM code is copied, we put the regular plain text
+ // on the clipboard as `text/plain`, and the GFM as `text/x-gfm`.
+ // This emulates the behavior of `getData` with that data.
+ if (mimeType === 'text/plain') {
+ return 'code';
+ }
+ if (mimeType === 'text/x-gfm') {
+ return '`code`';
+ }
+ return null;
+ },
+ },
+ },
+ preventDefault() {},
+ };
+
+ window.gl.CopyAsGFM.pasteGFM(e);
+ }
+
+ it('wraps pasted code when not already in code tags', () => {
+ spyOn(window.gl.utils, 'insertText').and.callFake((el, textFunc) => {
+ const insertedText = textFunc('This is code: ', '');
+ expect(insertedText).toEqual('`code`');
+ });
+
+ callPasteGFM();
+ });
+
+ it('does not wrap pasted code when already in code tags', () => {
+ spyOn(window.gl.utils, 'insertText').and.callFake((el, textFunc) => {
+ const insertedText = textFunc('This is code: `', '`');
+ expect(insertedText).toEqual('code');
+ });
+
+ callPasteGFM();
+ });
+ });
+ });
+})();
diff --git a/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js b/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js
index d0f09a561d5..79447787fc9 100644
--- a/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js
+++ b/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js
@@ -2,6 +2,8 @@ import Vue from 'vue';
import eventHub from '~/filtered_search/event_hub';
import RecentSearchesDropdownContent from '~/filtered_search/components/recent_searches_dropdown_content';
+import '~/filtered_search/filtered_search_token_keys';
+
const createComponent = (propsData) => {
const Component = Vue.extend(RecentSearchesDropdownContent);
@@ -17,12 +19,14 @@ const trimMarkupWhitespace = text => text.replace(/(\n|\s)+/gm, ' ').trim();
describe('RecentSearchesDropdownContent', () => {
const propsDataWithoutItems = {
items: [],
+ allowedKeys: gl.FilteredSearchTokenKeys.getKeys(),
};
const propsDataWithItems = {
items: [
'foo',
'author:@root label:~foo bar',
],
+ allowedKeys: gl.FilteredSearchTokenKeys.getKeys(),
};
let vm;
diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js
index 0d8bdf4c8e7..f7708301b6e 100644
--- a/spec/javascripts/filtered_search/dropdown_user_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_user_spec.js
@@ -12,7 +12,7 @@ describe('Dropdown User', () => {
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {});
- dropdownUser = new gl.DropdownUser();
+ dropdownUser = new gl.DropdownUser(null, null, null, gl.FilteredSearchTokenKeys);
});
it('should not return the double quote found in value', () => {
diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js
index a68e315e3e4..bb02abdeea2 100644
--- a/spec/javascripts/filtered_search/dropdown_utils_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js
@@ -122,6 +122,7 @@ describe('Dropdown Utils', () => {
describe('filterHint', () => {
let input;
+ let allowedKeys;
beforeEach(() => {
setFixtures(`
@@ -133,30 +134,38 @@ describe('Dropdown Utils', () => {
`);
input = document.getElementById('test');
+ allowedKeys = gl.FilteredSearchTokenKeys.getKeys();
});
+ function config() {
+ return {
+ input,
+ allowedKeys,
+ };
+ }
+
it('should filter', () => {
input.value = 'l';
- let updatedItem = gl.DropdownUtils.filterHint(input, {
+ let updatedItem = gl.DropdownUtils.filterHint(config(), {
hint: 'label',
});
expect(updatedItem.droplab_hidden).toBe(false);
input.value = 'o';
- updatedItem = gl.DropdownUtils.filterHint(input, {
+ updatedItem = gl.DropdownUtils.filterHint(config(), {
hint: 'label',
});
expect(updatedItem.droplab_hidden).toBe(true);
});
it('should return droplab_hidden false when item has no hint', () => {
- const updatedItem = gl.DropdownUtils.filterHint(input, {}, '');
+ const updatedItem = gl.DropdownUtils.filterHint(config(), {}, '');
expect(updatedItem.droplab_hidden).toBe(false);
});
it('should allow multiple if item.type is array', () => {
input.value = 'label:~first la';
- const updatedItem = gl.DropdownUtils.filterHint(input, {
+ const updatedItem = gl.DropdownUtils.filterHint(config(), {
hint: 'label',
type: 'array',
});
@@ -165,12 +174,12 @@ describe('Dropdown Utils', () => {
it('should prevent multiple if item.type is not array', () => {
input.value = 'milestone:~first mile';
- let updatedItem = gl.DropdownUtils.filterHint(input, {
+ let updatedItem = gl.DropdownUtils.filterHint(config(), {
hint: 'milestone',
});
expect(updatedItem.droplab_hidden).toBe(true);
- updatedItem = gl.DropdownUtils.filterHint(input, {
+ updatedItem = gl.DropdownUtils.filterHint(config(), {
hint: 'milestone',
type: 'string',
});
diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
index 7c7def3470d..8688332782d 100644
--- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
@@ -80,6 +80,7 @@ describe('Filtered Search Manager', () => {
expect(RecentSearchesService.isAvailable).toHaveBeenCalled();
expect(recentSearchesStoreSrc.default).toHaveBeenCalledWith({
isLocalStorageAvailable,
+ allowedKeys: gl.FilteredSearchTokenKeys.getKeys(),
});
});
diff --git a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js
index 9561580c839..e4a15c83c23 100644
--- a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js
@@ -3,9 +3,11 @@ import '~/filtered_search/filtered_search_token_keys';
import '~/filtered_search/filtered_search_tokenizer';
describe('Filtered Search Tokenizer', () => {
+ const allowedKeys = gl.FilteredSearchTokenKeys.getKeys();
+
describe('processTokens', () => {
it('returns for input containing only search value', () => {
- const results = gl.FilteredSearchTokenizer.processTokens('searchTerm');
+ const results = gl.FilteredSearchTokenizer.processTokens('searchTerm', allowedKeys);
expect(results.searchToken).toBe('searchTerm');
expect(results.tokens.length).toBe(0);
expect(results.lastToken).toBe(results.searchToken);
@@ -13,7 +15,7 @@ describe('Filtered Search Tokenizer', () => {
it('returns for input containing only tokens', () => {
const results = gl.FilteredSearchTokenizer
- .processTokens('author:@root label:~"Very Important" milestone:%v1.0 assignee:none');
+ .processTokens('author:@root label:~"Very Important" milestone:%v1.0 assignee:none', allowedKeys);
expect(results.searchToken).toBe('');
expect(results.tokens.length).toBe(4);
expect(results.tokens[3]).toBe(results.lastToken);
@@ -37,7 +39,7 @@ describe('Filtered Search Tokenizer', () => {
it('returns for input starting with search value and ending with tokens', () => {
const results = gl.FilteredSearchTokenizer
- .processTokens('searchTerm anotherSearchTerm milestone:none');
+ .processTokens('searchTerm anotherSearchTerm milestone:none', allowedKeys);
expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
expect(results.tokens.length).toBe(1);
expect(results.tokens[0]).toBe(results.lastToken);
@@ -48,7 +50,7 @@ describe('Filtered Search Tokenizer', () => {
it('returns for input starting with tokens and ending with search value', () => {
const results = gl.FilteredSearchTokenizer
- .processTokens('assignee:@user searchTerm');
+ .processTokens('assignee:@user searchTerm', allowedKeys);
expect(results.searchToken).toBe('searchTerm');
expect(results.tokens.length).toBe(1);
@@ -60,7 +62,7 @@ describe('Filtered Search Tokenizer', () => {
it('returns for input containing search value wrapped between tokens', () => {
const results = gl.FilteredSearchTokenizer
- .processTokens('author:@root label:~"Won\'t fix" searchTerm anotherSearchTerm milestone:none');
+ .processTokens('author:@root label:~"Won\'t fix" searchTerm anotherSearchTerm milestone:none', allowedKeys);
expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
expect(results.tokens.length).toBe(3);
@@ -81,7 +83,7 @@ describe('Filtered Search Tokenizer', () => {
it('returns for input containing search value in between tokens', () => {
const results = gl.FilteredSearchTokenizer
- .processTokens('author:@root searchTerm assignee:none anotherSearchTerm label:~Doing');
+ .processTokens('author:@root searchTerm assignee:none anotherSearchTerm label:~Doing', allowedKeys);
expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
expect(results.tokens.length).toBe(3);
expect(results.tokens[2]).toBe(results.lastToken);
@@ -100,14 +102,14 @@ describe('Filtered Search Tokenizer', () => {
});
it('returns search value for invalid tokens', () => {
- const results = gl.FilteredSearchTokenizer.processTokens('fake:token');
+ const results = gl.FilteredSearchTokenizer.processTokens('fake:token', allowedKeys);
expect(results.lastToken).toBe('fake:token');
expect(results.searchToken).toBe('fake:token');
expect(results.tokens.length).toEqual(0);
});
it('returns search value and token for mix of valid and invalid tokens', () => {
- const results = gl.FilteredSearchTokenizer.processTokens('label:real fake:token');
+ const results = gl.FilteredSearchTokenizer.processTokens('label:real fake:token', allowedKeys);
expect(results.tokens.length).toEqual(1);
expect(results.tokens[0].key).toBe('label');
expect(results.tokens[0].value).toBe('real');
@@ -117,13 +119,13 @@ describe('Filtered Search Tokenizer', () => {
});
it('returns search value for invalid symbols', () => {
- const results = gl.FilteredSearchTokenizer.processTokens('std::includes');
+ const results = gl.FilteredSearchTokenizer.processTokens('std::includes', allowedKeys);
expect(results.lastToken).toBe('std::includes');
expect(results.searchToken).toBe('std::includes');
});
it('removes duplicated values', () => {
- const results = gl.FilteredSearchTokenizer.processTokens('label:~foo label:~foo');
+ const results = gl.FilteredSearchTokenizer.processTokens('label:~foo label:~foo', allowedKeys);
expect(results.tokens.length).toBe(1);
expect(results.tokens[0].key).toBe('label');
expect(results.tokens[0].value).toBe('foo');
diff --git a/spec/javascripts/filtered_search/services/recent_searches_service_spec.js b/spec/javascripts/filtered_search/services/recent_searches_service_spec.js
index 31fa478804a..c293c0afa97 100644
--- a/spec/javascripts/filtered_search/services/recent_searches_service_spec.js
+++ b/spec/javascripts/filtered_search/services/recent_searches_service_spec.js
@@ -1,6 +1,5 @@
-/* eslint-disable promise/catch-or-return */
-
import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
+import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error';
import AccessorUtilities from '~/lib/utils/accessor';
describe('RecentSearchesService', () => {
@@ -22,11 +21,9 @@ describe('RecentSearchesService', () => {
fetchItemsPromise
.then((items) => {
expect(items).toEqual([]);
- done();
})
- .catch((err) => {
- done.fail('Shouldn\'t reject with empty localStorage key', err);
- });
+ .then(done)
+ .catch(done.fail);
});
it('should reject when unable to parse', (done) => {
@@ -34,19 +31,24 @@ describe('RecentSearchesService', () => {
const fetchItemsPromise = service.fetch();
fetchItemsPromise
+ .then(done.fail)
.catch((error) => {
expect(error).toEqual(jasmine.any(SyntaxError));
- done();
- });
+ })
+ .then(done)
+ .catch(done.fail);
});
it('should reject when service is unavailable', (done) => {
RecentSearchesService.isAvailable.and.returnValue(false);
- service.fetch().catch((error) => {
- expect(error).toEqual(jasmine.any(Error));
- done();
- });
+ service.fetch()
+ .then(done.fail)
+ .catch((error) => {
+ expect(error).toEqual(jasmine.any(Error));
+ })
+ .then(done)
+ .catch(done.fail);
});
it('should return items from localStorage', (done) => {
@@ -56,8 +58,9 @@ describe('RecentSearchesService', () => {
fetchItemsPromise
.then((items) => {
expect(items).toEqual(['foo', 'bar']);
- done();
- });
+ })
+ .then(done)
+ .catch(done.fail);
});
describe('if .isAvailable returns `false`', () => {
@@ -65,12 +68,17 @@ describe('RecentSearchesService', () => {
RecentSearchesService.isAvailable.and.returnValue(false);
spyOn(window.localStorage, 'getItem');
-
- RecentSearchesService.prototype.fetch();
});
- it('should not call .getItem', () => {
- expect(window.localStorage.getItem).not.toHaveBeenCalled();
+ it('should not call .getItem', (done) => {
+ RecentSearchesService.prototype.fetch()
+ .then(done.fail)
+ .catch((err) => {
+ expect(err).toEqual(new RecentSearchesServiceError());
+ expect(window.localStorage.getItem).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
@@ -105,11 +113,11 @@ describe('RecentSearchesService', () => {
RecentSearchesService.isAvailable.and.returnValue(true);
spyOn(JSON, 'stringify').and.returnValue(searchesString);
-
- RecentSearchesService.prototype.save.call(recentSearchesService);
});
it('should call .setItem', () => {
+ RecentSearchesService.prototype.save.call(recentSearchesService);
+
expect(window.localStorage.setItem).toHaveBeenCalledWith(localStorageKey, searchesString);
});
});
@@ -117,11 +125,11 @@ describe('RecentSearchesService', () => {
describe('if .isAvailable returns `false`', () => {
beforeEach(() => {
RecentSearchesService.isAvailable.and.returnValue(false);
-
- RecentSearchesService.prototype.save();
});
it('should not call .setItem', () => {
+ RecentSearchesService.prototype.save();
+
expect(window.localStorage.setItem).not.toHaveBeenCalled();
});
});
diff --git a/spec/javascripts/lib/utils/cache_spec.js b/spec/javascripts/lib/utils/cache_spec.js
new file mode 100644
index 00000000000..2fe02a7592c
--- /dev/null
+++ b/spec/javascripts/lib/utils/cache_spec.js
@@ -0,0 +1,65 @@
+import Cache from '~/lib/utils/cache';
+
+describe('Cache', () => {
+ const dummyKey = 'just some key';
+ const dummyValue = 'more than a value';
+ let cache;
+
+ beforeEach(() => {
+ cache = new Cache();
+ });
+
+ describe('get', () => {
+ it('return cached data', () => {
+ cache.internalStorage[dummyKey] = dummyValue;
+
+ expect(cache.get(dummyKey)).toBe(dummyValue);
+ });
+
+ it('returns undefined for missing data', () => {
+ expect(cache.internalStorage[dummyKey]).toBe(undefined);
+ expect(cache.get(dummyKey)).toBe(undefined);
+ });
+ });
+
+ describe('hasData', () => {
+ it('return true for cached data', () => {
+ cache.internalStorage[dummyKey] = dummyValue;
+
+ expect(cache.hasData(dummyKey)).toBe(true);
+ });
+
+ it('returns false for missing data', () => {
+ expect(cache.internalStorage[dummyKey]).toBe(undefined);
+ expect(cache.hasData(dummyKey)).toBe(false);
+ });
+ });
+
+ describe('remove', () => {
+ it('removes data from cache', () => {
+ cache.internalStorage[dummyKey] = dummyValue;
+
+ cache.remove(dummyKey);
+
+ expect(cache.internalStorage[dummyKey]).toBe(undefined);
+ });
+
+ it('does nothing for missing data', () => {
+ expect(cache.internalStorage[dummyKey]).toBe(undefined);
+
+ cache.remove(dummyKey);
+
+ expect(cache.internalStorage[dummyKey]).toBe(undefined);
+ });
+
+ it('does not remove wrong data', () => {
+ cache.internalStorage[dummyKey] = dummyValue;
+ cache.internalStorage[dummyKey + dummyKey] = dummyValue + dummyValue;
+
+ cache.remove(dummyKey);
+
+ expect(cache.internalStorage[dummyKey]).toBe(undefined);
+ expect(cache.internalStorage[dummyKey + dummyKey]).toBe(dummyValue + dummyValue);
+ });
+ });
+});
diff --git a/spec/javascripts/lib/utils/users_cache_spec.js b/spec/javascripts/lib/utils/users_cache_spec.js
new file mode 100644
index 00000000000..ec6ea35952b
--- /dev/null
+++ b/spec/javascripts/lib/utils/users_cache_spec.js
@@ -0,0 +1,136 @@
+import Api from '~/api';
+import UsersCache from '~/lib/utils/users_cache';
+
+describe('UsersCache', () => {
+ const dummyUsername = 'win';
+ const dummyUser = 'has a farm';
+
+ beforeEach(() => {
+ UsersCache.internalStorage = { };
+ });
+
+ describe('get', () => {
+ it('returns undefined for empty cache', () => {
+ expect(UsersCache.internalStorage).toEqual({ });
+
+ const user = UsersCache.get(dummyUsername);
+
+ expect(user).toBe(undefined);
+ });
+
+ it('returns undefined for missing user', () => {
+ UsersCache.internalStorage['no body'] = 'no data';
+
+ const user = UsersCache.get(dummyUsername);
+
+ expect(user).toBe(undefined);
+ });
+
+ it('returns matching user', () => {
+ UsersCache.internalStorage[dummyUsername] = dummyUser;
+
+ const user = UsersCache.get(dummyUsername);
+
+ expect(user).toBe(dummyUser);
+ });
+ });
+
+ describe('hasData', () => {
+ it('returns false for empty cache', () => {
+ expect(UsersCache.internalStorage).toEqual({ });
+
+ expect(UsersCache.hasData(dummyUsername)).toBe(false);
+ });
+
+ it('returns false for missing user', () => {
+ UsersCache.internalStorage['no body'] = 'no data';
+
+ expect(UsersCache.hasData(dummyUsername)).toBe(false);
+ });
+
+ it('returns true for matching user', () => {
+ UsersCache.internalStorage[dummyUsername] = dummyUser;
+
+ expect(UsersCache.hasData(dummyUsername)).toBe(true);
+ });
+ });
+
+ describe('remove', () => {
+ it('does nothing if cache is empty', () => {
+ expect(UsersCache.internalStorage).toEqual({ });
+
+ UsersCache.remove(dummyUsername);
+
+ expect(UsersCache.internalStorage).toEqual({ });
+ });
+
+ it('does nothing if cache contains no matching data', () => {
+ UsersCache.internalStorage['no body'] = 'no data';
+
+ UsersCache.remove(dummyUsername);
+
+ expect(UsersCache.internalStorage['no body']).toBe('no data');
+ });
+
+ it('removes matching data', () => {
+ UsersCache.internalStorage[dummyUsername] = dummyUser;
+
+ UsersCache.remove(dummyUsername);
+
+ expect(UsersCache.internalStorage).toEqual({ });
+ });
+ });
+
+ describe('retrieve', () => {
+ let apiSpy;
+
+ beforeEach(() => {
+ spyOn(Api, 'users').and.callFake((query, options) => apiSpy(query, options));
+ });
+
+ it('stores and returns data from API call if cache is empty', (done) => {
+ apiSpy = (query, options) => {
+ expect(query).toBe('');
+ expect(options).toEqual({ username: dummyUsername });
+ return Promise.resolve([dummyUser]);
+ };
+
+ UsersCache.retrieve(dummyUsername)
+ .then((user) => {
+ expect(user).toBe(dummyUser);
+ expect(UsersCache.internalStorage[dummyUsername]).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 = (query, options) => {
+ expect(query).toBe('');
+ expect(options).toEqual({ username: dummyUsername });
+ return Promise.reject(dummyError);
+ };
+
+ UsersCache.retrieve(dummyUsername)
+ .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[dummyUsername] = dummyUser;
+ apiSpy = () => fail(new Error('expected no Ajax call!'));
+
+ UsersCache.retrieve(dummyUsername)
+ .then((user) => {
+ expect(user).toBe(dummyUser);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index 3d1706aab68..7b910282cc8 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -1,4 +1,5 @@
/* eslint-disable no-var, comma-dangle, object-shorthand */
+/* global Notes */
import '~/merge_request_tabs';
import '~/commit/pipelines/pipelines_bundle';
@@ -7,6 +8,7 @@ import '~/lib/utils/common_utils';
import '~/diff';
import '~/single_file_diff';
import '~/files_comment_button';
+import '~/notes';
import 'vendor/jquery.scrollTo';
(function () {
@@ -29,7 +31,7 @@ import 'vendor/jquery.scrollTo';
};
$.extend(stubLocation, defaults, stubs || {});
};
- preloadFixtures('merge_requests/merge_request_with_task_list.html.raw');
+ preloadFixtures('merge_requests/merge_request_with_task_list.html.raw', 'merge_requests/diff_comment.html.raw');
beforeEach(function () {
this.class = new gl.MergeRequestTabs({ stubLocation: stubLocation });
@@ -286,8 +288,49 @@ import 'vendor/jquery.scrollTo';
spyOn($, 'ajax').and.callFake(function (options) {
expect(options.url).toEqual('/foo/bar/merge_requests/1/diffs.json');
});
+
this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
});
+
+ describe('with note fragment hash', () => {
+ beforeEach(() => {
+ loadFixtures('merge_requests/diff_comment.html.raw');
+ spyOn(window.gl.utils, 'getPagePath').and.returnValue('merge_requests');
+ window.notes = new Notes('', []);
+ spyOn(window.notes, 'toggleDiffNote').and.callThrough();
+ });
+
+ afterEach(() => {
+ delete window.notes;
+ });
+
+ it('should expand and scroll to linked fragment hash #note_xxx', function () {
+ const noteId = 'note_1';
+ spyOn(window.gl.utils, 'getLocationHash').and.returnValue(noteId);
+ spyOn($, 'ajax').and.callFake(function (options) {
+ options.success({ html: `<div id="${noteId}">foo</div>` });
+ });
+
+ this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
+
+ expect(window.notes.toggleDiffNote).toHaveBeenCalledWith({
+ target: jasmine.any(Object),
+ lineType: 'old',
+ forceShow: true,
+ });
+ });
+
+ it('should gracefully ignore non-existant fragment hash', function () {
+ spyOn(window.gl.utils, 'getLocationHash').and.returnValue('note_something-that-does-not-exist');
+ spyOn($, 'ajax').and.callFake(function (options) {
+ options.success({ html: '' });
+ });
+
+ this.class.loadDiff('/foo/bar/merge_requests/1/diffs');
+
+ expect(window.notes.toggleDiffNote).not.toHaveBeenCalled();
+ });
+ });
});
});
}).call(window);
diff --git a/spec/javascripts/raven/index_spec.js b/spec/javascripts/raven/index_spec.js
index b5662cd0331..a503a54029f 100644
--- a/spec/javascripts/raven/index_spec.js
+++ b/spec/javascripts/raven/index_spec.js
@@ -2,25 +2,23 @@ import RavenConfig from '~/raven/raven_config';
import index from '~/raven/index';
describe('RavenConfig options', () => {
- let sentryDsn;
- let currentUserId;
- let gitlabUrl;
- let isProduction;
+ const sentryDsn = 'sentryDsn';
+ const currentUserId = 'currentUserId';
+ const gitlabUrl = 'gitlabUrl';
+ const isProduction = 'isProduction';
+ const revision = 'revision';
let indexReturnValue;
beforeEach(() => {
- sentryDsn = 'sentryDsn';
- currentUserId = 'currentUserId';
- gitlabUrl = 'gitlabUrl';
- isProduction = 'isProduction';
-
window.gon = {
sentry_dsn: sentryDsn,
current_user_id: currentUserId,
gitlab_url: gitlabUrl,
+ revision,
};
process.env.NODE_ENV = isProduction;
+ process.env.HEAD_COMMIT_SHA = revision;
spyOn(RavenConfig, 'init');
@@ -33,6 +31,10 @@ describe('RavenConfig options', () => {
currentUserId,
whitelistUrls: [gitlabUrl],
isProduction,
+ release: revision,
+ tags: {
+ revision,
+ },
});
});
diff --git a/spec/javascripts/raven/raven_config_spec.js b/spec/javascripts/raven/raven_config_spec.js
index a2d720760fc..c82658b9262 100644
--- a/spec/javascripts/raven/raven_config_spec.js
+++ b/spec/javascripts/raven/raven_config_spec.js
@@ -25,17 +25,11 @@ describe('RavenConfig', () => {
});
describe('init', () => {
- let options;
+ const options = {
+ currentUserId: 1,
+ };
beforeEach(() => {
- options = {
- sentryDsn: '//sentryDsn',
- ravenAssetUrl: '//ravenAssetUrl',
- currentUserId: 1,
- whitelistUrls: ['//gitlabUrl'],
- isProduction: true,
- };
-
spyOn(RavenConfig, 'configure');
spyOn(RavenConfig, 'bindRavenErrors');
spyOn(RavenConfig, 'setUser');
@@ -62,30 +56,28 @@ describe('RavenConfig', () => {
it('should not call setUser if there is no current user ID', () => {
RavenConfig.setUser.calls.reset();
- RavenConfig.init({
- sentryDsn: '//sentryDsn',
- ravenAssetUrl: '//ravenAssetUrl',
- currentUserId: undefined,
- whitelistUrls: ['//gitlabUrl'],
- isProduction: true,
- });
+ options.currentUserId = undefined;
+
+ RavenConfig.init(options);
expect(RavenConfig.setUser).not.toHaveBeenCalled();
});
});
describe('configure', () => {
- let options;
let raven;
let ravenConfig;
+ const options = {
+ sentryDsn: '//sentryDsn',
+ whitelistUrls: ['//gitlabUrl'],
+ isProduction: true,
+ release: 'revision',
+ tags: {
+ revision: 'revision',
+ },
+ };
beforeEach(() => {
- options = {
- sentryDsn: '//sentryDsn',
- whitelistUrls: ['//gitlabUrl'],
- isProduction: true,
- };
-
ravenConfig = jasmine.createSpyObj('ravenConfig', ['shouldSendSample']);
raven = jasmine.createSpyObj('raven', ['install']);
@@ -100,6 +92,8 @@ describe('RavenConfig', () => {
it('should call Raven.config', () => {
expect(Raven.config).toHaveBeenCalledWith(options.sentryDsn, {
+ release: options.release,
+ tags: options.tags,
whitelistUrls: options.whitelistUrls,
environment: 'production',
ignoreErrors: ravenConfig.IGNORE_ERRORS,
@@ -118,6 +112,8 @@ describe('RavenConfig', () => {
RavenConfig.configure.call(ravenConfig);
expect(Raven.config).toHaveBeenCalledWith(options.sentryDsn, {
+ release: options.release,
+ tags: options.tags,
whitelistUrls: options.whitelistUrls,
environment: 'development',
ignoreErrors: ravenConfig.IGNORE_ERRORS,
@@ -144,24 +140,6 @@ describe('RavenConfig', () => {
});
});
- describe('bindRavenErrors', () => {
- let $document;
- let $;
-
- beforeEach(() => {
- $document = jasmine.createSpyObj('$document', ['on']);
- $ = jasmine.createSpy('$').and.returnValue($document);
-
- window.$ = $;
-
- RavenConfig.bindRavenErrors();
- });
-
- it('should call .on', function () {
- expect($document.on).toHaveBeenCalledWith('ajaxError.raven', RavenConfig.handleRavenErrors);
- });
- });
-
describe('handleRavenErrors', () => {
let event;
let req;
diff --git a/spec/javascripts/sidebar/sidebar_assignees_spec.js b/spec/javascripts/sidebar/sidebar_assignees_spec.js
index 865951b2ad7..929ba75e67d 100644
--- a/spec/javascripts/sidebar/sidebar_assignees_spec.js
+++ b/spec/javascripts/sidebar/sidebar_assignees_spec.js
@@ -43,4 +43,16 @@ describe('sidebar assignees', () => {
expect(SidebarMediator.prototype.assignYourself).toHaveBeenCalled();
expect(this.mediator.store.assignees.length).toEqual(1);
});
+
+ it('hides assignees until fetched', (done) => {
+ component = new SidebarAssigneeComponent().$mount(this.sidebarAssigneesEl);
+ const currentAssignee = this.sidebarAssigneesEl.querySelector('.value');
+ expect(currentAssignee).toBe(null);
+
+ component.store.isFetching.assignees = false;
+ Vue.nextTick(() => {
+ expect(component.$el.querySelector('.value')).toBeVisible();
+ done();
+ });
+ });
});
diff --git a/spec/javascripts/sidebar/sidebar_store_spec.js b/spec/javascripts/sidebar/sidebar_store_spec.js
index 29facf483b5..b3fa156eb64 100644
--- a/spec/javascripts/sidebar/sidebar_store_spec.js
+++ b/spec/javascripts/sidebar/sidebar_store_spec.js
@@ -35,6 +35,10 @@ describe('Sidebar store', () => {
SidebarStore.singleton = null;
});
+ it('has default isFetching values', () => {
+ expect(this.store.isFetching.assignees).toBe(true);
+ });
+
it('adds a new assignee', () => {
this.store.addAssignee(assignee);
expect(this.store.assignees.length).toEqual(1);
@@ -67,6 +71,7 @@ describe('Sidebar store', () => {
};
this.store.setAssigneeData(users);
+ expect(this.store.isFetching.assignees).toBe(false);
expect(this.store.assignees.length).toEqual(3);
});
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 9a331d99865..179e42a7cc4 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
@@ -25,6 +25,12 @@ describe('getStateKey', () => {
context.canBeMerged = true;
expect(bound()).toEqual('readyToMerge');
+ context.canMerge = false;
+ expect(bound()).toEqual('notAllowedToMerge');
+
+ context.mergeWhenPipelineSucceeds = true;
+ expect(bound()).toEqual('mergeWhenPipelineSucceeds');
+
context.hasSHAChanged = true;
expect(bound()).toEqual('shaMismatch');
@@ -38,12 +44,6 @@ describe('getStateKey', () => {
context.isPipelineFailed = true;
expect(bound()).toEqual('pipelineFailed');
- context.canMerge = false;
- expect(bound()).toEqual('notAllowedToMerge');
-
- context.mergeWhenPipelineSucceeds = true;
- expect(bound()).toEqual('mergeWhenPipelineSucceeds');
-
data.work_in_progress = true;
expect(bound()).toEqual('workInProgress');
diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
new file mode 100644
index 00000000000..1bf8916b3d0
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
@@ -0,0 +1,82 @@
+import Vue from 'vue';
+import headerCi from '~/vue_shared/components/header_ci_component.vue';
+
+describe('Header CI Component', () => {
+ let HeaderCi;
+ let vm;
+ let props;
+
+ beforeEach(() => {
+ HeaderCi = Vue.extend(headerCi);
+
+ props = {
+ status: {
+ group: 'failed',
+ icon: 'ci-status-failed',
+ label: 'failed',
+ text: 'failed',
+ details_path: 'path',
+ },
+ itemName: 'job',
+ itemId: 123,
+ time: '2017-05-08T14:57:39.781Z',
+ user: {
+ web_url: 'path',
+ name: 'Foo',
+ username: 'foobar',
+ email: 'foo@bar.com',
+ avatar_url: 'link',
+ },
+ actions: [
+ {
+ label: 'Retry',
+ path: 'path',
+ type: 'button',
+ cssClass: 'btn',
+ },
+ {
+ label: 'Go',
+ path: 'path',
+ type: 'link',
+ cssClass: 'link',
+ },
+ ],
+ };
+
+ vm = new HeaderCi({
+ propsData: props,
+ }).$mount();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('should render status badge', () => {
+ expect(vm.$el.querySelector('.ci-failed')).toBeDefined();
+ expect(vm.$el.querySelector('.ci-status-icon-failed svg')).toBeDefined();
+ expect(
+ vm.$el.querySelector('.ci-failed').getAttribute('href'),
+ ).toEqual(props.status.details_path);
+ });
+
+ it('should render item name and id', () => {
+ expect(vm.$el.querySelector('strong').textContent.trim()).toEqual('job #123');
+ });
+
+ it('should render timeago date', () => {
+ expect(vm.$el.querySelector('time')).toBeDefined();
+ });
+
+ it('should render user icon and name', () => {
+ expect(vm.$el.querySelector('.js-user-link').textContent.trim()).toEqual(props.user.name);
+ });
+
+ it('should render provided actions', () => {
+ expect(vm.$el.querySelector('.btn').tagName).toEqual('BUTTON');
+ expect(vm.$el.querySelector('.btn').textContent.trim()).toEqual(props.actions[0].label);
+ expect(vm.$el.querySelector('.link').tagName).toEqual('A');
+ expect(vm.$el.querySelector('.link').textContent.trim()).toEqual(props.actions[1].label);
+ expect(vm.$el.querySelector('.link').getAttribute('href')).toEqual(props.actions[0].path);
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js b/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js
new file mode 100644
index 00000000000..bf28019ef24
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js
@@ -0,0 +1,68 @@
+import Vue from 'vue';
+import timeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import '~/lib/utils/datetime_utility';
+
+describe('Time ago with tooltip component', () => {
+ let TimeagoTooltip;
+ let vm;
+
+ beforeEach(() => {
+ TimeagoTooltip = Vue.extend(timeagoTooltip);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('should render timeago with a bootstrap tooltip', () => {
+ vm = new TimeagoTooltip({
+ propsData: {
+ time: '2017-05-08T14:57:39.781Z',
+ },
+ }).$mount();
+
+ expect(vm.$el.tagName).toEqual('TIME');
+ expect(vm.$el.classList.contains('js-timeago')).toEqual(true);
+ expect(
+ vm.$el.getAttribute('data-original-title'),
+ ).toEqual(gl.utils.formatDate('2017-05-08T14:57:39.781Z'));
+ expect(vm.$el.getAttribute('data-placement')).toEqual('top');
+
+ const timeago = gl.utils.getTimeago();
+
+ expect(vm.$el.textContent.trim()).toEqual(timeago.format('2017-05-08T14:57:39.781Z'));
+ });
+
+ it('should render tooltip placed in bottom', () => {
+ vm = new TimeagoTooltip({
+ propsData: {
+ time: '2017-05-08T14:57:39.781Z',
+ tooltipPlacement: 'bottom',
+ },
+ }).$mount();
+
+ expect(vm.$el.getAttribute('data-placement')).toEqual('bottom');
+ });
+
+ it('should render short format class', () => {
+ vm = new TimeagoTooltip({
+ propsData: {
+ time: '2017-05-08T14:57:39.781Z',
+ shortFormat: true,
+ },
+ }).$mount();
+
+ expect(vm.$el.classList.contains('js-short-timeago')).toEqual(true);
+ });
+
+ it('should render provided html class', () => {
+ vm = new TimeagoTooltip({
+ propsData: {
+ time: '2017-05-08T14:57:39.781Z',
+ cssClass: 'foo',
+ },
+ }).$mount();
+
+ expect(vm.$el.classList.contains('foo')).toEqual(true);
+ });
+});
diff --git a/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb b/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb
new file mode 100644
index 00000000000..33b812ef425
--- /dev/null
+++ b/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe Banzai::Filter::AsciiDocPostProcessingFilter, lib: true do
+ include FilterSpecHelper
+
+ it "adds class for elements with data-math-style" do
+ result = filter('<pre data-math-style="inline">some code</pre><div data-math>and</div>').to_html
+ expect(result).to eq('<pre data-math-style="inline" class="code math js-render-math">some code</pre><div data-math>and</div>')
+ end
+
+ it "keeps content when no data-math-style found" do
+ result = filter('<pre>some code</pre><div data-math>and</div>').to_html
+ expect(result).to eq('<pre>some code</pre><div data-math>and</div>')
+ end
+end
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index fdbc65b5e00..fb7862f49a2 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -97,6 +97,22 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
expect(filter(act).to_html).to eq exp
end
+ it 'allows `data-math-style` attribute on `code` and `pre` elements' do
+ html = <<-HTML
+ <pre class="code" data-math-style="inline">something</pre>
+ <code class="code" data-math-style="inline">something</code>
+ <div class="code" data-math-style="inline">something</div>
+ HTML
+
+ output = <<-HTML
+ <pre data-math-style="inline">something</pre>
+ <code data-math-style="inline">something</code>
+ <div>something</div>
+ HTML
+
+ expect(filter(html).to_html).to eq(output)
+ end
+
it 'removes `rel` attribute from `a` elements' do
act = %q{<a href="#" rel="nofollow">Link</a>}
exp = %q{<a href="#">Link</a>}
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index 2c7ebb15fd7..43d52b941ab 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -70,6 +70,31 @@ module Gitlab
expect(output).to include('rel="nofollow noreferrer noopener"')
end
end
+
+ context 'LaTex code' do
+ it 'adds class js-render-math to the output' do
+ input = <<~MD
+ :stem: latexmath
+
+ [stem]
+ ++++
+ \sqrt{4} = 2
+ ++++
+
+ another part
+
+ [latexmath]
+ ++++
+ \beta_x \gamma
+ ++++
+
+ stem:[2+2] is 4
+ MD
+
+ expect(render(input, context)).to include('<pre data-math-style="display" class="code math js-render-math"><code>eta_x gamma</code></pre>')
+ expect(render(input, context)).to include('<p><code data-math-style="inline" class="code math js-render-math">2+2</code> is 4</p>')
+ end
+ end
end
def render(*args)
diff --git a/spec/lib/gitlab/backup/manager_spec.rb b/spec/lib/gitlab/backup/manager_spec.rb
index c59ff7fb290..1c3d2547fec 100644
--- a/spec/lib/gitlab/backup/manager_spec.rb
+++ b/spec/lib/gitlab/backup/manager_spec.rb
@@ -24,8 +24,9 @@ describe Backup::Manager, lib: true do
describe '#remove_old' do
let(:files) do
[
- '1451606400_2016_01_01_gitlab_backup.tar',
- '1451520000_2015_12_31_gitlab_backup.tar',
+ '1451606400_2016_01_01_1.2.3_gitlab_backup.tar',
+ '1451520000_2015_12_31_4.5.6_gitlab_backup.tar',
+ '1451510000_2015_12_30_gitlab_backup.tar',
'1450742400_2015_12_22_gitlab_backup.tar',
'1449878400_gitlab_backup.tar',
'1449014400_gitlab_backup.tar',
@@ -58,6 +59,7 @@ describe Backup::Manager, lib: true do
context 'when there are no files older than keep_time' do
before do
+ # Set to 30 days
allow(Gitlab.config.backup).to receive(:keep_time).and_return(2592000)
subject.remove_old
@@ -74,19 +76,24 @@ describe Backup::Manager, lib: true do
context 'when keep_time is set to remove files' do
before do
+ # Set to 1 second
allow(Gitlab.config.backup).to receive(:keep_time).and_return(1)
subject.remove_old
end
- it 'removes matching files with a human-readable timestamp' do
+ it 'removes matching files with a human-readable versioned timestamp' do
expect(FileUtils).to have_received(:rm).with(files[1])
+ end
+
+ it 'removes matching files with a human-readable non-versioned timestamp' do
expect(FileUtils).to have_received(:rm).with(files[2])
+ expect(FileUtils).to have_received(:rm).with(files[3])
end
it 'removes matching files without a human-readable timestamp' do
- expect(FileUtils).to have_received(:rm).with(files[3])
expect(FileUtils).to have_received(:rm).with(files[4])
+ expect(FileUtils).to have_received(:rm).with(files[5])
end
it 'does not remove files that are not old enough' do
@@ -94,11 +101,11 @@ describe Backup::Manager, lib: true do
end
it 'does not remove non-matching files' do
- expect(FileUtils).not_to have_received(:rm).with(files[5])
+ expect(FileUtils).not_to have_received(:rm).with(files[6])
end
it 'prints a done message' do
- expect(progress).to have_received(:puts).with('done. (4 removed)')
+ expect(progress).to have_received(:puts).with('done. (5 removed)')
end
end
@@ -117,10 +124,11 @@ describe Backup::Manager, lib: true do
expect(FileUtils).to have_received(:rm).with(files[2])
expect(FileUtils).to have_received(:rm).with(files[3])
expect(FileUtils).to have_received(:rm).with(files[4])
+ expect(FileUtils).to have_received(:rm).with(files[5])
end
it 'sets the correct removed count' do
- expect(progress).to have_received(:puts).with('done. (3 removed)')
+ expect(progress).to have_received(:puts).with('done. (4 removed)')
end
it 'prints the error from file that could not be removed' do
@@ -150,7 +158,7 @@ describe Backup::Manager, lib: true do
before do
allow(Dir).to receive(:glob).and_return(
[
- '1451606400_2016_01_01_gitlab_backup.tar',
+ '1451606400_2016_01_01_1.2.3_gitlab_backup.tar',
'1451520000_2015_12_31_gitlab_backup.tar'
]
)
@@ -187,21 +195,21 @@ describe Backup::Manager, lib: true do
before do
allow(Dir).to receive(:glob).and_return(
[
- '1451606400_2016_01_01_gitlab_backup.tar'
+ '1451606400_2016_01_01_1.2.3_gitlab_backup.tar'
]
)
allow(File).to receive(:exist?).and_return(true)
allow(Kernel).to receive(:system).and_return(true)
allow(YAML).to receive(:load_file).and_return(gitlab_version: Gitlab::VERSION)
- stub_env('BACKUP', '1451606400_2016_01_01')
+ stub_env('BACKUP', '1451606400_2016_01_01_1.2.3')
end
it 'unpacks the file' do
subject.unpack
expect(Kernel).to have_received(:system)
- .with("tar", "-xf", "1451606400_2016_01_01_gitlab_backup.tar")
+ .with("tar", "-xf", "1451606400_2016_01_01_1.2.3_gitlab_backup.tar")
expect(progress).to have_received(:puts).with(a_string_matching('done'))
end
end
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index 40ac5a3ed37..bbb3f9912a3 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -240,9 +240,50 @@ describe Gitlab::Ci::Trace::Stream do
end
context 'multiple results in content & regex' do
- let(:data) { ' (98.39%) covered. (98.29%) covered' }
+ let(:data) do
+ <<~HEREDOC
+ (98.39%) covered
+ (98.29%) covered
+ HEREDOC
+ end
+
+ let(:regex) { '\(\d+.\d+\%\) covered' }
+
+ it 'returns the last matched coverage' do
+ is_expected.to eq("98.29")
+ end
+ end
+
+ context 'when BUFFER_SIZE is smaller than stream.size' do
+ let(:data) { 'Coverage 1033 / 1051 LOC (98.29%) covered\n' }
+ let(:regex) { '\(\d+.\d+\%\) covered' }
+
+ before do
+ stub_const('Gitlab::Ci::Trace::Stream::BUFFER_SIZE', 5)
+ end
+
+ it { is_expected.to eq("98.29") }
+ end
+
+ context 'when regex is multi-byte char' do
+ let(:data) { '95.0 ゴッドファット\n' }
+ let(:regex) { '\d+\.\d+ ゴッドファット' }
+
+ before do
+ stub_const('Gitlab::Ci::Trace::Stream::BUFFER_SIZE', 5)
+ end
+
+ it { is_expected.to eq('95.0') }
+ end
+
+ context 'when BUFFER_SIZE is equal to stream.size' do
+ let(:data) { 'Coverage 1033 / 1051 LOC (98.29%) covered\n' }
let(:regex) { '\(\d+.\d+\%\) covered' }
+ before do
+ stub_const('Gitlab::Ci::Trace::Stream::BUFFER_SIZE', data.length)
+ end
+
it { is_expected.to eq("98.29") }
end
diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb
index 3610a0354e8..a1b3fe8509e 100644
--- a/spec/lib/gitlab/cycle_analytics/events_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb
@@ -126,12 +126,11 @@ describe 'cycle analytics events' do
create(:ci_pipeline,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha,
- project: context.project)
+ project: context.project,
+ head_pipeline_of: merge_request)
end
before do
- merge_request.update(head_pipeline: pipeline)
-
create(:ci_build, pipeline: pipeline, status: :success, author: user)
create(:ci_build, pipeline: pipeline, status: :success, author: user)
@@ -224,12 +223,11 @@ describe 'cycle analytics events' do
create(:ci_pipeline,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha,
- project: context.project)
+ project: context.project,
+ head_pipeline_of: merge_request)
end
before do
- merge_request.update(head_pipeline: pipeline)
-
create(:ci_build, pipeline: pipeline, status: :success, author: user)
create(:ci_build, pipeline: pipeline, status: :success, author: user)
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index dfa3ae9142e..3fdafd867da 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -66,16 +66,23 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
context 'using PostgreSQL' do
before do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
+ allow(model).to receive(:supports_drop_index_concurrently?).and_return(true)
allow(model).to receive(:disable_statement_timeout)
end
- it 'removes the index concurrently' do
+ it 'removes the index concurrently by column name' do
expect(model).to receive(:remove_index).
with(:users, { algorithm: :concurrently, column: :foo })
model.remove_concurrent_index(:users, :foo)
end
+
+ it 'removes the index concurrently by index name' do
+ expect(model).to receive(:remove_index).
+ with(:users, { algorithm: :concurrently, name: "index_x_by_y" })
+
+ model.remove_concurrent_index_by_name(:users, "index_x_by_y")
+ end
end
context 'using MySQL' do
@@ -247,6 +254,14 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
expect(Project.where(archived: true).count).to eq(1)
end
end
+
+ context 'when the value is Arel.sql (Arel::Nodes::SqlLiteral)' do
+ it 'updates the value as a SQL expression' do
+ model.update_column_in_batches(:projects, :star_count, Arel.sql('1+1'))
+
+ expect(Project.sum(:star_count)).to eq(2 * Project.count)
+ end
+ end
end
describe '#add_column_with_default' do
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
index c56fded7516..ce2b5d620fd 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
@@ -18,8 +18,8 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
let(:subject) { described_class.new(['parent/the-Path'], migration) }
it 'includes the namespace' do
- parent = create(:namespace, path: 'parent')
- child = create(:namespace, path: 'the-path', parent: parent)
+ parent = create(:group, path: 'parent')
+ child = create(:group, path: 'the-path', parent: parent)
found_ids = subject.namespaces_for_paths(type: :child).
map(&:id)
@@ -30,13 +30,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
context 'for child namespaces' do
it 'only returns child namespaces with the correct path' do
- _root_namespace = create(:namespace, path: 'THE-path')
- _other_path = create(:namespace,
+ _root_namespace = create(:group, path: 'THE-path')
+ _other_path = create(:group,
path: 'other',
- parent: create(:namespace))
- namespace = create(:namespace,
+ parent: create(:group))
+ namespace = create(:group,
path: 'the-path',
- parent: create(:namespace))
+ parent: create(:group))
found_ids = subject.namespaces_for_paths(type: :child).
map(&:id)
@@ -45,13 +45,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
end
it 'has no namespaces that look the same' do
- _root_namespace = create(:namespace, path: 'THE-path')
- _similar_path = create(:namespace,
+ _root_namespace = create(:group, path: 'THE-path')
+ _similar_path = create(:group,
path: 'not-really-the-path',
- parent: create(:namespace))
- namespace = create(:namespace,
+ parent: create(:group))
+ namespace = create(:group,
path: 'the-path',
- parent: create(:namespace))
+ parent: create(:group))
found_ids = subject.namespaces_for_paths(type: :child).
map(&:id)
@@ -62,11 +62,11 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
context 'for top levelnamespaces' do
it 'only returns child namespaces with the correct path' do
- root_namespace = create(:namespace, path: 'the-path')
- _other_path = create(:namespace, path: 'other')
- _child_namespace = create(:namespace,
+ root_namespace = create(:group, path: 'the-path')
+ _other_path = create(:group, path: 'other')
+ _child_namespace = create(:group,
path: 'the-path',
- parent: create(:namespace))
+ parent: create(:group))
found_ids = subject.namespaces_for_paths(type: :top_level).
map(&:id)
@@ -75,11 +75,11 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
end
it 'has no namespaces that just look the same' do
- root_namespace = create(:namespace, path: 'the-path')
- _similar_path = create(:namespace, path: 'not-really-the-path')
- _child_namespace = create(:namespace,
+ root_namespace = create(:group, path: 'the-path')
+ _similar_path = create(:group, path: 'not-really-the-path')
+ _child_namespace = create(:group,
path: 'the-path',
- parent: create(:namespace))
+ parent: create(:group))
found_ids = subject.namespaces_for_paths(type: :top_level).
map(&:id)
@@ -124,10 +124,10 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
describe "#child_ids_for_parent" do
it "collects child ids for all levels" do
- parent = create(:namespace)
- first_child = create(:namespace, parent: parent)
- second_child = create(:namespace, parent: parent)
- third_child = create(:namespace, parent: second_child)
+ parent = create(:group)
+ first_child = create(:group, parent: parent)
+ second_child = create(:group, parent: parent)
+ third_child = create(:group, parent: second_child)
all_ids = [parent.id, first_child.id, second_child.id, third_child.id]
collected_ids = subject.child_ids_for_parent(parent, ids: [parent.id])
@@ -205,9 +205,9 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
end
describe '#rename_namespaces' do
- let!(:top_level_namespace) { create(:namespace, path: 'the-path') }
+ let!(:top_level_namespace) { create(:group, path: 'the-path') }
let!(:child_namespace) do
- create(:namespace, path: 'the-path', parent: create(:namespace))
+ create(:group, path: 'the-path', parent: create(:group))
end
it 'renames top level namespaces the namespace' do
diff --git a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
new file mode 100644
index 00000000000..df77f4037af
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
@@ -0,0 +1,74 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::CartfileLinker, lib: true do
+ describe '.support?' do
+ it 'supports Cartfile' do
+ expect(described_class.support?('Cartfile')).to be_truthy
+ end
+
+ it 'supports Cartfile.private' do
+ expect(described_class.support?('Cartfile.private')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('test.Cartfile')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { "Cartfile" }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ # Require version 2.3.1 or later
+ github "ReactiveCocoa/ReactiveCocoa" >= 2.3.1
+
+ # Require version 1.x
+ github "Mantle/Mantle" ~> 1.0 # (1.0 or later, but less than 2.0)
+
+ # Require exactly version 0.4.1
+ github "jspahrsummers/libextobjc" == 0.4.1
+
+ # Use the latest version
+ github "jspahrsummers/xcconfigs"
+
+ # Use the branch
+ github "jspahrsummers/xcconfigs" "branch"
+
+ # Use a project from GitHub Enterprise
+ github "https://enterprise.local/ghe/desktop/git-error-translations"
+
+ # Use a project from any arbitrary server, on the "development" branch
+ git "https://enterprise.local/desktop/git-error-translations2.git" "development"
+
+ # Use a local project
+ git "file:///directory/to/project" "branch"
+
+ # A binary only framework
+ binary "https://my.domain.com/release/MyFramework.json" ~> 2.3
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links dependencies' do
+ expect(subject).to include(link('ReactiveCocoa/ReactiveCocoa', 'https://github.com/ReactiveCocoa/ReactiveCocoa'))
+ expect(subject).to include(link('Mantle/Mantle', 'https://github.com/Mantle/Mantle'))
+ expect(subject).to include(link('jspahrsummers/libextobjc', 'https://github.com/jspahrsummers/libextobjc'))
+ expect(subject).to include(link('jspahrsummers/xcconfigs', 'https://github.com/jspahrsummers/xcconfigs'))
+ end
+
+ it 'links Git repos' do
+ expect(subject).to include(link('https://enterprise.local/ghe/desktop/git-error-translations', 'https://enterprise.local/ghe/desktop/git-error-translations'))
+ expect(subject).to include(link('https://enterprise.local/desktop/git-error-translations2.git', 'https://enterprise.local/desktop/git-error-translations2.git'))
+ end
+
+ it 'links binary-only frameworks' do
+ expect(subject).to include(link('https://my.domain.com/release/MyFramework.json', 'https://my.domain.com/release/MyFramework.json'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
new file mode 100644
index 00000000000..d7a926e800f
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
@@ -0,0 +1,82 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::ComposerJsonLinker, lib: true do
+ describe '.support?' do
+ it 'supports composer.json' do
+ expect(described_class.support?('composer.json')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('composer.json.example')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { "composer.json" }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ {
+ "name": "laravel/laravel",
+ "homepage": "https://laravel.com/",
+ "description": "The Laravel Framework.",
+ "keywords": ["framework", "laravel"],
+ "license": "MIT",
+ "type": "project",
+ "repositories": [
+ {
+ "type": "git",
+ "url": "https://github.com/laravel/laravel.git"
+ }
+ ],
+ "require": {
+ "php": ">=5.5.9",
+ "laravel/framework": "5.2.*"
+ },
+ "require-dev": {
+ "fzaninotto/faker": "~1.4",
+ "mockery/mockery": "0.9.*",
+ "phpunit/phpunit": "~4.0",
+ "symfony/css-selector": "2.8.*|3.0.*",
+ "symfony/dom-crawler": "2.8.*|3.0.*"
+ }
+ }
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links the module name' do
+ expect(subject).to include(link('laravel/laravel', 'https://packagist.org/packages/laravel/laravel'))
+ end
+
+ it 'links the homepage' do
+ expect(subject).to include(link('https://laravel.com/', 'https://laravel.com/'))
+ end
+
+ it 'links the repository URL' do
+ expect(subject).to include(link('https://github.com/laravel/laravel.git', 'https://github.com/laravel/laravel.git'))
+ end
+
+ it 'links the license' do
+ expect(subject).to include(link('MIT', 'http://choosealicense.com/licenses/mit/'))
+ end
+
+ it 'links dependencies' do
+ expect(subject).to include(link('laravel/framework', 'https://packagist.org/packages/laravel/framework'))
+ expect(subject).to include(link('fzaninotto/faker', 'https://packagist.org/packages/fzaninotto/faker'))
+ expect(subject).to include(link('mockery/mockery', 'https://packagist.org/packages/mockery/mockery'))
+ expect(subject).to include(link('phpunit/phpunit', 'https://packagist.org/packages/phpunit/phpunit'))
+ expect(subject).to include(link('symfony/css-selector', 'https://packagist.org/packages/symfony/css-selector'))
+ expect(subject).to include(link('symfony/dom-crawler', 'https://packagist.org/packages/symfony/dom-crawler'))
+ end
+
+ it 'does not link core dependencies' do
+ expect(subject).not_to include(link('php', 'https://packagist.org/packages/php'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
index 2e52097a946..3f8335f03ea 100644
--- a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
@@ -33,7 +33,7 @@ describe Gitlab::DependencyLinker::GemfileLinker, lib: true do
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
- %{<a href="#{url}" rel="noopener noreferrer" target="_blank">#{name}</a>}
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
end
it 'links sources' do
diff --git a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
new file mode 100644
index 00000000000..d4a71403939
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
@@ -0,0 +1,66 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::GemspecLinker, lib: true do
+ describe '.support?' do
+ it 'supports *.gemspec' do
+ expect(described_class.support?('gitlab_git.gemspec')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('.gemspec.example')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { "gitlab_git.gemspec" }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ Gem::Specification.new do |s|
+ s.name = 'gitlab_git'
+ s.version = `cat VERSION`
+ s.date = Time.now.strftime('%Y-%m-%d')
+ s.summary = "Gitlab::Git library"
+ s.description = "GitLab wrapper around git objects"
+ s.authors = ["Dmitriy Zaporozhets"]
+ s.email = 'dmitriy.zaporozhets@gmail.com'
+ s.license = 'MIT'
+ s.files = `git ls-files lib/`.split('\n') << 'VERSION'
+ s.homepage = 'https://gitlab.com/gitlab-org/gitlab_git'
+
+ s.add_dependency('github-linguist', '~> 4.7.0')
+ s.add_dependency('activesupport', '~> 4.0')
+ s.add_dependency('rugged', '~> 0.24.0')
+ s.add_runtime_dependency('charlock_holmes', '~> 0.7.3')
+ s.add_development_dependency('listen', '~> 3.0.6')
+ end
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links the gem name' do
+ expect(subject).to include(link('gitlab_git', 'https://rubygems.org/gems/gitlab_git'))
+ end
+
+ it 'links the license' do
+ expect(subject).to include(link('MIT', 'http://choosealicense.com/licenses/mit/'))
+ end
+
+ it 'links the homepage' do
+ expect(subject).to include(link('https://gitlab.com/gitlab-org/gitlab_git', 'https://gitlab.com/gitlab-org/gitlab_git'))
+ end
+
+ it 'links dependencies' do
+ expect(subject).to include(link('github-linguist', 'https://rubygems.org/gems/github-linguist'))
+ expect(subject).to include(link('activesupport', 'https://rubygems.org/gems/activesupport'))
+ expect(subject).to include(link('rugged', 'https://rubygems.org/gems/rugged'))
+ expect(subject).to include(link('charlock_holmes', 'https://rubygems.org/gems/charlock_holmes'))
+ expect(subject).to include(link('listen', 'https://rubygems.org/gems/listen'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
new file mode 100644
index 00000000000..e279e0c9019
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
@@ -0,0 +1,84 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::GodepsJsonLinker, lib: true do
+ describe '.support?' do
+ it 'supports Godeps.json' do
+ expect(described_class.support?('Godeps.json')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('Godeps.json.example')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { "Godeps.json" }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ {
+ "ImportPath": "gitlab.com/gitlab-org/gitlab-pages",
+ "GoVersion": "go1.5",
+ "Packages": [
+ "./..."
+ ],
+ "Deps": [
+ {
+ "ImportPath": "github.com/kardianos/osext",
+ "Rev": "efacde03154693404c65e7aa7d461ac9014acd0c"
+ },
+ {
+ "ImportPath": "github.com/stretchr/testify/assert",
+ "Rev": "1297dc01ed0a819ff634c89707081a4df43baf6b"
+ },
+ {
+ "ImportPath": "github.com/stretchr/testify/require",
+ "Rev": "1297dc01ed0a819ff634c89707081a4df43baf6b"
+ },
+ {
+ "ImportPath": "gitlab.com/group/project/path",
+ "Rev": "1297dc01ed0a819ff634c89707081a4df43baf6b"
+ },
+ {
+ "ImportPath": "gitlab.com/group/subgroup/project.git/path",
+ "Rev": "1297dc01ed0a819ff634c89707081a4df43baf6b"
+ },
+ {
+ "ImportPath": "golang.org/x/crypto/ssh/terminal",
+ "Rev": "1351f936d976c60a0a48d728281922cf63eafb8d"
+ },
+ {
+ "ImportPath": "golang.org/x/net/http2",
+ "Rev": "b4e17d61b15679caf2335da776c614169a1b4643"
+ }
+ ]
+ }
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links the package name' do
+ expect(subject).to include(link('gitlab.com/gitlab-org/gitlab-pages', 'https://gitlab.com/gitlab-org/gitlab-pages'))
+ end
+
+ it 'links GitHub repos' do
+ expect(subject).to include(link('github.com/kardianos/osext', 'https://github.com/kardianos/osext'))
+ expect(subject).to include(link('github.com/stretchr/testify/assert', 'https://github.com/stretchr/testify/tree/master/assert'))
+ expect(subject).to include(link('github.com/stretchr/testify/require', 'https://github.com/stretchr/testify/tree/master/require'))
+ end
+
+ it 'links GitLab projects' do
+ expect(subject).to include(link('gitlab.com/group/project/path', 'https://gitlab.com/group/project/tree/master/path'))
+ expect(subject).to include(link('gitlab.com/group/subgroup/project.git/path', 'https://gitlab.com/group/subgroup/project/tree/master/path'))
+ end
+
+ it 'links Golang packages' do
+ expect(subject).to include(link('golang.org/x/net/http2', 'https://godoc.org/golang.org/x/net/http2'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
new file mode 100644
index 00000000000..8c979ae1869
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
@@ -0,0 +1,94 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::PackageJsonLinker, lib: true do
+ describe '.support?' do
+ it 'supports package.json' do
+ expect(described_class.support?('package.json')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('package.json.example')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { "package.json" }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ {
+ "name": "module-name",
+ "version": "10.3.1",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/vuejs/vue.git"
+ },
+ "homepage": "https://github.com/vuejs/vue#readme",
+ "scripts": {
+ "karma": "karma start config/karma.config.js --single-run"
+ },
+ "dependencies": {
+ "primus": "*",
+ "async": "~0.8.0",
+ "express": "4.2.x",
+ "bigpipe": "bigpipe/pagelet",
+ "plates": "https://github.com/flatiron/plates/tarball/master",
+ "karma": "^1.4.1"
+ },
+ "devDependencies": {
+ "vows": "^0.7.0",
+ "assume": "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0",
+ "pre-commit": "*"
+ },
+ "license": "MIT"
+ }
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links the module name' do
+ expect(subject).to include(link('module-name', 'https://npmjs.com/package/module-name'))
+ end
+
+ it 'links the homepage' do
+ expect(subject).to include(link('https://github.com/vuejs/vue#readme', 'https://github.com/vuejs/vue#readme'))
+ end
+
+ it 'links the repository URL' do
+ expect(subject).to include(link('https://github.com/vuejs/vue.git', 'https://github.com/vuejs/vue.git'))
+ end
+
+ it 'links the license' do
+ expect(subject).to include(link('MIT', 'http://choosealicense.com/licenses/mit/'))
+ end
+
+ it 'links dependencies' do
+ expect(subject).to include(link('primus', 'https://npmjs.com/package/primus'))
+ expect(subject).to include(link('async', 'https://npmjs.com/package/async'))
+ expect(subject).to include(link('express', 'https://npmjs.com/package/express'))
+ expect(subject).to include(link('bigpipe', 'https://npmjs.com/package/bigpipe'))
+ expect(subject).to include(link('plates', 'https://npmjs.com/package/plates'))
+ expect(subject).to include(link('karma', 'https://npmjs.com/package/karma'))
+ expect(subject).to include(link('vows', 'https://npmjs.com/package/vows'))
+ expect(subject).to include(link('assume', 'https://npmjs.com/package/assume'))
+ expect(subject).to include(link('pre-commit', 'https://npmjs.com/package/pre-commit'))
+ end
+
+ it 'links GitHub repos' do
+ expect(subject).to include(link('bigpipe/pagelet', 'https://github.com/bigpipe/pagelet'))
+ end
+
+ it 'links Git repos' do
+ expect(subject).to include(link('https://github.com/flatiron/plates/tarball/master', 'https://github.com/flatiron/plates/tarball/master'))
+ end
+
+ it 'does not link scripts with the same key as a package' do
+ expect(subject).not_to include(link('karma start config/karma.config.js --single-run', 'https://github.com/karma start config/karma.config.js --single-run'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
new file mode 100644
index 00000000000..06007cf97f7
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
@@ -0,0 +1,53 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::PodfileLinker, lib: true do
+ describe '.support?' do
+ it 'supports Podfile' do
+ expect(described_class.support?('Podfile')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('Podfile.lock')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { "Podfile" }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ source 'https://github.com/artsy/Specs.git'
+ source 'https://github.com/CocoaPods/Specs.git'
+
+ platform :ios, '8.0'
+ use_frameworks!
+ inhibit_all_warnings!
+
+ target 'Artsy' do
+ pod 'AFNetworking', "~> 2.5"
+ pod 'Interstellar/Core', git: 'https://github.com/ashfurrow/Interstellar.git', branch: 'observable-unsubscribe'
+ end
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links sources' do
+ expect(subject).to include(link('https://github.com/artsy/Specs.git', 'https://github.com/artsy/Specs.git'))
+ expect(subject).to include(link('https://github.com/CocoaPods/Specs.git', 'https://github.com/CocoaPods/Specs.git'))
+ end
+
+ it 'links packages' do
+ expect(subject).to include(link('AFNetworking', 'https://cocoapods.org/pods/AFNetworking'))
+ expect(subject).to include(link('Interstellar/Core', 'https://cocoapods.org/pods/Interstellar'))
+ end
+
+ it 'links Git repos' do
+ expect(subject).to include(link('https://github.com/ashfurrow/Interstellar.git', 'https://github.com/ashfurrow/Interstellar.git'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
new file mode 100644
index 00000000000..d722865264b
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
@@ -0,0 +1,96 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::PodspecJsonLinker, lib: true do
+ describe '.support?' do
+ it 'supports *.podspec.json' do
+ expect(described_class.support?('Reachability.podspec.json')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('.podspec.json.example')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { "AFNetworking.podspec.json" }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ {
+ "name": "AFNetworking",
+ "version": "2.0.0",
+ "license": "MIT",
+ "summary": "A delightful iOS and OS X networking framework.",
+ "homepage": "https://github.com/AFNetworking/AFNetworking",
+ "authors": {
+ "Mattt Thompson": "m@mattt.me"
+ },
+ "source": {
+ "git": "https://github.com/AFNetworking/AFNetworking.git",
+ "tag": "2.0.0",
+ "submodules": true
+ },
+ "requires_arc": true,
+ "platforms": {
+ "ios": "6.0",
+ "osx": "10.8"
+ },
+ "public_header_files": "AFNetworking/*.h",
+ "subspecs": [
+ {
+ "name": "NSURLConnection",
+ "dependencies": {
+ "AFNetworking/Serialization": [
+
+ ],
+ "AFNetworking/Reachability": [
+
+ ],
+ "AFNetworking/Security": [
+
+ ]
+ },
+ "source_files": [
+ "AFNetworking/AFURLConnectionOperation.{h,m}",
+ "AFNetworking/AFHTTPRequestOperation.{h,m}",
+ "AFNetworking/AFHTTPRequestOperationManager.{h,m}"
+ ]
+ }
+ ]
+ }
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links the gem name' do
+ expect(subject).to include(link('AFNetworking', 'https://cocoapods.org/pods/AFNetworking'))
+ end
+
+ it 'links the license' do
+ expect(subject).to include(link('MIT', 'http://choosealicense.com/licenses/mit/'))
+ end
+
+ it 'links the homepage' do
+ expect(subject).to include(link('https://github.com/AFNetworking/AFNetworking', 'https://github.com/AFNetworking/AFNetworking'))
+ end
+
+ it 'links the source URL' do
+ expect(subject).to include(link('https://github.com/AFNetworking/AFNetworking.git', 'https://github.com/AFNetworking/AFNetworking.git'))
+ end
+
+ it 'links dependencies' do
+ expect(subject).to include(link('AFNetworking/Serialization', 'https://cocoapods.org/pods/AFNetworking'))
+ expect(subject).to include(link('AFNetworking/Reachability', 'https://cocoapods.org/pods/AFNetworking'))
+ expect(subject).to include(link('AFNetworking/Security', 'https://cocoapods.org/pods/AFNetworking'))
+ end
+
+ it 'does not link subspec names' do
+ expect(subject).not_to include(link('NSURLConnection', 'https://cocoapods.org/pods/NSURLConnection'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
new file mode 100644
index 00000000000..dfc366b5817
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
@@ -0,0 +1,69 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::PodspecLinker, lib: true do
+ describe '.support?' do
+ it 'supports *.podspec' do
+ expect(described_class.support?('Reachability.podspec')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('.podspec.example')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { "Reachability.podspec" }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ Pod::Spec.new do |spec|
+ spec.name = 'Reachability'
+ spec.version = '3.1.0'
+ spec.license = { :type => 'GPL-3.0' }
+ spec.license = "MIT"
+ spec.license = { type: 'Apache-2.0' }
+ spec.homepage = 'https://github.com/tonymillion/Reachability'
+ spec.authors = { 'Tony Million' => 'tonymillion@gmail.com' }
+ spec.summary = 'ARC and GCD Compatible Reachability Class for iOS and OS X.'
+ spec.source = { :git => 'https://github.com/tonymillion/Reachability.git', :tag => 'v3.1.0' }
+ spec.source_files = 'Reachability.{h,m}'
+ spec.framework = 'SystemConfiguration'
+
+ spec.dependency 'AFNetworking', '~> 1.0'
+ spec.dependency 'RestKit/CoreData', '~> 0.20.0'
+ spec.ios.dependency 'MBProgressHUD', '~> 0.5'
+ end
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links the gem name' do
+ expect(subject).to include(link('Reachability', 'https://cocoapods.org/pods/Reachability'))
+ end
+
+ it 'links the license' do
+ expect(subject).to include(link('GPL-3.0', 'http://choosealicense.com/licenses/gpl-3.0/'))
+ expect(subject).to include(link('MIT', 'http://choosealicense.com/licenses/mit/'))
+ expect(subject).to include(link('Apache-2.0', 'http://choosealicense.com/licenses/apache-2.0/'))
+ end
+
+ it 'links the homepage' do
+ expect(subject).to include(link('https://github.com/tonymillion/Reachability', 'https://github.com/tonymillion/Reachability'))
+ end
+
+ it 'links the source URL' do
+ expect(subject).to include(link('https://github.com/tonymillion/Reachability.git', 'https://github.com/tonymillion/Reachability.git'))
+ end
+
+ it 'links dependencies' do
+ expect(subject).to include(link('AFNetworking', 'https://cocoapods.org/pods/AFNetworking'))
+ expect(subject).to include(link('RestKit/CoreData', 'https://cocoapods.org/pods/RestKit'))
+ expect(subject).to include(link('MBProgressHUD', 'https://cocoapods.org/pods/MBProgressHUD'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
new file mode 100644
index 00000000000..4da8821726c
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
@@ -0,0 +1,87 @@
+require 'rails_helper'
+
+describe Gitlab::DependencyLinker::RequirementsTxtLinker, lib: true do
+ describe '.support?' do
+ it 'supports requirements.txt' do
+ expect(described_class.support?('requirements.txt')).to be_truthy
+ end
+
+ it 'supports doc-requirements.txt' do
+ expect(described_class.support?('doc-requirements.txt')).to be_truthy
+ end
+
+ it 'does not support other files' do
+ expect(described_class.support?('requirements')).to be_falsey
+ end
+ end
+
+ describe '#link' do
+ let(:file_name) { "requirements.txt" }
+
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ #
+ ####### example-requirements.txt #######
+ #
+ ###### Requirements without Version Specifiers ######
+ nose
+ nose-cov
+ beautifulsoup4
+ #
+ ###### Requirements with Version Specifiers ######
+ # See https://www.python.org/dev/peps/pep-0440/#version-specifiers
+ docopt == 0.6.1 # Version Matching. Must be version 0.6.1
+ keyring >= 4.1.1 # Minimum version 4.1.1
+ coverage != 3.5 # Version Exclusion. Anything except version 3.5
+ Mopidy-Dirble ~= 1.1 # Compatible release. Same as >= 1.1, == 1.*
+ #
+ ###### Refer to other requirements files ######
+ -r other-requirements.txt
+ #
+ #
+ ###### A particular file ######
+ ./downloads/numpy-1.9.2-cp34-none-win32.whl
+ http://wxpython.org/Phoenix/snapshot-builds/wxPython_Phoenix-3.0.3.dev1820+49a8884-cp34-none-win_amd64.whl
+ #
+ ###### Additional Requirements without Version Specifiers ######
+ # Same as 1st section, just here to show that you can put things in any order.
+ rejected
+ green
+ #
+
+ Jinja2>=2.3
+ Pygments>=1.2
+ Sphinx>=1.3
+ docutils>=0.7
+ markupsafe
+ CONTENT
+ end
+
+ subject { Gitlab::Highlight.highlight(file_name, file_content) }
+
+ def link(name, url)
+ %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
+ end
+
+ it 'links dependencies' do
+ expect(subject).to include(link('nose', 'https://pypi.python.org/pypi/nose'))
+ expect(subject).to include(link('nose-cov', 'https://pypi.python.org/pypi/nose-cov'))
+ expect(subject).to include(link('beautifulsoup4', 'https://pypi.python.org/pypi/beautifulsoup4'))
+ expect(subject).to include(link('docopt', 'https://pypi.python.org/pypi/docopt'))
+ expect(subject).to include(link('keyring', 'https://pypi.python.org/pypi/keyring'))
+ expect(subject).to include(link('coverage', 'https://pypi.python.org/pypi/coverage'))
+ expect(subject).to include(link('Mopidy-Dirble', 'https://pypi.python.org/pypi/Mopidy-Dirble'))
+ expect(subject).to include(link('rejected', 'https://pypi.python.org/pypi/rejected'))
+ expect(subject).to include(link('green', 'https://pypi.python.org/pypi/green'))
+ expect(subject).to include(link('Jinja2', 'https://pypi.python.org/pypi/Jinja2'))
+ expect(subject).to include(link('Pygments', 'https://pypi.python.org/pypi/Pygments'))
+ expect(subject).to include(link('Sphinx', 'https://pypi.python.org/pypi/Sphinx'))
+ expect(subject).to include(link('docutils', 'https://pypi.python.org/pypi/docutils'))
+ expect(subject).to include(link('markupsafe', 'https://pypi.python.org/pypi/markupsafe'))
+ end
+
+ it 'links URLs' do
+ expect(subject).to include(link('http://wxpython.org/Phoenix/snapshot-builds/wxPython_Phoenix-3.0.3.dev1820+49a8884-cp34-none-win_amd64.whl', 'http://wxpython.org/Phoenix/snapshot-builds/wxPython_Phoenix-3.0.3.dev1820+49a8884-cp34-none-win_amd64.whl'))
+ end
+ end
+end
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
index 03d5b61d70c..3d1cfbcfbf7 100644
--- a/spec/lib/gitlab/dependency_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -9,5 +9,77 @@ describe Gitlab::DependencyLinker, lib: true do
described_class.link(blob_name, nil, nil)
end
+
+ it 'links using GemspecLinker' do
+ blob_name = 'gitlab_git.gemspec'
+
+ expect(described_class::GemspecLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
+
+ it 'links using PackageJsonLinker' do
+ blob_name = 'package.json'
+
+ expect(described_class::PackageJsonLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
+
+ it 'links using ComposerJsonLinker' do
+ blob_name = 'composer.json'
+
+ expect(described_class::ComposerJsonLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
+
+ it 'links using PodfileLinker' do
+ blob_name = 'Podfile'
+
+ expect(described_class::PodfileLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
+
+ it 'links using PodspecLinker' do
+ blob_name = 'Reachability.podspec'
+
+ expect(described_class::PodspecLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
+
+ it 'links using PodspecJsonLinker' do
+ blob_name = 'AFNetworking.podspec.json'
+
+ expect(described_class::PodspecJsonLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
+
+ it 'links using CartfileLinker' do
+ blob_name = 'Cartfile'
+
+ expect(described_class::CartfileLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
+
+ it 'links using GodepsJsonLinker' do
+ blob_name = 'Godeps.json'
+
+ expect(described_class::GodepsJsonLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
+
+ it 'links using RequirementsTxtLinker' do
+ blob_name = 'requirements.txt'
+
+ expect(described_class::RequirementsTxtLinker).to receive(:link)
+
+ described_class.link(blob_name, nil, nil)
+ end
end
end
diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb
index cdf0af6d7ef..7095104d75c 100644
--- a/spec/lib/gitlab/diff/position_spec.rb
+++ b/spec/lib/gitlab/diff/position_spec.rb
@@ -22,7 +22,7 @@ describe Gitlab::Diff::Position, lib: true do
it "returns the correct diff file" do
diff_file = subject.diff_file(project.repository)
- expect(diff_file.new_file).to be true
+ expect(diff_file.new_file?).to be true
expect(diff_file.new_path).to eq(subject.new_path)
expect(diff_file.diff_refs).to eq(subject.diff_refs)
end
@@ -314,7 +314,7 @@ describe Gitlab::Diff::Position, lib: true do
it "returns the correct diff file" do
diff_file = subject.diff_file(project.repository)
- expect(diff_file.deleted_file).to be true
+ expect(diff_file.deleted_file?).to be true
expect(diff_file.old_path).to eq(subject.old_path)
expect(diff_file.diff_refs).to eq(subject.diff_refs)
end
@@ -356,7 +356,7 @@ describe Gitlab::Diff::Position, lib: true do
it "returns the correct diff file" do
diff_file = subject.diff_file(project.repository)
- expect(diff_file.new_file).to be true
+ expect(diff_file.new_file?).to be true
expect(diff_file.new_path).to eq(subject.new_path)
expect(diff_file.diff_refs).to eq(subject.diff_refs)
end
diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb
index 4d202a76e1b..93d30b90937 100644
--- a/spec/lib/gitlab/diff/position_tracer_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer_spec.rb
@@ -61,9 +61,10 @@ describe Gitlab::Diff::PositionTracer, lib: true do
let(:old_diff_refs) { raise NotImplementedError }
let(:new_diff_refs) { raise NotImplementedError }
+ let(:change_diff_refs) { raise NotImplementedError }
let(:old_position) { raise NotImplementedError }
- let(:position_tracer) { described_class.new(repository: project.repository, old_diff_refs: old_diff_refs, new_diff_refs: new_diff_refs) }
+ let(:position_tracer) { described_class.new(project: project, old_diff_refs: old_diff_refs, new_diff_refs: new_diff_refs) }
subject { position_tracer.trace(old_position) }
def diff_refs(base_commit, head_commit)
@@ -77,16 +78,40 @@ describe Gitlab::Diff::PositionTracer, lib: true do
Gitlab::Diff::Position.new(attrs)
end
- def expect_new_position(attrs, new_position = subject)
- if attrs.nil?
- expect(new_position).to be_nil
- else
- expect(new_position).not_to be_nil
+ def expect_new_position(attrs, result = subject)
+ aggregate_failures("expect new position #{attrs.inspect}") do
+ if attrs.nil?
+ expect(result[:outdated]).to be_truthy
+ else
+ expect(result[:outdated]).to be_falsey
- expect(new_position.diff_refs).to eq(new_diff_refs)
+ new_position = result[:position]
+ expect(new_position).not_to be_nil
- attrs.each do |attr, value|
- expect(new_position.send(attr)).to eq(value)
+ expect(new_position.diff_refs).to eq(new_diff_refs)
+
+ attrs.each do |attr, value|
+ expect(new_position.send(attr)).to eq(value)
+ end
+ end
+ end
+ end
+
+ def expect_change_position(attrs, result = subject)
+ aggregate_failures("expect change position #{attrs.inspect}") do
+ expect(result[:outdated]).to be_truthy
+
+ change_position = result[:position]
+ if attrs.nil? || attrs.empty?
+ expect(change_position).to be_nil
+ else
+ expect(change_position).not_to be_nil
+
+ expect(change_position.diff_refs).to eq(change_diff_refs)
+
+ attrs.each do |attr, value|
+ expect(change_position.send(attr)).to eq(value)
+ end
end
end
end
@@ -395,6 +420,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when that line was changed between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -407,14 +433,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 + BB
# 3 + C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
end
end
context "when that line was deleted between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
let(:new_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
+ let(:change_diff_refs) { diff_refs(update_line_commit, delete_line_commit) }
let(:old_position) { position(new_path: file_name, new_line: 3) }
# old diff:
@@ -426,8 +458,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 1 + A
# 2 + BB
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 3,
+ new_line: nil
+ )
end
end
end
@@ -512,6 +549,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when that line was changed between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -525,14 +563,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 + BB
# 3 3 C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
end
end
context "when that line was deleted between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) }
let(:new_diff_refs) { diff_refs(move_line_commit, delete_line_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, delete_line_commit) }
let(:old_position) { position(new_path: file_name, new_line: 3) }
# old diff:
@@ -545,8 +589,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 2 A
# 3 - C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 3,
+ new_line: nil
+ )
end
end
end
@@ -558,6 +607,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when the file's content was unchanged between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
let(:new_diff_refs) { diff_refs(delete_line_commit, rename_file_commit) }
+ let(:change_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -569,8 +619,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 1 1 BB
# 2 2 A
- it "returns nil since the line doesn't exist in the new diffs anymore" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: nil,
+ new_line: 2
+ )
end
end
@@ -628,6 +683,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when that line was changed between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
let(:new_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) }
+ let(:change_diff_refs) { diff_refs(delete_line_commit, update_line_again_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -640,28 +696,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 - A
# 2 + AA
- it "returns nil" do
- expect(subject).to be_nil
- end
- end
-
- context "when that line was deleted between the old and the new diff" do
- let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
- let(:new_diff_refs) { diff_refs(delete_line_commit, delete_line_again_commit) }
- let(:old_position) { position(new_path: file_name, new_line: 1) }
-
- # old diff:
- # 1 + BB
- # 2 + A
- #
- # new diff:
- # file_name -> new_file_name
- # 1 - BB
- # 2 - A
- # 1 + AA
-
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: new_file_name,
+ old_line: 2,
+ new_line: nil
+ )
end
end
end
@@ -673,6 +714,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when the file's content was unchanged between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -683,8 +725,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 1 - BB
# 2 - A
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
end
end
@@ -692,6 +739,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when that line was unchanged between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) }
let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, delete_file_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -703,14 +751,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 1 - BB
# 2 - A
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
end
end
context "when that line was moved between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
let(:new_diff_refs) { diff_refs(move_line_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(update_line_commit, delete_file_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -723,14 +777,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 - A
# 3 - C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
end
end
context "when that line was changed between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
let(:new_diff_refs) { diff_refs(update_line_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, delete_file_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -743,14 +803,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 - BB
# 3 - C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
end
end
context "when that line was deleted between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) }
let(:new_diff_refs) { diff_refs(delete_line_commit, delete_file_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, delete_file_commit) }
let(:old_position) { position(new_path: file_name, new_line: 3) }
# old diff:
@@ -762,8 +828,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 1 - BB
# 2 - A
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 3,
+ new_line: nil
+ )
end
end
end
@@ -775,6 +846,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when the file's content was unchanged between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
let(:new_diff_refs) { diff_refs(create_file_commit, create_second_file_commit) }
+ let(:change_diff_refs) { diff_refs(initial_commit, create_file_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -787,8 +859,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 2 B
# 3 3 C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: nil,
+ new_line: 2
+ )
end
end
@@ -796,6 +873,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when that line was unchanged between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
let(:new_diff_refs) { diff_refs(update_line_commit, update_second_file_line_commit) }
+ let(:change_diff_refs) { diff_refs(initial_commit, update_line_commit) }
let(:old_position) { position(new_path: file_name, new_line: 1) }
# old diff:
@@ -808,14 +886,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 2 BB
# 3 3 C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: nil,
+ new_line: 1
+ )
end
end
context "when that line was moved between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, update_line_commit) }
let(:new_diff_refs) { diff_refs(move_line_commit, move_second_file_line_commit) }
+ let(:change_diff_refs) { diff_refs(initial_commit, move_line_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -828,14 +912,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 2 A
# 3 3 C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: nil,
+ new_line: 1
+ )
end
end
context "when that line was changed between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, create_file_commit) }
let(:new_diff_refs) { diff_refs(update_line_commit, update_second_file_line_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, update_second_file_line_commit) }
let(:old_position) { position(new_path: file_name, new_line: 2) }
# old diff:
@@ -848,14 +938,20 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 2 BB
# 3 3 C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
end
end
context "when that line was deleted between the old and the new diff" do
let(:old_diff_refs) { diff_refs(initial_commit, move_line_commit) }
let(:new_diff_refs) { diff_refs(delete_line_commit, delete_second_file_line_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, delete_second_file_line_commit) }
let(:old_position) { position(new_path: file_name, new_line: 3) }
# old diff:
@@ -867,8 +963,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 1 1 BB
# 2 2 A
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 3,
+ new_line: nil
+ )
end
end
end
@@ -957,6 +1058,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when that line was changed or deleted between the old and the new diff" do
let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) }
let(:new_diff_refs) { diff_refs(initial_commit, create_file_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, create_file_commit) }
let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) }
# old diff:
@@ -970,8 +1072,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 + B
# 3 + C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 1,
+ new_line: nil
+ )
end
end
end
@@ -980,6 +1087,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when the position pointed at a deleted line in the old diff" do
let(:old_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
let(:new_diff_refs) { diff_refs(initial_commit, update_line_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, initial_commit) }
let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 2) }
# old diff:
@@ -993,8 +1101,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 + BB
# 3 + C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 2,
+ new_line: nil
+ )
end
end
@@ -1076,6 +1189,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when that line was changed or deleted between the old and the new diff" do
let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) }
let(:new_diff_refs) { diff_refs(initial_commit, delete_line_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, delete_line_commit) }
let(:old_position) { position(old_path: file_name, new_path: file_name, old_line: 3, new_line: 3) }
# old diff:
@@ -1088,8 +1202,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 1 + A
# 2 + B
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 3,
+ new_line: nil
+ )
end
end
end
@@ -1182,6 +1301,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
context "when that line was changed or deleted between the old and the new diff" do
let(:old_diff_refs) { diff_refs(create_file_commit, move_line_commit) }
let(:new_diff_refs) { diff_refs(create_file_commit, update_line_commit) }
+ let(:change_diff_refs) { diff_refs(move_line_commit, update_line_commit) }
let(:old_position) { position(old_path: file_name, new_path: file_name, new_line: 1) }
# old diff:
@@ -1196,8 +1316,13 @@ describe Gitlab::Diff::PositionTracer, lib: true do
# 2 + BB
# 3 3 C
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns the position of the change" do
+ expect_change_position(
+ old_path: file_name,
+ new_path: file_name,
+ old_line: 1,
+ new_line: nil
+ )
end
end
end
@@ -1239,7 +1364,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
describe "typical use scenarios" do
let(:second_branch_name) { "#{branch_name}-2" }
- def expect_positions(old_attrs, new_attrs)
+ def expect_new_positions(old_attrs, new_attrs)
old_positions = old_attrs.map do |old_attrs|
position(old_attrs)
end
@@ -1248,8 +1373,14 @@ describe Gitlab::Diff::PositionTracer, lib: true do
position_tracer.trace(old_position)
end
- new_positions.zip(new_attrs).each do |new_position, new_attrs|
- expect_new_position(new_attrs, new_position)
+ aggregate_failures do
+ new_positions.zip(new_attrs).each do |new_position, new_attrs|
+ if new_attrs&.delete(:change)
+ expect_change_position(new_attrs, new_position)
+ else
+ expect_new_position(new_attrs, new_position)
+ end
+ end
end
end
@@ -1330,6 +1461,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
describe "simple push of new commit" do
let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
let(:new_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
+ let(:change_diff_refs) { diff_refs(update_file_commit, update_file_again_commit) }
# old diff:
# 1 1 A
@@ -1368,14 +1500,14 @@ describe Gitlab::Diff::PositionTracer, lib: true do
{ old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 },
{ old_path: file_name, old_line: 2 },
{ old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 },
- { old_path: file_name, old_line: 4, new_line: 4 },
- nil,
+ { new_path: file_name, new_line: 4, change: true },
+ { new_path: file_name, old_line: 3, change: true },
{ old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 },
- { old_path: file_name, old_line: 6 },
+ { new_path: file_name, old_line: 5, change: true },
{ new_path: file_name, new_line: 7 }
]
- expect_positions(old_position_attrs, new_position_attrs)
+ expect_new_positions(old_position_attrs, new_position_attrs)
end
end
@@ -1402,6 +1534,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
let(:old_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
let(:new_diff_refs) { diff_refs(create_file_commit, second_create_file_commit) }
+ let(:change_diff_refs) { diff_refs(update_file_commit, second_create_file_commit) }
# old diff:
# 1 1 A
@@ -1440,20 +1573,21 @@ describe Gitlab::Diff::PositionTracer, lib: true do
{ old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 },
{ old_path: file_name, old_line: 2 },
{ old_path: file_name, new_path: file_name, old_line: 3, new_line: 3 },
- { old_path: file_name, old_line: 4, new_line: 4 },
- nil,
+ { new_path: file_name, new_line: 4, change: true },
+ { old_path: file_name, old_line: 3, change: true },
{ old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 },
- { old_path: file_name, old_line: 6 },
+ { old_path: file_name, old_line: 5, change: true },
{ new_path: file_name, new_line: 7 }
]
- expect_positions(old_position_attrs, new_position_attrs)
+ expect_new_positions(old_position_attrs, new_position_attrs)
end
end
describe "force push to delete last commit" do
let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
let(:new_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
+ let(:change_diff_refs) { diff_refs(update_file_again_commit, update_file_commit) }
# old diff:
# 1 1 A
@@ -1492,16 +1626,16 @@ describe Gitlab::Diff::PositionTracer, lib: true do
new_position_attrs = [
{ old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 },
{ old_path: file_name, old_line: 2 },
- nil,
+ { old_path: file_name, old_line: 2, change: true },
{ old_path: file_name, new_path: file_name, old_line: 3, new_line: 2 },
- { old_path: file_name, old_line: 4 },
+ { old_path: file_name, old_line: 4, change: true },
{ old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 },
- { old_path: file_name, new_path: file_name, old_line: 6, new_line: 5 },
- nil,
+ { new_path: file_name, new_line: 5, change: true },
+ { old_path: file_name, old_line: 6, change: true },
{ new_path: file_name, new_line: 6 }
]
- expect_positions(old_position_attrs, new_position_attrs)
+ expect_new_positions(old_position_attrs, new_position_attrs)
end
end
@@ -1567,6 +1701,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
let(:new_diff_refs) { diff_refs(create_file_commit, overwrite_update_file_again_commit) }
+ let(:change_diff_refs) { diff_refs(update_file_again_commit, overwrite_update_file_again_commit) }
# old diff:
# 1 1 A
@@ -1618,7 +1753,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
{ new_path: file_name, new_line: 10 }, # + G
]
- expect_positions(old_position_attrs, new_position_attrs)
+ expect_new_positions(old_position_attrs, new_position_attrs)
end
end
@@ -1643,6 +1778,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
let(:new_diff_refs) { diff_refs(create_file_commit, merge_commit) }
+ let(:change_diff_refs) { diff_refs(update_file_again_commit, merge_commit) }
# old diff:
# 1 1 A
@@ -1694,13 +1830,14 @@ describe Gitlab::Diff::PositionTracer, lib: true do
{ new_path: file_name, new_line: 10 }, # + G
]
- expect_positions(old_position_attrs, new_position_attrs)
+ expect_new_positions(old_position_attrs, new_position_attrs)
end
end
describe "changing target branch" do
let(:old_diff_refs) { diff_refs(create_file_commit, update_file_again_commit) }
let(:new_diff_refs) { diff_refs(update_file_commit, update_file_again_commit) }
+ let(:change_diff_refs) { diff_refs(create_file_commit, update_file_commit) }
# old diff:
# 1 1 A
@@ -1739,7 +1876,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
new_position_attrs = [
{ old_path: file_name, new_path: file_name, old_line: 1, new_line: 1 },
- nil,
+ { old_path: file_name, old_line: 2, change: true },
{ new_path: file_name, new_line: 2 },
{ old_path: file_name, new_path: file_name, old_line: 2, new_line: 3 },
{ new_path: file_name, new_line: 4 },
@@ -1749,7 +1886,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do
{ new_path: file_name, new_line: 7 }
]
- expect_positions(old_position_attrs, new_position_attrs)
+ expect_new_positions(old_position_attrs, new_position_attrs)
end
end
end
diff --git a/spec/lib/gitlab/etag_caching/router_spec.rb b/spec/lib/gitlab/etag_caching/router_spec.rb
index 5ae4a19263c..46a238b17f4 100644
--- a/spec/lib/gitlab/etag_caching/router_spec.rb
+++ b/spec/lib/gitlab/etag_caching/router_spec.rb
@@ -77,6 +77,17 @@ describe Gitlab::EtagCaching::Router do
expect(result).to be_blank
end
+ it 'matches pipeline#show endpoint' do
+ env = build_env(
+ '/my-group/my-project/pipelines/2.json'
+ )
+
+ result = described_class.match(env)
+
+ expect(result).to be_present
+ expect(result.name).to eq 'project_pipeline'
+ end
+
def build_env(path)
{ 'PATH_INFO' => path }
end
diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb
index 122c93dcd69..ae617b313c5 100644
--- a/spec/lib/gitlab/git/diff_collection_spec.rb
+++ b/spec/lib/gitlab/git/diff_collection_spec.rb
@@ -10,7 +10,7 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
no_collapse: no_collapse
)
end
- let(:iterator) { Array.new(file_count, fake_diff(line_length, line_count)) }
+ let(:iterator) { MutatingConstantIterator.new(file_count, fake_diff(line_length, line_count)) }
let(:file_count) { 0 }
let(:line_length) { 1 }
let(:line_count) { 1 }
@@ -64,7 +64,15 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
subject { super().real_size }
it { is_expected.to eq('3') }
end
- it { expect(subject.size).to eq(3) }
+
+ describe '#size' do
+ it { expect(subject.size).to eq(3) }
+
+ it 'does not change after peeking' do
+ subject.any?
+ expect(subject.size).to eq(3)
+ end
+ end
context 'when limiting is disabled' do
let(:all_diffs) { true }
@@ -83,7 +91,15 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
subject { super().real_size }
it { is_expected.to eq('3') }
end
- it { expect(subject.size).to eq(3) }
+
+ describe '#size' do
+ it { expect(subject.size).to eq(3) }
+
+ it 'does not change after peeking' do
+ subject.any?
+ expect(subject.size).to eq(3)
+ end
+ end
end
end
@@ -457,4 +473,22 @@ describe Gitlab::Git::DiffCollection, seed_helper: true do
def fake_diff(line_length, line_count)
{ 'diff' => "#{'a' * line_length}\n" * line_count }
end
+
+ class MutatingConstantIterator
+ include Enumerable
+
+ def initialize(count, value)
+ @count = count
+ @value = value
+ end
+
+ def each
+ loop do
+ break if @count.zero?
+ # It is critical to decrement before yielding. We may never reach the lines after 'yield'.
+ @count -= 1
+ yield @value
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
index 45ccd3d6459..61c10d47434 100644
--- a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb
@@ -1,6 +1,24 @@
require 'spec_helper'
describe Gitlab::HealthChecks::FsShardsCheck do
+ def command_exists?(command)
+ _, status = Gitlab::Popen.popen(%W{ #{command} 1 echo })
+ status == 0
+ rescue Errno::ENOENT
+ false
+ end
+
+ def timeout_command
+ @timeout_command ||=
+ if command_exists?('timeout')
+ 'timeout'
+ elsif command_exists?('gtimeout')
+ 'gtimeout'
+ else
+ ''
+ end
+ end
+
let(:metric_class) { Gitlab::HealthChecks::Metric }
let(:result_class) { Gitlab::HealthChecks::Result }
let(:repository_storages) { [:default] }
@@ -15,6 +33,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do
before do
allow(described_class).to receive(:repository_storages) { repository_storages }
allow(described_class).to receive(:storages_paths) { storages_paths }
+ stub_const('Gitlab::HealthChecks::FsShardsCheck::TIMEOUT_EXECUTABLE', timeout_command)
end
after do
@@ -78,40 +97,76 @@ describe Gitlab::HealthChecks::FsShardsCheck do
}.with_indifferent_access
end
- it { is_expected.to include(metric_class.new(:filesystem_accessible, 0, shard: :default)) }
- it { is_expected.to include(metric_class.new(:filesystem_readable, 0, shard: :default)) }
- it { is_expected.to include(metric_class.new(:filesystem_writable, 0, shard: :default)) }
+ it { is_expected.to all(have_attributes(labels: { shard: :default })) }
+
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_accessible, value: 0)) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_readable, value: 0)) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_writable, value: 0)) }
- it { is_expected.to include(have_attributes(name: :filesystem_access_latency, value: be >= 0, labels: { shard: :default })) }
- it { is_expected.to include(have_attributes(name: :filesystem_read_latency, value: be >= 0, labels: { shard: :default })) }
- it { is_expected.to include(have_attributes(name: :filesystem_write_latency, value: be >= 0, labels: { shard: :default })) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) }
end
context 'storage points to directory that has both read and write rights' do
before do
FileUtils.chmod_R(0755, tmp_dir)
end
+ it { is_expected.to all(have_attributes(labels: { shard: :default })) }
- it { is_expected.to include(metric_class.new(:filesystem_accessible, 1, shard: :default)) }
- it { is_expected.to include(metric_class.new(:filesystem_readable, 1, shard: :default)) }
- it { is_expected.to include(metric_class.new(:filesystem_writable, 1, shard: :default)) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_accessible, value: 1)) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_readable, value: 1)) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_writable, value: 1)) }
- it { is_expected.to include(have_attributes(name: :filesystem_access_latency, value: be >= 0, labels: { shard: :default })) }
- it { is_expected.to include(have_attributes(name: :filesystem_read_latency, value: be >= 0, labels: { shard: :default })) }
- it { is_expected.to include(have_attributes(name: :filesystem_write_latency, value: be >= 0, labels: { shard: :default })) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) }
+ it { is_expected.to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) }
+ end
+ end
+ end
+
+ context 'when timeout kills fs checks' do
+ before do
+ stub_const('Gitlab::HealthChecks::FsShardsCheck::COMMAND_TIMEOUT', '1')
+
+ allow(described_class).to receive(:exec_with_timeout).and_wrap_original { |m| m.call(%w(sleep 60)) }
+ FileUtils.chmod_R(0755, tmp_dir)
+ end
+
+ describe '#readiness' do
+ subject { described_class.readiness }
+
+ it { is_expected.to include(result_class.new(false, 'cannot stat storage', shard: :default)) }
+ end
+
+ describe '#metrics' do
+ subject { described_class.metrics }
+
+ it 'provides metrics' do
+ expect(subject).to all(have_attributes(labels: { shard: :default }))
+
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_accessible, value: 0))
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 0))
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 0))
+
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0))
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0))
+ expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0))
end
end
end
context 'when popen always finds required binaries' do
before do
- allow(Gitlab::Popen).to receive(:popen).and_wrap_original do |method, *args, &block|
+ allow(described_class).to receive(:exec_with_timeout).and_wrap_original do |method, *args, &block|
begin
method.call(*args, &block)
- rescue RuntimeError
+ rescue RuntimeError, Errno::ENOENT
raise 'expected not to happen'
end
end
+
+ stub_const('Gitlab::HealthChecks::FsShardsCheck::COMMAND_TIMEOUT', '10')
end
it_behaves_like 'filesystem checks'
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index e57b3053871..a20cef3b000 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -59,8 +59,6 @@ describe Gitlab::Highlight, lib: true do
end
describe '#highlight' do
- subject { described_class.highlight(file_name, file_content, nowrap: false) }
-
it 'links dependencies via DependencyLinker' do
expect(Gitlab::DependencyLinker).to receive(:link).
with('file.name', 'Contents', anything).and_call_original
diff --git a/spec/lib/gitlab/i18n_spec.rb b/spec/lib/gitlab/i18n_spec.rb
index 52f2614d5ca..a3dbeaa3753 100644
--- a/spec/lib/gitlab/i18n_spec.rb
+++ b/spec/lib/gitlab/i18n_spec.rb
@@ -1,27 +1,27 @@
require 'spec_helper'
-module Gitlab
- describe I18n, lib: true do
- let(:user) { create(:user, preferred_language: 'es') }
+describe Gitlab::I18n, lib: true do
+ let(:user) { create(:user, preferred_language: 'es') }
- describe '.set_locale' do
- it 'sets the locale based on current user preferred language' do
- Gitlab::I18n.set_locale(user)
+ describe '.locale=' do
+ after { described_class.use_default_locale }
- expect(FastGettext.locale).to eq('es')
- expect(::I18n.locale).to eq(:es)
- end
+ it 'sets the locale based on current user preferred language' do
+ described_class.locale = user.preferred_language
+
+ expect(FastGettext.locale).to eq('es')
+ expect(::I18n.locale).to eq(:es)
end
+ end
- describe '.reset_locale' do
- it 'resets the locale to the default language' do
- Gitlab::I18n.set_locale(user)
+ describe '.use_default_locale' do
+ it 'resets the locale to the default language' do
+ described_class.locale = user.preferred_language
- Gitlab::I18n.reset_locale
+ described_class.use_default_locale
- expect(FastGettext.locale).to eq('en')
- expect(::I18n.locale).to eq(:en)
- end
+ expect(FastGettext.locale).to eq('en')
+ expect(::I18n.locale).to eq(:en)
end
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 34f617e23a5..2e9646286df 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -131,6 +131,7 @@ services:
- service_hook
hooks:
- project
+- web_hook_logs
protected_branches:
- project
- merge_access_levels
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index c22fba11225..96054c996fd 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -54,6 +54,7 @@ Note:
- type
- position
- original_position
+- change_position
- resolved_at
- resolved_by_id
- discussion_id
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
new file mode 100644
index 00000000000..1eea710c80b
--- /dev/null
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -0,0 +1,384 @@
+# coding: utf-8
+require 'spec_helper'
+
+describe Gitlab::PathRegex, lib: true do
+ # Pass in a full path to remove the format segment:
+ # `/ci/lint(.:format)` -> `/ci/lint`
+ def without_format(path)
+ path.split('(', 2)[0]
+ end
+
+ # Pass in a full path and get the last segment before a wildcard
+ # That's not a parameter
+ # `/*namespace_id/:project_id/builds/artifacts/*ref_name_and_path`
+ # -> 'builds/artifacts'
+ def path_before_wildcard(path)
+ path = path.gsub(STARTING_WITH_NAMESPACE, "")
+ path_segments = path.split('/').reject(&:empty?)
+ wildcard_index = path_segments.index { |segment| parameter?(segment) }
+
+ segments_before_wildcard = path_segments[0..wildcard_index - 1]
+
+ segments_before_wildcard.join('/')
+ end
+
+ def parameter?(segment)
+ segment =~ /[*:]/
+ end
+
+ # If the path is reserved. Then no conflicting paths can# be created for any
+ # route using this reserved word.
+ #
+ # Both `builds/artifacts` & `build` are covered by reserving the word
+ # `build`
+ def wildcards_include?(path)
+ described_class::PROJECT_WILDCARD_ROUTES.include?(path) ||
+ described_class::PROJECT_WILDCARD_ROUTES.include?(path.split('/').first)
+ end
+
+ def failure_message(missing_words, constant_name, migration_helper)
+ missing_words = Array(missing_words)
+ <<-MSG
+ Found new routes that could cause conflicts with existing namespaced routes
+ for groups or projects.
+
+ Add <#{missing_words.join(', ')}> to `Gitlab::PathRegex::#{constant_name}
+ to make sure no projects or namespaces can be created with those paths.
+
+ To rename any existing records with those paths you can use the
+ `Gitlab::Database::RenameReservedpathsMigration::<VERSION>.#{migration_helper}`
+ migration helper.
+
+ Make sure to make a note of the renamed records in the release blog post.
+
+ MSG
+ end
+
+ let(:all_routes) do
+ route_set = Rails.application.routes
+ routes_collection = route_set.routes
+ routes_array = routes_collection.routes
+ routes_array.map { |route| route.path.spec.to_s }
+ end
+
+ let(:routes_without_format) { all_routes.map { |path| without_format(path) } }
+
+ # Routes not starting with `/:` or `/*`
+ # all routes not starting with a param
+ let(:routes_not_starting_in_wildcard) { routes_without_format.select { |p| p !~ %r{^/[:*]} } }
+
+ let(:top_level_words) do
+ routes_not_starting_in_wildcard.map do |route|
+ route.split('/')[1]
+ end.compact.uniq
+ end
+
+ # All routes that start with a namespaced path, that have 1 or more
+ # path-segments before having another wildcard parameter.
+ # - Starting with paths:
+ # - `/*namespace_id/:project_id/`
+ # - `/*namespace_id/:id/`
+ # - Followed by one or more path-parts not starting with `:` or `*`
+ # - Followed by a path-part that includes a wildcard parameter `*`
+ # At the time of writing these routes match: http://rubular.com/r/Rv2pDE5Dvw
+ STARTING_WITH_NAMESPACE = %r{^/\*namespace_id/:(project_)?id}
+ NON_PARAM_PARTS = %r{[^:*][a-z\-_/]*}
+ ANY_OTHER_PATH_PART = %r{[a-z\-_/:]*}
+ WILDCARD_SEGMENT = %r{\*}
+ let(:namespaced_wildcard_routes) do
+ routes_without_format.select do |p|
+ p =~ %r{#{STARTING_WITH_NAMESPACE}/#{NON_PARAM_PARTS}/#{ANY_OTHER_PATH_PART}#{WILDCARD_SEGMENT}}
+ end
+ end
+
+ # This will return all paths that are used in a namespaced route
+ # before another wildcard path:
+ #
+ # /*namespace_id/:project_id/builds/artifacts/*ref_name_and_path
+ # /*namespace_id/:project_id/info/lfs/objects/*oid
+ # /*namespace_id/:project_id/commits/*id
+ # /*namespace_id/:project_id/builds/:build_id/artifacts/file/*path
+ # -> ['builds/artifacts', 'info/lfs/objects', 'commits', 'artifacts/file']
+ let(:all_wildcard_paths) do
+ namespaced_wildcard_routes.map do |route|
+ path_before_wildcard(route)
+ end.uniq
+ end
+
+ STARTING_WITH_GROUP = %r{^/groups/\*(group_)?id/}
+ let(:group_routes) do
+ routes_without_format.select do |path|
+ path =~ STARTING_WITH_GROUP
+ end
+ end
+
+ let(:paths_after_group_id) do
+ group_routes.map do |route|
+ route.gsub(STARTING_WITH_GROUP, '').split('/').first
+ end.uniq
+ end
+
+ describe 'TOP_LEVEL_ROUTES' do
+ it 'includes all the top level namespaces' do
+ failure_block = lambda do
+ missing_words = top_level_words - described_class::TOP_LEVEL_ROUTES
+ failure_message(missing_words, 'TOP_LEVEL_ROUTES', 'rename_root_paths')
+ end
+
+ expect(described_class::TOP_LEVEL_ROUTES)
+ .to include(*top_level_words), failure_block
+ end
+ end
+
+ describe 'GROUP_ROUTES' do
+ it "don't contain a second wildcard" do
+ failure_block = lambda do
+ missing_words = paths_after_group_id - described_class::GROUP_ROUTES
+ failure_message(missing_words, 'GROUP_ROUTES', 'rename_child_paths')
+ end
+
+ expect(described_class::GROUP_ROUTES)
+ .to include(*paths_after_group_id), failure_block
+ end
+ end
+
+ describe 'PROJECT_WILDCARD_ROUTES' do
+ it 'includes all paths that can be used after a namespace/project path' do
+ aggregate_failures do
+ all_wildcard_paths.each do |path|
+ expect(wildcards_include?(path))
+ .to be(true), failure_message(path, 'PROJECT_WILDCARD_ROUTES', 'rename_wildcard_paths')
+ end
+ end
+ end
+ end
+
+ describe '.root_namespace_path_regex' do
+ subject { described_class.root_namespace_path_regex }
+
+ it 'rejects top level routes' do
+ expect(subject).not_to match('admin/')
+ expect(subject).not_to match('api/')
+ expect(subject).not_to match('.well-known/')
+ end
+
+ it 'accepts project wildcard routes' do
+ expect(subject).to match('blob/')
+ expect(subject).to match('edit/')
+ expect(subject).to match('wikis/')
+ end
+
+ it 'accepts group routes' do
+ expect(subject).to match('activity/')
+ expect(subject).to match('group_members/')
+ expect(subject).to match('subgroups/')
+ end
+
+ it 'is not case sensitive' do
+ expect(subject).not_to match('Users/')
+ end
+
+ it 'does not allow extra slashes' do
+ expect(subject).not_to match('/blob/')
+ expect(subject).not_to match('blob//')
+ end
+ end
+
+ describe '.full_namespace_path_regex' do
+ subject { described_class.full_namespace_path_regex }
+
+ context 'at the top level' do
+ context 'when the final level' do
+ it 'rejects top level routes' do
+ expect(subject).not_to match('admin/')
+ expect(subject).not_to match('api/')
+ expect(subject).not_to match('.well-known/')
+ end
+
+ it 'accepts project wildcard routes' do
+ expect(subject).to match('blob/')
+ expect(subject).to match('edit/')
+ expect(subject).to match('wikis/')
+ end
+
+ it 'accepts group routes' do
+ expect(subject).to match('activity/')
+ expect(subject).to match('group_members/')
+ expect(subject).to match('subgroups/')
+ end
+ end
+
+ context 'when more levels follow' do
+ it 'rejects top level routes' do
+ expect(subject).not_to match('admin/more/')
+ expect(subject).not_to match('api/more/')
+ expect(subject).not_to match('.well-known/more/')
+ end
+
+ it 'accepts project wildcard routes' do
+ expect(subject).to match('blob/more/')
+ expect(subject).to match('edit/more/')
+ expect(subject).to match('wikis/more/')
+ expect(subject).to match('environments/folders/')
+ expect(subject).to match('info/lfs/objects/')
+ end
+
+ it 'accepts group routes' do
+ expect(subject).to match('activity/more/')
+ expect(subject).to match('group_members/more/')
+ expect(subject).to match('subgroups/more/')
+ end
+ end
+ end
+
+ context 'at the second level' do
+ context 'when the final level' do
+ it 'accepts top level routes' do
+ expect(subject).to match('root/admin/')
+ expect(subject).to match('root/api/')
+ expect(subject).to match('root/.well-known/')
+ end
+
+ it 'rejects project wildcard routes' do
+ expect(subject).not_to match('root/blob/')
+ expect(subject).not_to match('root/edit/')
+ expect(subject).not_to match('root/wikis/')
+ expect(subject).not_to match('root/environments/folders/')
+ expect(subject).not_to match('root/info/lfs/objects/')
+ end
+
+ it 'rejects group routes' do
+ expect(subject).not_to match('root/activity/')
+ expect(subject).not_to match('root/group_members/')
+ expect(subject).not_to match('root/subgroups/')
+ end
+ end
+
+ context 'when more levels follow' do
+ it 'accepts top level routes' do
+ expect(subject).to match('root/admin/more/')
+ expect(subject).to match('root/api/more/')
+ expect(subject).to match('root/.well-known/more/')
+ end
+
+ it 'rejects project wildcard routes' do
+ expect(subject).not_to match('root/blob/more/')
+ expect(subject).not_to match('root/edit/more/')
+ expect(subject).not_to match('root/wikis/more/')
+ expect(subject).not_to match('root/environments/folders/more/')
+ expect(subject).not_to match('root/info/lfs/objects/more/')
+ end
+
+ it 'rejects group routes' do
+ expect(subject).not_to match('root/activity/more/')
+ expect(subject).not_to match('root/group_members/more/')
+ expect(subject).not_to match('root/subgroups/more/')
+ end
+ end
+ end
+
+ it 'is not case sensitive' do
+ expect(subject).not_to match('root/Blob/')
+ end
+
+ it 'does not allow extra slashes' do
+ expect(subject).not_to match('/root/admin/')
+ expect(subject).not_to match('root/admin//')
+ end
+ end
+
+ describe '.project_path_regex' do
+ subject { described_class.project_path_regex }
+
+ it 'accepts top level routes' do
+ expect(subject).to match('admin/')
+ expect(subject).to match('api/')
+ expect(subject).to match('.well-known/')
+ end
+
+ it 'rejects project wildcard routes' do
+ expect(subject).not_to match('blob/')
+ expect(subject).not_to match('edit/')
+ expect(subject).not_to match('wikis/')
+ expect(subject).not_to match('environments/folders/')
+ expect(subject).not_to match('info/lfs/objects/')
+ end
+
+ it 'accepts group routes' do
+ expect(subject).to match('activity/')
+ expect(subject).to match('group_members/')
+ expect(subject).to match('subgroups/')
+ end
+
+ it 'is not case sensitive' do
+ expect(subject).not_to match('Blob/')
+ end
+
+ it 'does not allow extra slashes' do
+ expect(subject).not_to match('/admin/')
+ expect(subject).not_to match('admin//')
+ end
+ end
+
+ describe '.full_project_path_regex' do
+ subject { described_class.full_project_path_regex }
+
+ it 'accepts top level routes' do
+ expect(subject).to match('root/admin/')
+ expect(subject).to match('root/api/')
+ expect(subject).to match('root/.well-known/')
+ end
+
+ it 'rejects project wildcard routes' do
+ expect(subject).not_to match('root/blob/')
+ expect(subject).not_to match('root/edit/')
+ expect(subject).not_to match('root/wikis/')
+ expect(subject).not_to match('root/environments/folders/')
+ expect(subject).not_to match('root/info/lfs/objects/')
+ end
+
+ it 'accepts group routes' do
+ expect(subject).to match('root/activity/')
+ expect(subject).to match('root/group_members/')
+ expect(subject).to match('root/subgroups/')
+ end
+
+ it 'is not case sensitive' do
+ expect(subject).not_to match('root/Blob/')
+ end
+
+ it 'does not allow extra slashes' do
+ expect(subject).not_to match('/root/admin/')
+ expect(subject).not_to match('root/admin//')
+ end
+ end
+
+ describe '.namespace_format_regex' do
+ subject { described_class.namespace_format_regex }
+
+ it { is_expected.to match('gitlab-ce') }
+ it { is_expected.to match('gitlab_git') }
+ it { is_expected.to match('_underscore.js') }
+ it { is_expected.to match('100px.com') }
+ it { is_expected.to match('gitlab.org') }
+ it { is_expected.not_to match('?gitlab') }
+ it { is_expected.not_to match('git lab') }
+ it { is_expected.not_to match('gitlab.git') }
+ it { is_expected.not_to match('gitlab.org.') }
+ it { is_expected.not_to match('gitlab.org/') }
+ it { is_expected.not_to match('/gitlab.org') }
+ it { is_expected.not_to match('gitlab git') }
+ end
+
+ describe '.project_path_format_regex' do
+ subject { described_class.project_path_format_regex }
+
+ it { is_expected.to match('gitlab-ce') }
+ it { is_expected.to match('gitlab_git') }
+ it { is_expected.to match('_underscore.js') }
+ it { is_expected.to match('100px.com') }
+ it { is_expected.not_to match('?gitlab') }
+ it { is_expected.not_to match('git lab') }
+ it { is_expected.not_to match('gitlab.git') }
+ end
+end
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 72e947f2cc2..0bee892fe0c 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -2,18 +2,6 @@
require 'spec_helper'
describe Gitlab::Regex, lib: true do
- describe '.project_path_regex' do
- subject { described_class.project_path_regex }
-
- it { is_expected.to match('gitlab-ce') }
- it { is_expected.to match('gitlab_git') }
- it { is_expected.to match('_underscore.js') }
- it { is_expected.to match('100px.com') }
- it { is_expected.not_to match('?gitlab') }
- it { is_expected.not_to match('git lab') }
- it { is_expected.not_to match('gitlab.git') }
- end
-
describe '.project_name_regex' do
subject { described_class.project_name_regex }
@@ -44,16 +32,4 @@ describe Gitlab::Regex, lib: true do
it { is_expected.not_to match('9foo') }
it { is_expected.not_to match('foo-') }
end
-
- describe '.full_namespace_regex' do
- subject { described_class.full_namespace_regex }
-
- it { is_expected.to match('gitlab.org') }
- it { is_expected.to match('gitlab.org/gitlab-git') }
- it { is_expected.not_to match('gitlab.org.') }
- it { is_expected.not_to match('gitlab.org/') }
- it { is_expected.not_to match('/gitlab.org') }
- it { is_expected.not_to match('gitlab.git') }
- it { is_expected.not_to match('gitlab git') }
- end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 1e6260270fe..ec6f6c42eac 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -128,6 +128,15 @@ describe Notify do
is_expected.to have_body_text(namespace_project_issue_path(project.namespace, project, issue))
end
end
+
+ context 'with a preferred language' do
+ before { Gitlab::I18n.locale = :es }
+ after { Gitlab::I18n.use_default_locale }
+
+ it 'always generates the email using the default language' do
+ is_expected.to have_body_text('foo, bar, and baz')
+ end
+ end
end
describe 'status changed' do
diff --git a/spec/migrations/upate_retried_for_ci_builds_spec.rb b/spec/migrations/update_retried_for_ci_builds_spec.rb
index 5cdb8a3c7da..3742b4dafe5 100644
--- a/spec/migrations/upate_retried_for_ci_builds_spec.rb
+++ b/spec/migrations/update_retried_for_ci_builds_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20170503004427_upate_retried_for_ci_build.rb')
+require Rails.root.join('db', 'post_migrate', '20170503004427_update_retried_for_ci_build.rb')
-describe UpateRetriedForCiBuild, truncate: true do
+describe UpdateRetriedForCiBuild, truncate: true do
let(:pipeline) { create(:ci_pipeline) }
let!(:build_old) { create(:ci_build, pipeline: pipeline, name: 'test') }
let!(:build_new) { create(:ci_build, pipeline: pipeline, name: 'test') }
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 56b24ce62f3..c8023dc13b1 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -965,7 +965,7 @@ describe Ci::Pipeline, models: true do
end
before do
- ProjectWebHookWorker.drain
+ WebHookWorker.drain
end
context 'with pipeline hooks enabled' do
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 6947affcc1e..c50b8bf7b13 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -36,6 +36,16 @@ describe CommitStatus, :models do
it { is_expected.to eq(commit_status.user) }
end
+ describe 'status state machine' do
+ let!(:commit_status) { create(:commit_status, :running, project: project) }
+
+ it 'invalidates the cache after a transition' do
+ expect(ExpireJobCacheWorker).to receive(:perform_async).with(commit_status.id)
+
+ commit_status.success!
+ end
+ end
+
describe '#started?' do
subject { commit_status.started? }
diff --git a/spec/models/concerns/discussion_on_diff_spec.rb b/spec/models/concerns/discussion_on_diff_spec.rb
index 8571e85627c..f3e148f95f0 100644
--- a/spec/models/concerns/discussion_on_diff_spec.rb
+++ b/spec/models/concerns/discussion_on_diff_spec.rb
@@ -21,4 +21,30 @@ describe DiscussionOnDiff, model: true do
end
end
end
+
+ describe '#line_code_in_diffs' do
+ context 'when the discussion is active in the diff' do
+ let(:diff_refs) { subject.position.diff_refs }
+
+ it 'returns the current line code' do
+ expect(subject.line_code_in_diffs(diff_refs)).to eq(subject.line_code)
+ end
+ end
+
+ context 'when the discussion was created in the diff' do
+ let(:diff_refs) { subject.original_position.diff_refs }
+
+ it 'returns the original line code' do
+ expect(subject.line_code_in_diffs(diff_refs)).to eq(subject.original_line_code)
+ end
+ end
+
+ context 'when the discussion is unrelated to the diff' do
+ let(:diff_refs) { subject.project.commit(RepoHelpers.sample_commit.id).diff_refs }
+
+ it 'returns nil' do
+ expect(subject.line_code_in_diffs(diff_refs)).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index d0b919efcf9..fd58bd1d6ad 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -13,8 +13,7 @@ describe 'CycleAnalytics#test', feature: true do
data_fn: lambda do |context|
issue = context.create(:issue, project: context.project)
merge_request = context.create_merge_request_closing_issue(issue)
- pipeline = context.create(:ci_pipeline, ref: merge_request.source_branch, sha: merge_request.diff_head_sha, project: context.project)
- merge_request.update(head_pipeline: pipeline)
+ pipeline = context.create(:ci_pipeline, ref: merge_request.source_branch, sha: merge_request.diff_head_sha, project: context.project, head_pipeline_of: merge_request)
{ pipeline: pipeline, issue: issue }
end,
start_time_conditions: [["pipeline is started", -> (context, data) { data[:pipeline].run! }]],
diff --git a/spec/models/diff_discussion_spec.rb b/spec/models/diff_discussion_spec.rb
index 81f338745b1..45b2f6e4beb 100644
--- a/spec/models/diff_discussion_spec.rb
+++ b/spec/models/diff_discussion_spec.rb
@@ -48,7 +48,7 @@ describe DiffDiscussion, model: true do
end
it 'returns the diff ID for the version to show' do
- expect(diff_id: merge_request_diff1.id)
+ expect(subject.merge_request_version_params).to eq(diff_id: merge_request_diff1.id)
end
end
@@ -65,6 +65,11 @@ describe DiffDiscussion, model: true do
let(:diff_note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project, position: position) }
+ before do
+ diff_note.position = diff_note.original_position
+ diff_note.save!
+ end
+
it 'returns the diff ID and start sha of the versions to compare' do
expect(subject.merge_request_version_params).to eq(diff_id: merge_request_diff3.id, start_sha: merge_request_diff1.head_commit_sha)
end
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index ab4c51a87b0..96f075d4f7d 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -145,7 +145,7 @@ describe DiffNote, models: true do
context "when the merge request's diff refs don't match that of the diff note" do
before do
- allow(subject.noteable).to receive(:diff_sha_refs).and_return(commit.diff_refs)
+ allow(subject.noteable).to receive(:diff_refs).and_return(commit.diff_refs)
end
it "returns false" do
@@ -194,7 +194,7 @@ describe DiffNote, models: true do
context "when the note is outdated" do
before do
- allow(merge_request).to receive(:diff_sha_refs).and_return(commit.diff_refs)
+ allow(merge_request).to receive(:diff_refs).and_return(commit.diff_refs)
end
it "uses the DiffPositionUpdateService" do
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index 1a83c836652..57454d2a773 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -1,36 +1,19 @@
-require "spec_helper"
+require 'spec_helper'
describe ServiceHook, models: true do
- describe "Associations" do
+ describe 'associations' do
it { is_expected.to belong_to :service }
end
- describe "execute" do
- before(:each) do
- @service_hook = create(:service_hook)
- @data = { project_id: 1, data: {} }
+ describe 'execute' do
+ let(:hook) { build(:service_hook) }
+ let(:data) { { key: 'value' } }
- WebMock.stub_request(:post, @service_hook.url)
- end
-
- it "POSTs to the webhook URL" do
- @service_hook.execute(@data)
- expect(WebMock).to have_requested(:post, @service_hook.url).with(
- headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Service Hook' }
- ).once
- end
-
- it "POSTs the data as JSON" do
- @service_hook.execute(@data)
- expect(WebMock).to have_requested(:post, @service_hook.url).with(
- headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Service Hook' }
- ).once
- end
-
- it "catches exceptions" do
- expect(WebHook).to receive(:post).and_raise("Some HTTP Post error")
+ it '#execute' do
+ expect(WebHookService).to receive(:new).with(hook, data, 'service_hook').and_call_original
+ expect_any_instance_of(WebHookService).to receive(:execute)
- expect { @service_hook.execute(@data) }.to raise_error(RuntimeError)
+ hook.execute(data)
end
end
end
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index 4340170888d..0d2b622132e 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -126,4 +126,26 @@ describe SystemHook, models: true do
expect(SystemHook.repository_update_hooks).to eq([hook])
end
end
+
+ describe 'execute WebHookService' do
+ let(:hook) { build(:system_hook) }
+ let(:data) { { key: 'value' } }
+ let(:hook_name) { 'system_hook' }
+
+ before do
+ expect(WebHookService).to receive(:new).with(hook, data, hook_name).and_call_original
+ end
+
+ it '#execute' do
+ expect_any_instance_of(WebHookService).to receive(:execute)
+
+ hook.execute(data, hook_name)
+ end
+
+ it '#async_execute' do
+ expect_any_instance_of(WebHookService).to receive(:async_execute)
+
+ hook.async_execute(data, hook_name)
+ end
+ end
end
diff --git a/spec/models/hooks/web_hook_log_spec.rb b/spec/models/hooks/web_hook_log_spec.rb
new file mode 100644
index 00000000000..c649cf3b589
--- /dev/null
+++ b/spec/models/hooks/web_hook_log_spec.rb
@@ -0,0 +1,30 @@
+require 'rails_helper'
+
+describe WebHookLog, models: true do
+ it { is_expected.to belong_to(:web_hook) }
+
+ it { is_expected.to serialize(:request_headers).as(Hash) }
+ it { is_expected.to serialize(:request_data).as(Hash) }
+ it { is_expected.to serialize(:response_headers).as(Hash) }
+
+ it { is_expected.to validate_presence_of(:web_hook) }
+
+ describe '#success?' do
+ let(:web_hook_log) { build(:web_hook_log, response_status: status) }
+
+ describe '2xx' do
+ let(:status) { '200' }
+ it { expect(web_hook_log.success?).to be_truthy }
+ end
+
+ describe 'not 2xx' do
+ let(:status) { '500' }
+ it { expect(web_hook_log.success?).to be_falsey }
+ end
+
+ describe 'internal erorr' do
+ let(:status) { 'internal error' }
+ it { expect(web_hook_log.success?).to be_falsey }
+ end
+ end
+end
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 9d4db1bfb52..53157c24477 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -1,89 +1,54 @@
require 'spec_helper'
describe WebHook, models: true do
- describe "Validations" do
+ let(:hook) { build(:project_hook) }
+
+ describe 'associations' do
+ it { is_expected.to have_many(:web_hook_logs).dependent(:destroy) }
+ end
+
+ describe 'validations' do
it { is_expected.to validate_presence_of(:url) }
describe 'url' do
- it { is_expected.to allow_value("http://example.com").for(:url) }
- it { is_expected.to allow_value("https://example.com").for(:url) }
- it { is_expected.to allow_value(" https://example.com ").for(:url) }
- it { is_expected.to allow_value("http://test.com/api").for(:url) }
- it { is_expected.to allow_value("http://test.com/api?key=abc").for(:url) }
- it { is_expected.to allow_value("http://test.com/api?key=abc&type=def").for(:url) }
+ it { is_expected.to allow_value('http://example.com').for(:url) }
+ it { is_expected.to allow_value('https://example.com').for(:url) }
+ it { is_expected.to allow_value(' https://example.com ').for(:url) }
+ it { is_expected.to allow_value('http://test.com/api').for(:url) }
+ it { is_expected.to allow_value('http://test.com/api?key=abc').for(:url) }
+ it { is_expected.to allow_value('http://test.com/api?key=abc&type=def').for(:url) }
- it { is_expected.not_to allow_value("example.com").for(:url) }
- it { is_expected.not_to allow_value("ftp://example.com").for(:url) }
- it { is_expected.not_to allow_value("herp-and-derp").for(:url) }
+ it { is_expected.not_to allow_value('example.com').for(:url) }
+ it { is_expected.not_to allow_value('ftp://example.com').for(:url) }
+ it { is_expected.not_to allow_value('herp-and-derp').for(:url) }
it 'strips :url before saving it' do
- hook = create(:project_hook, url: ' https://example.com ')
+ hook.url = ' https://example.com '
+ hook.save
expect(hook.url).to eq('https://example.com')
end
end
end
- describe "execute" do
- let(:project) { create(:empty_project) }
- let(:project_hook) { create(:project_hook) }
-
- before(:each) do
- project.hooks << [project_hook]
- @data = { before: 'oldrev', after: 'newrev', ref: 'ref' }
-
- WebMock.stub_request(:post, project_hook.url)
- end
-
- context 'when token is defined' do
- let(:project_hook) { create(:project_hook, :token) }
-
- it 'POSTs to the webhook URL' do
- project_hook.execute(@data, 'push_hooks')
- expect(WebMock).to have_requested(:post, project_hook.url).with(
- headers: { 'Content-Type' => 'application/json',
- 'X-Gitlab-Event' => 'Push Hook',
- 'X-Gitlab-Token' => project_hook.token }
- ).once
- end
- end
-
- it "POSTs to the webhook URL" do
- project_hook.execute(@data, 'push_hooks')
- expect(WebMock).to have_requested(:post, project_hook.url).with(
- headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Push Hook' }
- ).once
- end
-
- it "POSTs the data as JSON" do
- project_hook.execute(@data, 'push_hooks')
- expect(WebMock).to have_requested(:post, project_hook.url).with(
- headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Push Hook' }
- ).once
- end
-
- it "catches exceptions" do
- expect(WebHook).to receive(:post).and_raise("Some HTTP Post error")
-
- expect { project_hook.execute(@data, 'push_hooks') }.to raise_error(RuntimeError)
- end
-
- it "handles SSL exceptions" do
- expect(WebHook).to receive(:post).and_raise(OpenSSL::SSL::SSLError.new('SSL error'))
+ describe 'execute' do
+ let(:data) { { key: 'value' } }
+ let(:hook_name) { 'project hook' }
- expect(project_hook.execute(@data, 'push_hooks')).to eq([false, 'SSL error'])
+ before do
+ expect(WebHookService).to receive(:new).with(hook, data, hook_name).and_call_original
end
- it "handles 200 status code" do
- WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: "Success")
+ it '#execute' do
+ expect_any_instance_of(WebHookService).to receive(:execute)
- expect(project_hook.execute(@data, 'push_hooks')).to eq([200, 'Success'])
+ hook.execute(data, hook_name)
end
- it "handles 2xx status codes" do
- WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: "Success")
+ it '#async_execute' do
+ expect_any_instance_of(WebHookService).to receive(:async_execute)
- expect(project_hook.execute(@data, 'push_hooks')).to eq([201, 'Success'])
+ hook.async_execute(data, hook_name)
end
end
end
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 80ca19acdda..84867e3d96b 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -49,6 +49,23 @@ describe Label, models: true do
expect(label.color).to eq('#abcdef')
end
+
+ it 'uses default color if color is missing' do
+ label = described_class.new(color: nil)
+
+ expect(label.color).to be(Label::DEFAULT_COLOR)
+ end
+ end
+
+ describe '#text_color' do
+ it 'uses default color if color is missing' do
+ expect(LabelsHelper).to receive(:text_color_for_bg).with(Label::DEFAULT_COLOR).
+ and_return(spy)
+
+ label = described_class.new(color: nil)
+
+ label.text_color
+ end
end
describe '#title' do
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index ce870fcc1d3..712470d6bf5 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -718,8 +718,7 @@ describe MergeRequest, models: true do
describe '#head_pipeline' do
describe 'when the source project exists' do
it 'returns the latest pipeline' do
- pipeline = create(:ci_empty_pipeline, project: subject.source_project, ref: 'master', status: 'running', sha: "123abc")
- subject.update(head_pipeline: pipeline)
+ pipeline = create(:ci_empty_pipeline, project: subject.source_project, ref: 'master', status: 'running', sha: "123abc", head_pipeline_of: subject)
expect(subject.head_pipeline).to eq(pipeline)
end
@@ -1214,7 +1213,7 @@ describe MergeRequest, models: true do
expect(Notes::DiffPositionUpdateService).to receive(:new).with(
subject.project,
- nil,
+ subject.author,
old_diff_refs: old_diff_refs,
new_diff_refs: commit.diff_refs,
paths: note.position.paths
@@ -1223,7 +1222,7 @@ describe MergeRequest, models: true do
expect_any_instance_of(Notes::DiffPositionUpdateService).to receive(:execute).with(note)
expect_any_instance_of(DiffNote).to receive(:save).once
- subject.reload_diff
+ subject.reload_diff(subject.author)
end
end
@@ -1244,7 +1243,7 @@ describe MergeRequest, models: true do
end
end
- describe "#diff_sha_refs" do
+ describe "#diff_refs" do
context "with diffs" do
subject { create(:merge_request, :with_diffs) }
@@ -1253,7 +1252,7 @@ describe MergeRequest, models: true do
expect_any_instance_of(Repository).not_to receive(:commit)
- subject.diff_sha_refs
+ subject.diff_refs
end
it "returns expected diff_refs" do
@@ -1263,7 +1262,7 @@ describe MergeRequest, models: true do
head_sha: subject.merge_request_diff.head_commit_sha
)
- expect(subject.diff_sha_refs).to eq(expected_diff_refs)
+ expect(subject.diff_refs).to eq(expected_diff_refs)
end
end
end
@@ -1396,9 +1395,8 @@ describe MergeRequest, models: true do
project: project,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha,
- status: status)
-
- merge_request.update(head_pipeline: pipeline)
+ status: status,
+ head_pipeline_of: merge_request)
pipeline
end
@@ -1536,4 +1534,36 @@ describe MergeRequest, models: true do
end
end
end
+
+ describe '#version_params_for' do
+ subject { create(:merge_request, importing: true) }
+ let(:project) { subject.project }
+ let!(:merge_request_diff1) { subject.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
+ let!(:merge_request_diff2) { subject.merge_request_diffs.create(head_commit_sha: nil) }
+ let!(:merge_request_diff3) { subject.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
+
+ context 'when the diff refs are for an older merge request version' do
+ let(:diff_refs) { merge_request_diff1.diff_refs }
+
+ it 'returns the diff ID for the version to show' do
+ expect(subject.version_params_for(diff_refs)).to eq(diff_id: merge_request_diff1.id)
+ end
+ end
+
+ context 'when the diff refs are for a comparison between merge request versions' do
+ let(:diff_refs) { merge_request_diff3.compare_with(merge_request_diff1.head_commit_sha).diff_refs }
+
+ it 'returns the diff ID and start sha of the versions to compare' do
+ expect(subject.version_params_for(diff_refs)).to eq(diff_id: merge_request_diff3.id, start_sha: merge_request_diff1.head_commit_sha)
+ end
+ end
+
+ context 'when the diff refs are not for a merge request version' do
+ let(:diff_refs) { project.commit(sample_commit.id).diff_refs }
+
+ it 'returns nil' do
+ expect(subject.version_params_for(diff_refs)).to be_nil
+ end
+ end
+ end
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 8624616316c..ff5e7c350aa 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -37,7 +37,7 @@ describe Namespace, models: true do
it 'rejects nested paths' do
parent = create(:group, :nested, path: 'environments')
- namespace = build(:project, path: 'folders', namespace: parent)
+ namespace = build(:group, path: 'folders', parent: parent)
expect(namespace).not_to be_valid
end
@@ -238,8 +238,8 @@ describe Namespace, models: true do
end
context 'in sub-groups' do
- let(:parent) { create(:namespace, path: 'parent') }
- let(:child) { create(:namespace, parent: parent, path: 'child') }
+ let(:parent) { create(:group, path: 'parent') }
+ let(:child) { create(:group, parent: parent, path: 'child') }
let!(:project) { create(:project_empty_repo, namespace: child) }
let(:path_in_dir) { File.join(repository_storage_path, 'parent', 'child') }
let(:deleted_path) { File.join('parent', "child+#{child.id}+deleted") }
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 4bca0229e7a..349067e73ab 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -22,6 +22,42 @@ describe JiraService, models: true do
it { is_expected.not_to validate_presence_of(:url) }
end
+
+ context 'validating urls' do
+ let(:service) do
+ described_class.new(
+ project: create(:empty_project),
+ active: true,
+ username: 'username',
+ password: 'test',
+ project_key: 'TEST',
+ jira_issue_transition_id: 24,
+ url: 'http://jira.test.com'
+ )
+ end
+
+ it 'is valid when all fields have required values' do
+ expect(service).to be_valid
+ end
+
+ it 'is not valid when url is not a valid url' do
+ service.url = 'not valid'
+
+ expect(service).not_to be_valid
+ end
+
+ it 'is not valid when api url is not a valid url' do
+ service.api_url = 'not valid'
+
+ expect(service).not_to be_valid
+ end
+
+ it 'is valid when api url is a valid url' do
+ service.api_url = 'http://jira.test.com/api'
+
+ expect(service).to be_valid
+ end
+ end
end
describe '#reference_pattern' do
@@ -187,22 +223,29 @@ describe JiraService, models: true do
describe '#test_settings' do
let(:jira_service) do
described_class.new(
+ project: create(:project),
url: 'http://jira.example.com',
- username: 'gitlab_jira_username',
- password: 'gitlab_jira_password',
+ username: 'jira_username',
+ password: 'jira_password',
project_key: 'GitLabProject'
)
end
- let(:project_url) { 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/project/GitLabProject' }
- before do
+ def test_settings(api_url)
+ project_url = "http://jira_username:jira_password@#{api_url}/rest/api/2/project/GitLabProject"
+
WebMock.stub_request(:get, project_url)
- end
- it 'tries to get JIRA project' do
jira_service.test_settings
+ end
+
+ it 'tries to get JIRA project with URL when API URL not set' do
+ test_settings('jira.example.com')
+ end
- expect(WebMock).to have_requested(:get, project_url)
+ it 'tries to get JIRA project with API URL if set' do
+ jira_service.update(api_url: 'http://jira.api.com')
+ test_settings('jira.api.com')
end
end
@@ -214,34 +257,75 @@ describe JiraService, models: true do
@jira_service = JiraService.create!(
project: project,
properties: {
- url: 'http://jira.example.com/rest/api/2',
+ url: 'http://jira.example.com/web',
username: 'mic',
password: "password"
}
)
end
- it "reset password if url changed" do
- @jira_service.url = 'http://jira_edited.example.com/rest/api/2'
- @jira_service.save
- expect(@jira_service.password).to be_nil
+ context 'when only web url present' do
+ it 'reset password if url changed' do
+ @jira_service.url = 'http://jira_edited.example.com/rest/api/2'
+ @jira_service.save
+
+ expect(@jira_service.password).to be_nil
+ end
+
+ it 'reset password if url not changed but api url added' do
+ @jira_service.api_url = 'http://jira_edited.example.com/rest/api/2'
+ @jira_service.save
+
+ expect(@jira_service.password).to be_nil
+ end
end
- it "does not reset password if username changed" do
- @jira_service.username = "some_name"
+ context 'when both web and api url present' do
+ before do
+ @jira_service.api_url = 'http://jira.example.com/rest/api/2'
+ @jira_service.password = 'password'
+
+ @jira_service.save
+ end
+ it 'reset password if api url changed' do
+ @jira_service.api_url = 'http://jira_edited.example.com/rest/api/2'
+ @jira_service.save
+
+ expect(@jira_service.password).to be_nil
+ end
+
+ it 'does not reset password if url changed' do
+ @jira_service.url = 'http://jira_edited.example.com/rweb'
+ @jira_service.save
+
+ expect(@jira_service.password).to eq("password")
+ end
+
+ it 'reset password if api url set to ""' do
+ @jira_service.api_url = ''
+ @jira_service.save
+
+ expect(@jira_service.password).to be_nil
+ end
+ end
+
+ it 'does not reset password if username changed' do
+ @jira_service.username = 'some_name'
@jira_service.save
- expect(@jira_service.password).to eq("password")
+
+ expect(@jira_service.password).to eq('password')
end
- it "does not reset password if new url is set together with password, even if it's the same password" do
+ it 'does not reset password if new url is set together with password, even if it\'s the same password' do
@jira_service.url = 'http://jira_edited.example.com/rest/api/2'
@jira_service.password = 'password'
@jira_service.save
- expect(@jira_service.password).to eq("password")
- expect(@jira_service.url).to eq("http://jira_edited.example.com/rest/api/2")
+
+ expect(@jira_service.password).to eq('password')
+ expect(@jira_service.url).to eq('http://jira_edited.example.com/rest/api/2')
end
- it "resets password if url changed, even if setter called multiple times" do
+ it 'resets password if url changed, even if setter called multiple times' do
@jira_service.url = 'http://jira1.example.com/rest/api/2'
@jira_service.url = 'http://jira1.example.com/rest/api/2'
@jira_service.save
@@ -249,7 +333,7 @@ describe JiraService, models: true do
end
end
- context "when no password was previously set" do
+ context 'when no password was previously set' do
before do
@jira_service = JiraService.create(
project: project,
@@ -260,26 +344,16 @@ describe JiraService, models: true do
)
end
- it "saves password if new url is set together with password" do
+ it 'saves password if new url is set together with password' do
@jira_service.url = 'http://jira_edited.example.com/rest/api/2'
@jira_service.password = 'password'
@jira_service.save
- expect(@jira_service.password).to eq("password")
- expect(@jira_service.url).to eq("http://jira_edited.example.com/rest/api/2")
+ expect(@jira_service.password).to eq('password')
+ expect(@jira_service.url).to eq('http://jira_edited.example.com/rest/api/2')
end
end
end
- describe "Validations" do
- context "active" do
- before do
- subject.active = true
- end
-
- it { is_expected.to validate_presence_of :url }
- end
- end
-
describe 'description and title' do
let(:project) { create(:empty_project) }
@@ -321,9 +395,10 @@ describe JiraService, models: true do
context 'when gitlab.yml was initialized' do
before do
settings = {
- "jira" => {
- "title" => "Jira",
- "url" => "http://jira.sample/projects/project_a"
+ 'jira' => {
+ 'title' => 'Jira',
+ 'url' => 'http://jira.sample/projects/project_a',
+ 'api_url' => 'http://jira.sample/api'
}
}
allow(Gitlab.config).to receive(:issues_tracker).and_return(settings)
@@ -335,8 +410,9 @@ describe JiraService, models: true do
end
it 'is prepopulated with the settings' do
- expect(@service.properties["title"]).to eq('Jira')
- expect(@service.properties["url"]).to eq('http://jira.sample/projects/project_a')
+ expect(@service.properties['title']).to eq('Jira')
+ expect(@service.properties['url']).to eq('http://jira.sample/projects/project_a')
+ expect(@service.properties['api_url']).to eq('http://jira.sample/api')
end
end
end
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
index c1c2f2a7219..0dcf4a4b5d6 100644
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ b/spec/models/project_services/kubernetes_service_spec.rb
@@ -13,7 +13,7 @@ describe KubernetesService, models: true, caching: true do
let(:discovery_url) { service.api_url + '/api/v1' }
let(:discovery_response) { { body: kube_discovery_body.to_json } }
- let(:pods_url) { service.api_url + "/api/v1/namespaces/#{service.namespace}/pods" }
+ let(:pods_url) { service.api_url + "/api/v1/namespaces/#{service.actual_namespace}/pods" }
let(:pods_response) { { body: kube_pods_body(kube_pod).to_json } }
def stub_kubeclient_discover
@@ -100,7 +100,35 @@ describe KubernetesService, models: true, caching: true do
it 'sets the namespace to the default' do
expect(kube_namespace).not_to be_nil
- expect(kube_namespace[:placeholder]).to match(/\A#{Gitlab::Regex::PATH_REGEX_STR}-\d+\z/)
+ expect(kube_namespace[:placeholder]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/)
+ end
+ end
+ end
+
+ describe '#actual_namespace' do
+ subject { service.actual_namespace }
+
+ it "returns the default namespace" do
+ is_expected.to eq(service.send(:default_namespace))
+ end
+
+ context 'when namespace is specified' do
+ before do
+ service.namespace = 'my-namespace'
+ end
+
+ it "returns the user-namespace" do
+ is_expected.to eq('my-namespace')
+ end
+ end
+
+ context 'when service is not assigned to project' do
+ before do
+ service.project = nil
+ end
+
+ it "does not return namespace" do
+ is_expected.to be_nil
end
end
end
@@ -187,13 +215,14 @@ describe KubernetesService, models: true, caching: true do
kube_namespace = subject.predefined_variables.find { |h| h[:key] == 'KUBE_NAMESPACE' }
expect(kube_namespace).not_to be_nil
- expect(kube_namespace[:value]).to match(/\A#{Gitlab::Regex::PATH_REGEX_STR}-\d+\z/)
+ expect(kube_namespace[:value]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/)
end
end
end
describe '#terminals' do
let(:environment) { build(:environment, project: project, name: "env", slug: "env-000000") }
+
subject { service.terminals(environment) }
context 'with invalid pods' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 6a15830a15c..aabdac4bb75 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -440,6 +440,22 @@ describe User, models: true do
end
end
+ describe 'ensure incoming email token' do
+ it 'has incoming email token' do
+ user = create(:user)
+ expect(user.incoming_email_token).not_to be_blank
+ end
+ end
+
+ describe 'rss token' do
+ it 'ensures an rss token on read' do
+ user = create(:user, rss_token: nil)
+ rss_token = user.rss_token
+ expect(rss_token).not_to be_blank
+ expect(user.reload.rss_token).to eq rss_token
+ end
+ end
+
describe '#recently_sent_password_reset?' do
it 'is false when reset_password_sent_at is nil' do
user = build_stubbed(:user, reset_password_sent_at: nil)
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 2ceb4648ece..cf232e7ff69 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -466,86 +466,87 @@ describe API::Internal do
end
end
- describe 'POST /notify_post_receive' do
- let(:valid_params) do
- { project: project.repository.path, secret_token: secret_token }
- end
-
- let(:valid_wiki_params) do
- { project: project.wiki.repository.path, secret_token: secret_token }
- end
-
- before do
- allow(Gitlab.config.gitaly).to receive(:enabled).and_return(true)
- end
-
- it "calls the Gitaly client with the project's repository" do
- expect(Gitlab::GitalyClient::Notifications).
- to receive(:new).with(gitlab_git_repository_with(path: project.repository.path)).
- and_call_original
- expect_any_instance_of(Gitlab::GitalyClient::Notifications).
- to receive(:post_receive)
-
- post api("/internal/notify_post_receive"), valid_params
-
- expect(response).to have_http_status(200)
- end
-
- it "calls the Gitaly client with the wiki's repository if it's a wiki" do
- expect(Gitlab::GitalyClient::Notifications).
- to receive(:new).with(gitlab_git_repository_with(path: project.wiki.repository.path)).
- and_call_original
- expect_any_instance_of(Gitlab::GitalyClient::Notifications).
- to receive(:post_receive)
-
- post api("/internal/notify_post_receive"), valid_wiki_params
-
- expect(response).to have_http_status(200)
- end
-
- it "returns 500 if the gitaly call fails" do
- expect_any_instance_of(Gitlab::GitalyClient::Notifications).
- to receive(:post_receive).and_raise(GRPC::Unavailable)
-
- post api("/internal/notify_post_receive"), valid_params
-
- expect(response).to have_http_status(500)
- end
-
- context 'with a gl_repository parameter' do
- let(:valid_params) do
- { gl_repository: "project-#{project.id}", secret_token: secret_token }
- end
-
- let(:valid_wiki_params) do
- { gl_repository: "wiki-#{project.id}", secret_token: secret_token }
- end
-
- it "calls the Gitaly client with the project's repository" do
- expect(Gitlab::GitalyClient::Notifications).
- to receive(:new).with(gitlab_git_repository_with(path: project.repository.path)).
- and_call_original
- expect_any_instance_of(Gitlab::GitalyClient::Notifications).
- to receive(:post_receive)
-
- post api("/internal/notify_post_receive"), valid_params
-
- expect(response).to have_http_status(200)
- end
-
- it "calls the Gitaly client with the wiki's repository if it's a wiki" do
- expect(Gitlab::GitalyClient::Notifications).
- to receive(:new).with(gitlab_git_repository_with(path: project.wiki.repository.path)).
- and_call_original
- expect_any_instance_of(Gitlab::GitalyClient::Notifications).
- to receive(:post_receive)
-
- post api("/internal/notify_post_receive"), valid_wiki_params
-
- expect(response).to have_http_status(200)
- end
- end
- end
+ # TODO: Uncomment when the end-point is reenabled
+ # describe 'POST /notify_post_receive' do
+ # let(:valid_params) do
+ # { project: project.repository.path, secret_token: secret_token }
+ # end
+ #
+ # let(:valid_wiki_params) do
+ # { project: project.wiki.repository.path, secret_token: secret_token }
+ # end
+ #
+ # before do
+ # allow(Gitlab.config.gitaly).to receive(:enabled).and_return(true)
+ # end
+ #
+ # it "calls the Gitaly client with the project's repository" do
+ # expect(Gitlab::GitalyClient::Notifications).
+ # to receive(:new).with(gitlab_git_repository_with(path: project.repository.path)).
+ # and_call_original
+ # expect_any_instance_of(Gitlab::GitalyClient::Notifications).
+ # to receive(:post_receive)
+ #
+ # post api("/internal/notify_post_receive"), valid_params
+ #
+ # expect(response).to have_http_status(200)
+ # end
+ #
+ # it "calls the Gitaly client with the wiki's repository if it's a wiki" do
+ # expect(Gitlab::GitalyClient::Notifications).
+ # to receive(:new).with(gitlab_git_repository_with(path: project.wiki.repository.path)).
+ # and_call_original
+ # expect_any_instance_of(Gitlab::GitalyClient::Notifications).
+ # to receive(:post_receive)
+ #
+ # post api("/internal/notify_post_receive"), valid_wiki_params
+ #
+ # expect(response).to have_http_status(200)
+ # end
+ #
+ # it "returns 500 if the gitaly call fails" do
+ # expect_any_instance_of(Gitlab::GitalyClient::Notifications).
+ # to receive(:post_receive).and_raise(GRPC::Unavailable)
+ #
+ # post api("/internal/notify_post_receive"), valid_params
+ #
+ # expect(response).to have_http_status(500)
+ # end
+ #
+ # context 'with a gl_repository parameter' do
+ # let(:valid_params) do
+ # { gl_repository: "project-#{project.id}", secret_token: secret_token }
+ # end
+ #
+ # let(:valid_wiki_params) do
+ # { gl_repository: "wiki-#{project.id}", secret_token: secret_token }
+ # end
+ #
+ # it "calls the Gitaly client with the project's repository" do
+ # expect(Gitlab::GitalyClient::Notifications).
+ # to receive(:new).with(gitlab_git_repository_with(path: project.repository.path)).
+ # and_call_original
+ # expect_any_instance_of(Gitlab::GitalyClient::Notifications).
+ # to receive(:post_receive)
+ #
+ # post api("/internal/notify_post_receive"), valid_params
+ #
+ # expect(response).to have_http_status(200)
+ # end
+ #
+ # it "calls the Gitaly client with the wiki's repository if it's a wiki" do
+ # expect(Gitlab::GitalyClient::Notifications).
+ # to receive(:new).with(gitlab_git_repository_with(path: project.wiki.repository.path)).
+ # and_call_original
+ # expect_any_instance_of(Gitlab::GitalyClient::Notifications).
+ # to receive(:post_receive)
+ #
+ # post api("/internal/notify_post_receive"), valid_wiki_params
+ #
+ # expect(response).to have_http_status(200)
+ # end
+ # end
+ # end
def project_with_repo_path(path)
double().tap do |fake_project|
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 4919ad19833..a2503dbeb69 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -287,7 +287,7 @@ describe API::Users do
expect(json_response['message']['projects_limit']).
to eq(['must be greater than or equal to 0'])
expect(json_response['message']['username']).
- to eq([Gitlab::Regex.namespace_regex_message])
+ to eq([Gitlab::PathRegex.namespace_format_message])
end
it "is not available for non admin users" do
@@ -459,7 +459,7 @@ describe API::Users do
expect(json_response['message']['projects_limit']).
to eq(['must be greater than or equal to 0'])
expect(json_response['message']['username']).
- to eq([Gitlab::Regex.namespace_regex_message])
+ to eq([Gitlab::PathRegex.namespace_format_message])
end
it 'returns 400 if provider is missing for identity update' do
diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb
index d92daa345b3..d4d3c9478a0 100644
--- a/spec/requests/projects/cycle_analytics_events_spec.rb
+++ b/spec/requests/projects/cycle_analytics_events_spec.rb
@@ -121,8 +121,7 @@ describe 'cycle analytics events', api: true do
issue.update(milestone: milestone)
mr = create_merge_request_closing_issue(issue, commit_message: "References #{issue.to_reference}")
- pipeline = create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha)
- mr.update(head_pipeline_id: pipeline.id)
+ pipeline = create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr)
pipeline.run
create(:ci_build, pipeline: pipeline, status: :success, author: user)
diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb
index e5fc0b676af..179fc9733ad 100644
--- a/spec/routing/admin_routing_spec.rb
+++ b/spec/routing/admin_routing_spec.rb
@@ -103,6 +103,18 @@ describe Admin::HooksController, "routing" do
end
end
+# admin_hook_hook_log_retry GET /admin/hooks/:hook_id/hook_logs/:id/retry(.:format) admin/hook_logs#retry
+# admin_hook_hook_log GET /admin/hooks/:hook_id/hook_logs/:id(.:format) admin/hook_logs#show
+describe Admin::HookLogsController, 'routing' do
+ it 'to #retry' do
+ expect(get('/admin/hooks/1/hook_logs/1/retry')).to route_to('admin/hook_logs#retry', hook_id: '1', id: '1')
+ end
+
+ it 'to #show' do
+ expect(get('/admin/hooks/1/hook_logs/1')).to route_to('admin/hook_logs#show', hook_id: '1', id: '1')
+ end
+end
+
# admin_logs GET /admin/logs(.:format) admin/logs#show
describe Admin::LogsController, "routing" do
it "to #show" do
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index d5400bbaaf1..54417f6b3e1 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -349,6 +349,18 @@ describe 'project routing' do
end
end
+ # retry_namespace_project_hook_hook_log GET /:project_id/hooks/:hook_id/hook_logs/:id/retry(.:format) projects/hook_logs#retry
+ # namespace_project_hook_hook_log GET /:project_id/hooks/:hook_id/hook_logs/:id(.:format) projects/hook_logs#show
+ describe Projects::HookLogsController, 'routing' do
+ it 'to #retry' do
+ expect(get('/gitlab/gitlabhq/hooks/1/hook_logs/1/retry')).to route_to('projects/hook_logs#retry', namespace_id: 'gitlab', project_id: 'gitlabhq', hook_id: '1', id: '1')
+ end
+
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/hooks/1/hook_logs/1')).to route_to('projects/hook_logs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', hook_id: '1', id: '1')
+ end
+ end
+
# project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /\h{7,40}/, project_id: /[^\/]+/}
describe Projects::CommitController, 'routing' do
it 'to #show' do
@@ -462,6 +474,8 @@ describe 'project routing' do
expect(get('/gitlab/gitlabhq/blob/master/app/models/compare.rb')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/compare.rb')
expect(get('/gitlab/gitlabhq/blob/master/app/models/diff.js')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/diff.js')
expect(get('/gitlab/gitlabhq/blob/master/files.scss')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss')
+ expect(get('/gitlab/gitlabhq/blob/master/blob/index.js')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/blob/index.js')
+ expect(get('/gitlab/gitlabhq/blob/blob/master/blob/index.js')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'blob/master/blob/index.js')
end
end
@@ -470,6 +484,8 @@ describe 'project routing' do
it 'to #show' do
expect(get('/gitlab/gitlabhq/tree/master/app/models/project.rb')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb')
expect(get('/gitlab/gitlabhq/tree/master/files.scss')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss')
+ expect(get('/gitlab/gitlabhq/tree/master/tree/files')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/tree/files')
+ expect(get('/gitlab/gitlabhq/tree/tree/master/tree/files')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'tree/master/tree/files')
end
end
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index abacc50a371..a62af13cf0c 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -151,6 +151,10 @@ describe ProfilesController, "routing" do
expect(put("/profile/reset_private_token")).to route_to('profiles#reset_private_token')
end
+ it "to #reset_rss_token" do
+ expect(put("/profile/reset_rss_token")).to route_to('profiles#reset_rss_token')
+ end
+
it "to #show" do
expect(get("/profile")).to route_to('profiles#show')
end
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 0a1f41719f7..be0e829880e 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -41,6 +41,12 @@ describe Issues::CloseService, services: true do
service.execute(issue)
end
+
+ it 'invalidates counter cache for assignees' do
+ expect_any_instance_of(User).to receive(:invalidate_issue_cache_counts)
+
+ service.execute(issue)
+ end
end
describe '#close_issue' do
diff --git a/spec/services/issues/reopen_service_spec.rb b/spec/services/issues/reopen_service_spec.rb
index 93a8270fd16..391ecad303a 100644
--- a/spec/services/issues/reopen_service_spec.rb
+++ b/spec/services/issues/reopen_service_spec.rb
@@ -27,6 +27,13 @@ describe Issues::ReopenService, services: true do
project.team << [user, :master]
end
+ it 'invalidates counter cache for assignees' do
+ issue.assignees << user
+ expect_any_instance_of(User).to receive(:invalidate_issue_cache_counts)
+
+ described_class.new(project, user).execute(issue)
+ end
+
context 'when issue is not confidential' do
it 'executes issue hooks' do
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index d55a7657c0e..154f30aac3b 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -15,6 +15,8 @@ describe MergeRequests::CloseService, services: true do
end
describe '#execute' do
+ it_behaves_like 'cache counters invalidator'
+
context 'valid params' do
let(:service) { described_class.new(project, user, {}) }
diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb
index 3ef5135e6a3..f17db70faf6 100644
--- a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb
+++ b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb
@@ -79,11 +79,8 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
context 'when triggered by pipeline with valid ref and sha' do
let(:triggering_pipeline) do
create(:ci_pipeline, project: project, ref: merge_request_ref,
- sha: merge_request_head, status: 'success')
- end
-
- before do
- mr_merge_if_green_enabled.update(head_pipeline: triggering_pipeline)
+ sha: merge_request_head, status: 'success',
+ head_pipeline_of: mr_merge_if_green_enabled)
end
it "merges all merge requests with merge when the pipeline succeeds enabled" do
@@ -125,11 +122,10 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
let(:conflict_pipeline) do
create(:ci_pipeline, project: project, ref: mr_conflict.source_branch,
- sha: mr_conflict.diff_head_sha, status: 'success')
+ sha: mr_conflict.diff_head_sha, status: 'success',
+ head_pipeline_of: mr_conflict)
end
- before { mr_conflict.update(head_pipeline: conflict_pipeline) }
-
it 'does not merge the merge request' do
expect(MergeWorker).not_to receive(:perform_async)
diff --git a/spec/services/merge_requests/post_merge_service_spec.rb b/spec/services/merge_requests/post_merge_service_spec.rb
new file mode 100644
index 00000000000..a20b32eda5f
--- /dev/null
+++ b/spec/services/merge_requests/post_merge_service_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe MergeRequests::PostMergeService, services: true do
+ let(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request, assignee: user) }
+ let(:project) { merge_request.project }
+
+ before do
+ project.team << [user, :master]
+ end
+
+ describe '#execute' do
+ it_behaves_like 'cache counters invalidator'
+ end
+end
diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb
index a99d4eac9bd..b6d4db2f922 100644
--- a/spec/services/merge_requests/reopen_service_spec.rb
+++ b/spec/services/merge_requests/reopen_service_spec.rb
@@ -14,6 +14,8 @@ describe MergeRequests::ReopenService, services: true do
end
describe '#execute' do
+ it_behaves_like 'cache counters invalidator'
+
context 'valid params' do
let(:service) { described_class.new(project, user, {}) }
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 860a7798857..d371fc68312 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -180,12 +180,13 @@ describe MergeRequests::UpdateService, services: true do
context 'with active pipeline' do
before do
service_mock = double
- pipeline = create(:ci_pipeline_with_one_job,
+ create(
+ :ci_pipeline_with_one_job,
project: project,
- ref: merge_request.source_branch,
- sha: merge_request.diff_head_sha)
-
- merge_request.update(head_pipeline: pipeline)
+ ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha,
+ head_pipeline_of: merge_request
+ )
expect(MergeRequests::MergeWhenPipelineSucceedsService).to receive(:new).with(project, user).
and_return(service_mock)
diff --git a/spec/services/notes/diff_position_update_service_spec.rb b/spec/services/notes/diff_position_update_service_spec.rb
index d73ae51fbc3..380c296fd3a 100644
--- a/spec/services/notes/diff_position_update_service_spec.rb
+++ b/spec/services/notes/diff_position_update_service_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe Notes::DiffPositionUpdateService, services: true do
let(:project) { create(:project, :repository) }
+ let(:current_user) { project.owner }
let(:create_commit) { project.commit("913c66a37b4a45b9769037c55c2d238bd0942d2e") }
let(:modify_commit) { project.commit("874797c3a73b60d2187ed6e2fcabd289ff75171e") }
let(:edit_commit) { project.commit("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") }
@@ -25,7 +26,7 @@ describe Notes::DiffPositionUpdateService, services: true do
subject do
described_class.new(
project,
- nil,
+ current_user,
old_diff_refs: old_diff_refs,
new_diff_refs: new_diff_refs,
paths: [path]
@@ -170,6 +171,23 @@ describe Notes::DiffPositionUpdateService, services: true do
expect(note.original_position).to eq(old_position)
expect(note.position).to eq(old_position)
end
+
+ it 'sets the change position' do
+ subject.execute(note)
+
+ change_position = note.change_position
+ expect(change_position.start_sha).to eq(old_diff_refs.head_sha)
+ expect(change_position.head_sha).to eq(new_diff_refs.head_sha)
+ expect(change_position.old_line).to eq(9)
+ expect(change_position.new_line).to be_nil
+ end
+
+ it 'creates a system note' do
+ expect(SystemNoteService).to receive(:diff_discussion_outdated).with(
+ note.to_discussion, project, current_user, instance_of(Gitlab::Diff::Position))
+
+ subject.execute(note)
+ end
end
end
end
diff --git a/spec/services/projects/propagate_service_template_spec.rb b/spec/services/projects/propagate_service_template_spec.rb
index 90eff3bbc1e..8a6a9f09f74 100644
--- a/spec/services/projects/propagate_service_template_spec.rb
+++ b/spec/services/projects/propagate_service_template_spec.rb
@@ -71,14 +71,18 @@ describe Projects::PropagateServiceTemplate, services: true do
end
describe 'bulk update' do
- it 'creates services for all projects' do
- project_total = 5
+ let(:project_total) { 5 }
+
+ before do
stub_const 'Projects::PropagateServiceTemplate::BATCH_SIZE', 3
project_total.times { create(:empty_project) }
- expect { described_class.propagate(service_template) }.
- to change { Service.count }.by(project_total + 1)
+ described_class.propagate(service_template)
+ end
+
+ it 'creates services for all projects' do
+ expect(Service.all.reload.count).to eq(project_total + 2)
end
end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 29ccce59c53..b957517c715 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -26,6 +26,7 @@ describe Projects::TransferService, services: true do
it { expect(@result).to eq false }
it { expect(project.namespace).to eq(user.namespace) }
+ it { expect(project.errors.messages[:new_namespace].first).to eq 'Please select a new namespace for your project.' }
end
context 'disallow transfering of project with tags' do
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 7a9cd7553b1..5a7cfaff7fb 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -1034,4 +1034,35 @@ describe SystemNoteService, services: true do
expect(subject.note).to eq 'resolved all discussions'
end
end
+
+ describe '.diff_discussion_outdated' do
+ let(:discussion) { create(:diff_note_on_merge_request).to_discussion }
+ let(:merge_request) { discussion.noteable }
+ let(:project) { merge_request.source_project }
+ let(:change_position) { discussion.position }
+
+ def reloaded_merge_request
+ MergeRequest.find(merge_request.id)
+ end
+
+ subject { described_class.diff_discussion_outdated(discussion, project, author, change_position) }
+
+ it_behaves_like 'a system note' do
+ let(:expected_noteable) { discussion.first_note.noteable }
+ let(:action) { 'outdated' }
+ end
+
+ it 'creates a new note in the discussion' do
+ # we need to completely rebuild the merge request object, or the `@discussions` on the merge request are not reloaded.
+ expect { subject }.to change { reloaded_merge_request.discussions.first.notes.size }.by(1)
+ end
+
+ it 'links to the diff in the system note' do
+ expect(subject.note).to include('version 1')
+
+ diff_id = merge_request.merge_request_diff.id
+ line_code = change_position.line_code(project.repository)
+ expect(subject.note).to include(diffs_namespace_project_merge_request_url(project.namespace, project, merge_request, diff_id: diff_id, anchor: line_code))
+ end
+ end
end
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
new file mode 100644
index 00000000000..b5abc46e80c
--- /dev/null
+++ b/spec/services/web_hook_service_spec.rb
@@ -0,0 +1,137 @@
+require 'spec_helper'
+
+describe WebHookService, services: true do
+ let(:project) { create(:empty_project) }
+ let(:project_hook) { create(:project_hook) }
+ let(:headers) do
+ {
+ 'Content-Type' => 'application/json',
+ 'X-Gitlab-Event' => 'Push Hook'
+ }
+ end
+ let(:data) do
+ { before: 'oldrev', after: 'newrev', ref: 'ref' }
+ end
+ let(:service_instance) { WebHookService.new(project_hook, data, 'push_hooks') }
+
+ describe '#execute' do
+ before(:each) do
+ project.hooks << [project_hook]
+
+ WebMock.stub_request(:post, project_hook.url)
+ end
+
+ context 'when token is defined' do
+ let(:project_hook) { create(:project_hook, :token) }
+
+ it 'POSTs to the webhook URL' do
+ service_instance.execute
+ expect(WebMock).to have_requested(:post, project_hook.url).with(
+ headers: headers.merge({ 'X-Gitlab-Token' => project_hook.token })
+ ).once
+ end
+ end
+
+ it 'POSTs to the webhook URL' do
+ service_instance.execute
+ expect(WebMock).to have_requested(:post, project_hook.url).with(
+ headers: headers
+ ).once
+ end
+
+ it 'POSTs the data as JSON' do
+ service_instance.execute
+ expect(WebMock).to have_requested(:post, project_hook.url).with(
+ headers: headers
+ ).once
+ end
+
+ it 'catches exceptions' do
+ WebMock.stub_request(:post, project_hook.url).to_raise(StandardError.new('Some error'))
+
+ expect { service_instance.execute }.to raise_error(StandardError)
+ end
+
+ it 'handles exceptions' do
+ exceptions = [SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout]
+ exceptions.each do |exception_class|
+ exception = exception_class.new('Exception message')
+
+ WebMock.stub_request(:post, project_hook.url).to_raise(exception)
+ expect(service_instance.execute).to eq([nil, exception.message])
+ expect { service_instance.execute }.not_to raise_error
+ end
+ end
+
+ it 'handles 200 status code' do
+ WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: 'Success')
+
+ expect(service_instance.execute).to eq([200, 'Success'])
+ end
+
+ it 'handles 2xx status codes' do
+ WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: 'Success')
+
+ expect(service_instance.execute).to eq([201, 'Success'])
+ end
+
+ context 'execution logging' do
+ let(:hook_log) { project_hook.web_hook_logs.last }
+
+ context 'with success' do
+ before do
+ WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: 'Success')
+ service_instance.execute
+ end
+
+ it 'log successful execution' do
+ expect(hook_log.trigger).to eq('push_hooks')
+ expect(hook_log.url).to eq(project_hook.url)
+ expect(hook_log.request_headers).to eq(headers)
+ expect(hook_log.response_body).to eq('Success')
+ expect(hook_log.response_status).to eq('200')
+ expect(hook_log.execution_duration).to be > 0
+ expect(hook_log.internal_error_message).to be_nil
+ end
+ end
+
+ context 'with exception' do
+ before do
+ WebMock.stub_request(:post, project_hook.url).to_raise(SocketError.new('Some HTTP Post error'))
+ service_instance.execute
+ end
+
+ it 'log failed execution' do
+ expect(hook_log.trigger).to eq('push_hooks')
+ expect(hook_log.url).to eq(project_hook.url)
+ expect(hook_log.request_headers).to eq(headers)
+ expect(hook_log.response_body).to eq('')
+ expect(hook_log.response_status).to eq('internal error')
+ expect(hook_log.execution_duration).to be > 0
+ expect(hook_log.internal_error_message).to eq('Some HTTP Post error')
+ end
+ end
+
+ context 'should not log ServiceHooks' do
+ let(:service_hook) { create(:service_hook) }
+ let(:service_instance) { WebHookService.new(service_hook, data, 'service_hook') }
+
+ before do
+ WebMock.stub_request(:post, service_hook.url).to_return(status: 200, body: 'Success')
+ end
+
+ it { expect { service_instance.execute }.not_to change(WebHookLog, :count) }
+ end
+ end
+ end
+
+ describe '#async_execute' do
+ let(:system_hook) { create(:system_hook) }
+
+ it 'enqueue WebHookWorker' do
+ expect(Sidekiq::Client).to receive(:enqueue).with(WebHookWorker, project_hook.id, data, 'push_hooks')
+
+ WebHookService.new(project_hook, data, 'push_hooks').async_execute
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index a58f4e664b7..51571ddebe9 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -44,7 +44,6 @@ RSpec.configure do |config|
config.include LoginHelpers, type: :feature
config.include SearchHelpers, type: :feature
config.include WaitForRequests, :js
- config.include WaitForAjax, :js
config.include StubConfiguration
config.include EmailHelpers, type: :mailer
config.include TestEnv
diff --git a/spec/support/controllers/githubish_import_controller_shared_examples.rb b/spec/support/controllers/githubish_import_controller_shared_examples.rb
index c59b30c772d..d6b40db09ce 100644
--- a/spec/support/controllers/githubish_import_controller_shared_examples.rb
+++ b/spec/support/controllers/githubish_import_controller_shared_examples.rb
@@ -209,9 +209,13 @@ shared_examples 'a GitHub-ish import controller: POST create' do
end
context 'user has chosen a namespace and name for the project' do
- let(:test_namespace) { create(:namespace, name: 'test_namespace', owner: user) }
+ let(:test_namespace) { create(:group, name: 'test_namespace') }
let(:test_name) { 'test_name' }
+ before do
+ test_namespace.add_owner(user)
+ end
+
it 'takes the selected namespace and name' do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(provider_repo, test_name, test_namespace, user, access_params, type: provider).
@@ -230,10 +234,14 @@ shared_examples 'a GitHub-ish import controller: POST create' do
end
context 'user has chosen an existing nested namespace and name for the project' do
- let(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
- let(:nested_namespace) { create(:namespace, name: 'bar', parent: parent_namespace, owner: user) }
+ let(:parent_namespace) { create(:group, name: 'foo', owner: user) }
+ let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
let(:test_name) { 'test_name' }
+ before do
+ nested_namespace.add_owner(user)
+ end
+
it 'takes the selected namespace and name' do
expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(provider_repo, test_name, nested_namespace, user, access_params, type: provider).
@@ -276,7 +284,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
context 'user has chosen existent and non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
- let!(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
+ let!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
it 'takes the selected namespace and name' do
expect(Gitlab::GithubImport::ProjectCreator).
diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb
index ad46b163cd6..fa82dc5e9f9 100644
--- a/spec/support/features/issuable_slash_commands_shared_examples.rb
+++ b/spec/support/features/issuable_slash_commands_shared_examples.rb
@@ -22,7 +22,7 @@ shared_examples 'issuable record that supports slash commands in its description
after do
# Ensure all outstanding Ajax requests are complete to avoid database deadlocks
- wait_for_ajax
+ wait_for_requests
end
describe "new #{issuable_type}", js: true do
@@ -58,7 +58,7 @@ shared_examples 'issuable record that supports slash commands in its description
expect(page).not_to have_content '/label ~bug'
expect(page).not_to have_content '/milestone %"ASAP"'
- wait_for_ajax
+ wait_for_requests
issuable.reload
note = issuable.notes.user.first
diff --git a/spec/support/features/rss_shared_examples.rb b/spec/support/features/rss_shared_examples.rb
index 9a3b0a731ad..1cbb4134995 100644
--- a/spec/support/features/rss_shared_examples.rb
+++ b/spec/support/features/rss_shared_examples.rb
@@ -1,23 +1,23 @@
-shared_examples "an autodiscoverable RSS feed with current_user's private token" do
- it "has an RSS autodiscovery link tag with current_user's private token" do
- expect(page).to have_css("link[type*='atom+xml'][href*='private_token=#{Thread.current[:current_user].private_token}']", visible: false)
+shared_examples "an autodiscoverable RSS feed with current_user's RSS token" do
+ it "has an RSS autodiscovery link tag with current_user's RSS token" do
+ expect(page).to have_css("link[type*='atom+xml'][href*='rss_token=#{Thread.current[:current_user].rss_token}']", visible: false)
end
end
-shared_examples "it has an RSS button with current_user's private token" do
- it "shows the RSS button with current_user's private token" do
- expect(page).to have_css("a:has(.fa-rss)[href*='private_token=#{Thread.current[:current_user].private_token}']")
+shared_examples "it has an RSS button with current_user's RSS token" do
+ it "shows the RSS button with current_user's RSS token" do
+ expect(page).to have_css("a:has(.fa-rss)[href*='rss_token=#{Thread.current[:current_user].rss_token}']")
end
end
-shared_examples "an autodiscoverable RSS feed without a private token" do
- it "has an RSS autodiscovery link tag without a private token" do
- expect(page).to have_css("link[type*='atom+xml']:not([href*='private_token'])", visible: false)
+shared_examples "an autodiscoverable RSS feed without an RSS token" do
+ it "has an RSS autodiscovery link tag without an RSS token" do
+ expect(page).to have_css("link[type*='atom+xml']:not([href*='rss_token'])", visible: false)
end
end
-shared_examples "it has an RSS button without a private token" do
- it "shows the RSS button without a private token" do
- expect(page).to have_css("a:has(.fa-rss):not([href*='private_token'])")
+shared_examples "it has an RSS button without an RSS token" do
+ it "shows the RSS button without an RSS token" do
+ expect(page).to have_css("a:has(.fa-rss):not([href*='rss_token'])")
end
end
diff --git a/spec/support/issuable_shared_examples.rb b/spec/support/issuable_shared_examples.rb
new file mode 100644
index 00000000000..03011535351
--- /dev/null
+++ b/spec/support/issuable_shared_examples.rb
@@ -0,0 +1,7 @@
+shared_examples 'cache counters invalidator' do
+ it 'invalidates counter cache for assignees' do
+ expect_any_instance_of(User).to receive(:invalidate_merge_request_cache_counts)
+
+ described_class.new(project, user, {}).execute(merge_request)
+ end
+end
diff --git a/spec/support/kubernetes_helpers.rb b/spec/support/kubernetes_helpers.rb
index d2a1ded57ff..9280fad4ace 100644
--- a/spec/support/kubernetes_helpers.rb
+++ b/spec/support/kubernetes_helpers.rb
@@ -41,7 +41,7 @@ module KubernetesHelpers
containers.map do |container|
terminal = {
selectors: { pod: pod_name, container: container['name'] },
- url: container_exec_url(service.api_url, service.namespace, pod_name, container['name']),
+ url: container_exec_url(service.api_url, service.actual_namespace, pod_name, container['name']),
subprotocols: ['channel.k8s.io'],
headers: { 'Authorization' => ["Bearer #{service.token}"] },
created_at: DateTime.parse(pod['metadata']['creationTimestamp']),
diff --git a/spec/support/protected_branches/access_control_ce_shared_examples.rb b/spec/support/protected_branches/access_control_ce_shared_examples.rb
index 7fda4ade665..287d6bb13c3 100644
--- a/spec/support/protected_branches/access_control_ce_shared_examples.rb
+++ b/spec/support/protected_branches/access_control_ce_shared_examples.rb
@@ -38,7 +38,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
end
end
- wait_for_ajax
+ wait_for_requests
expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to include(access_type_id)
end
@@ -83,7 +83,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
end
end
- wait_for_ajax
+ wait_for_requests
expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to include(access_type_id)
end
diff --git a/spec/support/protected_tags/access_control_ce_shared_examples.rb b/spec/support/protected_tags/access_control_ce_shared_examples.rb
index 12622cd548a..1d11512ef82 100644
--- a/spec/support/protected_tags/access_control_ce_shared_examples.rb
+++ b/spec/support/protected_tags/access_control_ce_shared_examples.rb
@@ -39,7 +39,7 @@ RSpec.shared_examples "protected tags > access control > CE" do
end
end
- wait_for_ajax
+ wait_for_requests
expect(ProtectedTag.last.create_access_levels.map(&:access_level)).to include(access_type_id)
end
diff --git a/spec/support/snippets_shared_examples.rb b/spec/support/snippets_shared_examples.rb
index 57dfff3471f..85f0facd5c3 100644
--- a/spec/support/snippets_shared_examples.rb
+++ b/spec/support/snippets_shared_examples.rb
@@ -7,7 +7,7 @@ RSpec.shared_examples 'paginated snippets' do |remote: false|
context 'clicking on the link to the second page' do
before do
click_link('2')
- wait_for_ajax if remote
+ wait_for_requests if remote
end
it 'shows the remaining snippets' do
diff --git a/spec/support/target_branch_helpers.rb b/spec/support/target_branch_helpers.rb
index 3ee8f0f657e..01d1c53fe6c 100644
--- a/spec/support/target_branch_helpers.rb
+++ b/spec/support/target_branch_helpers.rb
@@ -1,7 +1,7 @@
module TargetBranchHelpers
def select_branch(name)
first('button.js-target-branch').click
- wait_for_ajax
+ wait_for_requests
all('a[data-group="Branches"]').find do |el|
el.text == name
end.click
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index b168098edea..3c000feba5d 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -123,7 +123,7 @@ module TestEnv
socket_path = Gitlab::GitalyClient.address('default').sub(/\Aunix:/, '')
gitaly_dir = File.dirname(socket_path)
- unless File.directory?(gitaly_dir) || system('rake', "gitlab:gitaly:install[#{gitaly_dir}]")
+ unless !gitaly_needs_update?(gitaly_dir) || system('rake', "gitlab:gitaly:install[#{gitaly_dir}]")
raise "Can't clone gitaly"
end
@@ -252,4 +252,15 @@ module TestEnv
cleanup && init unless reset.call
end
end
+
+ def gitaly_needs_update?(gitaly_dir)
+ gitaly_version = File.read(File.join(gitaly_dir, 'VERSION')).strip
+
+ # Notice that this will always yield true when using branch versions
+ # (`=branch_name`), but that actually makes sure the server is always based
+ # on the latest branch revision.
+ gitaly_version != Gitlab::GitalyClient.expected_server_version
+ rescue Errno::ENOENT
+ true
+ end
end
diff --git a/spec/support/time_tracking_shared_examples.rb b/spec/support/time_tracking_shared_examples.rb
index 84ef46ffa27..b407b8097d2 100644
--- a/spec/support/time_tracking_shared_examples.rb
+++ b/spec/support/time_tracking_shared_examples.rb
@@ -8,7 +8,7 @@ shared_examples 'issuable time tracker' do
it 'updates the sidebar component when estimate is added' do
submit_time('/estimate 3w 1d 1h')
- wait_for_ajax
+ wait_for_requests
page.within '.time-tracking-estimate-only-pane' do
expect(page).to have_content '3w 1d 1h'
end
@@ -17,7 +17,7 @@ shared_examples 'issuable time tracker' do
it 'updates the sidebar component when spent is added' do
submit_time('/spend 3w 1d 1h')
- wait_for_ajax
+ wait_for_requests
page.within '.time-tracking-spend-only-pane' do
expect(page).to have_content '3w 1d 1h'
end
@@ -27,7 +27,7 @@ shared_examples 'issuable time tracker' do
submit_time('/estimate 3w 1d 1h')
submit_time('/spend 3w 1d 1h')
- wait_for_ajax
+ wait_for_requests
page.within '.time-tracking-comparison-pane' do
expect(page).to have_content '3w 1d 1h'
end
@@ -81,5 +81,5 @@ end
def submit_time(slash_command)
fill_in 'note[note]', with: slash_command
find('.js-comment-submit-button').trigger('click')
- wait_for_ajax
+ wait_for_requests
end
diff --git a/spec/support/wait_for_ajax.rb b/spec/support/wait_for_ajax.rb
deleted file mode 100644
index 508de2ee8e1..00000000000
--- a/spec/support/wait_for_ajax.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-module WaitForAjax
- def wait_for_ajax
- Timeout.timeout(Capybara.default_max_wait_time) do
- loop until finished_all_ajax_requests?
- end
- end
-
- def finished_all_ajax_requests?
- return true unless javascript_test?
- return true if page.evaluate_script('typeof jQuery === "undefined"')
-
- page.evaluate_script('jQuery.active').zero?
- end
-
- def javascript_test?
- Capybara.current_driver == Capybara.javascript_driver
- end
-end
diff --git a/spec/support/wait_for_requests.rb b/spec/support/wait_for_requests.rb
index d41e83ae128..05ec9026141 100644
--- a/spec/support/wait_for_requests.rb
+++ b/spec/support/wait_for_requests.rb
@@ -1,21 +1,31 @@
-require_relative './wait_for_ajax'
-require_relative './wait_for_vue_resource'
+require_relative './wait_for_requests'
module WaitForRequests
extend self
- include WaitForAjax
- include WaitForVueResource
# This is inspired by http://www.salsify.com/blog/engineering/tearing-capybara-ajax-tests
- def wait_for_requests_complete
+ def block_and_wait_for_requests_complete
Gitlab::Testing::RequestBlockerMiddleware.block_requests!
- wait_for('pending AJAX requests complete') do
+ wait_for('pending requests complete') do
Gitlab::Testing::RequestBlockerMiddleware.num_active_requests.zero?
end
ensure
Gitlab::Testing::RequestBlockerMiddleware.allow_requests!
end
+ def wait_for_requests
+ wait_for('JS requests') { finished_all_requests? }
+ end
+
+ private
+
+ def finished_all_requests?
+ return true unless javascript_test?
+
+ finished_all_ajax_requests? &&
+ finished_all_vue_resource_requests?
+ end
+
# Waits until the passed block returns true
def wait_for(condition_name, max_wait_time: Capybara.default_max_wait_time, polling_interval: 0.01)
wait_until = Time.now + max_wait_time.seconds
@@ -28,10 +38,24 @@ module WaitForRequests
end
end
end
+
+ def finished_all_vue_resource_requests?
+ page.evaluate_script('window.activeVueResources || 0').zero?
+ end
+
+ def finished_all_ajax_requests?
+ return true if page.evaluate_script('typeof jQuery === "undefined"')
+
+ page.evaluate_script('jQuery.active').zero?
+ end
+
+ def javascript_test?
+ Capybara.current_driver == Capybara.javascript_driver
+ end
end
RSpec.configure do |config|
config.after(:each, :js) do
- wait_for_requests_complete
+ block_and_wait_for_requests_complete
end
end
diff --git a/spec/support/wait_for_vue_resource.rb b/spec/support/wait_for_vue_resource.rb
deleted file mode 100644
index 3bb3d9c2e51..00000000000
--- a/spec/support/wait_for_vue_resource.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-module WaitForVueResource
- def wait_for_vue_resource(spinner: true)
- Timeout.timeout(Capybara.default_max_wait_time) do
- loop until finished_all_vue_resource_requests?
- end
- end
-
- private
-
- def finished_all_vue_resource_requests?
- return true unless javascript_test?
-
- page.evaluate_script('window.activeVueResources || 0').zero?
- end
-
- def javascript_test?
- Capybara.current_driver == Capybara.javascript_driver
- end
-end
diff --git a/spec/tasks/tokens_spec.rb b/spec/tasks/tokens_spec.rb
index 19036c7677c..b84137eb365 100644
--- a/spec/tasks/tokens_spec.rb
+++ b/spec/tasks/tokens_spec.rb
@@ -18,4 +18,10 @@ describe 'tokens rake tasks' do
expect { run_rake_task('tokens:reset_all_email') }.to change { user.reload.incoming_email_token }
end
end
+
+ describe 'reset_all_rss task' do
+ it 'invokes create_hooks task' do
+ expect { run_rake_task('tokens:reset_all_rss') }.to change { user.reload.rss_token }
+ end
+ end
end
diff --git a/spec/validators/dynamic_path_validator_spec.rb b/spec/validators/dynamic_path_validator_spec.rb
index b114bfc1bca..5f998e78f07 100644
--- a/spec/validators/dynamic_path_validator_spec.rb
+++ b/spec/validators/dynamic_path_validator_spec.rb
@@ -3,246 +3,46 @@ require 'spec_helper'
describe DynamicPathValidator do
let(:validator) { described_class.new(attributes: [:path]) }
- # Pass in a full path to remove the format segment:
- # `/ci/lint(.:format)` -> `/ci/lint`
- def without_format(path)
- path.split('(', 2)[0]
- end
-
- # Pass in a full path and get the last segment before a wildcard
- # That's not a parameter
- # `/*namespace_id/:project_id/builds/artifacts/*ref_name_and_path`
- # -> 'builds/artifacts'
- def path_before_wildcard(path)
- path = path.gsub(STARTING_WITH_NAMESPACE, "")
- path_segments = path.split('/').reject(&:empty?)
- wildcard_index = path_segments.index { |segment| parameter?(segment) }
-
- segments_before_wildcard = path_segments[0..wildcard_index - 1]
-
- segments_before_wildcard.join('/')
- end
-
- def parameter?(segment)
- segment =~ /[*:]/
- end
-
- # If the path is reserved. Then no conflicting paths can# be created for any
- # route using this reserved word.
- #
- # Both `builds/artifacts` & `build` are covered by reserving the word
- # `build`
- def wildcards_include?(path)
- described_class::WILDCARD_ROUTES.include?(path) ||
- described_class::WILDCARD_ROUTES.include?(path.split('/').first)
- end
-
- def failure_message(missing_words, constant_name, migration_helper)
- missing_words = Array(missing_words)
- <<-MSG
- Found new routes that could cause conflicts with existing namespaced routes
- for groups or projects.
+ describe '#path_valid_for_record?' do
+ context 'for project' do
+ it 'calls valid_project_path?' do
+ project = build(:project, path: 'activity')
- Add <#{missing_words.join(', ')}> to `DynamicPathValidator::#{constant_name}
- to make sure no projects or namespaces can be created with those paths.
+ expect(described_class).to receive(:valid_project_path?).with(project.full_path).and_call_original
- To rename any existing records with those paths you can use the
- `Gitlab::Database::RenameReservedpathsMigration::<VERSION>.#{migration_helper}`
- migration helper.
-
- Make sure to make a note of the renamed records in the release blog post.
-
- MSG
- end
-
- let(:all_routes) do
- Rails.application.routes.routes.routes.
- map { |r| r.path.spec.to_s }
- end
-
- let(:routes_without_format) { all_routes.map { |path| without_format(path) } }
-
- # Routes not starting with `/:` or `/*`
- # all routes not starting with a param
- let(:routes_not_starting_in_wildcard) { routes_without_format.select { |p| p !~ %r{^/[:*]} } }
-
- let(:top_level_words) do
- routes_not_starting_in_wildcard.map do |route|
- route.split('/')[1]
- end.compact.uniq
- end
-
- # All routes that start with a namespaced path, that have 1 or more
- # path-segments before having another wildcard parameter.
- # - Starting with paths:
- # - `/*namespace_id/:project_id/`
- # - `/*namespace_id/:id/`
- # - Followed by one or more path-parts not starting with `:` or `*`
- # - Followed by a path-part that includes a wildcard parameter `*`
- # At the time of writing these routes match: http://rubular.com/r/Rv2pDE5Dvw
- STARTING_WITH_NAMESPACE = %r{^/\*namespace_id/:(project_)?id}
- NON_PARAM_PARTS = %r{[^:*][a-z\-_/]*}
- ANY_OTHER_PATH_PART = %r{[a-z\-_/:]*}
- WILDCARD_SEGMENT = %r{\*}
- let(:namespaced_wildcard_routes) do
- routes_without_format.select do |p|
- p =~ %r{#{STARTING_WITH_NAMESPACE}/#{NON_PARAM_PARTS}/#{ANY_OTHER_PATH_PART}#{WILDCARD_SEGMENT}}
- end
- end
-
- # This will return all paths that are used in a namespaced route
- # before another wildcard path:
- #
- # /*namespace_id/:project_id/builds/artifacts/*ref_name_and_path
- # /*namespace_id/:project_id/info/lfs/objects/*oid
- # /*namespace_id/:project_id/commits/*id
- # /*namespace_id/:project_id/builds/:build_id/artifacts/file/*path
- # -> ['builds/artifacts', 'info/lfs/objects', 'commits', 'artifacts/file']
- let(:all_wildcard_paths) do
- namespaced_wildcard_routes.map do |route|
- path_before_wildcard(route)
- end.uniq
- end
-
- STARTING_WITH_GROUP = %r{^/groups/\*(group_)?id/}
- let(:group_routes) do
- routes_without_format.select do |path|
- path =~ STARTING_WITH_GROUP
- end
- end
-
- let(:paths_after_group_id) do
- group_routes.map do |route|
- route.gsub(STARTING_WITH_GROUP, '').split('/').first
- end.uniq
- end
-
- describe 'TOP_LEVEL_ROUTES' do
- it 'includes all the top level namespaces' do
- failure_block = lambda do
- missing_words = top_level_words - described_class::TOP_LEVEL_ROUTES
- failure_message(missing_words, 'TOP_LEVEL_ROUTES', 'rename_root_paths')
+ expect(validator.path_valid_for_record?(project, 'activity')).to be_truthy
end
-
- expect(described_class::TOP_LEVEL_ROUTES)
- .to include(*top_level_words), failure_block
end
- end
- describe 'GROUP_ROUTES' do
- it "don't contain a second wildcard" do
- failure_block = lambda do
- missing_words = paths_after_group_id - described_class::GROUP_ROUTES
- failure_message(missing_words, 'GROUP_ROUTES', 'rename_child_paths')
- end
+ context 'for group' do
+ it 'calls valid_group_path?' do
+ group = build(:group, :nested, path: 'activity')
- expect(described_class::GROUP_ROUTES)
- .to include(*paths_after_group_id), failure_block
- end
- end
+ expect(described_class).to receive(:valid_group_path?).with(group.full_path).and_call_original
- describe 'WILDCARD_ROUTES' do
- it 'includes all paths that can be used after a namespace/project path' do
- aggregate_failures do
- all_wildcard_paths.each do |path|
- expect(wildcards_include?(path))
- .to be(true), failure_message(path, 'WILDCARD_ROUTES', 'rename_wildcard_paths')
- end
+ expect(validator.path_valid_for_record?(group, 'activity')).to be_falsey
end
end
- end
- describe '.without_reserved_wildcard_paths_regex' do
- subject { described_class.without_reserved_wildcard_paths_regex }
+ context 'for user' do
+ it 'calls valid_user_path?' do
+ user = build(:user, username: 'activity')
- it 'rejects paths starting with a reserved top level' do
- expect(subject).not_to match('dashboard/hello/world')
- expect(subject).not_to match('dashboard')
- end
+ expect(described_class).to receive(:valid_user_path?).with(user.full_path).and_call_original
- it 'matches valid paths with a toplevel word in a different place' do
- expect(subject).to match('parent/dashboard/project-path')
- end
-
- it 'rejects paths containing a wildcard reserved word' do
- expect(subject).not_to match('hello/edit')
- expect(subject).not_to match('hello/edit/in-the-middle')
- expect(subject).not_to match('foo/bar1/refs/master/logs_tree')
- end
-
- it 'matches valid paths' do
- expect(subject).to match('parent/child/project-path')
- end
- end
-
- describe '.regex_excluding_child_paths' do
- let(:subject) { described_class.without_reserved_child_paths_regex }
-
- it 'rejects paths containing a child reserved word' do
- expect(subject).not_to match('hello/group_members')
- expect(subject).not_to match('hello/activity/in-the-middle')
- expect(subject).not_to match('foo/bar1/refs/master/logs_tree')
- end
-
- it 'allows a child path on the top level' do
- expect(subject).to match('activity/foo')
- expect(subject).to match('avatar')
- end
- end
-
- describe ".valid?" do
- it 'is not case sensitive' do
- expect(described_class.valid?("Users")).to be_falsey
- end
-
- it "isn't valid when the top level is reserved" do
- test_path = 'u/should-be-a/reserved-word'
-
- expect(described_class.valid?(test_path)).to be_falsey
- end
-
- it "isn't valid if any of the path segments is reserved" do
- test_path = 'the-wildcard/wikis/is-not-allowed'
-
- expect(described_class.valid?(test_path)).to be_falsey
- end
-
- it "is valid if the path doesn't contain reserved words" do
- test_path = 'there-are/no-wildcards/in-this-path'
-
- expect(described_class.valid?(test_path)).to be_truthy
- end
-
- it 'allows allows a child path on the last spot' do
- test_path = 'there/can-be-a/project-called/labels'
-
- expect(described_class.valid?(test_path)).to be_truthy
- end
-
- it 'rejects a child path somewhere else' do
- test_path = 'there/can-be-no/labels/group'
-
- expect(described_class.valid?(test_path)).to be_falsey
+ expect(validator.path_valid_for_record?(user, 'activity')).to be_truthy
+ end
end
- it 'rejects paths that are in an incorrect format' do
- test_path = 'incorrect/format.git'
-
- expect(described_class.valid?(test_path)).to be_falsey
- end
- end
+ context 'for user namespace' do
+ it 'calls valid_user_path?' do
+ user = create(:user, username: 'activity')
+ namespace = user.namespace
- describe '#path_reserved_for_record?' do
- it 'reserves a sub-group named activity' do
- group = build(:group, :nested, path: 'activity')
+ expect(described_class).to receive(:valid_user_path?).with(namespace.full_path).and_call_original
- expect(validator.path_reserved_for_record?(group, 'activity')).to be_truthy
- end
-
- it "doesn't reserve a project called activity" do
- project = build(:project, path: 'activity')
-
- expect(validator.path_reserved_for_record?(project, 'activity')).to be_falsey
+ expect(validator.path_valid_for_record?(namespace, 'activity')).to be_truthy
+ end
end
end
@@ -252,7 +52,7 @@ describe DynamicPathValidator do
validator.validate_each(group, :path, "Path with spaces, and comma's!")
- expect(group.errors[:path]).to include(Gitlab::Regex.namespace_regex_message)
+ expect(group.errors[:path]).to include(Gitlab::PathRegex.namespace_format_message)
end
it 'adds a message when the path is not in the correct format' do
diff --git a/spec/workers/expire_job_cache_worker_spec.rb b/spec/workers/expire_job_cache_worker_spec.rb
new file mode 100644
index 00000000000..1b614342a18
--- /dev/null
+++ b/spec/workers/expire_job_cache_worker_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe ExpireJobCacheWorker do
+ set(:pipeline) { create(:ci_empty_pipeline) }
+ let(:project) { pipeline.project }
+ subject { described_class.new }
+
+ describe '#perform' do
+ context 'with a job in the pipeline' do
+ let(:job) { create(:ci_build, pipeline: pipeline) }
+
+ it 'invalidates Etag caching for the job path' do
+ pipeline_path = "/#{project.full_path}/pipelines/#{pipeline.id}.json"
+ job_path = "/#{project.full_path}/builds/#{job.id}.json"
+
+ expect_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch).with(pipeline_path)
+ expect_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch).with(job_path)
+
+ subject.perform(job.id)
+ end
+ end
+
+ context 'when there is no job in the pipeline' do
+ it 'does not change the etag store' do
+ expect(Gitlab::EtagCaching::Store).not_to receive(:new)
+
+ subject.perform(9999)
+ end
+ end
+ end
+end
diff --git a/spec/workers/expire_pipeline_cache_worker_spec.rb b/spec/workers/expire_pipeline_cache_worker_spec.rb
index ceba604dea2..28e5b706803 100644
--- a/spec/workers/expire_pipeline_cache_worker_spec.rb
+++ b/spec/workers/expire_pipeline_cache_worker_spec.rb
@@ -10,9 +10,11 @@ describe ExpirePipelineCacheWorker do
it 'invalidates Etag caching for project pipelines path' do
pipelines_path = "/#{project.full_path}/pipelines.json"
new_mr_pipelines_path = "/#{project.full_path}/merge_requests/new.json"
+ pipeline_path = "/#{project.full_path}/pipelines/#{pipeline.id}.json"
expect_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch).with(pipelines_path)
expect_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch).with(new_mr_pipelines_path)
+ expect_any_instance_of(Gitlab::EtagCaching::Store).to receive(:touch).with(pipeline_path)
subject.perform(pipeline.id)
end
diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb
index 6295856b461..4e036285e8c 100644
--- a/spec/workers/process_commit_worker_spec.rb
+++ b/spec/workers/process_commit_worker_spec.rb
@@ -20,14 +20,6 @@ describe ProcessCommitWorker do
worker.perform(project.id, -1, commit.to_hash)
end
- it 'does not process the commit when no issues are referenced' do
- allow(worker).to receive(:build_commit).and_return(double(matches_cross_reference_regex?: false))
-
- expect(worker).not_to receive(:process_commit_message)
-
- worker.perform(project.id, user.id, commit.to_hash)
- end
-
it 'processes the commit message' do
expect(worker).to receive(:process_commit_message).and_call_original
@@ -39,6 +31,18 @@ describe ProcessCommitWorker do
worker.perform(project.id, user.id, commit.to_hash)
end
+
+ context 'when commit already exists in upstream project' do
+ let(:forked) { create(:project, :public) }
+
+ it 'does not process commit message' do
+ create(:forked_project_link, forked_to_project: forked, forked_from_project: project)
+
+ expect(worker).not_to receive(:process_commit_message)
+
+ worker.perform(forked.id, user.id, forked.commit.to_hash)
+ end
+ end
end
describe '#process_commit_message' do
diff --git a/spec/workers/remove_old_web_hook_logs_worker_spec.rb b/spec/workers/remove_old_web_hook_logs_worker_spec.rb
new file mode 100644
index 00000000000..6d26ba5dfa0
--- /dev/null
+++ b/spec/workers/remove_old_web_hook_logs_worker_spec.rb
@@ -0,0 +1,18 @@
+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/yarn.lock b/yarn.lock
index 8aac2b1b1cd..8221711960d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -25,7 +25,7 @@ acorn-jsx@^3.0.0:
dependencies:
acorn "^3.0.4"
-acorn@4.0.4:
+acorn@4.0.4, acorn@^4.0.3:
version "4.0.4"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.4.tgz#17a8d6a7a6c4ef538b814ec9abac2779293bf30a"
@@ -33,9 +33,9 @@ acorn@^3.0.4:
version "3.3.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
-acorn@^4.0.11, acorn@^4.0.3, acorn@^4.0.4:
- version "4.0.11"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.11.tgz#edcda3bd937e7556410d42ed5860f67399c794c0"
+acorn@^5.0.0, acorn@^5.0.3:
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.0.3.tgz#c460df08491463f028ccb82eab3730bf01087b3d"
after@0.8.2:
version "0.8.2"
@@ -1560,6 +1560,12 @@ debug@2.6.0, debug@^2.1.0, debug@^2.1.1, debug@^2.2.0:
dependencies:
ms "0.7.2"
+debug@2.6.7:
+ version "2.6.7"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e"
+ dependencies:
+ ms "2.0.0"
+
decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@@ -1612,7 +1618,7 @@ delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
-depd@~1.1.0:
+depd@1.1.0, depd@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3"
@@ -1733,7 +1739,7 @@ ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
-ejs@^2.5.5:
+ejs@^2.5.6:
version "2.5.6"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.6.tgz#479636bfa3fe3b1debd52087f0acb204b4f19c88"
@@ -2081,9 +2087,9 @@ esutils@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
-etag@~1.7.0:
- version "1.7.0"
- resolved "https://registry.yarnpkg.com/etag/-/etag-1.7.0.tgz#03d30b5f67dd6e632d2945d30d6652731a34d5d8"
+etag@~1.8.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.0.tgz#6f631aef336d6c46362b51764044ce216be3c051"
eve-raphael@0.5.0:
version "0.5.0"
@@ -2166,9 +2172,9 @@ exports-loader@^0.6.4:
loader-utils "^1.0.2"
source-map "0.5.x"
-express@^4.13.3, express@^4.14.1:
- version "4.14.1"
- resolved "https://registry.yarnpkg.com/express/-/express-4.14.1.tgz#646c237f766f148c2120aff073817b9e4d7e0d33"
+express@^4.13.3, express@^4.15.2:
+ version "4.15.3"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.15.3.tgz#bab65d0f03aa80c358408972fc700f916944b662"
dependencies:
accepts "~1.3.3"
array-flatten "1.1.1"
@@ -2176,26 +2182,28 @@ express@^4.13.3, express@^4.14.1:
content-type "~1.0.2"
cookie "0.3.1"
cookie-signature "1.0.6"
- debug "~2.2.0"
+ debug "2.6.7"
depd "~1.1.0"
encodeurl "~1.0.1"
escape-html "~1.0.3"
- etag "~1.7.0"
- finalhandler "0.5.1"
- fresh "0.3.0"
+ etag "~1.8.0"
+ finalhandler "~1.0.3"
+ fresh "0.5.0"
merge-descriptors "1.0.1"
methods "~1.1.2"
on-finished "~2.3.0"
parseurl "~1.3.1"
path-to-regexp "0.1.7"
- proxy-addr "~1.1.3"
- qs "6.2.0"
+ proxy-addr "~1.1.4"
+ qs "6.4.0"
range-parser "~1.2.0"
- send "0.14.2"
- serve-static "~1.11.2"
- type-is "~1.6.14"
+ send "0.15.3"
+ serve-static "1.12.3"
+ setprototypeof "1.0.3"
+ statuses "~1.3.1"
+ type-is "~1.6.15"
utils-merge "1.0.0"
- vary "~1.1.0"
+ vary "~1.1.1"
extend@^3.0.0, extend@~3.0.0:
version "3.0.0"
@@ -2287,9 +2295,9 @@ filesize@3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.3.0.tgz#53149ea3460e3b2e024962a51648aa572cf98122"
-filesize@^3.5.4:
- version "3.5.4"
- resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.4.tgz#742fc7fb6aef4ee3878682600c22f840731e1fda"
+filesize@^3.5.9:
+ version "3.5.10"
+ resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.10.tgz#fc8fa23ddb4ef9e5e0ab6e1e64f679a24a56761f"
fill-range@^2.1.0:
version "2.2.3"
@@ -2311,13 +2319,15 @@ finalhandler@0.5.0:
statuses "~1.3.0"
unpipe "~1.0.0"
-finalhandler@0.5.1:
- version "0.5.1"
- resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.5.1.tgz#2c400d8d4530935bc232549c5fa385ec07de6fcd"
+finalhandler@~1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.3.tgz#ef47e77950e999780e86022a560e3217e0d0cc89"
dependencies:
- debug "~2.2.0"
+ debug "2.6.7"
+ encodeurl "~1.0.1"
escape-html "~1.0.3"
on-finished "~2.3.0"
+ parseurl "~1.3.1"
statuses "~1.3.1"
unpipe "~1.0.0"
@@ -2385,9 +2395,9 @@ forwarded@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363"
-fresh@0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.3.0.tgz#651f838e22424e7566de161d8358caa199f83d4f"
+fresh@0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.0.tgz#f474ca5e6a9246d6fd8e0953cfa9b9c805afa78e"
from@~0:
version "0.1.7"
@@ -2689,6 +2699,15 @@ http-errors@~1.5.0, http-errors@~1.5.1:
setprototypeof "1.0.2"
statuses ">= 1.3.1 < 2"
+http-errors@~1.6.1:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.1.tgz#5f8b8ed98aca545656bf572997387f904a722257"
+ dependencies:
+ depd "1.1.0"
+ inherits "2.0.3"
+ setprototypeof "1.0.3"
+ statuses ">= 1.3.1 < 2"
+
http-proxy-middleware@~0.17.4:
version "0.17.4"
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz#642e8848851d66f09d4f124912846dbaeb41b833"
@@ -2808,9 +2827,9 @@ invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
-ipaddr.js@1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.2.0.tgz#8aba49c9192799585bdd643e0ccb50e8ae777ba4"
+ipaddr.js@1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec"
is-absolute-url@^2.0.0:
version "2.1.0"
@@ -3198,7 +3217,7 @@ json3@3.3.2, json3@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
-json5@^0.5.0:
+json5@^0.5.0, json5@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
@@ -3643,7 +3662,17 @@ miller-rabin@^4.0.0:
version "1.26.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.26.0.tgz#eaffcd0e4fc6935cf8134da246e2e6c35305adff"
-mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.13, mime-types@~2.1.7:
+mime-db@~1.27.0:
+ version "1.27.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1"
+
+mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.7:
+ version "2.1.15"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed"
+ dependencies:
+ mime-db "~1.27.0"
+
+mime-types@~2.1.11:
version "2.1.14"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.14.tgz#f7ef7d97583fcaf3b7d282b6f8b5679dab1e94ee"
dependencies:
@@ -3699,10 +3728,18 @@ ms@0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+
mute-stream@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
+name-all-modules-plugin@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/name-all-modules-plugin/-/name-all-modules-plugin-1.0.1.tgz#0abfb6ad835718b9fb4def0674e06657a954375c"
+
nan@^2.0.0, nan@^2.3.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.1.tgz#d5b01691253326a97a2bbee9e61c55d8d60351e2"
@@ -3931,7 +3968,7 @@ onetime@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
-opener@^1.4.2:
+opener@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8"
@@ -4485,12 +4522,12 @@ proto-list@~1.2.1:
version "1.2.4"
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
-proxy-addr@~1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.3.tgz#dc97502f5722e888467b3fa2297a7b1ff47df074"
+proxy-addr@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.4.tgz#27e545f6960a44a627d9b44467e35c1b6b4ce2f3"
dependencies:
forwarded "~0.1.0"
- ipaddr.js "1.2.0"
+ ipaddr.js "1.3.0"
prr@~0.0.0:
version "0.0.0"
@@ -4532,14 +4569,14 @@ qjobs@^1.1.4:
version "1.1.5"
resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.1.5.tgz#659de9f2cf8dcc27a1481276f205377272382e73"
-qs@6.2.0:
- version "6.2.0"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b"
-
qs@6.2.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.1.tgz#ce03c5ff0935bc1d9d69a9f14cbd18e568d67625"
+qs@6.4.0:
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
+
qs@~6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.0.tgz#f403b264f23bc01228c74131b407f18d5ea5d442"
@@ -4913,7 +4950,7 @@ rx-lite@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
-safe-buffer@^5.0.1:
+safe-buffer@^5.0.1, safe-buffer@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
@@ -4947,20 +4984,20 @@ semver@~4.3.3:
version "4.3.6"
resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da"
-send@0.14.2:
- version "0.14.2"
- resolved "https://registry.yarnpkg.com/send/-/send-0.14.2.tgz#39b0438b3f510be5dc6f667a11f71689368cdeef"
+send@0.15.3:
+ version "0.15.3"
+ resolved "https://registry.yarnpkg.com/send/-/send-0.15.3.tgz#5013f9f99023df50d1bd9892c19e3defd1d53309"
dependencies:
- debug "~2.2.0"
+ debug "2.6.7"
depd "~1.1.0"
destroy "~1.0.4"
encodeurl "~1.0.1"
escape-html "~1.0.3"
- etag "~1.7.0"
- fresh "0.3.0"
- http-errors "~1.5.1"
+ etag "~1.8.0"
+ fresh "0.5.0"
+ http-errors "~1.6.1"
mime "1.3.4"
- ms "0.7.2"
+ ms "2.0.0"
on-finished "~2.3.0"
range-parser "~1.2.0"
statuses "~1.3.1"
@@ -4977,14 +5014,14 @@ serve-index@^1.7.2:
mime-types "~2.1.11"
parseurl "~1.3.1"
-serve-static@~1.11.2:
- version "1.11.2"
- resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.11.2.tgz#2cf9889bd4435a320cc36895c9aa57bd662e6ac7"
+serve-static@1.12.3:
+ version "1.12.3"
+ resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.12.3.tgz#9f4ba19e2f3030c547f8af99107838ec38d5b1e2"
dependencies:
encodeurl "~1.0.1"
escape-html "~1.0.3"
parseurl "~1.3.1"
- send "0.14.2"
+ send "0.15.3"
set-blocking@^2.0.0, set-blocking@~2.0.0:
version "2.0.0"
@@ -5002,6 +5039,10 @@ setprototypeof@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.2.tgz#81a552141ec104b88e89ce383103ad5c66564d08"
+setprototypeof@1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04"
+
sha.js@^2.3.6:
version "2.4.8"
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.8.tgz#37068c2c476b6baf402d14a49c67f597921f634f"
@@ -5496,20 +5537,20 @@ type-check@~0.3.2:
dependencies:
prelude-ls "~1.1.2"
-type-is@~1.6.14:
- version "1.6.14"
- resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.14.tgz#e219639c17ded1ca0789092dd54a03826b817cb2"
+type-is@~1.6.14, type-is@~1.6.15:
+ version "1.6.15"
+ resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410"
dependencies:
media-typer "0.3.0"
- mime-types "~2.1.13"
+ mime-types "~2.1.15"
typedarray@^0.0.6, typedarray@~0.0.5:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
-uglify-js@^2.6, uglify-js@^2.8.5:
- version "2.8.21"
- resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.21.tgz#1733f669ae6f82fc90c7b25ec0f5c783ee375314"
+uglify-js@^2.6, uglify-js@^2.8.27:
+ version "2.8.27"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.27.tgz#47787f912b0f242e5b984343be8e35e95f694c9c"
dependencies:
source-map "~0.5.1"
yargs "~3.10.0"
@@ -5528,6 +5569,10 @@ ultron@1.0.x:
version "1.0.2"
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa"
+ultron@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.0.tgz#b07a2e6a541a815fc6a34ccd4533baec307ca864"
+
unc-path-regex@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
@@ -5640,9 +5685,9 @@ validate-npm-package-license@^3.0.1:
spdx-correct "~1.0.0"
spdx-expression-parse "~1.0.0"
-vary@~1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.0.tgz#e1e5affbbd16ae768dd2674394b9ad3022653140"
+vary@~1.1.0, vary@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37"
vendors@^1.0.0:
version "1.0.1"
@@ -5729,20 +5774,21 @@ wbuf@^1.1.0, wbuf@^1.4.0:
dependencies:
minimalistic-assert "^1.0.0"
-webpack-bundle-analyzer@^2.3.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.3.0.tgz#0d05e96a43033f7cc57f6855b725782ba61e93a4"
+webpack-bundle-analyzer@^2.8.2:
+ version "2.8.2"
+ resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.8.2.tgz#8b6240c29a9d63bc72f09d920fb050adbcce9fe8"
dependencies:
- acorn "^4.0.11"
+ acorn "^5.0.3"
chalk "^1.1.3"
commander "^2.9.0"
- ejs "^2.5.5"
- express "^4.14.1"
- filesize "^3.5.4"
+ ejs "^2.5.6"
+ express "^4.15.2"
+ filesize "^3.5.9"
gzip-size "^3.0.0"
lodash "^4.17.4"
mkdirp "^0.5.1"
- opener "^1.4.2"
+ opener "^1.4.3"
+ ws "^2.3.1"
webpack-dev-middleware@^1.0.11, webpack-dev-middleware@^1.9.0:
version "1.10.0"
@@ -5789,11 +5835,11 @@ webpack-sources@^0.2.3:
source-list-map "^1.1.1"
source-map "~0.5.3"
-webpack@^2.3.3:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.3.3.tgz#eecc083c18fb7bf958ea4f40b57a6640c5a0cc78"
+webpack@^2.6.1:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.6.1.tgz#2e0457f0abb1ac5df3ab106c69c672f236785f07"
dependencies:
- acorn "^4.0.4"
+ acorn "^5.0.0"
acorn-dynamic-import "^2.0.0"
ajv "^4.7.0"
ajv-keywords "^1.1.1"
@@ -5801,6 +5847,7 @@ webpack@^2.3.3:
enhanced-resolve "^3.0.0"
interpret "^1.0.0"
json-loader "^0.5.4"
+ json5 "^0.5.1"
loader-runner "^2.3.0"
loader-utils "^0.2.16"
memory-fs "~0.4.1"
@@ -5809,7 +5856,7 @@ webpack@^2.3.3:
source-map "^0.5.3"
supports-color "^3.1.0"
tapable "~0.2.5"
- uglify-js "^2.8.5"
+ uglify-js "^2.8.27"
watchpack "^1.3.1"
webpack-sources "^0.2.3"
yargs "^6.0.0"
@@ -5898,6 +5945,13 @@ ws@1.1.1:
options ">=0.0.5"
ultron "1.0.x"
+ws@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-2.3.1.tgz#6b94b3e447cb6a363f785eaf94af6359e8e81c80"
+ dependencies:
+ safe-buffer "~5.0.1"
+ ultron "~1.1.0"
+
wtf-8@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/wtf-8/-/wtf-8-1.0.0.tgz#392d8ba2d0f1c34d1ee2d630f15d0efb68e1048a"