summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Knox <psimyn@gmail.com>2017-11-06 20:35:14 +1100
committerSimon Knox <psimyn@gmail.com>2017-11-06 20:35:14 +1100
commitd5baa33f7adf8bdb619f82008333e10ba67b9cf5 (patch)
treee4b6a7301dc0fa798f4bcfcf7dad9036529cb0f1
parent64549f7025dd405cbc08d85b514808c28e4685e3 (diff)
parent823a9d351b49a6be8c12cfe06edb4aa6ec08fe95 (diff)
downloadgitlab-ce-d5baa33f7adf8bdb619f82008333e10ba67b9cf5.tar.gz
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into 2518-saved-configuration-for-issue-board
-rw-r--r--.gitlab-ci.yml6
-rw-r--r--.nvmrc2
-rw-r--r--.scss-lint.yml2
-rw-r--r--CHANGELOG.md42
-rw-r--r--CONTRIBUTING.md3
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock18
-rw-r--r--app/assets/javascripts/clusters.js2
-rw-r--r--app/assets/javascripts/dispatcher.js5
-rw-r--r--app/assets/javascripts/droplab/plugins/filter.js2
-rw-r--r--app/assets/javascripts/droplab/utils.js2
-rw-r--r--app/assets/javascripts/dropzone_input.js4
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue22
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors.js3
-rw-r--r--app/assets/javascripts/lib/utils/axios_utils.js6
-rw-r--r--app/assets/javascripts/namespace_select.js134
-rw-r--r--app/assets/javascripts/notes/components/issue_note.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/graph/action_component.vue24
-rw-r--r--app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue14
-rw-r--r--app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_component.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/stage.vue10
-rw-r--r--app/assets/javascripts/profile/account/components/delete_account_modal.vue2
-rw-r--r--app/assets/javascripts/repo/components/repo_editor.vue8
-rw-r--r--app/assets/javascripts/settings_panels.js45
-rw-r--r--app/assets/javascripts/test_utils/index.js2
-rw-r--r--app/assets/javascripts/test_utils/simulate_input.js23
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js12
-rw-r--r--app/assets/javascripts/vue_shared/ci_action_icons.js21
-rw-r--r--app/assets/javascripts/vue_shared/ci_status_icons.js43
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_badge_link.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_icon.vue15
-rw-r--r--app/assets/javascripts/vue_shared/components/icon.vue52
-rw-r--r--app/assets/stylesheets/framework.scss2
-rw-r--r--app/assets/stylesheets/framework/blocks.scss8
-rw-r--r--app/assets/stylesheets/framework/callout.scss14
-rw-r--r--app/assets/stylesheets/framework/common.scss100
-rw-r--r--app/assets/stylesheets/framework/contextual-sidebar.scss17
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss15
-rw-r--r--app/assets/stylesheets/framework/files.scss52
-rw-r--r--app/assets/stylesheets/framework/filters.scss12
-rw-r--r--app/assets/stylesheets/framework/header.scss72
-rw-r--r--app/assets/stylesheets/framework/layout.scss38
-rw-r--r--app/assets/stylesheets/framework/lists.scss52
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss26
-rw-r--r--app/assets/stylesheets/framework/responsive_tables.scss (renamed from app/assets/stylesheets/framework/responsive-tables.scss)94
-rw-r--r--app/assets/stylesheets/framework/secondary-navigation-elements.scss63
-rw-r--r--app/assets/stylesheets/framework/selects.scss92
-rw-r--r--app/assets/stylesheets/highlight/white.scss26
-rw-r--r--app/assets/stylesheets/mailers/highlighted_diff_email.scss26
-rw-r--r--app/assets/stylesheets/pages/boards.scss13
-rw-r--r--app/assets/stylesheets/pages/builds.scss36
-rw-r--r--app/assets/stylesheets/pages/clusters.scss4
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss10
-rw-r--r--app/assets/stylesheets/pages/diff.scss8
-rw-r--r--app/assets/stylesheets/pages/environments.scss87
-rw-r--r--app/assets/stylesheets/pages/issuable.scss22
-rw-r--r--app/assets/stylesheets/pages/login.scss98
-rw-r--r--app/assets/stylesheets/pages/members.scss18
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss62
-rw-r--r--app/assets/stylesheets/pages/milestone.scss36
-rw-r--r--app/assets/stylesheets/pages/note_form.scss26
-rw-r--r--app/assets/stylesheets/pages/notes.scss147
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss176
-rw-r--r--app/assets/stylesheets/pages/projects.scss54
-rw-r--r--app/assets/stylesheets/pages/search.scss4
-rw-r--r--app/assets/stylesheets/pages/settings.scss21
-rw-r--r--app/assets/stylesheets/pages/sherlock.scss16
-rw-r--r--app/assets/stylesheets/pages/stat_graph.scss16
-rw-r--r--app/assets/stylesheets/pages/wiki.scss6
-rw-r--r--app/assets/stylesheets/test.scss5
-rw-r--r--app/controllers/admin/applications_controller.rb15
-rw-r--r--app/controllers/admin/impersonation_tokens_controller.rb2
-rw-r--r--app/controllers/application_controller.rb7
-rw-r--r--app/controllers/concerns/issuable_actions.rb72
-rw-r--r--app/controllers/concerns/lfs_request.rb7
-rw-r--r--app/controllers/concerns/notes_actions.rb3
-rw-r--r--app/controllers/jwt_controller.rb6
-rw-r--r--app/controllers/oauth/applications_controller.rb21
-rw-r--r--app/controllers/profiles/keys_controller.rb10
-rw-r--r--app/controllers/profiles/personal_access_tokens_controller.rb2
-rw-r--r--app/controllers/profiles_controller.rb14
-rw-r--r--app/controllers/projects/git_http_client_controller.rb6
-rw-r--r--app/controllers/projects/issues_controller.rb69
-rw-r--r--app/controllers/projects/merge_requests_controller.rb10
-rw-r--r--app/helpers/ci_status_helper.rb24
-rw-r--r--app/helpers/gitlab_routing_helper.rb6
-rw-r--r--app/helpers/issuables_helper.rb27
-rw-r--r--app/helpers/projects_helper.rb14
-rw-r--r--app/models/concerns/cache_markdown_field.rb3
-rw-r--r--app/models/concerns/issuable.rb3
-rw-r--r--app/models/environment.rb7
-rw-r--r--app/models/epic.rb7
-rw-r--r--app/models/fork_network.rb4
-rw-r--r--app/models/group.rb17
-rw-r--r--app/models/issue.rb3
-rw-r--r--app/models/merge_request.rb3
-rw-r--r--app/models/merge_request_diff.rb4
-rw-r--r--app/models/note.rb8
-rw-r--r--app/models/oauth_access_token.rb10
-rw-r--r--app/models/project.rb7
-rw-r--r--app/models/user.rb11
-rw-r--r--app/serializers/issuable_entity.rb8
-rw-r--r--app/serializers/issue_entity.rb4
-rw-r--r--app/serializers/merge_request_entity.rb4
-rw-r--r--app/serializers/time_trackable_entity.rb11
-rw-r--r--app/services/access_token_validation_service.rb7
-rw-r--r--app/services/applications/create_service.rb13
-rw-r--r--app/services/issuable/common_system_notes_service.rb81
-rw-r--r--app/services/issuable_base_service.rb93
-rw-r--r--app/services/issues/update_service.rb4
-rw-r--r--app/services/keys/base_service.rb1
-rw-r--r--app/services/merge_requests/update_service.rb10
-rw-r--r--app/services/metrics_service.rb3
-rw-r--r--app/services/projects/unlink_fork_service.rb16
-rw-r--r--app/services/system_hooks_service.rb48
-rw-r--r--app/views/admin/hook_logs/_index.html.haml2
-rw-r--r--app/views/admin/projects/index.html.haml2
-rw-r--r--app/views/admin/projects/show.html.haml2
-rw-r--r--app/views/ci/status/_badge.html.haml4
-rw-r--r--app/views/ci/status/_dropdown_graph_badge.html.haml8
-rw-r--r--app/views/dashboard/todos/index.html.haml2
-rw-r--r--app/views/profiles/accounts/_reset_token.html.haml11
-rw-r--r--app/views/profiles/accounts/show.html.haml16
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml37
-rw-r--r--app/views/projects/_export.html.haml4
-rw-r--r--app/views/projects/_home_panel.html.haml9
-rw-r--r--app/views/projects/clusters/show.html.haml12
-rw-r--r--app/views/projects/deploy_keys/_index.html.haml4
-rw-r--r--app/views/projects/edit.html.haml21
-rw-r--r--app/views/projects/graphs/show.html.haml14
-rw-r--r--app/views/projects/hook_logs/_index.html.haml2
-rw-r--r--app/views/projects/jobs/_sidebar.html.haml5
-rw-r--r--app/views/projects/merge_requests/_mr_title.html.haml2
-rw-r--r--app/views/projects/protected_branches/shared/_index.html.haml4
-rw-r--r--app/views/projects/protected_tags/shared/_index.html.haml4
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml16
-rw-r--r--app/views/shared/_mini_pipeline_graph.html.haml2
-rw-r--r--app/views/shared/hook_logs/_content.html.haml2
-rw-r--r--changelogs/unreleased/3274-geo-route-whitelisting.yml5
-rw-r--r--changelogs/unreleased/37473-expose-project-visibility-as-ci-variable.yml5
-rw-r--r--changelogs/unreleased/37631-add-a-merge_request_diff_id-column-to-merge_requests.yml5
-rw-r--r--changelogs/unreleased/39054-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml5
-rw-r--r--changelogs/unreleased/39188-change-default-disabled-merge-message.yml5
-rw-r--r--changelogs/unreleased/39366-email-confirmation-fails.yml5
-rw-r--r--changelogs/unreleased/39417-todos-spelled-correctly-on-todos-list-page.yml5
-rw-r--r--changelogs/unreleased/39441-bring-edit-form-back.yml5
-rw-r--r--changelogs/unreleased/39495-fix-bitbucket-login.yml5
-rw-r--r--changelogs/unreleased/39639-clusters-poll.yml5
-rw-r--r--changelogs/unreleased/39704_fix_webhooks_log_time.yml5
-rw-r--r--changelogs/unreleased/39776-remove-responsive-table-bottom-border.yml5
-rw-r--r--changelogs/unreleased/bvl-circuitbreaker-backoff.yml6
-rw-r--r--changelogs/unreleased/bvl-circuitbreaker-improvements.yml5
-rw-r--r--changelogs/unreleased/bvl-do-not-use-redis-keys.yml5
-rw-r--r--changelogs/unreleased/bvl-dont-rename-free-names.yml5
-rw-r--r--changelogs/unreleased/bvl-fix-push-event-service-for-forks.yml5
-rw-r--r--changelogs/unreleased/bvl-fix-system-hook-project-visibility.yml5
-rw-r--r--changelogs/unreleased/bvl-unlink-fixes.yml5
-rw-r--r--changelogs/unreleased/dm-add-sudo-scope.yml6
-rw-r--r--changelogs/unreleased/dm-convert-private-tokens.yml5
-rw-r--r--changelogs/unreleased/dm-ldap-identity-normalize-dn.yml5
-rw-r--r--changelogs/unreleased/dm-remove-private-token-from-interface.yml5
-rw-r--r--changelogs/unreleased/dm-remove-private-token.yml5
-rw-r--r--changelogs/unreleased/enable-scss-lint-mergeable-selector.yml4
-rw-r--r--changelogs/unreleased/fix-add-path-attr-to-wiki-file.yml5
-rw-r--r--changelogs/unreleased/fix-import-issue-assignees.yml5
-rw-r--r--changelogs/unreleased/fix_global_board_routes_39073.yml5
-rw-r--r--changelogs/unreleased/fix_migration_that_adds_ff_merge_field.yml5
-rw-r--r--changelogs/unreleased/issue_39176.yml5
-rw-r--r--changelogs/unreleased/jivl-fix-cancel-button-file-upload-new-issue.yml5
-rw-r--r--changelogs/unreleased/make-merge-jid-handling-less-stateful.yml5
-rw-r--r--changelogs/unreleased/mr-14642.yml6
-rw-r--r--changelogs/unreleased/pawel-disable_nfs_metrics_checks_39730.yml5
-rw-r--r--changelogs/unreleased/sh-fix-container-registry-destroy.yml5
-rw-r--r--changelogs/unreleased/sh-fix-environment-slug-generation.yml5
-rw-r--r--changelogs/unreleased/sh-fix-environment-write-ref.yml5
-rw-r--r--changelogs/unreleased/winh-admin-projects-namespace-filter.yml5
-rw-r--r--changelogs/unreleased/winh-i18n-contributors-page.yml5
-rw-r--r--changelogs/unreleased/winh-namespace-rename-hooks.yml5
-rw-r--r--config/environments/test.rb1
-rw-r--r--config/gitlab.yml.example2
-rw-r--r--config/locales/doorkeeper.en.yml5
-rw-r--r--config/routes/profile.rb1
-rw-r--r--db/migrate/20150827121444_add_fast_forward_option_to_project.rb6
-rw-r--r--db/migrate/20171012125712_migrate_user_authentication_token_to_personal_access_token.rb78
-rw-r--r--db/migrate/20171025110159_add_latest_merge_request_diff_id_to_merge_requests.rb26
-rw-r--r--db/post_migrate/20171012150314_remove_user_authentication_token.rb20
-rw-r--r--db/post_migrate/20171026082505_populate_merge_requests_latest_merge_request_diff_id.rb27
-rw-r--r--db/schema.rb7
-rw-r--r--doc/administration/operations/sidekiq_memory_killer.md4
-rw-r--r--doc/administration/troubleshooting/debug.md2
-rw-r--r--doc/api/README.md126
-rw-r--r--doc/api/session.md55
-rw-r--r--doc/api/users.md3
-rw-r--r--doc/ci/docker/using_docker_build.md8
-rw-r--r--doc/ci/docker/using_docker_images.md4
-rw-r--r--doc/ci/examples/php.md4
-rw-r--r--doc/ci/examples/test-and-deploy-python-application-to-heroku.md2
-rw-r--r--doc/ci/git_submodules.md4
-rw-r--r--doc/ci/variables/README.md1
-rw-r--r--doc/development/doc_styleguide.md4
-rw-r--r--doc/development/testing_guide/best_practices.md29
-rw-r--r--doc/development/testing_guide/testing_levels.md2
-rw-r--r--doc/install/requirements.md2
-rw-r--r--doc/raketasks/user_management.md15
-rw-r--r--doc/system_hooks/system_hooks.md70
-rw-r--r--doc/topics/autodevops/index.md8
-rw-r--r--doc/university/README.md12
-rw-r--r--doc/university/glossary/README.md2
-rw-r--r--doc/university/training/topics/git_log.md4
-rw-r--r--doc/user/profile/index.md2
-rw-r--r--doc/user/profile/personal_access_tokens.md12
-rw-r--r--doc/user/project/integrations/img/webhook_logs.pngbin24066 -> 132319 bytes
-rw-r--r--doc/user/project/pipelines/job_artifacts.md6
-rw-r--r--features/steps/profile/notifications.rb2
-rw-r--r--features/steps/project/commits/branches.rb8
-rw-r--r--features/steps/project/issues/labels.rb2
-rw-r--r--features/steps/project/issues/milestones.rb3
-rw-r--r--features/steps/shared/diff_note.rb9
-rw-r--r--features/steps/shared/note.rb2
-rw-r--r--features/support/capybara.rb29
-rw-r--r--features/support/capybara_helpers.rb10
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/api_guard.rb108
-rw-r--r--lib/api/entities.rb4
-rw-r--r--lib/api/helpers.rb21
-rw-r--r--lib/api/session.rb20
-rw-r--r--lib/api/users.rb4
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb6
-rw-r--r--lib/banzai/filter/reference_filter.rb4
-rw-r--r--lib/banzai/filter/user_reference_filter.rb42
-rw-r--r--lib/gitlab/auth.rb16
-rw-r--r--lib/gitlab/ci/status/build/cancelable.rb2
-rw-r--r--lib/gitlab/ci/status/build/failed_allowed.rb2
-rw-r--r--lib/gitlab/ci/status/build/play.rb2
-rw-r--r--lib/gitlab/ci/status/build/retryable.rb2
-rw-r--r--lib/gitlab/ci/status/build/stop.rb2
-rw-r--r--lib/gitlab/ci/status/canceled.rb2
-rw-r--r--lib/gitlab/ci/status/created.rb2
-rw-r--r--lib/gitlab/ci/status/failed.rb2
-rw-r--r--lib/gitlab/ci/status/manual.rb2
-rw-r--r--lib/gitlab/ci/status/pending.rb2
-rw-r--r--lib/gitlab/ci/status/running.rb2
-rw-r--r--lib/gitlab/ci/status/skipped.rb2
-rw-r--r--lib/gitlab/ci/status/success.rb2
-rw-r--r--lib/gitlab/ci/status/success_warning.rb2
-rw-r--r--lib/gitlab/git/commit.rb2
-rw-r--r--lib/gitlab/git/repository.rb32
-rw-r--r--lib/gitlab/gitaly_client.rb6
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb2
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb17
-rw-r--r--lib/gitlab/import_export/import_export.yml1
-rw-r--r--lib/gitlab/metrics/system.rb4
-rw-r--r--lib/gitlab/middleware/read_only.rb5
-rw-r--r--lib/gitlab/performance_bar/peek_query_tracker.rb2
-rw-r--r--lib/gitlab/sherlock/transaction.rb4
-rw-r--r--lib/gitlab/sidekiq_middleware/memory_killer.rb41
-rw-r--r--lib/gitlab/testing/request_blocker_middleware.rb12
-rw-r--r--lib/gitlab/testing/request_inspector_middleware.rb71
-rw-r--r--lib/system_check/app/git_user_default_ssh_config_check.rb4
-rw-r--r--lib/tasks/gitlab/users.rake11
-rw-r--r--lib/tasks/tokens.rake12
-rw-r--r--package.json4
-rw-r--r--spec/controllers/application_controller_spec.rb86
-rw-r--r--spec/controllers/concerns/lfs_request_spec.rb50
-rw-r--r--spec/controllers/metrics_controller_spec.rb11
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb39
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb56
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb13
-rw-r--r--spec/features/admin/admin_disables_two_factor_spec.rb2
-rw-r--r--spec/features/admin/admin_groups_spec.rb2
-rw-r--r--spec/features/admin/admin_hooks_spec.rb4
-rw-r--r--spec/features/admin/admin_labels_spec.rb2
-rw-r--r--spec/features/admin/admin_settings_spec.rb2
-rw-r--r--spec/features/admin/admin_users_impersonation_tokens_spec.rb4
-rw-r--r--spec/features/admin/admin_users_spec.rb4
-rw-r--r--spec/features/admin/admin_uses_repository_checks_spec.rb2
-rw-r--r--spec/features/atom/dashboard_issues_spec.rb6
-rw-r--r--spec/features/atom/dashboard_spec.rb6
-rw-r--r--spec/features/atom/issues_spec.rb6
-rw-r--r--spec/features/atom/users_spec.rb6
-rw-r--r--spec/features/boards/add_issues_modal_spec.rb2
-rw-r--r--spec/features/boards/boards_spec.rb16
-rw-r--r--spec/features/boards/sidebar_spec.rb4
-rw-r--r--spec/features/calendar_spec.rb6
-rw-r--r--spec/features/container_registry_spec.rb2
-rw-r--r--spec/features/dashboard/group_spec.rb2
-rw-r--r--spec/features/dashboard/groups_list_spec.rb2
-rw-r--r--spec/features/dashboard/issues_spec.rb8
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb2
-rw-r--r--spec/features/dashboard/todos/todos_spec.rb16
-rw-r--r--spec/features/discussion_comments/commit_spec.rb2
-rw-r--r--spec/features/discussion_comments/snippets_spec.rb2
-rw-r--r--spec/features/explore/new_menu_spec.rb10
-rw-r--r--spec/features/groups_spec.rb2
-rw-r--r--spec/features/issues/bulk_assignment_labels_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb7
-rw-r--r--spec/features/issues/filtered_search/dropdown_author_spec.rb6
-rw-r--r--spec/features/issues/filtered_search/dropdown_emoji_spec.rb6
-rw-r--r--spec/features/issues/filtered_search/dropdown_label_spec.rb6
-rw-r--r--spec/features/issues/filtered_search/dropdown_milestone_spec.rb7
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/recent_searches_spec.rb24
-rw-r--r--spec/features/issues/filtered_search/visual_tokens_spec.rb3
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb31
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb10
-rw-r--r--spec/features/issues/move_spec.rb6
-rw-r--r--spec/features/issues/update_issues_spec.rb2
-rw-r--r--spec/features/issues/user_uses_slash_commands_spec.rb4
-rw-r--r--spec/features/issues_spec.rb14
-rw-r--r--spec/features/merge_requests/conflicts_spec.rb6
-rw-r--r--spec/features/merge_requests/diff_notes_avatars_spec.rb20
-rw-r--r--spec/features/merge_requests/diff_notes_resolve_spec.rb12
-rw-r--r--spec/features/merge_requests/diffs_spec.rb10
-rw-r--r--spec/features/merge_requests/form_spec.rb2
-rw-r--r--spec/features/merge_requests/mini_pipeline_graph_spec.rb2
-rw-r--r--spec/features/merge_requests/update_merge_requests_spec.rb2
-rw-r--r--spec/features/merge_requests/user_posts_diff_notes_spec.rb9
-rw-r--r--spec/features/merge_requests/user_posts_notes_spec.rb2
-rw-r--r--spec/features/merge_requests/versions_spec.rb8
-rw-r--r--spec/features/merge_requests/widget_deployments_spec.rb2
-rw-r--r--spec/features/profile_spec.rb37
-rw-r--r--spec/features/profiles/oauth_applications_spec.rb4
-rw-r--r--spec/features/profiles/personal_access_tokens_spec.rb6
-rw-r--r--spec/features/profiles/user_visits_notifications_tab_spec.rb2
-rw-r--r--spec/features/profiles/user_visits_profile_preferences_page_spec.rb2
-rw-r--r--spec/features/projects/artifacts/download_spec.rb2
-rw-r--r--spec/features/projects/artifacts/file_spec.rb1
-rw-r--r--spec/features/projects/branches_spec.rb2
-rw-r--r--spec/features/projects/commit/diff_notes_spec.rb4
-rw-r--r--spec/features/projects/deploy_keys_spec.rb2
-rw-r--r--spec/features/projects/environments/environment_spec.rb8
-rw-r--r--spec/features/projects/environments/environments_spec.rb2
-rw-r--r--spec/features/projects/features_visibility_spec.rb4
-rw-r--r--spec/features/projects/files/edit_file_soft_wrap_spec.rb26
-rw-r--r--spec/features/projects/import_export/export_file_spec.rb2
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb2
-rw-r--r--spec/features/projects/import_export/namespace_export_file_spec.rb2
-rw-r--r--spec/features/projects/jobs/user_browses_job_spec.rb4
-rw-r--r--spec/features/projects/jobs_spec.rb38
-rw-r--r--spec/features/projects/members/groups_with_access_list_spec.rb3
-rw-r--r--spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb4
-rw-r--r--spec/features/projects/members/share_with_group_spec.rb4
-rw-r--r--spec/features/projects/members/user_requests_access_spec.rb2
-rw-r--r--spec/features/projects/merge_requests/user_comments_on_diff_spec.rb11
-rw-r--r--spec/features/projects/merge_requests/user_edits_merge_request_spec.rb5
-rw-r--r--spec/features/projects/new_project_spec.rb4
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb2
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb26
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb26
-rw-r--r--spec/features/projects/ref_switcher_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_jira_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb8
-rw-r--r--spec/features/projects/settings/forked_project_settings_spec.rb40
-rw-r--r--spec/features/projects/settings/merge_requests_settings_spec.rb6
-rw-r--r--spec/features/projects/settings/pipelines_settings_spec.rb2
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb3
-rw-r--r--spec/features/projects/snippets/create_snippet_spec.rb4
-rw-r--r--spec/features/projects/tree/create_directory_spec.rb5
-rw-r--r--spec/features/projects/tree/create_file_spec.rb5
-rw-r--r--spec/features/projects/tree/upload_file_spec.rb6
-rw-r--r--spec/features/projects/user_browses_files_spec.rb5
-rw-r--r--spec/features/projects/wiki/markdown_preview_spec.rb6
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb4
-rw-r--r--spec/features/projects/wiki/user_views_wiki_page_spec.rb2
-rw-r--r--spec/features/projects_spec.rb4
-rw-r--r--spec/features/protected_branches_spec.rb8
-rw-r--r--spec/features/raven_js_spec.rb6
-rw-r--r--spec/features/search/user_searches_for_code_spec.rb4
-rw-r--r--spec/features/search/user_searches_for_issues_spec.rb8
-rw-r--r--spec/features/search/user_searches_for_merge_requests_spec.rb6
-rw-r--r--spec/features/search/user_searches_for_milestones_spec.rb6
-rw-r--r--spec/features/search/user_searches_for_wiki_pages_spec.rb4
-rw-r--r--spec/features/search/user_uses_search_filters_spec.rb6
-rw-r--r--spec/features/snippets/notes_on_personal_snippets_spec.rb7
-rw-r--r--spec/features/snippets/user_creates_snippet_spec.rb16
-rw-r--r--spec/features/tags/master_creates_tag_spec.rb2
-rw-r--r--spec/features/tags/master_deletes_tag_spec.rb2
-rw-r--r--spec/features/triggers_spec.rb22
-rw-r--r--spec/features/u2f_spec.rb24
-rw-r--r--spec/features/uploads/user_uploads_file_to_note_spec.rb26
-rw-r--r--spec/features/users_spec.rb1
-rw-r--r--spec/features/variables_spec.rb2
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/user/login.json6
-rw-r--r--spec/helpers/ci_status_helper_spec.rb12
-rw-r--r--spec/helpers/gitlab_routing_helper_spec.rb26
-rw-r--r--spec/helpers/issuables_helper_spec.rb32
-rw-r--r--spec/javascripts/groups/components/app_spec.js4
-rw-r--r--spec/javascripts/issue_spec.js34
-rw-r--r--spec/javascripts/jobs/mock_data.js2
-rw-r--r--spec/javascripts/namespace_select_spec.js65
-rw-r--r--spec/javascripts/pipelines/graph/action_component_spec.js2
-rw-r--r--spec/javascripts/pipelines/graph/dropdown_action_component_spec.js2
-rw-r--r--spec/javascripts/pipelines/graph/job_component_spec.js2
-rw-r--r--spec/javascripts/pipelines/graph/mock_data.js12
-rw-r--r--spec/javascripts/pipelines/graph/stage_column_component_spec.js2
-rw-r--r--spec/javascripts/repo/components/repo_editor_spec.js18
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js10
-rw-r--r--spec/javascripts/vue_shared/ci_action_icons_spec.js27
-rw-r--r--spec/javascripts/vue_shared/ci_status_icon_spec.js27
-rw-r--r--spec/javascripts/vue_shared/components/ci_badge_link_spec.js18
-rw-r--r--spec/javascripts/vue_shared/components/icon_spec.js48
-rw-r--r--spec/javascripts/vue_shared/components/markdown/field_spec.js14
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb62
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb12
-rw-r--r--spec/lib/banzai/filter/merge_request_reference_filter_spec.rb10
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb11
-rw-r--r--spec/lib/banzai/filter/snippet_reference_filter_spec.rb10
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb33
-rw-r--r--spec/lib/gitlab/auth_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/cron_parser_spec.rb48
-rw-r--r--spec/lib/gitlab/ci/status/build/cancelable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/factory_spec.rb18
-rw-r--r--spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/play_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/retryable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/stop_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/canceled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/created_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/failed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/manual_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pending_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/running_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/skipped_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/success_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/success_warning_spec.rb2
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb65
-rw-r--r--spec/lib/gitlab/gitaly_client/operation_service_spec.rb34
-rw-r--r--spec/lib/gitlab/middleware/read_only_spec.rb26
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb63
-rw-r--r--spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb8
-rw-r--r--spec/migrations/migrate_user_authentication_token_to_personal_access_token_spec.rb25
-rw-r--r--spec/migrations/populate_merge_requests_latest_merge_request_diff_id_spec.rb61
-rw-r--r--spec/models/ci/build_spec.rb1
-rw-r--r--spec/models/concerns/routable_spec.rb1
-rw-r--r--spec/models/concerns/token_authenticatable_spec.rb2
-rw-r--r--spec/models/environment_spec.rb16
-rw-r--r--spec/models/fork_network_spec.rb10
-rw-r--r--spec/models/group_spec.rb41
-rw-r--r--spec/models/project_spec.rb14
-rw-r--r--spec/models/user_spec.rb45
-rw-r--r--spec/requests/api/doorkeeper_access_spec.rb10
-rw-r--r--spec/requests/api/helpers_spec.rb432
-rw-r--r--spec/requests/api/session_spec.rb107
-rw-r--r--spec/requests/api/users_spec.rb30
-rw-r--r--spec/routing/routing_spec.rb5
-rw-r--r--spec/serializers/issue_entity_spec.rb20
-rw-r--r--spec/serializers/merge_request_entity_spec.rb11
-rw-r--r--spec/services/applications/create_service_spec.rb13
-rw-r--r--spec/services/issuable/common_system_notes_service_spec.rb49
-rw-r--r--spec/services/projects/unlink_fork_service_spec.rb18
-rw-r--r--spec/services/system_hooks_service_spec.rb38
-rw-r--r--spec/spec_helper.rb4
-rw-r--r--spec/support/api_helpers.rb28
-rw-r--r--spec/support/capybara.rb44
-rw-r--r--spec/support/capybara_helpers.rb2
-rw-r--r--spec/support/cookie_helper.rb17
-rw-r--r--spec/support/features/discussion_comments_shared_example.rb27
-rw-r--r--spec/support/features/issuable_slash_commands_shared_examples.rb4
-rw-r--r--spec/support/features/reportable_note_shared_examples.rb2
-rw-r--r--spec/support/gitlab-git-test.git/objects/88/3e379fcaa5f818fca81cdbabd7a497794d6535bin0 -> 304 bytes
-rw-r--r--spec/support/gitlab-git-test.git/objects/c8/b1ab16c858c67b680eea4644cf652485f555cfbin0 -> 597 bytes
-rw-r--r--spec/support/gitlab-git-test.git/objects/e3/7697aea12699f0b44544332a7c0f41ace5fb162
-rw-r--r--spec/support/gitlab-git-test.git/objects/eb/a0c153ed20d927bab00507f356043b6b4be31ebin0 -> 185 bytes
-rw-r--r--spec/support/gitlab-git-test.git/objects/f6/5ad228d96e2a2ae7088e8557fe8906f6dd2b3fbin0 -> 642 bytes
-rw-r--r--spec/support/gitlab_stubs/session.json4
-rw-r--r--spec/support/gitlab_stubs/user.json6
-rw-r--r--spec/support/helpers/merge_request_diff_helpers.rb2
-rw-r--r--spec/support/helpers/note_interaction_helpers.rb2
-rw-r--r--spec/support/input_helper.rb7
-rw-r--r--spec/support/inspect_requests.rb17
-rw-r--r--spec/support/live_debugger.rb17
-rw-r--r--spec/support/login_helpers.rb18
-rw-r--r--spec/support/mobile_helpers.rb2
-rw-r--r--spec/support/protected_tags/access_control_ce_shared_examples.rb2
-rw-r--r--spec/support/quick_actions_helpers.rb2
-rw-r--r--spec/support/shared_examples/features/protected_branches_access_control_ce.rb6
-rw-r--r--spec/support/stub_configuration.rb4
-rw-r--r--spec/support/test_env.rb2
-rw-r--r--spec/support/time_tracking_shared_examples.rb2
-rw-r--r--spec/support/wait_for_requests.rb36
-rw-r--r--spec/tasks/gitlab/users_rake_spec.rb38
-rw-r--r--spec/tasks/tokens_spec.rb6
-rw-r--r--vendor/assets/javascripts/peek.js28
-rw-r--r--yarn.lock12
485 files changed, 4349 insertions, 3127 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 38fb743b0c9..fed5971233d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.5-golang-1.8-git-2.13-phantomjs-2.1-node-8.x-yarn-1.0-postgresql-9.6"
+image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.5-golang-1.8-git-2.13-chrome-62.0-node-8.x-yarn-1.2-postgresql-9.6"
.default-cache: &default-cache
key: "ruby-235-with-yarn"
@@ -23,7 +23,6 @@ variables:
SIMPLECOV: "true"
GIT_DEPTH: "20"
GIT_SUBMODULE_STRATEGY: "none"
- PHANTOMJS_VERSION: "2.1.1"
GET_SOURCES_ATTEMPTS: "3"
KNAPSACK_RSPEC_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/rspec_report-master.json
KNAPSACK_SPINACH_SUITE_REPORT_PATH: knapsack/${CI_PROJECT_NAME}/spinach_report-master.json
@@ -455,7 +454,7 @@ db:migrate:reset-mysql:
variables:
SETUP_DB: "false"
script:
- - git fetch origin v9.3.0
+ - git fetch https://gitlab.com/gitlab-org/gitlab-ce.git v9.3.0
- git checkout -f FETCH_HEAD
- bundle install $BUNDLE_INSTALL_FLAGS
- cp config/gitlab.yml.example config/gitlab.yml
@@ -551,7 +550,6 @@ karma:
<<: *dedicated-runner
<<: *except-docs
<<: *pull-cache
- image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.5-golang-1.8-git-2.13-chrome-61.0-node-8.x-yarn-1.0-postgresql-9.6"
stage: test
variables:
BABEL_ENV: "coverage"
diff --git a/.nvmrc b/.nvmrc
index 72906051c5c..f7ee06693c1 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-7.5 \ No newline at end of file
+9.0.0
diff --git a/.scss-lint.yml b/.scss-lint.yml
index d2c972fa9c4..16a168b7c60 100644
--- a/.scss-lint.yml
+++ b/.scss-lint.yml
@@ -112,7 +112,7 @@ linters:
# Reports when you define the same selector twice in a single sheet.
MergeableSelector:
- enabled: false
+ enabled: true
# Functions, mixins, variables, and placeholders should be declared
# with all lowercase letters and hyphens instead of underscores.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6bca9944bb1..2f13eca2caf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,30 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 10.1.1 (2017-10-31)
+
+- [FIXED] Auto Devops kubernetes default namespace is now correctly built out of gitlab project group-name. !14642 (Mircea Danila Dumitrescu)
+- [FIXED] Forbid the usage of `Redis#keys`. !14889
+- [FIXED] Make the circuitbreaker more robust by adding higher thresholds, and multiple access attempts. !14933
+- [FIXED] Only cache last push event for existing projects when pushing to a fork. !14989
+- [FIXED] Fix bug preventing secondary emails from being confirmed. !15010
+- [FIXED] Fix broken wiki pages that link to a wiki file. !15019
+- [FIXED] Don't rename paths that were freed up when upgrading. !15029
+- [FIXED] Fix bitbucket login. !15051
+- [FIXED] Update gitaly in Gitlab 10.1 to 0.43.1 for temp file cleanup. !15055
+- [FIXED] Use the correct visibility attribute for projects in system hooks. !15065
+- [FIXED] Normalize LDAP DN when looking up identity.
+- [FIXED] Adds callback functions for initial request in clusters page.
+- [FIXED] Fix missing Import/Export issue assignees.
+- [FIXED] Allow boards as top level route.
+- [FIXED] Fix widget of locked merge requests not being presented.
+- [FIXED] Fix editing issue description in mobile view.
+- [FIXED] Fix deletion of container registry or images returning an error.
+- [FIXED] Fix the writing of invalid environment refs.
+- [CHANGED] Store circuitbreaker settings in the database instead of config. !14842
+- [CHANGED] Update default disabled merge request widget message to reflect a general failure. !14960
+- [PERFORMANCE] Stop merge requests with thousands of commits from timing out. !15063
+
## 10.1.0 (2017-10-22)
- [SECURITY] Use a timeout on certain git operations. !14872
@@ -194,6 +218,24 @@ entry.
- creation of keys moved to services. !13331 (haseebeqx)
- Add username as GL_USERNAME in hooks.
+## 10.0.5 (2017-11-03)
+
+- [FIXED] Fix incorrect X-axis labels in Prometheus graphs. !14258
+- [FIXED] Fix `rake gitlab:incoming_email:check` and make it report the actual error. !14423
+- [FIXED] Does not check if an invariant hashed storage path exists on disk when renaming projects. !14428
+- [FIXED] Fix bottom spacing for dropdowns that open upwards. !14535
+- [FIXED] Fix the project import with issues and milestones. !14657
+- [FIXED] Fix broken Y-axis scaling in some Prometheus graphs. !14693
+- [FIXED] Fixed duplicate notifications when added multiple labels on an issue. !14798
+- [FIXED] Don't rename paths that were freed up when upgrading. !15029
+- [FIXED] Fixed issue/merge request breadcrumb titles not having links.
+- [FIXED] Fix application setting to cache nil object.
+- [FIXED] Fix missing Import/Export issue assignees.
+- [FIXED] Allow boards as top level route.
+- [FIXED] Fixed milestone breadcrumb links.
+- [FIXED] Fixed merge request widget merged & closed date tooltip text.
+- [FIXED] fix merge request widget status icon for failed CI.
+
## 10.0.4 (2017-10-16)
- [SECURITY] Move project repositories between namespaces when renaming users.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9a26bed3d29..c4e5fd842df 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -104,8 +104,7 @@ the remaining issues on the GitHub issue tracker.
## I want to contribute!
-If you want to contribute to GitLab, but are not sure where to start,
-look for [issues with the label `Accepting Merge Requests` and small weight][accepting-mrs-weight].
+If you want to contribute to GitLab, [issues with the label `Accepting Merge Requests` and small weight][accepting-mrs-weight] is a great place to start. Issues with a lower weight (1 or 2) are deemed suitable for beginners.
These issues will be of reasonable size and challenge, for anyone to start
contributing to GitLab.
diff --git a/Gemfile b/Gemfile
index c97b1896d06..63d3d214a5a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -324,9 +324,9 @@ group :development, :test do
# Generate Fake data
gem 'ffaker', '~> 2.4'
- gem 'capybara', '~> 2.15.0'
+ gem 'capybara', '~> 2.15'
gem 'capybara-screenshot', '~> 1.0.0'
- gem 'poltergeist', '~> 1.9.0'
+ gem 'selenium-webdriver', '~> 3.5'
gem 'spring', '~> 2.0.0'
gem 'spring-commands-rspec', '~> 1.0.4'
diff --git a/Gemfile.lock b/Gemfile.lock
index 8ccf18818a6..ae145ca5f69 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -113,12 +113,13 @@ GEM
mime-types (>= 1.16)
cause (0.1)
charlock_holmes (0.7.5)
+ childprocess (0.7.0)
+ ffi (~> 1.0, >= 1.0.11)
chronic (0.10.2)
chronic_duration (0.10.6)
numerizer (~> 0.1.1)
chunky_png (1.3.5)
citrus (3.0.2)
- cliver (0.3.2)
coderay (1.1.1)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
@@ -604,11 +605,6 @@ GEM
pg (0.18.4)
po_to_json (1.0.1)
json (>= 1.6.0)
- poltergeist (1.9.0)
- capybara (~> 2.1)
- cliver (~> 0.3.1)
- multi_json (~> 1.0)
- websocket-driver (>= 0.2.0)
posix-spawn (0.3.13)
powerpack (0.1.1)
premailer (1.10.4)
@@ -818,6 +814,9 @@ GEM
activesupport (>= 3.1)
select2-rails (3.5.9.3)
thor (~> 0.14)
+ selenium-webdriver (3.5.0)
+ childprocess (~> 0.5)
+ rubyzip (~> 1.0)
sentry-raven (2.5.3)
faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9)
@@ -949,9 +948,6 @@ GEM
hashdiff
webpack-rails (0.9.10)
railties (>= 3.2.0)
- websocket-driver (0.6.3)
- websocket-extensions (>= 0.1.0)
- websocket-extensions (0.1.2)
wikicloth (0.8.1)
builder
expression_parser
@@ -988,7 +984,7 @@ DEPENDENCIES
browser (~> 2.2)
bullet (~> 5.5.0)
bundler-audit (~> 0.5.0)
- capybara (~> 2.15.0)
+ capybara (~> 2.15)
capybara-screenshot (~> 1.0.0)
carrierwave (~> 1.2)
charlock_holmes (~> 0.7.5)
@@ -1104,7 +1100,6 @@ DEPENDENCIES
peek-redis (~> 1.2.0)
peek-sidekiq (~> 1.0.3)
pg (~> 0.18.2)
- poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.7.0.beta18)
pry-byebug (~> 3.4.1)
@@ -1150,6 +1145,7 @@ DEPENDENCIES
scss_lint (~> 0.54.0)
seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9)
+ selenium-webdriver (~> 3.5)
sentry-raven (~> 2.5.3)
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
diff --git a/app/assets/javascripts/clusters.js b/app/assets/javascripts/clusters.js
index 661870c226c..c9fef94efea 100644
--- a/app/assets/javascripts/clusters.js
+++ b/app/assets/javascripts/clusters.js
@@ -1,6 +1,7 @@
/* globals Flash */
import Visibility from 'visibilityjs';
import axios from 'axios';
+import setAxiosCsrfToken from './lib/utils/axios_utils';
import Poll from './lib/utils/poll';
import { s__ } from './locale';
import initSettingsPanels from './settings_panels';
@@ -17,6 +18,7 @@ import Flash from './flash';
class ClusterService {
constructor(options = {}) {
this.options = options;
+ setAxiosCsrfToken();
}
fetchData() {
return axios.get(this.options.endpoint);
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 5930868412b..760fb0cdf67 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -16,7 +16,7 @@ import CILintEditor from './ci_lint_editor';
import groupsSelect from './groups_select';
/* global Search */
/* global Admin */
-/* global NamespaceSelects */
+import NamespaceSelect from './namespace_select';
/* global NewCommitForm */
/* global NewBranchForm */
/* global Project */
@@ -575,7 +575,8 @@ import Diff from './diff';
new UsersSelect();
break;
case 'projects':
- new NamespaceSelects();
+ document.querySelectorAll('.js-namespace-select')
+ .forEach(dropdown => new NamespaceSelect({ dropdown }));
break;
case 'labels':
switch (path[2]) {
diff --git a/app/assets/javascripts/droplab/plugins/filter.js b/app/assets/javascripts/droplab/plugins/filter.js
index d6a1aadd49c..404d707cf7a 100644
--- a/app/assets/javascripts/droplab/plugins/filter.js
+++ b/app/assets/javascripts/droplab/plugins/filter.js
@@ -79,8 +79,6 @@ const Filter = {
this.hook.trigger.addEventListener('keydown.dl', this.eventWrapper.debounceKeydown);
this.hook.trigger.addEventListener('mousedown.dl', this.eventWrapper.debounceKeydown);
-
- this.debounceKeydown({ detail: { hook: this.hook } });
},
destroy: function destroy() {
diff --git a/app/assets/javascripts/droplab/utils.js b/app/assets/javascripts/droplab/utils.js
index 4da7344604e..bfe056a0fcc 100644
--- a/app/assets/javascripts/droplab/utils.js
+++ b/app/assets/javascripts/droplab/utils.js
@@ -30,7 +30,7 @@ const utils = {
},
isDropDownParts(target) {
- if (!target || target.tagName === 'HTML') return false;
+ if (!target || !target.hasAttribute || target.tagName === 'HTML') return false;
return target.hasAttribute(DATA_TRIGGER) || target.hasAttribute(DATA_DROPDOWN);
},
};
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index 7a17adcd44e..b7747ee3f83 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -119,11 +119,9 @@ export default function dropzoneInput(form) {
// removeAllFiles(true) stops uploading files (if any)
// and remove them from dropzone files queue.
$cancelButton.on('click', (e) => {
- const target = e.target.closest('.js-main-target-form').querySelector('.div-dropzone');
-
e.preventDefault();
e.stopPropagation();
- Dropzone.forElement(target).removeAllFiles(true);
+ Dropzone.forElement($formDropzone.get(0)).removeAllFiles(true);
});
// If 'error' event is fired, we store a failed files,
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index 6de01fa53d0..fc0308b81ba 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -421,7 +421,11 @@ export default {
</script>
<template>
<div
- :class="{ 'js-child-row environment-child-row': model.isChildren, 'folder-row': model.isFolder, 'gl-responsive-table-row': !model.isFolder }"
+ class="gl-responsive-table-row"
+ :class="{
+ 'js-child-row environment-child-row': model.isChildren,
+ 'folder-row': model.isFolder,
+ }"
role="row">
<div class="table-section section-10" role="gridcell">
<div
@@ -495,15 +499,16 @@ export default {
</a>
</div>
- <div class="table-section section-25" role="gridcell">
+ <div
+ v-if="!model.isFolder"
+ class="table-section section-25" role="gridcell">
<div
- v-if="!model.isFolder"
role="rowheader"
class="table-mobile-header">
Commit
</div>
<div
- v-if="!model.isFolder && hasLastDeploymentKey"
+ v-if="hasLastDeploymentKey"
class="js-commit-component table-mobile-content">
<commit-component
:tag="commitTag"
@@ -514,21 +519,22 @@ export default {
:author="commitAuthor"/>
</div>
<div
- v-if="!model.isFolder && !hasLastDeploymentKey"
+ v-if="!hasLastDeploymentKey"
class="commit-title table-mobile-content">
No deployments yet
</div>
</div>
- <div class="table-section section-10" role="gridcell">
+ <div
+ v-if="!model.isFolder"
+ class="table-section section-10" role="gridcell">
<div
- v-if="!model.isFolder"
role="rowheader"
class="table-mobile-header">
Updated
</div>
<span
- v-if="!model.isFolder && canShowDate"
+ v-if="canShowDate"
class="environment-created-date-timeago table-mobile-content">
{{createdDate}}
</span>
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors.js b/app/assets/javascripts/graphs/stat_graph_contributors.js
index cdc4fcf6573..e7232ca3712 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors.js
@@ -4,6 +4,7 @@ import _ from 'underscore';
import d3 from 'd3';
import { ContributorsGraph, ContributorsAuthorGraph, ContributorsMasterGraph } from './stat_graph_contributors_graph';
import ContributorsStatGraphUtil from './stat_graph_contributors_util';
+import { n__ } from '../locale';
export default (function() {
function ContributorsStatGraph() {}
@@ -44,7 +45,7 @@ export default (function() {
commits = $('<span/>', {
"class": 'graph-author-commits-count'
});
- commits.text(author.commits + " commits");
+ commits.text(n__('%d commit', '%d commits', author.commits));
return $('<span/>').append(commits);
};
diff --git a/app/assets/javascripts/lib/utils/axios_utils.js b/app/assets/javascripts/lib/utils/axios_utils.js
new file mode 100644
index 00000000000..45bff245827
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/axios_utils.js
@@ -0,0 +1,6 @@
+import axios from 'axios';
+import csrf from './csrf';
+
+export default function setAxiosCsrfToken() {
+ axios.defaults.headers.common[csrf.headerKey] = csrf.token;
+}
diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js
index 5da2db063a4..1d496c64e53 100644
--- a/app/assets/javascripts/namespace_select.js
+++ b/app/assets/javascripts/namespace_select.js
@@ -1,85 +1,57 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, vars-on-top, one-var-declaration-per-line, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, no-param-reassign, no-cond-assign, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, max-len */
import Api from './api';
+import './lib/utils/url_utility';
-(function() {
- window.NamespaceSelect = (function() {
- function NamespaceSelect(opts) {
- this.onSelectItem = this.onSelectItem.bind(this);
- var fieldName, showAny;
- this.dropdown = opts.dropdown;
- showAny = true;
- fieldName = 'namespace_id';
- if (this.dropdown.attr('data-field-name')) {
- fieldName = this.dropdown.data('fieldName');
- }
- if (this.dropdown.attr('data-show-any')) {
- showAny = this.dropdown.data('showAny');
- }
- this.dropdown.glDropdown({
- filterable: true,
- selectable: true,
- filterRemote: true,
- search: {
- fields: ['path']
- },
- fieldName: fieldName,
- toggleLabel: function(selected) {
- if (selected.id == null) {
- return selected.text;
- } else {
- return selected.kind + ": " + selected.full_path;
- }
- },
- data: function(term, dataCallback) {
- return Api.namespaces(term, function(namespaces) {
- var anyNamespace;
- if (showAny) {
- anyNamespace = {
- text: 'Any namespace',
- id: null
- };
- namespaces.unshift(anyNamespace);
- namespaces.splice(1, 0, 'divider');
- }
- return dataCallback(namespaces);
- });
- },
- text: function(namespace) {
- if (namespace.id == null) {
- return namespace.text;
- } else {
- return namespace.kind + ": " + namespace.full_path;
- }
- },
- renderRow: this.renderRow,
- clicked: this.onSelectItem
- });
- }
-
- NamespaceSelect.prototype.onSelectItem = function(options) {
- const { e } = options;
- return e.preventDefault();
- };
+export default class NamespaceSelect {
+ constructor(opts) {
+ const isFilter = opts.dropdown.dataset.isFilter === 'true';
+ const fieldName = opts.dropdown.dataset.fieldName || 'namespace_id';
- return NamespaceSelect;
- })();
-
- window.NamespaceSelects = (function() {
- function NamespaceSelects(opts) {
- var ref;
- if (opts == null) {
- opts = {};
- }
- this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-namespace-select');
- this.$dropdowns.each(function(i, dropdown) {
- var $dropdown;
- $dropdown = $(dropdown);
- return new window.NamespaceSelect({
- dropdown: $dropdown
+ $(opts.dropdown).glDropdown({
+ filterable: true,
+ selectable: true,
+ filterRemote: true,
+ search: {
+ fields: ['path']
+ },
+ fieldName: fieldName,
+ toggleLabel: function(selected) {
+ if (selected.id == null) {
+ return selected.text;
+ } else {
+ return selected.kind + ": " + selected.full_path;
+ }
+ },
+ data: function(term, dataCallback) {
+ return Api.namespaces(term, function(namespaces) {
+ if (isFilter) {
+ const anyNamespace = {
+ text: 'Any namespace',
+ id: null
+ };
+ namespaces.unshift(anyNamespace);
+ namespaces.splice(1, 0, 'divider');
+ }
+ return dataCallback(namespaces);
});
- });
- }
-
- return NamespaceSelects;
- })();
-}).call(window);
+ },
+ text: function(namespace) {
+ if (namespace.id == null) {
+ return namespace.text;
+ } else {
+ return namespace.kind + ": " + namespace.full_path;
+ }
+ },
+ renderRow: this.renderRow,
+ clicked(options) {
+ if (!isFilter) {
+ const { e } = options;
+ e.preventDefault();
+ }
+ },
+ url(namespace) {
+ return gl.utils.mergeUrlParams({ [fieldName]: namespace.id }, window.location.href);
+ },
+ });
+ }
+}
diff --git a/app/assets/javascripts/notes/components/issue_note.vue b/app/assets/javascripts/notes/components/issue_note.vue
index 0ddbd672bed..40318f9a600 100644
--- a/app/assets/javascripts/notes/components/issue_note.vue
+++ b/app/assets/javascripts/notes/components/issue_note.vue
@@ -122,7 +122,9 @@
// we need to do this to prevent noteForm inconsistent content warning
// this is something we intentionally do so we need to recover the content
this.note.note = noteText;
- this.$refs.noteBody.$refs.noteForm.note = noteText; // TODO: This could be better
+ if (this.$refs.noteBody) {
+ this.$refs.noteBody.$refs.noteForm.note = noteText; // TODO: This could be better
+ }
},
},
created() {
diff --git a/app/assets/javascripts/pipelines/components/graph/action_component.vue b/app/assets/javascripts/pipelines/components/graph/action_component.vue
index 54227425d2a..547140b1a43 100644
--- a/app/assets/javascripts/pipelines/components/graph/action_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/action_component.vue
@@ -1,6 +1,6 @@
<script>
- import getActionIcon from '../../../vue_shared/ci_action_icons';
import tooltip from '../../../vue_shared/directives/tooltip';
+ import icon from '../../../vue_shared/components/icon.vue';
/**
* Renders either a cancel, retry or play icon pointing to the given path.
@@ -29,17 +29,18 @@
},
},
+ components: {
+ icon,
+ },
+
directives: {
tooltip,
},
computed: {
- actionIconSvg() {
- return getActionIcon(this.actionIcon);
- },
-
cssClass() {
- return `js-${gl.text.dasherize(this.actionIcon)}`;
+ const actionIconDash = gl.text.dasherize(this.actionIcon);
+ return `${actionIconDash} js-icon-${actionIconDash}`;
},
},
};
@@ -50,14 +51,9 @@
:data-method="actionMethod"
:title="tooltipText"
:href="link"
- class="ci-action-icon-container"
+ class="ci-action-icon-container ci-action-icon-wrapper"
+ :class="cssClass"
data-container="body">
-
- <i
- class="ci-action-icon-wrapper"
- :class="cssClass"
- v-html="actionIconSvg"
- aria-hidden="true"
- />
+ <icon :name="actionIcon"/>
</a>
</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue
index 18fe1847eef..1c0944d45fc 100644
--- a/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/dropdown_action_component.vue
@@ -1,5 +1,5 @@
<script>
- import getActionIcon from '../../../vue_shared/ci_action_icons';
+ import icon from '../../../vue_shared/components/icon.vue';
import tooltip from '../../../vue_shared/directives/tooltip';
/**
@@ -29,14 +29,12 @@
},
},
- directives: {
- tooltip,
+ components: {
+ icon,
},
- computed: {
- actionIconSvg() {
- return getActionIcon(this.actionIcon);
- },
+ directives: {
+ tooltip,
},
};
</script>
@@ -49,7 +47,7 @@
rel="nofollow"
class="ci-action-icon-wrapper js-ci-status-icon"
data-container="body"
- v-html="actionIconSvg"
aria-label="Job's action">
+ <icon :name="actionIcon"/>
</a>
</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
index 3e5d6d15909..7006d05e7b2 100644
--- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
@@ -18,7 +18,7 @@
* "group": "success",
* "details_path": "/root/ci-mock/builds/4256",
* "action": {
- * "icon": "icon_action_retry",
+ * "icon": "retry",
* "title": "Retry",
* "path": "/root/ci-mock/builds/4256/retry",
* "method": "post"
diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue
index 3933509a6f4..5dea4555515 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue
@@ -19,7 +19,7 @@
* "group": "success",
* "details_path": "/root/ci-mock/builds/4256",
* "action": {
- * "icon": "icon_action_retry",
+ * "icon": "retry",
* "title": "Retry",
* "path": "/root/ci-mock/builds/4256/retry",
* "method": "post"
diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue
index 1a7a5c2a415..ac9d9c901ca 100644
--- a/app/assets/javascripts/pipelines/components/stage.vue
+++ b/app/assets/javascripts/pipelines/components/stage.vue
@@ -14,7 +14,7 @@
*/
import Flash from '../../flash';
-import { borderlessStatusIconEntityMap } from '../../vue_shared/ci_status_icons';
+import icon from '../../vue_shared/components/icon.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
@@ -45,6 +45,7 @@ export default {
components: {
loadingIcon,
+ icon,
},
updated() {
@@ -122,8 +123,8 @@ export default {
return `ci-status-icon-${this.stage.status.group}`;
},
- svgIcon() {
- return borderlessStatusIconEntityMap[this.stage.status.icon];
+ borderlessIcon() {
+ return `${this.stage.status.icon}_borderless`;
},
},
};
@@ -145,9 +146,10 @@ export default {
aria-expanded="false">
<span
- v-html="svgIcon"
aria-hidden="true"
:aria-label="stage.title">
+ <icon
+ :name="borderlessIcon"/>
</span>
<i
diff --git a/app/assets/javascripts/profile/account/components/delete_account_modal.vue b/app/assets/javascripts/profile/account/components/delete_account_modal.vue
index b2b34cb83e1..6348a2e331d 100644
--- a/app/assets/javascripts/profile/account/components/delete_account_modal.vue
+++ b/app/assets/javascripts/profile/account/components/delete_account_modal.vue
@@ -98,7 +98,7 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
@toggle="toggleOpen"
@submit="onSubmit">
- <template slot="body" scope="props">
+ <template slot="body" slot-scope="props">
<p v-html="props.text"></p>
<form
diff --git a/app/assets/javascripts/repo/components/repo_editor.vue b/app/assets/javascripts/repo/components/repo_editor.vue
index 0d6729bb99b..1c864b176b1 100644
--- a/app/assets/javascripts/repo/components/repo_editor.vue
+++ b/app/assets/javascripts/repo/components/repo_editor.vue
@@ -27,6 +27,8 @@ export default {
'changeFileContent',
]),
initMonaco() {
+ if (this.shouldHideEditor) return;
+
if (this.monacoInstance) {
this.monacoInstance.setModel(null);
}
@@ -94,8 +96,12 @@ export default {
<template>
<div
id="ide"
- v-if='!shouldHideEditor'
class="blob-viewer-container blob-editor-container"
>
+ <div
+ v-if="shouldHideEditor"
+ v-html="activeFile.html"
+ >
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/settings_panels.js b/app/assets/javascripts/settings_panels.js
index 8635ccece6e..d34a21b37e1 100644
--- a/app/assets/javascripts/settings_panels.js
+++ b/app/assets/javascripts/settings_panels.js
@@ -1,34 +1,26 @@
-function expandSectionParent($section, $content) {
- $section.addClass('expanded');
- $content.off('animationend.expandSectionParent');
-}
-
function expandSection($section) {
$section.find('.js-settings-toggle').text('Collapse');
-
- const $content = $section.find('.settings-content');
- $content.addClass('expanded').off('scroll.expandSection').scrollTop(0);
-
- if ($content.hasClass('no-animate')) {
- expandSectionParent($section, $content);
- } else {
- $content.on('animationend.expandSectionParent', () => expandSectionParent($section, $content));
+ $section.find('.settings-content').off('scroll.expandSection').scrollTop(0);
+ $section.addClass('expanded');
+ if (!$section.hasClass('no-animate')) {
+ $section.addClass('animating')
+ .one('animationend.animateSection', () => $section.removeClass('animating'));
}
}
function closeSection($section) {
$section.find('.js-settings-toggle').text('Expand');
-
- const $content = $section.find('.settings-content');
- $content.removeClass('expanded').on('scroll.expandSection', () => expandSection($section));
-
+ $section.find('.settings-content').on('scroll.expandSection', () => expandSection($section));
$section.removeClass('expanded');
+ if (!$section.hasClass('no-animate')) {
+ $section.addClass('animating')
+ .one('animationend.animateSection', () => $section.removeClass('animating'));
+ }
}
function toggleSection($section) {
- const $content = $section.find('.settings-content');
- $content.removeClass('no-animate');
- if ($content.hasClass('expanded')) {
+ $section.removeClass('no-animate');
+ if ($section.hasClass('expanded')) {
closeSection($section);
} else {
expandSection($section);
@@ -39,10 +31,19 @@ export default function initSettingsPanels() {
$('.settings').each((i, elm) => {
const $section = $(elm);
$section.on('click.toggleSection', '.js-settings-toggle', () => toggleSection($section));
- $section.find('.settings-content:not(.expanded)').on('scroll.expandSection', () => expandSection($section));
+
+ if (!$section.hasClass('expanded')) {
+ $section.find('.settings-content').on('scroll.expandSection', () => {
+ $section.removeClass('no-animate');
+ expandSection($section);
+ });
+ }
});
if (location.hash) {
- expandSection($(location.hash));
+ const $target = $(location.hash);
+ if ($target.length && $target.hasClass('.settings')) {
+ expandSection($target);
+ }
}
}
diff --git a/app/assets/javascripts/test_utils/index.js b/app/assets/javascripts/test_utils/index.js
index 8875590f0f2..a55a338eea8 100644
--- a/app/assets/javascripts/test_utils/index.js
+++ b/app/assets/javascripts/test_utils/index.js
@@ -1,6 +1,8 @@
import 'core-js/es6/map';
import 'core-js/es6/set';
import simulateDrag from './simulate_drag';
+import simulateInput from './simulate_input';
// Export to global space for rspec to use
window.simulateDrag = simulateDrag;
+window.simulateInput = simulateInput;
diff --git a/app/assets/javascripts/test_utils/simulate_input.js b/app/assets/javascripts/test_utils/simulate_input.js
new file mode 100644
index 00000000000..90c1b7cb57e
--- /dev/null
+++ b/app/assets/javascripts/test_utils/simulate_input.js
@@ -0,0 +1,23 @@
+function triggerEvents(input) {
+ input.dispatchEvent(new Event('keydown'));
+ input.dispatchEvent(new Event('keypress'));
+ input.dispatchEvent(new Event('input'));
+ input.dispatchEvent(new Event('keyup'));
+}
+
+export default function simulateInput(target, text) {
+ const input = document.querySelector(target);
+ if (!input || !input.matches('textarea, input')) {
+ return false;
+ }
+
+ if (text.length > 0) {
+ Array.prototype.forEach.call(text, (char) => {
+ input.value += char;
+ triggerEvents(input);
+ });
+ } else {
+ triggerEvents(input);
+ }
+ return true;
+}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
index c79b5c720eb..029832bdd27 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.js
@@ -1,6 +1,6 @@
import PipelineStage from '../../pipelines/components/stage.vue';
import ciIcon from '../../vue_shared/components/ci_icon.vue';
-import { statusIconEntityMap } from '../../vue_shared/ci_status_icons';
+import icon from '../../vue_shared/components/icon.vue';
export default {
name: 'MRWidgetPipeline',
@@ -10,6 +10,7 @@ export default {
components: {
'pipeline-stage': PipelineStage,
ciIcon,
+ icon,
},
computed: {
hasPipeline() {
@@ -20,9 +21,6 @@ export default {
return hasCI && !ciStatus;
},
- svg() {
- return statusIconEntityMap.icon_status_failed;
- },
stageText() {
return this.mr.pipeline.details.stages.length > 1 ? 'stages' : 'stage';
},
@@ -38,8 +36,10 @@ export default {
<template v-if="hasCIError">
<div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-10">
<span
- v-html="svg"
- aria-hidden="true"></span>
+ aria-hidden="true">
+ <icon
+ name="status_failed"/>
+ </span>
</div>
<div class="media-body">
Could not connect to the CI server. Please check your settings and try again
diff --git a/app/assets/javascripts/vue_shared/ci_action_icons.js b/app/assets/javascripts/vue_shared/ci_action_icons.js
deleted file mode 100644
index b21f0ab49fd..00000000000
--- a/app/assets/javascripts/vue_shared/ci_action_icons.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import cancelSVG from 'icons/_icon_action_cancel.svg';
-import retrySVG from 'icons/_icon_action_retry.svg';
-import playSVG from 'icons/_icon_action_play.svg';
-import stopSVG from 'icons/_icon_action_stop.svg';
-
-/**
- * For the provided action returns the respective SVG
- *
- * @param {String} action
- * @return {SVG|String}
- */
-export default function getActionIcon(action) {
- const icons = {
- icon_action_cancel: cancelSVG,
- icon_action_play: playSVG,
- icon_action_retry: retrySVG,
- icon_action_stop: stopSVG,
- };
-
- return icons[action] || '';
-}
diff --git a/app/assets/javascripts/vue_shared/ci_status_icons.js b/app/assets/javascripts/vue_shared/ci_status_icons.js
deleted file mode 100644
index d9d0cad38e4..00000000000
--- a/app/assets/javascripts/vue_shared/ci_status_icons.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import BORDERLESS_CANCELED_SVG from 'icons/_icon_status_canceled_borderless.svg';
-import BORDERLESS_CREATED_SVG from 'icons/_icon_status_created_borderless.svg';
-import BORDERLESS_FAILED_SVG from 'icons/_icon_status_failed_borderless.svg';
-import BORDERLESS_MANUAL_SVG from 'icons/_icon_status_manual_borderless.svg';
-import BORDERLESS_PENDING_SVG from 'icons/_icon_status_pending_borderless.svg';
-import BORDERLESS_RUNNING_SVG from 'icons/_icon_status_running_borderless.svg';
-import BORDERLESS_SKIPPED_SVG from 'icons/_icon_status_skipped_borderless.svg';
-import BORDERLESS_SUCCESS_SVG from 'icons/_icon_status_success_borderless.svg';
-import BORDERLESS_WARNING_SVG from 'icons/_icon_status_warning_borderless.svg';
-
-import CANCELED_SVG from 'icons/_icon_status_canceled.svg';
-import CREATED_SVG from 'icons/_icon_status_created.svg';
-import FAILED_SVG from 'icons/_icon_status_failed.svg';
-import MANUAL_SVG from 'icons/_icon_status_manual.svg';
-import PENDING_SVG from 'icons/_icon_status_pending.svg';
-import RUNNING_SVG from 'icons/_icon_status_running.svg';
-import SKIPPED_SVG from 'icons/_icon_status_skipped.svg';
-import SUCCESS_SVG from 'icons/_icon_status_success.svg';
-import WARNING_SVG from 'icons/_icon_status_warning.svg';
-
-export const borderlessStatusIconEntityMap = {
- icon_status_canceled: BORDERLESS_CANCELED_SVG,
- icon_status_created: BORDERLESS_CREATED_SVG,
- icon_status_failed: BORDERLESS_FAILED_SVG,
- icon_status_manual: BORDERLESS_MANUAL_SVG,
- icon_status_pending: BORDERLESS_PENDING_SVG,
- icon_status_running: BORDERLESS_RUNNING_SVG,
- icon_status_skipped: BORDERLESS_SKIPPED_SVG,
- icon_status_success: BORDERLESS_SUCCESS_SVG,
- icon_status_warning: BORDERLESS_WARNING_SVG,
-};
-
-export const statusIconEntityMap = {
- icon_status_canceled: CANCELED_SVG,
- icon_status_created: CREATED_SVG,
- icon_status_failed: FAILED_SVG,
- icon_status_manual: MANUAL_SVG,
- icon_status_pending: PENDING_SVG,
- icon_status_running: RUNNING_SVG,
- icon_status_skipped: SKIPPED_SVG,
- icon_status_success: SUCCESS_SVG,
- icon_status_warning: WARNING_SVG,
-};
diff --git a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
index 5b6c6e8d0b9..fc795936abf 100644
--- a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
@@ -43,7 +43,6 @@
computed: {
cssClass() {
const className = this.status.group;
-
return className ? `ci-status ci-${className}` : 'ci-status';
},
},
diff --git a/app/assets/javascripts/vue_shared/components/ci_icon.vue b/app/assets/javascripts/vue_shared/components/ci_icon.vue
index ec88119e16c..2a018f38366 100644
--- a/app/assets/javascripts/vue_shared/components/ci_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_icon.vue
@@ -1,5 +1,5 @@
<script>
- import { statusIconEntityMap } from '../ci_status_icons';
+ import icon from '../../vue_shared/components/icon.vue';
/**
* Renders CI icon based on API response shared between all places where it is used.
@@ -30,11 +30,11 @@
},
},
- computed: {
- statusIconSvg() {
- return statusIconEntityMap[this.status.icon];
- },
+ components: {
+ icon,
+ },
+ computed: {
cssClass() {
const status = this.status.group;
return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status}`;
@@ -44,7 +44,8 @@
</script>
<template>
<span
- :class="cssClass"
- v-html="statusIconSvg">
+ :class="cssClass">
+ <icon
+ :name="status.icon"/>
</span>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/icon.vue b/app/assets/javascripts/vue_shared/components/icon.vue
new file mode 100644
index 00000000000..2e5f9f1088f
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/icon.vue
@@ -0,0 +1,52 @@
+<script>
+
+/* This is a re-usable vue component for rendering a svg sprite
+ icon
+
+ Sample configuration:
+
+ <icon
+ :img-src="userAvatarSrc"
+ :img-alt="tooltipText"
+ :tooltip-text="tooltipText"
+ tooltip-placement="top"
+ />
+
+*/
+ export default {
+ props: {
+ name: {
+ type: String,
+ required: true,
+ },
+
+ size: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+
+ cssClasses: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+
+ computed: {
+ spriteHref() {
+ return `${gon.sprite_icons}#${this.name}`;
+ },
+ iconSizeClass() {
+ return this.size ? `s${this.size}` : '';
+ },
+ },
+ };
+</script>
+<template>
+ <svg
+ :class="[iconSizeClass, cssClasses]">
+ <use
+ v-bind="{'xlink:href':spriteHref}"/>
+ </svg>
+</template>
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 7b1ef003bb2..c334f39f416 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -56,4 +56,4 @@
@import "framework/icons";
@import "framework/snippets";
@import "framework/memory_graph";
-@import "framework/responsive-tables";
+@import "framework/responsive_tables";
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 8819a0c20f4..def986180fc 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -40,6 +40,10 @@
&.top-block {
border-top: none;
+
+ .container-fluid {
+ background-color: inherit;
+ }
}
&.middle-block {
@@ -98,10 +102,6 @@
background-color: $white-light;
border-top: none;
}
-
- &.top-block .container-fluid {
- background-color: inherit;
- }
}
.sub-header-block {
diff --git a/app/assets/stylesheets/framework/callout.scss b/app/assets/stylesheets/framework/callout.scss
index e0e46dd73af..1bd94c0acba 100644
--- a/app/assets/stylesheets/framework/callout.scss
+++ b/app/assets/stylesheets/framework/callout.scss
@@ -12,15 +12,15 @@
border-left: 3px solid $border-color;
color: $text-color;
background: $gray-light;
-}
-.bs-callout h4 {
- margin-top: 0;
- margin-bottom: 5px;
-}
+ h4 {
+ margin-top: 0;
+ margin-bottom: 5px;
+ }
-.bs-callout p:last-child {
- margin-bottom: 0;
+ p:last-child {
+ margin-bottom: 0;
+ }
}
/* Variations */
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 1cfd7ef01a8..a0dbb9fc726 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -8,32 +8,6 @@
color: $gl-text-color-secondary;
}
-/** COMMON CLASSES **/
-.prepend-top-0 { margin-top: 0; }
-.prepend-top-5 { margin-top: 5px; }
-.prepend-top-10 { margin-top: 10px; }
-.prepend-top-default { margin-top: $gl-padding !important; }
-.prepend-top-20 { margin-top: 20px; }
-.prepend-left-4 { margin-left: 4px; }
-.prepend-left-5 { margin-left: 5px; }
-.prepend-left-10 { margin-left: 10px; }
-.prepend-left-default { margin-left: $gl-padding; }
-.prepend-left-20 { margin-left: 20px; }
-.append-right-5 { margin-right: 5px; }
-.append-right-8 { margin-right: 8px; }
-.append-right-10 { margin-right: 10px; }
-.append-right-default { margin-right: $gl-padding; }
-.append-right-20 { margin-right: 20px; }
-.append-bottom-0 { margin-bottom: 0; }
-.append-bottom-5 { margin-bottom: 5px; }
-.append-bottom-10 { margin-bottom: 10px; }
-.append-bottom-15 { margin-bottom: 15px; }
-.append-bottom-20 { margin-bottom: 20px; }
-.append-bottom-default { margin-bottom: $gl-padding; }
-.inline { display: inline-block; }
-.center { text-align: center; }
-.vertical-align-middle { vertical-align: middle; }
-
.underlined-link { text-decoration: underline; }
.hint { font-style: italic; color: $hint-color; }
.light { color: $common-gray; }
@@ -82,6 +56,14 @@ hr {
.str-truncated {
@include str-truncated;
+
+ &-60 {
+ @include str-truncated(60%);
+ }
+
+ &-100 {
+ @include str-truncated(100%);
+ }
}
.block-truncated {
@@ -107,10 +89,17 @@ hr {
font-size: 14px;
}
-table a code {
- position: relative;
- top: -2px;
- margin-right: 3px;
+table {
+ a code {
+ position: relative;
+ top: -2px;
+ margin-right: 3px;
+ }
+
+ td.permission-x {
+ background: $table-permission-x-bg !important;
+ text-align: center;
+ }
}
.loading {
@@ -295,13 +284,6 @@ img.emoji {
margin-bottom: 10px;
}
-table {
- td.permission-x {
- background: $table-permission-x-bg !important;
- text-align: center;
- }
-}
-
.btn-sign-in {
text-shadow: none;
@@ -367,10 +349,11 @@ table {
.dropzone .dz-preview .dz-progress {
border-color: $border-color !important;
-}
-.dropzone .dz-preview .dz-progress .dz-upload {
- background: $gl-success !important;
+ .dz-upload {
+ background: $gl-success !important;
+ }
+
}
.dz-message {
@@ -431,16 +414,6 @@ table {
border-radius: $border-radius-default;
}
-.str-truncated {
- &-60 {
- @include str-truncated(60%);
- }
-
- &-100 {
- @include str-truncated(100%);
- }
-}
-
.tooltip {
.tooltip-inner {
word-wrap: break-word;
@@ -451,3 +424,30 @@ table {
pointer-events: none;
opacity: .5;
}
+
+/** COMMON CLASSES **/
+.prepend-top-0 { margin-top: 0; }
+.prepend-top-5 { margin-top: 5px; }
+.prepend-top-10 { margin-top: 10px; }
+.prepend-top-15 { margin-top: 15px; }
+.prepend-top-default { margin-top: $gl-padding !important; }
+.prepend-top-20 { margin-top: 20px; }
+.prepend-left-4 { margin-left: 4px; }
+.prepend-left-5 { margin-left: 5px; }
+.prepend-left-10 { margin-left: 10px; }
+.prepend-left-default { margin-left: $gl-padding; }
+.prepend-left-20 { margin-left: 20px; }
+.append-right-5 { margin-right: 5px; }
+.append-right-8 { margin-right: 8px; }
+.append-right-10 { margin-right: 10px; }
+.append-right-default { margin-right: $gl-padding; }
+.append-right-20 { margin-right: 20px; }
+.append-bottom-0 { margin-bottom: 0; }
+.append-bottom-5 { margin-bottom: 5px; }
+.append-bottom-10 { margin-bottom: 10px; }
+.append-bottom-15 { margin-bottom: 15px; }
+.append-bottom-20 { margin-bottom: 20px; }
+.append-bottom-default { margin-bottom: $gl-padding; }
+.inline { display: inline-block; }
+.center { text-align: center; }
+.vertical-align-middle { vertical-align: middle; }
diff --git a/app/assets/stylesheets/framework/contextual-sidebar.scss b/app/assets/stylesheets/framework/contextual-sidebar.scss
index fa5d3833f3e..320f458630a 100644
--- a/app/assets/stylesheets/framework/contextual-sidebar.scss
+++ b/app/assets/stylesheets/framework/contextual-sidebar.scss
@@ -141,15 +141,15 @@
svg {
fill: $gl-text-color-secondary;
}
- }
- .nav-item-name {
- flex: 1;
- }
+ .nav-item-name {
+ flex: 1;
+ }
- li.active {
- > a {
- font-weight: $gl-font-weight-bold;
+ &.active {
+ > a {
+ font-weight: $gl-font-weight-bold;
+ }
}
}
@@ -484,10 +484,7 @@
height: calc(100vh - #{$header-height});
@media (min-width: $screen-sm-min) {
- height: 475px; // Needed for PhantomJS
- // scss-lint:disable DuplicateProperty
height: calc(100vh - 180px);
- // scss-lint:enable DuplicateProperty
}
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 3ff124496d2..579bd48fac6 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -728,11 +728,11 @@
.pika-single.animate-picker.is-bound {
@include set-visible;
-}
-.pika-single.animate-picker.is-bound.is-hidden {
- @include set-invisible;
- overflow: hidden;
+ &.is-hidden {
+ @include set-invisible;
+ overflow: hidden;
+ }
}
@mixin dropdown-item-hover {
@@ -939,9 +939,7 @@ header.header-content .dropdown-menu.projects-dropdown-menu {
border-right: 0;
}
}
-}
-.projects-dropdown-container {
.projects-list-frequent-container,
.projects-list-search-container, {
padding: 8px 0;
@@ -952,11 +950,6 @@ header.header-content .dropdown-menu.projects-dropdown-menu {
.projects-list-frequent-container li.section-empty,
.projects-list-search-container li.section-empty {
padding: 0 15px;
- }
-
- .section-header,
- .projects-list-frequent-container li.section-empty,
- .projects-list-search-container li.section-empty {
color: $gl-text-color-secondary;
font-size: $gl-font-size;
}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 5833ef939e9..6382551fcc9 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -165,22 +165,36 @@
&:last-child {
border-right: none;
}
- }
- td.blame-commit {
- padding: 5px 10px;
- min-width: 400px;
- max-width: 400px;
- background: $gray-light;
- border-left: 3px solid;
+ &.blame-commit {
+ padding: 5px 10px;
+ min-width: 400px;
+ max-width: 400px;
+ background: $gray-light;
+ border-left: 3px solid;
+
+ .commit-row-title {
+ display: flex;
+ }
+
+ .item-title {
+ flex: 1;
+ margin-right: 0.5em;
+ }
+ }
+
+ &.line-numbers {
+ float: none;
+ border-left: 1px solid $blame-line-numbers-border;
- .commit-row-title {
- display: flex;
+ i {
+ float: none;
+ margin-right: 0;
+ }
}
- .item-title {
- flex: 1;
- margin-right: 0.5em;
+ &.lines {
+ padding: 0;
}
}
@@ -195,20 +209,6 @@
border-left-color: mix($blame-gray, $blame-cyan, $i / 4.0 * 100%);
}
}
-
- td.line-numbers {
- float: none;
- border-left: 1px solid $blame-line-numbers-border;
-
- i {
- float: none;
- margin-right: 0;
- }
- }
-
- td.lines {
- padding: 0;
- }
}
&.logs {
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 0d80a85d521..a7333925f80 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -268,12 +268,6 @@
.filtered-search-box-input-container {
flex: 1;
position: relative;
- // Fix PhantomJS not supporting `flex: 1;` properly.
- // This is important because it can change the expected `e.target` when clicking things in tests.
- // See https://gitlab.com/gitlab-org/gitlab-ce/blob/b54acba8b732688c59fe2f38510c469dc86ee499/spec/features/issues/filtered_search/visual_tokens_spec.rb#L61
- // - With `width: 100%`: `e.target` = `.tokens-container`, https://i.imgur.com/jGq7wbx.png
- // - Without `width: 100%`: `e.target` = `.filtered-search`, https://i.imgur.com/cNI2CyT.png
- width: 100%;
min-width: 0;
}
@@ -469,10 +463,10 @@
word-break: break-all;
}
}
-}
-.filter-dropdown-item.droplab-item-active .btn {
- @extend %filter-dropdown-item-btn-hover;
+ &.droplab-item-active .btn {
+ @extend %filter-dropdown-item-btn-hover;
+ }
}
.filter-dropdown-loading {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 62ba74ff582..5d777f0d468 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -352,7 +352,77 @@
.header-user .dropdown-menu-nav,
.header-new .dropdown-menu-nav {
- margin-top: $dropdown-vertical-offset;
+ margin-top: 4px;
+}
+
+.search {
+ margin: 4px 8px 0;
+
+ form {
+ height: 32px;
+ border: 0;
+ border-radius: $border-radius-default;
+ transition: border-color ease-in-out 0.15s, background-color ease-in-out 0.15s;
+
+ &:hover {
+ box-shadow: none;
+ }
+ }
+
+ .search-input {
+ color: $white-light;
+ background: none;
+ transition: color ease-in-out 0.15s;
+ }
+
+ .search-input::placeholder {
+ transition: color ease-in-out 0.15s;
+ }
+
+ .location-badge {
+ font-size: 12px;
+ margin: -4px 4px -4px -4px;
+ line-height: 25px;
+ padding: 4px 8px;
+ border-radius: 2px 0 0 2px;
+ height: 32px;
+ transition: border-color ease-in-out 0.15s;
+ }
+
+ &.search-active {
+ form {
+ background-color: rgba($indigo-200, .3);
+ box-shadow: none;
+
+ .search-input {
+ color: $gl-text-color;
+ transition: color ease-in-out 0.15s;
+ }
+
+ .search-input::placeholder {
+ color: $gl-text-color-tertiary;
+ }
+
+ .search-input-wrap {
+ .search-icon,
+ .clear-icon {
+ color: $gl-text-color-tertiary;
+ transition: color ease-in-out 0.15s;
+ }
+ }
+ }
+
+ .location-badge {
+ background-color: $nav-badge-bg;
+ border-color: $border-color;
+ }
+
+ .search-input-wrap {
+ .clear-icon {
+ color: $white-light;
+ }
+ }
+ }
}
.breadcrumbs {
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index 69d19ea2962..cb324ccc440 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -30,10 +30,10 @@ body {
.container {
padding-top: 0;
z-index: 5;
-}
-.container .content {
- margin: 0;
+ .content {
+ margin: 0;
+ }
}
.navless-container {
@@ -82,26 +82,26 @@ body {
transition: background-color 0.15s, border-color 0.15s;
background-color: $orange-500;
border-color: $orange-500;
- }
- .alert-warning + .alert-warning {
- background-color: $orange-600;
- border-color: $orange-600;
- }
+ &:only-of-type {
+ background-color: $orange-500;
+ border-color: $orange-500;
+ }
- .alert-warning + .alert-warning + .alert-warning {
- background-color: $orange-700;
- border-color: $orange-700;
- }
+ + .alert-warning {
+ background-color: $orange-600;
+ border-color: $orange-600;
- .alert-warning + .alert-warning + .alert-warning + .alert-warning {
- background-color: $orange-800;
- border-color: $orange-800;
- }
+ + .alert-warning {
+ background-color: $orange-700;
+ border-color: $orange-700;
- .alert-warning:only-of-type {
- background-color: $orange-500;
- border-color: $orange-500;
+ + .alert-warning {
+ background-color: $orange-800;
+ border-color: $orange-800;
+ }
+ }
+ }
}
}
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index d43f998cb82..511608c618c 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -299,40 +299,40 @@ ul.indent-list {
}
}
-.group-list-tree .avatar-container.content-loading {
- position: relative;
+.group-list-tree {
+ .avatar-container.content-loading {
+ position: relative;
- > a,
- > a .avatar {
- height: 100%;
- border-radius: 50%;
- }
+ > a,
+ > a .avatar {
+ height: 100%;
+ border-radius: 50%;
+ }
- > a {
- padding: 2px;
- }
+ > a {
+ padding: 2px;
- > a .avatar {
- border: 2px solid $white-normal;
+ .avatar {
+ border: 2px solid $white-normal;
- &.identicon {
- line-height: 30px;
+ &.identicon {
+ line-height: 30px;
+ }
+ }
}
- }
- &::after {
- content: "";
- position: absolute;
- height: 100%;
- width: 100%;
- background-color: transparent;
- border: 2px outset $kdb-border;
- border-radius: 50%;
- animation: spin-avatar 3s infinite linear;
+ &::after {
+ content: "";
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ background-color: transparent;
+ border: 2px outset $kdb-border;
+ border-radius: 50%;
+ animation: spin-avatar 3s infinite linear;
+ }
}
-}
-.group-list-tree {
.folder-toggle-wrap {
float: left;
line-height: $list-text-height;
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index e3920b5d3d9..0a5a16c09b0 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -173,21 +173,8 @@
ul > li {
white-space: nowrap;
}
-}
-
-@media(max-width: $screen-xs-max) {
- .atwho-view-ul {
- width: 350px;
- }
-
- .atwho-view ul li {
- overflow: hidden;
- text-overflow: ellipsis;
- }
-}
-// TODO: fallback to global style
-.atwho-view {
+ // TODO: fallback to global style
.atwho-view-ul {
padding: 8px 1px;
@@ -220,3 +207,14 @@
}
}
}
+
+@media(max-width: $screen-xs-max) {
+ .atwho-view-ul {
+ width: 350px;
+ }
+
+ .atwho-view ul li {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+}
diff --git a/app/assets/stylesheets/framework/responsive-tables.scss b/app/assets/stylesheets/framework/responsive_tables.scss
index 8e653c443cf..8b7afdbe1a5 100644
--- a/app/assets/stylesheets/framework/responsive-tables.scss
+++ b/app/assets/stylesheets/framework/responsive_tables.scss
@@ -3,57 +3,77 @@
max-width: #{$max + '%'};
}
+.gl-responsive-table-row-layout {
+ width: 100%;
+
+ @media (min-width: $screen-md-min) {
+ display: flex;
+ align-items: center;
+
+ & > &:not(:first-child) {
+ margin-top: $gl-padding;
+ }
+ }
+}
+
.gl-responsive-table-row {
+ @extend .gl-responsive-table-row-layout;
margin-top: 10px;
border: 1px solid $border-color;
@media (min-width: $screen-md-min) {
- padding: 15px 0;
margin: 0;
- display: flex;
- align-items: center;
+ padding: $gl-padding 0;
border: none;
- border-bottom: 1px solid $white-normal;
+
+ &:not(:last-child) {
+ border-bottom: 1px solid $white-normal;
+ }
}
+}
- .table-section {
- white-space: nowrap;
+.gl-responsive-table-row-col-span {
+ flex-wrap: wrap;
+}
+
+.table-section {
+ white-space: nowrap;
- $section-widths: 10 15 20 25 30 40;
- @each $width in $section-widths {
- &.section-#{$width} {
- flex: 0 0 #{$width + '%'};
+ $section-widths: 10 15 20 25 30 40 100;
+ @each $width in $section-widths {
+ &.section-#{$width} {
+ flex: 0 0 #{$width + '%'};
- @media (min-width: $screen-md-min) {
- max-width: #{$width + '%'};
- }
+ @media (min-width: $screen-md-min) {
+ max-width: #{$width + '%'};
}
}
+ }
- &:not(.table-button-footer) {
- @media (max-width: $screen-sm-max) {
- display: flex;
- align-self: stretch;
- padding: 10px;
- align-items: center;
- min-height: 62px;
+ @media (max-width: $screen-sm-max) {
+ display: flex;
+ align-self: stretch;
+ padding: 10px;
+ align-items: center;
+ min-height: 62px;
- &:not(:first-of-type) {
- border-top: 1px solid $white-normal;
- }
- }
+ &:not(:first-child) {
+ border-top: 1px solid $white-normal;
}
+ }
- &.section-wrap {
- white-space: normal;
+ &.section-wrap {
+ white-space: normal;
- @media (max-width: $screen-sm-max) {
- flex-wrap: wrap;
- }
+ @media (max-width: $screen-sm-max) {
+ flex-wrap: wrap;
}
}
-}
+ &.section-align-top {
+ align-self: flex-start;
+ }
+}
.table-button-footer {
@media (min-width: $screen-md-min) {
@@ -61,12 +81,13 @@
}
@media (max-width: $screen-sm-max) {
- background-color: $gray-normal;
+ display: block;
align-self: stretch;
+ min-height: 0;
+ background-color: $gray-normal;
border-top: 1px solid $border-color;
.table-action-buttons {
- padding: 10px 5px;
display: flex;
.btn {
@@ -77,7 +98,14 @@
> .external-url,
> .btn {
flex: 1 1 28px;
- margin: 0 5px;
+
+ &:not(:first-child) {
+ margin-left: 5px;
+ }
+
+ &:not(:last-child) {
+ margin-right: 5px;
+ }
}
.dropdown-new {
diff --git a/app/assets/stylesheets/framework/secondary-navigation-elements.scss b/app/assets/stylesheets/framework/secondary-navigation-elements.scss
index 3fd2549b143..9e1f77e5726 100644
--- a/app/assets/stylesheets/framework/secondary-navigation-elements.scss
+++ b/app/assets/stylesheets/framework/secondary-navigation-elements.scss
@@ -340,11 +340,64 @@
}
}
-.project-item-select-holder.btn-group {
- display: flex;
- max-width: 350px;
- overflow: hidden;
- float: right;
+.page-with-layout-nav {
+ .right-sidebar {
+ top: ($header-height + 1) * 2;
+ }
+
+ &.page-with-sub-nav {
+ .right-sidebar {
+ top: ($header-height + 1) * 3;
+
+ &.affix {
+ top: $header-height;
+ }
+ }
+ }
+}
+
+.with-performance-bar .page-with-layout-nav {
+ .right-sidebar {
+ top: ($header-height + 1) * 2 + $performance-bar-height;
+ }
+
+ &.page-with-sub-nav {
+ .right-sidebar {
+ top: ($header-height + 1) * 3 + $performance-bar-height;
+
+ &.affix {
+ top: $header-height + $performance-bar-height;
+ }
+ }
+ }
+}
+
+@media (max-width: $screen-xs-max) {
+ .top-area {
+ flex-flow: row wrap;
+
+ .nav-controls {
+ $controls-margin: $btn-xs-side-margin - 2px;
+ flex: 0 0 100%;
+
+ &.controls-flex {
+ display: flex;
+ flex-flow: row wrap;
+ align-items: center;
+ justify-content: center;
+ padding: 0 0 $gl-padding-top;
+ }
+
+ .controls-item,
+ .controls-item-full,
+ .controls-item:last-child {
+ flex: 1 1 35%;
+ display: block;
+ width: 100%;
+ margin: $controls-margin;
+ }
+ }
+ }
.new-project-item-link {
white-space: nowrap;
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index 621eec4f158..aa35cd9bea4 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -60,22 +60,12 @@
border-radius: $border-radius-base;
border: 1px solid $dropdown-border-color;
min-width: 175px;
- color: $gl-text-color;
- z-index: 999;
+ color: $gl-grayish-blue;
}
-.select2-drop-mask {
- z-index: 998;
-}
-
-.select2-drop.select2-drop-above.select2-drop-active {
- border-top: 1px solid $dropdown-border-color;
- margin-top: -6px;
-}
-
-.select2-results li.select2-result-with-children > .select2-result-label {
- font-weight: $gl-font-weight-bold;
- color: $gl-text-color;
+.select2-results .select2-result-label,
+.select2-more-results {
+ padding: 10px 15px;
}
.select2-container-active {
@@ -144,58 +134,46 @@
.select2-drop-auto-width & {
padding: 15px 15px 5px;
}
-}
-.select2-search input {
- padding: 2px 25px 2px 5px;
- background: $white-light image-url('select2.png');
- background-repeat: no-repeat;
- background-position: right 0 bottom 6px;
- border: 1px solid $input-border;
- border-radius: $border-radius-default;
- transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
-
- &:focus {
- border-color: $input-border-focus;
+ input {
+ padding: 2px 25px 2px 5px;
+ background: $white-light image-url('select2.png');
+ background-repeat: no-repeat;
+ background-position: right 0 bottom 6px;
+ border: 1px solid $input-border;
+ border-radius: $border-radius-default;
+ transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
+
+ &:focus {
+ border-color: $input-border-focus;
+ }
+
+ &.select2-active {
+ background-color: $white-light;
+ background-image: image-url('select2-spinner.gif') !important;
+ background-repeat: no-repeat;
+ background-position: right 5px center !important;
+ background-size: 16px 16px !important;
+ }
}
}
-.select2-search input.select2-active {
- background-color: $white-light;
- background-image: image-url('select2-spinner.gif') !important;
- background-repeat: no-repeat;
- background-position: right 5px center !important;
- background-size: 16px 16px !important;
+.select2-results .select2-no-results,
+.select2-results .select2-searching,
+.select2-results .select2-ajax-error,
+.select2-results .select2-selection-limit {
+ background: $gray-light;
+ display: list-item;
+ padding: 10px 15px;
}
.select2-results {
margin: 0;
- padding: #{$gl-padding / 2} 0;
-
- .select2-no-results,
- .select2-searching,
- .select2-ajax-error,
- .select2-selection-limit {
- background: transparent;
- padding: #{$gl-padding / 2} $gl-padding;
- }
-
- .select2-result-label,
- .select2-more-results {
- padding: #{$gl-padding / 2} $gl-padding;
- }
+ padding: 10px 0;
- .select2-highlighted {
- background: transparent;
+ li.select2-result-with-children > .select2-result-label {
+ font-weight: $gl-font-weight-bold;
color: $gl-text-color;
-
- .select2-result-label {
- background: $dropdown-item-hover-bg;
- }
- }
-
- .select2-result {
- padding: 0 1px;
}
}
@@ -212,6 +190,8 @@
}
.select2-highlighted {
+ background: $gl-link-color !important;
+
.group-result {
.group-path {
color: $white-light;
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 65b140cd7f8..c3d8f0c61a2 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -217,13 +217,31 @@ $white-gc-bg: #eaf2f5;
.cp { color: $white-cp; font-weight: $gl-font-weight-bold; }
.c1 { color: $white-c1; font-style: italic; }
.cs { color: $white-cs; font-weight: $gl-font-weight-bold; font-style: italic; }
- .gd { color: $white-gd; background-color: $white-gd-bg; }
- .gd .x { color: $white-gd-x; background-color: $white-gd-x-bg; }
+
+ .gd {
+ color: $white-gd;
+ background-color: $white-gd-bg;
+
+ .x {
+ color: $white-gd-x;
+ background-color: $white-gd-x-bg;
+ }
+ }
+
.ge { font-style: italic; }
.gr { color: $white-gr; }
.gh { color: $white-gh; }
- .gi { color: $white-gi; background-color: $white-gi-bg; }
- .gi .x { color: $white-gi-x; background-color: $white-gi-x-bg; }
+
+ .gi {
+ color: $white-gi;
+ background-color: $white-gi-bg;
+
+ .x {
+ color: $white-gi-x;
+ background-color: $white-gi-x-bg;
+ }
+ }
+
.go { color: $white-go; }
.gp { color: $white-gp; }
.gs { font-weight: $gl-font-weight-bold; }
diff --git a/app/assets/stylesheets/mailers/highlighted_diff_email.scss b/app/assets/stylesheets/mailers/highlighted_diff_email.scss
index fbe538ad1d7..658ac26fca9 100644
--- a/app/assets/stylesheets/mailers/highlighted_diff_email.scss
+++ b/app/assets/stylesheets/mailers/highlighted_diff_email.scss
@@ -158,13 +158,31 @@ span.highlight_word {
.cp { color: $highlighted-cp; font-weight: $gl-font-weight-bold; }
.c1 { color: $highlighted-c1; font-style: italic; }
.cs { color: $highlighted-cs; font-weight: $gl-font-weight-bold; font-style: italic; }
-.gd { color: $highlighted-gd; background-color: $highlighted-gd-bg; }
-.gd .x { color: $highlighted-gd; background-color: $highlighted-gd-x-bg; }
+
+.gd {
+ color: $highlighted-gd;
+ background-color: $highlighted-gd-bg;
+
+ .x {
+ color: $highlighted-gd;
+ background-color: $highlighted-gd-x-bg;
+ }
+}
+
.ge { font-style: italic; }
.gr { color: $highlighted-gr; }
.gh { color: $highlighted-gh; }
-.gi { color: $highlighted-gi; background-color: $highlighted-gi-bg; }
-.gi .x { color: $highlighted-gi; background-color: $highlighted-gi-x-bg; }
+
+.gi {
+ color: $highlighted-gi;
+ background-color: $highlighted-gi-bg;
+
+ .x {
+ color: $highlighted-gi;
+ background-color: $highlighted-gi-x-bg;
+ }
+}
+
.go { color: $highlighted-go; }
.gp { color: $highlighted-gp; }
.gs { font-weight: $gl-font-weight-bold; }
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 91296b354a7..3683afa07de 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -72,7 +72,7 @@
}
.boards-list {
- height: calc(100vh - 152px);
+ height: calc(100vh - 105px);
width: 100%;
padding-top: 25px;
padding-bottom: 25px;
@@ -81,11 +81,12 @@
overflow-x: scroll;
white-space: nowrap;
- @media (min-width: $screen-sm-min) {
- height: 475px; // Needed for PhantomJS
- // scss-lint:disable DuplicateProperty
- height: calc(100vh - 222px);
- // scss-lint:enable DuplicateProperty
+ @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
+ height: calc(100vh - 90px);
+ }
+
+ @media (min-width: $screen-md-min) {
+ height: calc(100vh - 160px);
min-height: 475px;
}
}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 50ec5110bf1..46978be8ba0 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -68,18 +68,18 @@
&.affix {
top: $header-height;
- }
- // with sidebar
- &.affix.sidebar-expanded {
- right: 306px;
- left: 16px;
- }
+ // with sidebar
+ &.sidebar-expanded {
+ right: 306px;
+ left: 16px;
+ }
- // without sidebar
- &.affix.sidebar-collapsed {
- right: 16px;
- left: 16px;
+ // without sidebar
+ &.sidebar-collapsed {
+ right: 16px;
+ left: 16px;
+ }
}
&.affix-top {
@@ -333,8 +333,10 @@
svg {
position: relative;
- top: 2px;
+ top: 3px;
margin-right: 3px;
+ width: 14px;
+ height: 14px;
}
}
@@ -348,9 +350,10 @@
svg {
position: relative;
- top: 2px;
+ top: 3px;
margin-right: 3px;
- height: 13px;
+ height: 14px;
+ width: 14px;
}
a {
@@ -369,7 +372,7 @@
.build-job {
position: relative;
- .fa-arrow-right {
+ .icon-arrow-right {
position: absolute;
left: 15px;
top: 20px;
@@ -379,7 +382,7 @@
&.active {
font-weight: $gl-font-weight-bold;
- .fa-arrow-right {
+ .icon-arrow-right {
display: block;
}
}
@@ -392,8 +395,7 @@
background-color: $row-hover;
}
- .fa-refresh {
- font-size: 13px;
+ .icon-retry {
margin-left: 3px;
}
}
diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss
index 8d6f30e3b84..5c91579c69c 100644
--- a/app/assets/stylesheets/pages/clusters.scss
+++ b/app/assets/stylesheets/pages/clusters.scss
@@ -2,8 +2,4 @@
.clipboard-addon {
background-color: $white-light;
}
-
- .alert-block {
- margin-bottom: 10px;
- }
}
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index 2a92673d9fa..82d9be29201 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -22,6 +22,11 @@
}
}
}
+
+ svg {
+ width: 136px;
+ height: 136px;
+ }
}
.col-headers {
@@ -155,11 +160,6 @@
}
}
- .landing svg {
- width: 136px;
- height: 136px;
- }
-
.fa-spinner {
font-size: 28px;
position: relative;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 09f831dcb29..faa3d1fb4d5 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -380,6 +380,10 @@
}
}
}
+
+ .line_content {
+ white-space: pre-wrap;
+ }
}
.file-content .diff-file {
@@ -387,10 +391,6 @@
border: none;
}
-.diff-file .line_content {
- white-space: pre-wrap;
-}
-
.diff-wrap-lines .line_content {
white-space: pre-wrap;
}
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 3b5e411e2c5..b5b0f3d9dfa 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -133,12 +133,11 @@
}
.folder-row {
- padding: 15px 0;
- border-bottom: 1px solid $white-normal;
+ border-left: none;
+ border-right: none;
- @media (max-width: $screen-sm-max) {
- border-top: 1px solid $white-normal;
- margin-top: 10px;
+ @media (min-width: $screen-sm-max) {
+ border-top: none;
}
}
@@ -256,23 +255,6 @@
width: 100%;
padding: 0;
padding-bottom: 100%;
-}
-
-.prometheus-svg-container > svg {
- position: absolute;
- height: 100%;
- width: 100%;
- left: 0;
- top: 0;
-
- text {
- fill: $gl-text-color;
- stroke-width: 0;
- }
-
- .text-metric-bold {
- font-weight: $gl-font-weight-bold;
- }
.label-axis-text {
fill: $black;
@@ -287,42 +269,51 @@
font-size: 12px;
}
- .legend-axis-text {
- fill: $black;
- }
+ > svg {
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ left: 0;
+ top: 0;
- .tick {
- > line {
- stroke: $gray-darker;
+ .label-axis-text,
+ .text-metric-usage {
+ fill: $black;
+ font-weight: $gl-font-weight-normal;
+ font-size: 12px;
}
- > text {
- font-size: 12px;
+ .legend-axis-text {
+ fill: $black;
}
- }
- .text-metric-title {
- font-size: 12px;
- }
+ .tick > text {
+ font-size: 12px;
+ }
- .y-label-text,
- .x-label-text {
- fill: $gray-darkest;
- }
+ .text-metric-title {
+ font-size: 12px;
+ }
- .axis-tick {
- stroke: $gray-darker;
- }
+ .y-label-text,
+ .x-label-text {
+ fill: $gray-darkest;
+ }
- @media (max-width: $screen-sm-max) {
- .label-axis-text,
- .text-metric-usage,
- .legend-axis-text {
- font-size: 8px;
+ .axis-tick {
+ stroke: $gray-darker;
}
- .tick > text {
- font-size: 8px;
+ @media (max-width: $screen-sm-max) {
+ .label-axis-text,
+ .text-metric-usage,
+ .legend-axis-text {
+ font-size: 8px;
+ }
+
+ .tick > text {
+ font-size: 8px;
+ }
}
}
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 88600a0e6d3..7059a4cfe85 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -127,7 +127,16 @@
}
.right-sidebar {
- a:not(.btn-retry),
+ position: absolute;
+ top: $header-height;
+ bottom: 0;
+ right: 0;
+ transition: width .3s;
+ background: $gray-light;
+ z-index: 200;
+ overflow: hidden;
+
+ a,
.btn-link {
color: inherit;
}
@@ -228,17 +237,6 @@
.btn-clipboard:hover {
color: $gl-text-color;
}
-}
-
-.right-sidebar {
- position: absolute;
- top: $header-height;
- bottom: 0;
- right: 0;
- transition: width $right-sidebar-transition-duration;
- background: $gray-light;
- z-index: 200;
- overflow: hidden;
.issuable-sidebar {
width: calc(100% + 100px);
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index cf5f933a762..92d49bd864a 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -109,6 +109,30 @@
border-top-right-radius: $border-radius-default;
border-top-left-radius: $border-radius-default;
+ // Ldap configurations may need more tabs & the tab labels are user generated (arbitrarily long).
+ // These styles prevent this from breaking the layout, and only applied when providers are configured.
+ &.custom-provider-tabs {
+ flex-wrap: wrap;
+
+ li {
+ min-width: 85px;
+ flex-basis: auto;
+
+ // This styles tab elements that have wrapped to a second line. We cannot easily predict when this will happen.
+ // We are making somewhat of an assumption about the configuration here: that users do not have more than
+ // 3 LDAP servers configured (in addition to standard login) and they are not using especially long names for any
+ // of them. If either condition is false, this will work as expected. If both are true, there may be a missing border
+ // above one of the bottom row elements. If you know a better way, please implement it!
+ &:nth-child(n+5) {
+ border-top: 1px solid $border-color;
+ }
+ }
+
+ a {
+ font-size: 16px;
+ }
+ }
+
li {
flex: 1;
text-align: center;
@@ -154,32 +178,6 @@
}
}
- // Ldap configurations may need more tabs & the tab labels are user generated (arbitrarily long).
- // These styles prevent this from breaking the layout, and only applied when providers are configured.
-
- .new-session-tabs.custom-provider-tabs {
- flex-wrap: wrap;
-
- li {
- min-width: 85px;
- flex-basis: auto;
-
- // This styles tab elements that have wrapped to a second line. We cannot easily predict when this will happen.
- // We are making somewhat of an assumption about the configuration here: that users do not have more than
- // 3 LDAP servers configured (in addition to standard login) and they are not using especially long names for any
- // of them. If either condition is false, this will work as expected. If both are true, there may be a missing border
- // above one of the bottom row elements. If you know a better way, please implement it!
- &:nth-child(n+5) {
- border-top: 1px solid $border-color;
- }
- }
-
- a {
- font-size: 16px;
- }
- }
-
-
.form-control {
&:active,
&:focus {
@@ -231,35 +229,35 @@
margin: 0;
padding: 0;
height: 100%;
-}
-// Fixes footer container to bottom of viewport
-.devise-layout-html body {
- // offset height of fixed header + 1 to avoid scroll
- height: calc(100% - 51px);
- margin: 0;
- padding: 0;
+ // Fixes footer container to bottom of viewport
+ body {
+ // offset height of fixed header + 1 to avoid scroll
+ height: calc(100% - 51px);
+ margin: 0;
+ padding: 0;
- .page-wrap {
- min-height: 100%;
- position: relative;
- }
+ .page-wrap {
+ min-height: 100%;
+ position: relative;
+ }
- .footer-container,
- hr.footer-fixed {
- position: absolute;
- bottom: 0;
- left: 0;
- right: 0;
- height: 40px;
- background: $white-light;
- }
+ .footer-container,
+ hr.footer-fixed {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 40px;
+ background: $white-light;
+ }
- .navless-container {
- padding: 65px 15px; // height of footer + bottom padding of email confirmation link
+ .navless-container {
+ padding: 65px 15px; // height of footer + bottom padding of email confirmation link
- @media (max-width: $screen-xs-max) {
- padding: 0 15px 65px;
+ @media (max-width: $screen-xs-max) {
+ padding: 0 15px 65px;
+ }
}
}
}
diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss
index 692acf74a58..18c48405ecd 100644
--- a/app/assets/stylesheets/pages/members.scss
+++ b/app/assets/stylesheets/pages/members.scss
@@ -49,9 +49,17 @@
width: auto;
}
}
+
+ &.existing-title {
+ @media (min-width: $screen-sm-min) {
+ float: left;
+ }
+ }
}
.member-form-control {
+ @include new-style-dropdown;
+
@media (max-width: $screen-xs-max) {
padding-bottom: 5px;
margin-left: 0;
@@ -64,12 +72,6 @@
line-height: 43px;
}
-.member.existing-title {
- @media (min-width: $screen-sm-min) {
- float: left;
- }
-}
-
.member-search-form {
@include new-style-dropdown;
@@ -281,7 +283,3 @@
}
}
}
-
-.member-form-control {
- @include new-style-dropdown;
-}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index d9fb3b44d29..6e485ebad1b 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -156,6 +156,10 @@
&.media > *:first-child {
margin-right: 10px;
}
+
+ .approve-btn {
+ margin-right: 5px;
+ }
}
.mr-widget-pipeline-graph {
@@ -165,8 +169,9 @@
z-index: 300;
}
- .ci-action-icon-wrapper {
- line-height: 16px;
+ .ci-action-icon-wrapper svg {
+ width: 16px;
+ height: 16px;
}
}
@@ -190,6 +195,10 @@
overflow: hidden;
word-break: break-all;
+ &.media > *:first-child {
+ margin-right: 10px;
+ }
+
&.label-truncated {
position: relative;
display: inline-block;
@@ -207,14 +216,7 @@
background-color: $gray-light;
}
}
- }
- .mr-widget-help {
- padding: 10px 16px 10px 48px;
- font-style: italic;
- }
-
- .mr-widget-body {
h4 {
float: left;
font-weight: $gl-font-weight-bold;
@@ -237,6 +239,10 @@
margin-right: 7px;
}
+ .approve-btn {
+ margin-right: 5px;
+ }
+
label {
font-weight: $gl-font-weight-normal;
}
@@ -336,6 +342,22 @@
}
}
+ .mini-pipeline-graph-dropdown-menu .mini-pipeline-graph-dropdown-item {
+ display: flex;
+ align-items: center;
+
+ .ci-status-text,
+ .ci-status-icon {
+ top: 0;
+ margin-right: 10px;
+ }
+ }
+
+ .mr-widget-help {
+ padding: 10px 16px 10px 48px;
+ font-style: italic;
+ }
+
.ci-coverage {
float: right;
}
@@ -350,12 +372,6 @@
}
}
-.mr-state-widget .mr-widget-body {
- .approve-btn {
- margin-right: 5px;
- }
-}
-
.mr-widget-body-controls {
flex-wrap: wrap;
}
@@ -469,16 +485,16 @@
padding-bottom: 0;
}
}
-}
-.mr-info-list.mr-memory-usage {
- p {
- float: left;
- }
+ &.mr-memory-usage {
+ p {
+ float: left;
+ }
- .memory-graph-container {
- float: left;
- margin-left: 5px;
+ .memory-graph-container {
+ float: left;
+ margin-left: 5px;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index 32039936be7..ae8fa45a2d7 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -66,6 +66,15 @@
height: 6px;
margin: 0;
}
+
+ .sidebar-collapsed-icon {
+ clear: both;
+ padding: 15px 5px 5px;
+
+ .progress {
+ margin: 5px 0;
+ }
+ }
}
.collapsed-milestone-date {
@@ -93,17 +102,6 @@
margin-right: 0;
}
- .milestone-progress {
- .sidebar-collapsed-icon {
- clear: both;
- padding: 15px 5px 5px;
-
- .progress {
- margin: 5px 0;
- }
- }
- }
-
.right-sidebar-collapsed & {
.reference {
border-top: 1px solid $border-gray-normal;
@@ -156,18 +154,16 @@
.status-box {
margin-top: 0;
- }
-
- .milestone-buttons {
- margin-left: auto;
- }
-
- .status-box {
order: 1;
}
.milestone-buttons {
+ margin-left: auto;
order: 2;
+
+ .verbose {
+ display: none;
+ }
}
.header-text-content {
@@ -175,10 +171,6 @@
width: 100%;
}
- .milestone-buttons .verbose {
- display: none;
- }
-
@media (min-width: $screen-xs-min) {
.milestone-buttons .verbose {
display: inline;
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index f0cad30f4f3..5127307c5e7 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -111,24 +111,9 @@
margin: auto;
align-items: center;
- .icon {
- margin-right: $issuable-warning-icon-margin;
- }
-}
-
-.disabled-comment .issuable-note-warning {
- border: none;
- border-radius: $label-border-radius;
- padding-top: $gl-vert-padding;
- padding-bottom: $gl-vert-padding;
-
- .icon svg {
- position: relative;
- top: 2px;
- margin-right: $btn-xs-side-margin;
- width: $gl-font-size;
- height: $gl-font-size;
- fill: $orange-600;
+ + .md-area {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
}
}
@@ -155,11 +140,6 @@
}
}
-.issuable-note-warning + .md-area {
- border-top-left-radius: 0;
- border-top-right-radius: 0;
-}
-
.discussion-form {
background-color: $white-light;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 312917bd13a..ca363c6eac4 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -312,57 +312,72 @@ ul.notes {
}
}
-.diff-file .notes_holder {
- font-family: $regular_font;
+.diff-file {
+ .is-over {
+ .add-diff-note {
+ display: inline-block;
+ }
+ }
- td {
- border: 1px solid $white-normal;
- border-left: none;
+ // Merge request notes in diffs
+ // Diff is inline
+ .notes_content .note-header .note-headline-light {
+ display: inline-block;
+ position: relative;
+ }
- &.notes_line {
- vertical-align: middle;
- text-align: center;
- padding: 10px 0;
- background: $gray-light;
- color: $text-color;
- }
+ .notes_holder {
+ font-family: $regular_font;
- &.notes_line2 {
- text-align: center;
- padding: 10px 0;
- border-left: 1px solid $note-line2-border !important;
- }
+ td {
+ border: 1px solid $white-normal;
+ border-left: none;
- &.notes_content {
- background-color: $gray-light;
- border-width: 1px 0;
- padding: 0;
- vertical-align: top;
- white-space: normal;
+ &.notes_line {
+ vertical-align: middle;
+ text-align: center;
+ padding: 10px 0;
+ background: $gray-light;
+ color: $text-color;
+ }
- &.parallel {
- border-width: 1px;
+ &.notes_line2 {
+ text-align: center;
+ padding: 10px 0;
+ border-left: 1px solid $note-line2-border !important;
}
- .discussion-notes {
- &:not(:first-child) {
- border-top: 1px solid $white-normal;
- margin-top: 20px;
+ &.notes_content {
+ background-color: $gray-light;
+ border-width: 1px 0;
+ padding: 0;
+ vertical-align: top;
+ white-space: normal;
+
+ &.parallel {
+ border-width: 1px;
}
- &:not(:last-child) {
- border-bottom: 1px solid $white-normal;
- margin-bottom: 20px;
+ .discussion-notes {
+ &:not(:first-child) {
+ border-top: 1px solid $white-normal;
+ margin-top: 20px;
+ }
+
+ &:not(:last-child) {
+ border-bottom: 1px solid $white-normal;
+ margin-bottom: 20px;
+ }
}
- }
- .notes {
- background-color: $white-light;
- }
+ .notes {
+ background-color: $white-light;
+ }
- a code {
- top: 0;
- margin-right: 0;
+ a code {
+ top: 0;
+ margin-right: 0;
+ }
}
}
}
@@ -457,8 +472,9 @@ ul.notes {
margin-left: 10px;
color: $gray-darkest;
- .btn-group > .discussion-next-btn {
- margin-left: -1px;
+ @include notes-media('max', $screen-md-max) {
+ float: none;
+ margin-left: 0;
}
}
@@ -469,8 +485,6 @@ ul.notes {
flex-shrink: 0;
display: inline-flex;
align-items: center;
- // For PhantomJS that does not support flex
- float: right;
margin-left: 10px;
color: $gray-darkest;
@@ -481,7 +495,6 @@ ul.notes {
}
.more-actions {
- float: right; // phantomjs fallback
display: flex;
align-items: flex-end;
@@ -502,13 +515,6 @@ ul.notes {
min-width: 180px;
}
-.discussion-actions {
- @include notes-media('max', $screen-md-max) {
- float: none;
- margin-left: 0;
- }
-}
-
.note-actions-item {
margin-left: 12px;
display: flex;
@@ -665,14 +671,6 @@ ul.notes {
}
}
-.diff-file {
- .is-over {
- .add-diff-note {
- display: inline-block;
- }
- }
-}
-
.disabled-comment {
background-color: $gray-light;
border-radius: $border-radius-base;
@@ -714,20 +712,20 @@ ul.notes {
svg path {
fill: $gray-darkest;
}
- }
- .btn.discussion-create-issue-btn {
- margin-left: -4px;
- border-radius: 0;
- border-right: 0;
+ &.discussion-create-issue-btn {
+ margin-left: -4px;
+ border-radius: 0;
+ border-right: 0;
- a {
- padding: 0;
- line-height: 0;
+ a {
+ padding: 0;
+ line-height: 0;
- &:hover {
- text-decoration: none;
- border: 0;
+ &:hover {
+ text-decoration: none;
+ border: 0;
+ }
}
}
}
@@ -801,12 +799,3 @@ ul.notes {
.line-resolve-text {
vertical-align: middle;
}
-
-// Merge request notes in diffs
-.diff-file {
- // Diff is inline
- .notes_content .note-header .note-headline-light {
- display: inline-block;
- position: relative;
- }
-}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 8fc7a5eec9b..2a8cbc61af7 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -31,7 +31,6 @@
}
.pipeline-actions {
- padding-right: 0;
min-width: 170px; //Guarantees buttons don't break in several lines.
.btn-default {
@@ -176,6 +175,25 @@
}
}
+ /**
+ * Play button with icon in dropdowns
+ */
+ .no-btn {
+ border: none;
+ background: none;
+ outline: none;
+ width: 100%;
+ text-align: left;
+
+ .icon-play {
+ position: relative;
+ top: 2px;
+ margin-right: 5px;
+ height: 13px;
+ width: 12px;
+ }
+ }
+
.duration,
.finished-at {
color: $gl-text-color-secondary;
@@ -451,36 +469,46 @@
@extend .build-content:hover;
}
- // Action Icons in big pipeline-graph nodes
- .ci-action-icon-container .ci-action-icon-wrapper {
- height: 30px;
- width: 30px;
- background: $white-light;
- border: 1px solid $border-color;
- border-radius: 100%;
- display: block;
-
- &:hover {
- background-color: $stage-hover-bg;
- border: 1px solid $dropdown-toggle-active-border-color;
- }
-
- svg {
- fill: $gl-text-color-secondary;
- position: relative;
- left: -1px;
- top: -1px;
- }
-
- &:hover svg {
- fill: $gl-text-color;
- }
- }
-
.ci-action-icon-container {
position: absolute;
right: 5px;
top: 5px;
+
+ // Action Icons in big pipeline-graph nodes
+ &.ci-action-icon-wrapper {
+ height: 30px;
+ width: 30px;
+ background: $white-light;
+ border: 1px solid $border-color;
+ border-radius: 100%;
+ display: block;
+
+ &:hover {
+ background-color: $stage-hover-bg;
+ border: 1px solid $dropdown-toggle-active-border-color;
+
+ svg {
+ fill: $gl-text-color;
+ }
+ }
+
+ svg {
+ fill: $gl-text-color-secondary;
+ position: relative;
+ left: 5px;
+ top: 2px;
+ width: 18px;
+ height: 18px;
+ }
+
+ &.play {
+ svg {
+ width: #{$ci-action-icon-size - 8};
+ height: #{$ci-action-icon-size - 8};
+ left: 8px;
+ }
+ }
+ }
}
.ci-status-icon svg {
@@ -721,17 +749,50 @@ button.mini-pipeline-graph-dropdown-toggle {
svg {
fill: $gl-text-color-secondary;
- width: $ci-action-icon-size;
- height: $ci-action-icon-size;
- left: -6px;
+ width: #{$ci-action-icon-size - 6};
+ height: #{$ci-action-icon-size - 6};
+ left: -3px;
position: relative;
- top: -3px;
+ top: -2px;
+
+ &.icon-action-stop,
+ &.icon-action-cancel {
+ width: 12px;
+ height: 12px;
+ top: 1px;
+ left: -1px;
+ }
+
+ &.icon-action-play {
+ width: 11px;
+ height: 11px;
+ top: 1px;
+ left: 1px;
+ }
+
+ &.icon-action-retry {
+ width: 16px;
+ height: 16px;
+ top: 0;
+ left: -3px;
+ }
}
&:hover svg,
&:focus svg {
fill: $gl-text-color;
}
+
+ &.icon-action-retry,
+ &.icon-action-play {
+ svg {
+ width: #{$ci-action-icon-size - 6};
+ height: #{$ci-action-icon-size - 6};
+ left: 8px;
+ }
+ }
+
+
}
// link to the build
@@ -799,13 +860,10 @@ button.mini-pipeline-graph-dropdown-toggle {
left: 100%;
top: -10px;
box-shadow: 0 1px 5px $black-transparent;
-}
-
-/**
- * Top arrow in the dropdown in the big pipeline graph
- */
-.big-pipeline-graph-dropdown-menu {
+ /**
+ * Top arrow in the dropdown in the big pipeline graph
+ */
&::before,
&::after {
content: '';
@@ -867,22 +925,23 @@ button.mini-pipeline-graph-dropdown-toggle {
margin-top: 1px;
border-bottom-color: $white-light;
}
-}
-/**
- * Center dropdown menu in mini graph
- */
-.mini-pipeline-graph-dropdown-menu.dropdown-menu {
- transform: translate(-80%, 0);
- min-width: 150px;
+ /**
+ * Center dropdown menu in mini graph
+ */
+ &.dropdown-menu {
+ transform: translate(-80%, 0);
+ min-width: 150px;
- @media(min-width: $screen-md-min) {
- transform: translate(-50%, 0);
- right: auto;
- left: 50%;
- min-width: 240px;
+ @media(min-width: $screen-md-min) {
+ transform: translate(-50%, 0);
+ right: auto;
+ left: 50%;
+ min-width: 240px;
+ }
}
}
+
/**
* Terminal
*/
@@ -906,25 +965,6 @@ button.mini-pipeline-graph-dropdown-toggle {
}
}
-/**
- * Play button with icon in dropdowns
- */
-.ci-table .no-btn {
- border: none;
- background: none;
- outline: none;
- width: 100%;
- text-align: left;
-
- .icon-play {
- position: relative;
- top: 2px;
- margin-right: 5px;
- height: 13px;
- width: 12px;
- }
-}
-
.ci-header-container {
min-height: 55px;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index bd385db9692..b0c3474e3d5 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -88,7 +88,8 @@
transition: background 2s ease-out;
&:disabled {
- opacity: 0.75;
+ opacity: 0.5;
+ pointer-events: none;
}
.highlight-changes & {
@@ -778,35 +779,35 @@ a.deploy-project-label {
.nav {
padding-top: 12px;
padding-bottom: 12px;
- }
- .nav > li {
- display: inline-block;
+ > li {
+ display: inline-block;
- &:not(:last-child) {
- margin-right: $gl-padding;
- }
+ &:not(:last-child) {
+ margin-right: $gl-padding;
+ }
- &.right {
- vertical-align: top;
- margin-top: 0;
+ &.right {
+ vertical-align: top;
+ margin-top: 0;
- @media (min-width: $screen-lg-min) {
- float: right;
+ @media (min-width: $screen-lg-min) {
+ float: right;
+ }
}
- }
- }
- .nav > li > a {
- padding: 0;
- background-color: transparent;
- font-size: 14px;
- line-height: 29px;
- color: $notes-light-color;
+ > a {
+ padding: 0;
+ background-color: transparent;
+ font-size: 14px;
+ line-height: 29px;
+ color: $notes-light-color;
- &:hover,
- &:focus {
- color: $gl-text-color;
+ &:hover,
+ &:focus {
+ color: $gl-text-color;
+ }
+ }
}
}
@@ -1160,13 +1161,6 @@ pre.light-well {
}
}
-.project-repo-select {
- &.disabled {
- opacity: 0.5;
- pointer-events: none;
- }
-}
-
.variables-table {
table-layout: fixed;
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index db0a04a5eb3..eed711b1b66 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -78,6 +78,10 @@ input[type="checkbox"]:hover {
}
.search-input-wrap {
+ // Fallback if flexbox is not supported
+ display: inline-block;
+ width: 100%;
+
.search-icon,
.clear-icon {
position: absolute;
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index 41a6ba2023a..8b9b47a41bc 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -23,15 +23,14 @@
}
.settings {
- overflow: hidden;
border-bottom: 1px solid $gray-darker;
&:first-of-type {
margin-top: 10px;
}
- &.expanded {
- overflow: visible;
+ &.animating {
+ overflow: hidden;
}
}
@@ -56,14 +55,18 @@
overflow-y: scroll;
padding-right: 110px;
animation: collapseMaxHeight 300ms ease-out;
+ // Keep the section from expanding when we scroll over it
+ pointer-events: none;
- &.expanded {
+ .settings.expanded & {
max-height: none;
overflow-y: visible;
animation: expandMaxHeight 300ms ease-in;
+ // Reset and allow clicks again when expanded
+ pointer-events: auto;
}
- &.no-animate {
+ .settings.no-animate & {
animation: none;
}
@@ -238,11 +241,11 @@
margin-left: 5px;
background: $badge-bg;
}
- }
- /* Ensure we don't add border if there's only single li */
- li + li {
- border-top: 1px solid $border-color;
+ /* Ensure we don't add border if there's only single li */
+ + li {
+ border-top: 1px solid $border-color;
+ }
}
}
}
diff --git a/app/assets/stylesheets/pages/sherlock.scss b/app/assets/stylesheets/pages/sherlock.scss
index bfe065dbbaf..2bf0bedb1f5 100644
--- a/app/assets/stylesheets/pages/sherlock.scss
+++ b/app/assets/stylesheets/pages/sherlock.scss
@@ -5,10 +5,10 @@ table .sherlock-code {
.sherlock-code {
pre {
word-wrap: normal;
- }
- pre code {
- white-space: pre;
+ code {
+ white-space: pre;
+ }
}
}
@@ -21,13 +21,13 @@ table .sherlock-code {
text-align: right;
padding: 0 10px !important;
}
+
+ .slow {
+ color: $red-500;
+ font-weight: $gl-font-weight-bold;
+ }
}
.sherlock-file-sample pre {
padding-top: 28px !important;
}
-
-.sherlock-line-samples-table .slow {
- color: $red-500;
- font-weight: $gl-font-weight-bold;
-}
diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss
index dfa4d033fb8..cede147d559 100644
--- a/app/assets/stylesheets/pages/stat_graph.scss
+++ b/app/assets/stylesheets/pages/stat_graph.scss
@@ -40,16 +40,16 @@
@media (max-width: $screen-xs-max) {
width: 100%;
}
- }
- .person .spark {
- display: block;
- background: $stat-graph-common-bg;
- width: 100%;
- }
+ .spark {
+ display: block;
+ background: $stat-graph-common-bg;
+ width: 100%;
+ }
- .person .area-contributor {
- fill: $stat-graph-orange-fill;
+ .area-contributor {
+ fill: $stat-graph-orange-fill;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index b7d4e7bf582..e150f96f3fa 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -161,10 +161,10 @@ ul.wiki-pages-list.content-list {
list-style: none;
margin-left: 0;
padding-left: 15px;
- }
- ul li {
- padding: 5px 0;
+ li {
+ padding: 5px 0;
+ }
}
}
diff --git a/app/assets/stylesheets/test.scss b/app/assets/stylesheets/test.scss
index 06733b7f1a9..e65b49c36f3 100644
--- a/app/assets/stylesheets/test.scss
+++ b/app/assets/stylesheets/test.scss
@@ -4,11 +4,6 @@
-ms-transition: none !important;
-webkit-transition: none !important;
transition: none !important;
- -o-transform: none !important;
- -moz-transform: none !important;
- -ms-transform: none !important;
- -webkit-transform: none !important;
- transform: none !important;
-webkit-animation: none !important;
-moz-animation: none !important;
-o-animation: none !important;
diff --git a/app/controllers/admin/applications_controller.rb b/app/controllers/admin/applications_controller.rb
index fb6d8c0bb81..5be23c76a95 100644
--- a/app/controllers/admin/applications_controller.rb
+++ b/app/controllers/admin/applications_controller.rb
@@ -19,10 +19,12 @@ class Admin::ApplicationsController < Admin::ApplicationController
end
def create
- @application = Doorkeeper::Application.new(application_params)
+ @application = Applications::CreateService.new(current_user, application_params).execute(request)
- if @application.save
- redirect_to_admin_page
+ if @application.persisted?
+ flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
+
+ redirect_to admin_application_url(@application)
else
render :new
end
@@ -41,13 +43,6 @@ class Admin::ApplicationsController < Admin::ApplicationController
redirect_to admin_applications_url, status: 302, notice: 'Application was successfully destroyed.'
end
- protected
-
- def redirect_to_admin_page
- flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
- redirect_to admin_application_url(@application)
- end
-
private
def set_application
diff --git a/app/controllers/admin/impersonation_tokens_controller.rb b/app/controllers/admin/impersonation_tokens_controller.rb
index 07c8bf714fc..7a2c7234a1e 100644
--- a/app/controllers/admin/impersonation_tokens_controller.rb
+++ b/app/controllers/admin/impersonation_tokens_controller.rb
@@ -44,7 +44,7 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
end
def set_index_vars
- @scopes = Gitlab::Auth::API_SCOPES
+ @scopes = Gitlab::Auth.available_scopes(current_user)
@impersonation_token ||= finder.build
@inactive_impersonation_tokens = finder(state: 'inactive').execute
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 391a0519195..3be7aee69bc 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -11,7 +11,7 @@ class ApplicationController < ActionController::Base
include EnforcesTwoFactorAuthentication
include WithPerformanceBar
- before_action :authenticate_user_from_private_token!
+ before_action :authenticate_user_from_personal_access_token!
before_action :authenticate_user_from_rss_token!
before_action :authenticate_user!
before_action :validate_user_service_ticket!
@@ -100,13 +100,12 @@ class ApplicationController < ActionController::Base
return try(:authenticated_user)
end
- # This filter handles both private tokens and personal access tokens
- def authenticate_user_from_private_token!
+ def authenticate_user_from_personal_access_token!
token = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
return unless token.present?
- user = User.find_by_authentication_token(token) || User.find_by_personal_access_token(token)
+ user = User.find_by_personal_access_token(token)
sessionless_sign_in(user)
end
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index 4079072a930..b1ed973d178 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -7,6 +7,54 @@ module IssuableActions
before_action :authorize_admin_issuable!, only: :bulk_update
end
+ def show
+ respond_to do |format|
+ format.html do
+ render show_view
+ end
+ format.json do
+ render json: serializer.represent(issuable, serializer: params[:serializer])
+ end
+ end
+ end
+
+ def update
+ @issuable = update_service.execute(issuable)
+
+ respond_to do |format|
+ format.html do
+ recaptcha_check_with_fallback { render :edit }
+ end
+
+ format.json do
+ render_entity_json
+ end
+ end
+
+ rescue ActiveRecord::StaleObjectError
+ render_conflict_response
+ end
+
+ def realtime_changes
+ Gitlab::PollingInterval.set_header(response, interval: 3_000)
+
+ response = {
+ title: view_context.markdown_field(issuable, :title),
+ title_text: issuable.title,
+ description: view_context.markdown_field(issuable, :description),
+ description_text: issuable.description,
+ task_status: issuable.task_status
+ }
+
+ if issuable.edited?
+ response[:updated_at] = issuable.updated_at
+ response[:updated_by_name] = issuable.last_edited_by.name
+ response[:updated_by_path] = user_path(issuable.last_edited_by)
+ end
+
+ render json: response
+ end
+
def destroy
issuable.destroy
destroy_method = "destroy_#{issuable.class.name.underscore}".to_sym
@@ -68,6 +116,10 @@ module IssuableActions
end
end
+ def authorize_update_issuable!
+ render_404 unless can?(current_user, :"update_#{resource_name}", issuable)
+ end
+
def bulk_update_params
permitted_keys = [
:issuable_ids,
@@ -92,4 +144,24 @@ module IssuableActions
def resource_name
@resource_name ||= controller_name.singularize
end
+
+ def render_entity_json
+ if @issuable.valid?
+ render json: serializer.represent(@issuable)
+ else
+ render json: { errors: @issuable.errors.full_messages }, status: :unprocessable_entity
+ end
+ end
+
+ def show_view
+ 'show'
+ end
+
+ def serializer
+ raise NotImplementedError
+ end
+
+ def update_service
+ raise NotImplementedError
+ end
end
diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb
index 2b6afaa6233..738afd612f0 100644
--- a/app/controllers/concerns/lfs_request.rb
+++ b/app/controllers/concerns/lfs_request.rb
@@ -94,10 +94,9 @@ module LfsRequest
@storage_project ||= begin
result = project
- loop do
- break unless result.forked?
- result = result.forked_from_project
- end
+ # TODO: Make this go to the fork_network root immeadiatly
+ # dependant on the discussion in: https://gitlab.com/gitlab-org/gitlab-ce/issues/39769
+ result = result.fork_source while result.forked?
result
end
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index 1126f706393..fb9c942d302 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -4,6 +4,7 @@ module NotesActions
included do
before_action :set_polling_interval_header, only: [:index]
+ before_action :noteable, only: :index
before_action :authorize_admin_note!, only: [:update, :destroy]
before_action :note_project, only: [:create]
end
@@ -188,7 +189,7 @@ module NotesActions
end
def noteable
- @noteable ||= notes_finder.target
+ @noteable ||= notes_finder.target || render_404
end
def last_fetched_at
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
index 4bceb1d67a3..7d6fe6a0232 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -30,11 +30,11 @@ class JwtController < ApplicationController
render_unauthorized
end
end
- rescue Gitlab::Auth::MissingPersonalTokenError
- render_missing_personal_token
+ rescue Gitlab::Auth::MissingPersonalAccessTokenError
+ render_missing_personal_access_token
end
- def render_missing_personal_token
+ def render_missing_personal_access_token
render json: {
errors: [
{ code: 'UNAUTHORIZED',
diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
index b02e64a132b..2443f529c7b 100644
--- a/app/controllers/oauth/applications_controller.rb
+++ b/app/controllers/oauth/applications_controller.rb
@@ -16,25 +16,18 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
end
def create
- @application = Doorkeeper::Application.new(application_params)
+ @application = Applications::CreateService.new(current_user, create_application_params).execute(request)
- @application.owner = current_user
+ if @application.persisted?
+ flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
- if @application.save
- redirect_to_oauth_application_page
+ redirect_to oauth_application_url(@application)
else
set_index_vars
render :index
end
end
- protected
-
- def redirect_to_oauth_application_page
- flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
- redirect_to oauth_application_url(@application)
- end
-
private
def verify_user_oauth_applications_enabled
@@ -61,4 +54,10 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
rescue_from ActiveRecord::RecordNotFound do |exception|
render "errors/not_found", layout: "errors", status: 404
end
+
+ def create_application_params
+ application_params.tap do |params|
+ params[:owner] = current_user
+ end
+ end
end
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index 069e6a810f2..f0e5d2aa94e 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -11,10 +11,10 @@ class Profiles::KeysController < Profiles::ApplicationController
end
def create
- @key = Keys::CreateService.new(current_user, key_params).execute
+ @key = Keys::CreateService.new(current_user, key_params.merge(ip_address: request.remote_ip)).execute
if @key.persisted?
- redirect_to_profile_key_path
+ redirect_to profile_key_path(@key)
else
@keys = current_user.keys.select(&:persisted?)
render :index
@@ -50,12 +50,6 @@ class Profiles::KeysController < Profiles::ApplicationController
end
end
- protected
-
- def redirect_to_profile_key_path
- redirect_to profile_key_path(@key)
- end
-
private
def key_params
diff --git a/app/controllers/profiles/personal_access_tokens_controller.rb b/app/controllers/profiles/personal_access_tokens_controller.rb
index 4146deefa89..6d9873e38df 100644
--- a/app/controllers/profiles/personal_access_tokens_controller.rb
+++ b/app/controllers/profiles/personal_access_tokens_controller.rb
@@ -39,7 +39,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
end
def set_index_vars
- @scopes = Gitlab::Auth.available_scopes
+ @scopes = Gitlab::Auth.available_scopes(current_user)
@inactive_personal_access_tokens = finder(state: 'inactive').execute
@active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at)
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 5d87037f012..dbf61a17724 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -24,16 +24,6 @@ class ProfilesController < Profiles::ApplicationController
end
end
- def reset_private_token
- Users::UpdateService.new(current_user, user: @user).execute! do |user|
- user.reset_authentication_token!
- end
-
- flash[:notice] = "Private token was successfully reset"
-
- redirect_to profile_account_path
- end
-
def reset_incoming_email_token
Users::UpdateService.new(current_user, user: @user).execute! do |user|
user.reset_incoming_email_token!
@@ -41,7 +31,7 @@ class ProfilesController < Profiles::ApplicationController
flash[:notice] = "Incoming email token was successfully reset"
- redirect_to profile_account_path
+ redirect_to profile_personal_access_tokens_path
end
def reset_rss_token
@@ -51,7 +41,7 @@ class ProfilesController < Profiles::ApplicationController
flash[:notice] = "RSS token was successfully reset"
- redirect_to profile_account_path
+ redirect_to profile_personal_access_tokens_path
end
def audit_log
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index 95d7a02e9e9..dd5e66f60e3 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -53,8 +53,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController
send_challenges
render plain: "HTTP Basic: Access denied\n", status: 401
- rescue Gitlab::Auth::MissingPersonalTokenError
- render_missing_personal_token
+ rescue Gitlab::Auth::MissingPersonalAccessTokenError
+ render_missing_personal_access_token
end
def basic_auth_provided?
@@ -78,7 +78,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
@project, @wiki, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}")
end
- def render_missing_personal_token
+ def render_missing_personal_access_token
render plain: "HTTP Basic: Access denied\n" \
"You must use a personal access token with 'api' scope for Git over HTTP.\n" \
"You can generate one at #{profile_personal_access_tokens_url}",
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 6a5e4538717..d4e763aa5b8 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -16,7 +16,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_create_issue!, only: [:new, :create]
# Allow modify issue
- before_action :authorize_update_issue!, only: [:edit, :update, :move]
+ before_action :authorize_update_issuable!, only: [:edit, :update, :move]
# Allow create a new branch and empty WIP merge request from current issue
before_action :authorize_create_merge_request!, only: [:create_merge_request]
@@ -67,18 +67,6 @@ class Projects::IssuesController < Projects::ApplicationController
respond_with(@issue)
end
- def show
- @noteable = @issue
- @note = @project.notes.new(noteable: @issue)
-
- respond_to do |format|
- format.html
- format.json do
- render json: serializer.represent(@issue, serializer: params[:serializer])
- end
- end
- end
-
def discussions
notes = @issue.notes
.inc_relations_for_view
@@ -120,25 +108,6 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
- def update
- update_params = issue_params.merge(spammable_params)
-
- @issue = Issues::UpdateService.new(project, current_user, update_params).execute(issue)
-
- respond_to do |format|
- format.html do
- recaptcha_check_with_fallback { render :edit }
- end
-
- format.json do
- render_issue_json
- end
- end
-
- rescue ActiveRecord::StaleObjectError
- render_conflict_response
- end
-
def move
params.require(:move_to_project_id)
@@ -196,26 +165,6 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
- def realtime_changes
- Gitlab::PollingInterval.set_header(response, interval: 3_000)
-
- response = {
- title: view_context.markdown_field(@issue, :title),
- title_text: @issue.title,
- description: view_context.markdown_field(@issue, :description),
- description_text: @issue.description,
- task_status: @issue.task_status
- }
-
- if @issue.edited?
- response[:updated_at] = @issue.updated_at
- response[:updated_by_name] = @issue.last_edited_by.name
- response[:updated_by_path] = user_path(@issue.last_edited_by)
- end
-
- render json: response
- end
-
def create_merge_request
result = ::MergeRequests::CreateFromIssueService.new(project, current_user, issue_iid: issue.iid).execute
@@ -231,7 +180,8 @@ class Projects::IssuesController < Projects::ApplicationController
def issue
return @issue if defined?(@issue)
# The Sortable default scope causes performance issues when used with find_by
- @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take!
+ @issuable = @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take!
+ @note = @project.notes.new(noteable: @issuable)
return render_404 unless can?(current_user, :read_issue, @issue)
@@ -246,14 +196,6 @@ class Projects::IssuesController < Projects::ApplicationController
project_issue_path(@project, @issue)
end
- def authorize_update_issue!
- render_404 unless can?(current_user, :update_issue, @issue)
- end
-
- def authorize_admin_issues!
- render_404 unless can?(current_user, :admin_issue, @project)
- end
-
def authorize_create_merge_request!
render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user)
end
@@ -305,4 +247,9 @@ class Projects::IssuesController < Projects::ApplicationController
def serializer
IssueSerializer.new(current_user: current_user, project: issue.project)
end
+
+ def update_service
+ update_params = issue_params.merge(spammable_params)
+ Issues::UpdateService.new(project, current_user, update_params)
+ end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 2b0294c8387..17cac69e588 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -9,7 +9,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
skip_before_action :merge_request, only: [:index, :bulk_update]
skip_before_action :ensure_ref_fetched, only: [:index, :bulk_update]
- before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :remove_wip, :sort]
+ before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
before_action :authenticate_user!, only: [:assign_related_issues]
@@ -256,14 +256,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
alias_method :issuable, :merge_request
alias_method :awardable, :merge_request
- def authorize_update_merge_request!
- return render_404 unless can?(current_user, :update_merge_request, @merge_request)
- end
-
- def authorize_admin_merge_request!
- return render_404 unless can?(current_user, :admin_merge_request, @merge_request)
- end
-
def validates_merge_request
# Show git not found page
# if there is no saved commits between source & target branch
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 8022547a6ad..4dd573c61f1 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -63,34 +63,34 @@ module CiStatusHelper
def ci_icon_for_status(status)
if detailed_status?(status)
- return custom_icon(status.icon)
+ return sprite_icon(status.icon)
end
icon_name =
case status
when 'success'
- 'icon_status_success'
+ 'status_success'
when 'success_with_warnings'
- 'icon_status_warning'
+ 'status_warning'
when 'failed'
- 'icon_status_failed'
+ 'status_failed'
when 'pending'
- 'icon_status_pending'
+ 'status_pending'
when 'running'
- 'icon_status_running'
+ 'status_running'
when 'play'
- 'icon_play'
+ 'play'
when 'created'
- 'icon_status_created'
+ 'status_created'
when 'skipped'
- 'icon_status_skipped'
+ 'status_skipped'
when 'manual'
- 'icon_status_manual'
+ 'status_manual'
else
- 'icon_status_canceled'
+ 'status_canceled'
end
- custom_icon(icon_name)
+ sprite_icon(icon_name, size: 16)
end
def pipeline_status_cache_key(pipeline_status)
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index d4a91e533c1..a77aa0ad2cc 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -71,11 +71,13 @@ module GitlabRoutingHelper
project_commit_url(entity.project, entity.sha, *args)
end
- def preview_markdown_path(project, *args)
+ def preview_markdown_path(parent, *args)
+ return group_preview_markdown_path(parent) if parent.is_a?(Group)
+
if @snippet.is_a?(PersonalSnippet)
preview_markdown_snippets_path
else
- preview_markdown_project_path(project, *args)
+ preview_markdown_project_path(parent, *args)
end
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index d0069cd48cf..85407e38532 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -211,15 +211,13 @@ module IssuablesHelper
def issuable_initial_data(issuable)
data = {
- endpoint: project_issue_path(@project, issuable),
- canUpdate: can?(current_user, :update_issue, issuable),
- canDestroy: can?(current_user, :destroy_issue, issuable),
+ endpoint: issuable_path(issuable),
+ canUpdate: can?(current_user, :"update_#{issuable.to_ability_name}", issuable),
+ canDestroy: can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable),
issuableRef: issuable.to_reference,
- markdownPreviewPath: preview_markdown_path(@project),
+ markdownPreviewPath: preview_markdown_path(parent),
markdownDocsPath: help_page_path('user/markdown'),
issuableTemplates: issuable_templates(issuable),
- projectPath: ref_project.path,
- projectNamespace: ref_project.namespace.full_path,
initialTitleHtml: markdown_field(issuable, :title),
initialTitleText: issuable.title,
initialDescriptionHtml: markdown_field(issuable, :description),
@@ -227,6 +225,12 @@ module IssuablesHelper
initialTaskStatus: issuable.task_status
}
+ if parent.is_a?(Group)
+ data[:groupPath] = parent.path
+ else
+ data.merge!(projectPath: ref_project.path, projectNamespace: ref_project.namespace.full_path)
+ end
+
data.merge!(updated_at_by(issuable))
data.to_json
@@ -263,12 +267,7 @@ module IssuablesHelper
end
def issuable_path(issuable, *options)
- case issuable
- when Issue
- issue_path(issuable, *options)
- when MergeRequest
- merge_request_path(issuable, *options)
- end
+ polymorphic_path(issuable, *options)
end
def issuable_url(issuable, *options)
@@ -369,4 +368,8 @@ module IssuablesHelper
fullPath: @project.full_path
}
end
+
+ def parent
+ @project || @group
+ end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index d085c1a0e57..f48d47953e4 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -110,7 +110,15 @@ module ProjectsHelper
def remove_fork_project_message(project)
_("You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?") %
- { forked_from_project: @project.forked_from_project.name_with_namespace }
+ { forked_from_project: fork_source_name(project) }
+ end
+
+ def fork_source_name(project)
+ if @project.fork_source
+ @project.fork_source.full_name
+ else
+ @project.fork_network&.deleted_root_project_name
+ end
end
def project_nav_tabs
@@ -140,8 +148,8 @@ module ProjectsHelper
def can_change_visibility_level?(project, current_user)
return false unless can?(current_user, :change_visibility_level, project)
- if project.forked?
- project.forked_from_project.visibility_level > Gitlab::VisibilityLevel::PRIVATE
+ if project.fork_source
+ project.fork_source.visibility_level > Gitlab::VisibilityLevel::PRIVATE
else
true
end
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 9417033d1f6..98776eab424 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -49,7 +49,8 @@ module CacheMarkdownField
# Always include a project key, or Banzai complains
project = self.project if self.respond_to?(:project)
- context = cached_markdown_fields[field].merge(project: project)
+ group = self.group if self.respond_to?(:group)
+ context = cached_markdown_fields[field].merge(project: project, group: group)
# Banzai is less strict about authors, so don't always have an author key
context[:author] = self.author if self.respond_to?(:author)
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 27f4dedffd3..a928b9d6367 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -14,7 +14,6 @@ module Issuable
include StripAttribute
include Awardable
include Taskable
- include TimeTrackable
include Importable
include Editable
include AfterCommitQueue
@@ -95,8 +94,6 @@ module Issuable
strip_attributes :title
- acts_as_paranoid
-
after_save :record_metrics, unless: :imported?
# We want to use optimistic lock for cases when only title or description are involved
diff --git a/app/models/environment.rb b/app/models/environment.rb
index e613d21add6..21a028e351c 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -30,7 +30,6 @@ class Environment < ActiveRecord::Base
message: Gitlab::Regex.environment_slug_regex_message }
validates :external_url,
- uniqueness: { scope: :project_id },
length: { maximum: 255 },
allow_nil: true,
addressable_url: true
@@ -110,7 +109,7 @@ class Environment < ActiveRecord::Base
end
def ref_path
- "refs/#{Repository::REF_ENVIRONMENTS}/#{generate_slug}"
+ "refs/#{Repository::REF_ENVIRONMENTS}/#{slug}"
end
def formatted_external_url
@@ -164,6 +163,10 @@ class Environment < ActiveRecord::Base
end
end
+ def slug
+ super.presence || generate_slug
+ end
+
# An environment name is not necessarily suitable for use in URLs, DNS
# or other third-party contexts, so provide a slugified version. A slug has
# the following properties:
diff --git a/app/models/epic.rb b/app/models/epic.rb
new file mode 100644
index 00000000000..62898a02e2d
--- /dev/null
+++ b/app/models/epic.rb
@@ -0,0 +1,7 @@
+# Placeholder class for model that is implemented in EE
+# It will reserve (ee#3853) '&' as a reference prefix, but the table does not exists in CE
+class Epic < ActiveRecord::Base
+ # TODO: this will be implemented as part of #3853
+ def to_reference
+ end
+end
diff --git a/app/models/fork_network.rb b/app/models/fork_network.rb
index 218e37a5312..7f1728e8c77 100644
--- a/app/models/fork_network.rb
+++ b/app/models/fork_network.rb
@@ -12,4 +12,8 @@ class ForkNetwork < ActiveRecord::Base
def find_forks_in(other_projects)
projects.where(id: other_projects)
end
+
+ def merge_requests
+ MergeRequest.where(target_project: projects)
+ end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 07fb62bb249..c660de7fcb6 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -42,6 +42,7 @@ class Group < Namespace
after_create :post_create_hook
after_destroy :post_destroy_hook
after_save :update_two_factor_requirement
+ after_update :path_changed_hook, if: :path_changed?
class << self
def supports_nested_groups?
@@ -180,6 +181,12 @@ class Group < Namespace
add_user(user, :owner, current_user: current_user)
end
+ def member?(user, min_access_level = Gitlab::Access::GUEST)
+ return false unless user
+
+ max_member_access_for_user(user) >= min_access_level
+ end
+
def has_owner?(user)
return false unless user
@@ -289,6 +296,12 @@ class Group < Namespace
list_of_ids.reverse.map { |group| variables[group.id] }.compact.flatten
end
+ def full_path_was
+ return path_was unless has_parent?
+
+ "#{parent.full_path}/#{path_was}"
+ end
+
private
def update_two_factor_requirement
@@ -297,6 +310,10 @@ class Group < Namespace
users.find_each(&:update_two_factor_requirement)
end
+ def path_changed_hook
+ system_hook_service.execute_hooks_for(self, :rename)
+ end
+
def visibility_level_allowed_by_parent
return if visibility_level_allowed_by_parent?
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 36e4108b9d6..fc590f9257e 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -10,6 +10,7 @@ class Issue < ActiveRecord::Base
include FasterCacheKeys
include RelativePositioning
include CreatedAtFilterable
+ include TimeTrackable
DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
@@ -74,6 +75,8 @@ class Issue < ActiveRecord::Base
end
end
+ acts_as_paranoid
+
def self.reference_prefix
'#'
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index d45b9c805a4..3133dc9e7eb 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -6,6 +6,7 @@ class MergeRequest < ActiveRecord::Base
include Sortable
include IgnorableColumn
include CreatedAtFilterable
+ include TimeTrackable
ignore_column :locked_at
@@ -119,6 +120,8 @@ class MergeRequest < ActiveRecord::Base
after_save :keep_around_commit
+ acts_as_paranoid
+
def self.reference_prefix
'!'
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index faf0b95f842..1eda0f9cbbd 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -48,6 +48,10 @@ class MergeRequestDiff < ActiveRecord::Base
# Collect information about commits and diff from repository
# and save it to the database as serialized data
def save_git_content
+ MergeRequest
+ .where('id = ? AND COALESCE(latest_merge_request_diff_id, 0) < ?', self.merge_request_id, self.id)
+ .update_all(latest_merge_request_diff_id: self.id)
+
ensure_commit_shas
save_commits
save_diffs
diff --git a/app/models/note.rb b/app/models/note.rb
index 8939e590ef1..f9676361072 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -69,7 +69,7 @@ class Note < ActiveRecord::Base
delegate :title, to: :noteable, allow_nil: true
validates :note, presence: true
- validates :project, presence: true, unless: :for_personal_snippet?
+ validates :project, presence: true, if: :for_project_noteable?
# Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size }
@@ -114,7 +114,7 @@ class Note < ActiveRecord::Base
after_initialize :ensure_discussion_id
before_validation :nullify_blank_type, :nullify_blank_line_code
before_validation :set_discussion_id, on: :create
- after_save :keep_around_commit, unless: :for_personal_snippet?
+ after_save :keep_around_commit, if: :for_project_noteable?
after_save :expire_etag_cache
after_destroy :expire_etag_cache
@@ -208,6 +208,10 @@ class Note < ActiveRecord::Base
noteable.is_a?(PersonalSnippet)
end
+ def for_project_noteable?
+ !for_personal_snippet?
+ end
+
def skip_project_check?
for_personal_snippet?
end
diff --git a/app/models/oauth_access_token.rb b/app/models/oauth_access_token.rb
index f89e60ad9f4..e8595b13d6d 100644
--- a/app/models/oauth_access_token.rb
+++ b/app/models/oauth_access_token.rb
@@ -2,5 +2,13 @@ class OauthAccessToken < Doorkeeper::AccessToken
belongs_to :resource_owner, class_name: 'User'
belongs_to :application, class_name: 'Doorkeeper::Application'
- alias_method :user, :resource_owner
+ alias_attribute :user, :resource_owner
+
+ def scopes=(value)
+ if value.is_a?(Array)
+ super(Doorkeeper::OAuth::Scopes.from_array(value).to_s)
+ else
+ super
+ end
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 413866b994a..b04aec550b1 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1040,6 +1040,10 @@ class Project < ActiveRecord::Base
!(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
end
+ def fork_source
+ forked_from_project || fork_network&.root_project
+ end
+
def personal?
!group
end
@@ -1488,7 +1492,8 @@ class Project < ActiveRecord::Base
{ key: 'CI_PROJECT_PATH', value: full_path, public: true },
{ key: 'CI_PROJECT_PATH_SLUG', value: full_path_slug, public: true },
{ key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path, public: true },
- { key: 'CI_PROJECT_URL', value: web_url, public: true }
+ { key: 'CI_PROJECT_URL', value: web_url, public: true },
+ { key: 'CI_PROJECT_VISIBILITY', value: Gitlab::VisibilityLevel.string_level(visibility_level), public: true }
]
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 9459b6d4fa4..bcda4564595 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -21,8 +21,8 @@ class User < ActiveRecord::Base
ignore_column :external_email
ignore_column :email_provider
+ ignore_column :authentication_token
- add_authentication_token_field :authentication_token
add_authentication_token_field :incoming_email_token
add_authentication_token_field :rss_token
@@ -163,11 +163,12 @@ class User < ActiveRecord::Base
before_validation :sanitize_attrs
before_validation :set_notification_email, if: :email_changed?
before_validation :set_public_email, if: :public_email_changed?
- before_save :ensure_authentication_token, :ensure_incoming_email_token
+ before_save :ensure_incoming_email_token
before_save :ensure_user_rights_and_limits, if: :external_changed?
before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) }
before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? }
after_save :ensure_namespace_correct
+ after_update :username_changed_hook, if: :username_changed?
after_destroy :post_destroy_hook
after_commit :update_emails_with_primary_email, on: :update, if: -> { previous_changes.key?('email') }
after_commit :update_invalid_gpg_signatures, on: :update, if: -> { previous_changes.key?('email') }
@@ -185,8 +186,6 @@ class User < ActiveRecord::Base
# Note: When adding an option, it MUST go on the end of the array.
enum project_view: [:readme, :activity, :files]
- alias_attribute :private_token, :authentication_token
-
delegate :path, to: :namespace, allow_nil: true, prefix: true
state_machine :state, initial: :active do
@@ -873,6 +872,10 @@ class User < ActiveRecord::Base
end
end
+ def username_changed_hook
+ system_hook_service.execute_hooks_for(self, :rename)
+ end
+
def post_destroy_hook
log_info("User \"#{name}\" (#{email}) was removed")
system_hook_service.execute_hooks_for(self, :destroy)
diff --git a/app/serializers/issuable_entity.rb b/app/serializers/issuable_entity.rb
index 61c7a428745..3b5a4fd4f79 100644
--- a/app/serializers/issuable_entity.rb
+++ b/app/serializers/issuable_entity.rb
@@ -1,20 +1,16 @@
class IssuableEntity < Grape::Entity
+ include RequestAwareEntity
+
expose :id
expose :iid
expose :author_id
expose :description
expose :lock_version
expose :milestone_id
- expose :state
expose :title
expose :updated_by_id
expose :created_at
expose :updated_at
- expose :deleted_at
- expose :time_estimate
- expose :total_time_spent
- expose :human_time_estimate
- expose :human_total_time_spent
expose :milestone, using: API::Entities::Milestone
expose :labels, using: LabelEntity
end
diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb
index 10d3ad0214b..5f47592e4ad 100644
--- a/app/serializers/issue_entity.rb
+++ b/app/serializers/issue_entity.rb
@@ -1,6 +1,8 @@
class IssueEntity < IssuableEntity
- include RequestAwareEntity
+ include TimeTrackableEntity
+ expose :state
+ expose :deleted_at
expose :branch_name
expose :confidential
expose :discussion_locked
diff --git a/app/serializers/merge_request_entity.rb b/app/serializers/merge_request_entity.rb
index 297a459e394..b53a49fe59e 100644
--- a/app/serializers/merge_request_entity.rb
+++ b/app/serializers/merge_request_entity.rb
@@ -1,6 +1,8 @@
class MergeRequestEntity < IssuableEntity
- include RequestAwareEntity
+ include TimeTrackableEntity
+ expose :state
+ expose :deleted_at
expose :in_progress_merge_commit_sha
expose :merge_commit_sha
expose :merge_error
diff --git a/app/serializers/time_trackable_entity.rb b/app/serializers/time_trackable_entity.rb
new file mode 100644
index 00000000000..e81cd7bec72
--- /dev/null
+++ b/app/serializers/time_trackable_entity.rb
@@ -0,0 +1,11 @@
+module TimeTrackableEntity
+ extend ActiveSupport::Concern
+ extend Grape
+
+ included do
+ expose :time_estimate
+ expose :total_time_spent
+ expose :human_time_estimate
+ expose :human_total_time_spent
+ end
+end
diff --git a/app/services/access_token_validation_service.rb b/app/services/access_token_validation_service.rb
index 9c00ea789ec..46e19230328 100644
--- a/app/services/access_token_validation_service.rb
+++ b/app/services/access_token_validation_service.rb
@@ -39,11 +39,8 @@ class AccessTokenValidationService
token_scopes = token.scopes.map(&:to_sym)
required_scopes.any? do |scope|
- if scope.respond_to?(:sufficient?)
- scope.sufficient?(token_scopes, request)
- else
- API::Scope.new(scope).sufficient?(token_scopes, request)
- end
+ scope = API::Scope.new(scope) unless scope.is_a?(API::Scope)
+ scope.sufficient?(token_scopes, request)
end
end
end
diff --git a/app/services/applications/create_service.rb b/app/services/applications/create_service.rb
new file mode 100644
index 00000000000..35d45f25a71
--- /dev/null
+++ b/app/services/applications/create_service.rb
@@ -0,0 +1,13 @@
+module Applications
+ class CreateService
+ def initialize(current_user, params)
+ @current_user = current_user
+ @params = params
+ @ip_address = @params.delete(:ip_address)
+ end
+
+ def execute(request = nil)
+ Doorkeeper::Application.create(@params)
+ end
+ end
+end
diff --git a/app/services/issuable/common_system_notes_service.rb b/app/services/issuable/common_system_notes_service.rb
new file mode 100644
index 00000000000..92eaa5d5115
--- /dev/null
+++ b/app/services/issuable/common_system_notes_service.rb
@@ -0,0 +1,81 @@
+module Issuable
+ class CommonSystemNotesService < ::BaseService
+ attr_reader :issuable
+
+ def execute(issuable, old_labels)
+ @issuable = issuable
+
+ if issuable.previous_changes.include?('title')
+ create_title_change_note(issuable.previous_changes['title'].first)
+ end
+
+ handle_description_change_note
+
+ handle_time_tracking_note if issuable.is_a?(TimeTrackable)
+ create_labels_note(old_labels) if issuable.labels != old_labels
+ create_discussion_lock_note if issuable.previous_changes.include?('discussion_locked')
+ create_milestone_note if issuable.previous_changes.include?('milestone_id')
+ end
+
+ private
+
+ def handle_time_tracking_note
+ if issuable.previous_changes.include?('time_estimate')
+ create_time_estimate_note
+ end
+
+ if issuable.time_spent?
+ create_time_spent_note
+ end
+ end
+
+ def handle_description_change_note
+ if issuable.previous_changes.include?('description')
+ if issuable.tasks? && issuable.updated_tasks.any?
+ create_task_status_note
+ else
+ # TODO: Show this note if non-task content was modified.
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/33577
+ create_description_change_note
+ end
+ end
+ end
+
+ def create_labels_note(old_labels)
+ added_labels = issuable.labels - old_labels
+ removed_labels = old_labels - issuable.labels
+
+ SystemNoteService.change_label(issuable, issuable.project, current_user, added_labels, removed_labels)
+ end
+
+ def create_title_change_note(old_title)
+ SystemNoteService.change_title(issuable, issuable.project, current_user, old_title)
+ end
+
+ def create_description_change_note
+ SystemNoteService.change_description(issuable, issuable.project, current_user)
+ end
+
+ def create_task_status_note
+ issuable.updated_tasks.each do |task|
+ SystemNoteService.change_task_status(issuable, issuable.project, current_user, task)
+ end
+ end
+
+ def create_time_estimate_note
+ SystemNoteService.change_time_estimate(issuable, issuable.project, current_user)
+ end
+
+ def create_time_spent_note
+ SystemNoteService.change_time_spent(issuable, issuable.project, issuable.time_spent_user)
+ end
+
+ def create_milestone_note
+ SystemNoteService.change_milestone(issuable, issuable.project, current_user, issuable.milestone)
+ end
+
+ def create_discussion_lock_note
+ SystemNoteService.discussion_lock(issuable, current_user)
+ end
+ end
+end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index d61a342ebad..68b49d880f7 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -1,56 +1,10 @@
class IssuableBaseService < BaseService
private
- def create_milestone_note(issuable)
- SystemNoteService.change_milestone(
- issuable, issuable.project, current_user, issuable.milestone)
- end
-
- def create_labels_note(issuable, old_labels)
- added_labels = issuable.labels - old_labels
- removed_labels = old_labels - issuable.labels
-
- SystemNoteService.change_label(
- issuable, issuable.project, current_user, added_labels, removed_labels)
- end
-
- def create_title_change_note(issuable, old_title)
- SystemNoteService.change_title(
- issuable, issuable.project, current_user, old_title)
- end
-
- def create_description_change_note(issuable)
- SystemNoteService.change_description(issuable, issuable.project, current_user)
- end
-
- def create_branch_change_note(issuable, branch_type, old_branch, new_branch)
- SystemNoteService.change_branch(
- issuable, issuable.project, current_user, branch_type,
- old_branch, new_branch)
- end
-
- def create_task_status_note(issuable)
- issuable.updated_tasks.each do |task|
- SystemNoteService.change_task_status(issuable, issuable.project, current_user, task)
- end
- end
-
- def create_time_estimate_note(issuable)
- SystemNoteService.change_time_estimate(issuable, issuable.project, current_user)
- end
-
- def create_time_spent_note(issuable)
- SystemNoteService.change_time_spent(issuable, issuable.project, current_user)
- end
-
- def create_discussion_lock_note(issuable)
- SystemNoteService.discussion_lock(issuable, current_user)
- end
-
def filter_params(issuable)
ability_name = :"admin_#{issuable.to_ability_name}"
- unless can?(current_user, ability_name, project)
+ unless can?(current_user, ability_name, issuable)
params.delete(:milestone_id)
params.delete(:labels)
params.delete(:add_label_ids)
@@ -233,15 +187,14 @@ class IssuableBaseService < BaseService
# We have to perform this check before saving the issuable as Rails resets
# the changed fields upon calling #save.
- update_project_counters = issuable.update_project_counter_caches?
+ update_project_counters = issuable.project && issuable.update_project_counter_caches?
if issuable.with_transaction_returning_status { issuable.save }
# We do not touch as it will affect a update on updated_at field
ActiveRecord::Base.no_touching do
- handle_common_system_notes(issuable, old_labels: old_labels)
+ Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels)
end
- change_discussion_lock(issuable)
handle_changes(
issuable,
old_labels: old_labels,
@@ -300,12 +253,6 @@ class IssuableBaseService < BaseService
end
end
- def change_discussion_lock(issuable)
- if issuable.previous_changes.include?('discussion_locked')
- create_discussion_lock_note(issuable)
- end
- end
-
def toggle_award(issuable)
award = params.delete(:emoji_award)
if award
@@ -328,35 +275,17 @@ class IssuableBaseService < BaseService
attrs_changed || labels_changed || assignees_changed
end
- def handle_common_system_notes(issuable, old_labels: [])
- if issuable.previous_changes.include?('title')
- create_title_change_note(issuable, issuable.previous_changes['title'].first)
- end
-
- if issuable.previous_changes.include?('description')
- if issuable.tasks? && issuable.updated_tasks.any?
- create_task_status_note(issuable)
- else
- # TODO: Show this note if non-task content was modified.
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/33577
- create_description_change_note(issuable)
- end
- end
-
- if issuable.previous_changes.include?('time_estimate')
- create_time_estimate_note(issuable)
- end
-
- if issuable.time_spent?
- create_time_spent_note(issuable)
- end
-
- create_labels_note(issuable, old_labels) if issuable.labels != old_labels
- end
-
def invalidate_cache_counts(issuable, users: [])
users.each do |user|
user.public_send("invalidate_#{issuable.model_name.singular}_cache_counts") # rubocop:disable GitlabSecurity/PublicSend
end
end
+
+ # override if needed
+ def handle_changes(issuable, options)
+ end
+
+ # override if needed
+ def execute_hooks(issuable, action = 'open', params = {})
+ end
end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index e0339ddf9bb..1b7b5927c5a 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -27,10 +27,6 @@ module Issues
todo_service.update_issue(issue, current_user, old_mentioned_users)
end
- if issue.previous_changes.include?('milestone_id')
- create_milestone_note(issue)
- end
-
if issue.assignees != old_assignees
create_assignee_note(issue, old_assignees)
notification_service.reassigned_issue(issue, current_user, old_assignees)
diff --git a/app/services/keys/base_service.rb b/app/services/keys/base_service.rb
index 545832d0bd4..f78791932a7 100644
--- a/app/services/keys/base_service.rb
+++ b/app/services/keys/base_service.rb
@@ -4,6 +4,7 @@ module Keys
def initialize(user, params)
@user, @params = user, params
+ @ip_address = @params.delete(:ip_address)
end
def notification_service
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 2832d893e95..1f394cacc64 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -40,10 +40,6 @@ module MergeRequests
merge_request.target_branch)
end
- if merge_request.previous_changes.include?('milestone_id')
- create_milestone_note(merge_request)
- end
-
if merge_request.previous_changes.include?('assignee_id')
create_assignee_note(merge_request)
notification_service.reassigned_merge_request(merge_request, current_user)
@@ -111,5 +107,11 @@ module MergeRequests
end
end
end
+
+ def create_branch_change_note(issuable, branch_type, old_branch, new_branch)
+ SystemNoteService.change_branch(
+ issuable, issuable.project, current_user, branch_type,
+ old_branch, new_branch)
+ end
end
end
diff --git a/app/services/metrics_service.rb b/app/services/metrics_service.rb
index a02eee4961b..6b3939aeba5 100644
--- a/app/services/metrics_service.rb
+++ b/app/services/metrics_service.rb
@@ -6,8 +6,7 @@ class MetricsService
Gitlab::HealthChecks::Redis::RedisCheck,
Gitlab::HealthChecks::Redis::CacheCheck,
Gitlab::HealthChecks::Redis::QueuesCheck,
- Gitlab::HealthChecks::Redis::SharedStateCheck,
- Gitlab::HealthChecks::FsShardsCheck
+ Gitlab::HealthChecks::Redis::SharedStateCheck
].freeze
def prometheus_metrics_text
diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb
index 2b82e5732e4..c499f384426 100644
--- a/app/services/projects/unlink_fork_service.rb
+++ b/app/services/projects/unlink_fork_service.rb
@@ -3,18 +3,24 @@ module Projects
def execute
return unless @project.forked?
- @project.forked_from_project.lfs_objects.find_each do |lfs_object|
- lfs_object.projects << @project
+ if fork_source = @project.fork_source
+ fork_source.lfs_objects.find_each do |lfs_object|
+ lfs_object.projects << @project
+ end
+
+ refresh_forks_count(fork_source)
end
- merge_requests = @project.forked_from_project.merge_requests.opened.from_project(@project)
+ merge_requests = @project.fork_network
+ .merge_requests
+ .opened
+ .where.not(target_project: @project)
+ .from_project(@project)
merge_requests.each do |mr|
::MergeRequests::CloseService.new(@project, @current_user).execute(mr)
end
- refresh_forks_count(@project.forked_from_project)
-
@project.fork_network_member.destroy
@project.forked_project_link.destroy
end
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index 5d275967821..911cc919bb8 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -35,24 +35,22 @@ class SystemHooksService
data[:old_path_with_namespace] = model.old_path_with_namespace
end
when User
- data.merge!({
- name: model.name,
- email: model.email,
- user_id: model.id,
- username: model.username
- })
+ data.merge!(user_data(model))
+
+ if event == :rename
+ data[:old_username] = model.username_was
+ end
when ProjectMember
data.merge!(project_member_data(model))
when Group
- owner = model.owner
+ data.merge!(group_data(model))
- data.merge!(
- name: model.name,
- path: model.path,
- group_id: model.id,
- owner_name: owner.respond_to?(:name) ? owner.name : nil,
- owner_email: owner.respond_to?(:email) ? owner.email : nil
- )
+ if event == :rename
+ data.merge!(
+ old_path: model.path_was,
+ old_full_path: model.full_path_was
+ )
+ end
when GroupMember
data.merge!(group_member_data(model))
end
@@ -104,6 +102,19 @@ class SystemHooksService
}
end
+ def group_data(model)
+ owner = model.owner
+
+ {
+ name: model.name,
+ path: model.path,
+ full_path: model.full_path,
+ group_id: model.id,
+ owner_name: owner.try(:name),
+ owner_email: owner.try(:email)
+ }
+ end
+
def group_member_data(model)
{
group_name: model.group.name,
@@ -116,4 +127,13 @@ class SystemHooksService
group_access: model.human_access
}
end
+
+ def user_data(model)
+ {
+ name: model.name,
+ email: model.email,
+ user_id: model.id,
+ username: model.username
+ }
+ end
end
diff --git a/app/views/admin/hook_logs/_index.html.haml b/app/views/admin/hook_logs/_index.html.haml
index 7dd9943190f..91a8c0c62fe 100644
--- a/app/views/admin/hook_logs/_index.html.haml
+++ b/app/views/admin/hook_logs/_index.html.haml
@@ -24,7 +24,7 @@
%td
= truncate(hook_log.url, length: 50)
%td.light
- #{number_with_precision(hook_log.execution_duration, precision: 2)} ms
+ #{number_with_precision(hook_log.execution_duration, precision: 2)} sec
%td.light
= time_ago_with_tooltip(hook_log.created_at)
%td
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index 4d8754afdd2..c37d8ac45b9 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -14,7 +14,7 @@
= hidden_field_tag :namespace_id, params[:namespace_id]
- namespace = Namespace.find(params[:namespace_id])
- toggle_text = "#{namespace.kind}: #{namespace.full_path}"
- = dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { toggle_class: 'js-namespace-select large' })
+ = dropdown_toggle(toggle_text, { toggle: 'dropdown', is_filter: 'true' }, { toggle_class: 'js-namespace-select large' })
.dropdown-menu.dropdown-select.dropdown-menu-align-right
= dropdown_title('Namespaces')
= dropdown_filter("Search for Namespace")
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index ab4165c0bf2..42f92079d85 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -115,7 +115,7 @@
= f.label :new_namespace_id, "Namespace", class: 'control-label'
.col-sm-10
.dropdown
- = dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id', show_any: 'false' }, { toggle_class: 'js-namespace-select large' })
+ = dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id' }, { toggle_class: 'js-namespace-select large' })
.dropdown-menu.dropdown-select
= dropdown_title('Namespaces')
= dropdown_filter("Search for Namespace")
diff --git a/app/views/ci/status/_badge.html.haml b/app/views/ci/status/_badge.html.haml
index 39c7fb0eba2..35a3563dff1 100644
--- a/app/views/ci/status/_badge.html.haml
+++ b/app/views/ci/status/_badge.html.haml
@@ -5,9 +5,9 @@
- if link && status.has_details?
= link_to status.details_path, class: css_classes, title: title do
- = custom_icon(status.icon)
+ = sprite_icon(status.icon)
= status.text
- else
%span{ class: css_classes, title: title }
- = custom_icon(status.icon)
+ = sprite_icon(status.icon)
= status.text
diff --git a/app/views/ci/status/_dropdown_graph_badge.html.haml b/app/views/ci/status/_dropdown_graph_badge.html.haml
index dcfb7f0c32d..c5b4439e273 100644
--- a/app/views/ci/status/_dropdown_graph_badge.html.haml
+++ b/app/views/ci/status/_dropdown_graph_badge.html.haml
@@ -7,13 +7,13 @@
- if status.has_details?
= link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip, container: 'body' } do
- %span{ class: klass }= custom_icon(status.icon)
+ %span{ class: klass }= sprite_icon(status.icon)
%span.ci-build-text= subject.name
- else
.menu-item.mini-pipeline-graph-dropdown-item{ data: { toggle: 'tooltip', title: tooltip, container: 'body' } }
- %span{ class: klass }= custom_icon(status.icon)
+ %span{ class: klass }= sprite_icon(status.icon)
%span.ci-build-text= subject.name
- if status.has_action?
- = link_to status.action_path, class: 'ci-action-icon-wrapper js-ci-action-icon', method: status.action_method, data: { toggle: 'tooltip', title: status.action_title, container: 'body' } do
- = custom_icon(status.action_icon)
+ = link_to status.action_path, class: "ci-action-icon-wrapper js-ci-action-icon", method: status.action_method, data: { toggle: 'tooltip', title: status.action_title, container: 'body' } do
+ = sprite_icon(status.action_icon, css_class: "icon-action-#{status.action_icon}")
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index f62a0cd681e..a5686002328 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -8,7 +8,7 @@
%li.todos-pending{ class: active_when(params[:state].blank? || params[:state] == 'pending') }>
= link_to todos_filter_path(state: 'pending') do
%span
- To do
+ Todos
%span.badge
= number_with_delimiter(todos_pending_count)
%li.todos-done{ class: active_when(params[:state] == 'done') }>
diff --git a/app/views/profiles/accounts/_reset_token.html.haml b/app/views/profiles/accounts/_reset_token.html.haml
deleted file mode 100644
index c31a4a8ecd4..00000000000
--- a/app/views/profiles/accounts/_reset_token.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-- 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 7f79168dfb3..ced58dffcdc 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -9,22 +9,6 @@
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
- Private Tokens
- %p
- Keep these tokens secret, anyone with access to them can interact with
- GitLab as if they were you.
- .col-lg-8.private-tokens-reset
- = 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?
- = 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
- .col-lg-4.profile-settings-sidebar
- %h4.prepend-top-0
Two-Factor Authentication
%p
Increase your account's security by enabling Two-Factor Authentication (2FA).
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index 06bb72b9f0d..26c2e4c5936 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -30,3 +30,40 @@
= render "shared/personal_access_tokens_form", path: profile_personal_access_tokens_path, impersonation: false, token: @personal_access_token, scopes: @scopes
= render "shared/personal_access_tokens_table", impersonation: false, active_tokens: @active_personal_access_tokens, inactive_tokens: @inactive_personal_access_tokens
+
+%hr
+.row.prepend-top-default
+ .col-lg-4.profile-settings-sidebar
+ %h4.prepend-top-0
+ RSS token
+ %p
+ Your RSS token is used to authenticate you when your RSS reader loads a personalized RSS feed, and is included in your personal RSS feed URLs.
+ %p
+ It cannot be used to access any other data.
+ .col-lg-8.rss-token-reset
+ = label_tag :rss_token, 'RSS token', class: "label-light"
+ = text_field_tag :rss_token, current_user.rss_token, class: 'form-control', readonly: true, onclick: 'this.select()'
+ %p.help-block
+ Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds as if they were you.
+ You should
+ = link_to 'reset it', [:reset, :rss_token, :profile], method: :put, data: { confirm: 'Are you sure? Any RSS URLs currently in use will stop working.' }
+ if that ever happens.
+
+- if incoming_email_token_enabled?
+ %hr
+ .row.prepend-top-default
+ .col-lg-4.profile-settings-sidebar
+ %h4.prepend-top-0
+ Incoming email token
+ %p
+ Your incoming email token is used to authenticate you when you create a new issue by email, and is included in your personal project-specific email addresses.
+ %p
+ It cannot be used to access any other data.
+ .col-lg-8.incoming-email-token-reset
+ = 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
+ Keep this token secret. Anyone who gets ahold of it can create issues as if they were you.
+ You should
+ = link_to 'reset it', [:reset, :incoming_email_token, :profile], method: :put, data: { confirm: 'Are you sure? Any issue email addresses currently in use will stop working.' }
+ if that ever happens.
diff --git a/app/views/projects/_export.html.haml b/app/views/projects/_export.html.haml
index 623d3bc91c6..c5b1897c492 100644
--- a/app/views/projects/_export.html.haml
+++ b/app/views/projects/_export.html.haml
@@ -3,7 +3,7 @@
- project = local_assigns.fetch(:project)
- expanded = Rails.env.test?
-%section.settings
+%section.settings.no-animate{ class: ('expanded' if expanded) }
.settings-header
%h4
Export project
@@ -11,7 +11,7 @@
= expanded ? 'Collapse' : 'Expand'
%p
Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the "New Project" page.
- .settings-content.no-animate{ class: ('expanded' if expanded) }
+ .settings-content
.bs-callout.bs-callout-info
%p.append-bottom-0
%p
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 619b632918e..1d644dda177 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,6 +1,5 @@
- empty_repo = @project.empty_repo?
- fork_network = @project.fork_network
-- forked_from_project = @project.forked_from_project || fork_network&.root_project
.project-home-panel.text-center{ class: ("empty-project" if empty_repo) }
.limit-container-width{ class: container_class }
.avatar-container.s70.project-avatar
@@ -16,13 +15,13 @@
- if @project.forked?
%p
- - if forked_from_project
+ - if @project.fork_source
#{ s_('ForkedFromProjectPath|Forked from') }
- = link_to project_path(forked_from_project) do
- = forked_from_project.full_name
+ = link_to project_path(@project.fork_source) do
+ = fork_source_name(@project)
- else
- deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)')
- = deleted_message % { project_name: fork_network.deleted_root_project_name }
+ = deleted_message % { project_name: fork_source_name(@project) }
.project-repo-buttons
.count-buttons
diff --git a/app/views/projects/clusters/show.html.haml b/app/views/projects/clusters/show.html.haml
index b127e06030e..dbe6f8beb95 100644
--- a/app/views/projects/clusters/show.html.haml
+++ b/app/views/projects/clusters/show.html.haml
@@ -10,9 +10,9 @@
cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason } }
- %section.settings
+ %section.settings.no-animate.expanded
%h4= s_('ClusterIntegration|Enable cluster integration')
- .settings-content.expanded
+ .settings-content
.hidden.js-cluster-error.alert.alert-danger.alert-block.append-bottom-10{ role: 'alert' }
= s_('ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine')
@@ -49,14 +49,14 @@
.form-group
= field.submit _('Save'), class: 'btn btn-success'
- %section.settings#js-cluster-details
+ %section.settings.no-animate#js-cluster-details{ class: ('expanded' if expanded) }
.settings-header
%h4= s_('ClusterIntegration|Cluster details')
%button.btn.js-settings-toggle
= expanded ? 'Collapse' : 'Expand'
%p= s_('ClusterIntegration|See and edit the details for your cluster')
- .settings-content.no-animate{ class: ('expanded' if expanded) }
+ .settings-content
.form_group.append-bottom-20
%label.append-bottom-10{ for: 'cluter-name' }
@@ -66,11 +66,11 @@
%span.input-group-addon.clipboard-addon
= clipboard_button(text: @cluster.gcp_cluster_name, title: s_('ClusterIntegration|Copy cluster name'))
- %section.settings#js-cluster-advanced-settings
+ %section.settings.no-animate#js-cluster-advanced-settings{ class: ('expanded' if expanded) }
.settings-header
%h4= _('Advanced settings')
%button.btn.js-settings-toggle
= expanded ? 'Collapse' : 'Expand'
%p= s_('ClusterIntegration|Manage Cluster integration on your GitLab project')
- .settings-content.no-animate{ class: ('expanded' if expanded) }
+ .settings-content
= render 'advanced_settings'
diff --git a/app/views/projects/deploy_keys/_index.html.haml b/app/views/projects/deploy_keys/_index.html.haml
index 45985a5ecef..e75ae87e771 100644
--- a/app/views/projects/deploy_keys/_index.html.haml
+++ b/app/views/projects/deploy_keys/_index.html.haml
@@ -1,5 +1,5 @@
- expanded = Rails.env.test?
-%section.settings
+%section.settings.no-animate{ class: ('expanded' if expanded) }
.settings-header
%h4
Deploy Keys
@@ -7,7 +7,7 @@
= expanded ? 'Collapse' : 'Expand'
%p
Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.
- .settings-content.no-animate{ class: ('expanded' if expanded) }
+ .settings-content
%h5.prepend-top-0
Create a new deploy key for this project
= render @deploy_keys.form_partial_path
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 893e536e289..5ebeae5c35f 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -4,7 +4,7 @@
- expanded = Rails.env.test?
.project-edit-container
- %section.settings.general-settings
+ %section.settings.general-settings.no-animate{ class: ('expanded' if expanded) }
.settings-header
%h4
General project settings
@@ -12,7 +12,7 @@
= expanded ? 'Collapse' : 'Expand'
%p
Update your project name, description, avatar, and other general settings.
- .settings-content.no-animate{ class: ('expanded' if expanded) }
+ .settings-content
.project-edit-errors
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f|
%fieldset
@@ -61,7 +61,7 @@
= link_to 'Remove avatar', project_avatar_path(@project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
= f.submit 'Save changes', class: "btn btn-save"
- %section.settings.sharing-permissions
+ %section.settings.sharing-permissions.no-animate{ class: ('expanded' if expanded) }
.settings-header
%h4
Permissions
@@ -69,13 +69,13 @@
= expanded ? 'Collapse' : 'Expand'
%p
Enable or disable certain project features and choose access levels.
- .settings-content.no-animate{ class: ('expanded' if expanded) }
+ .settings-content
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f|
%script.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data(@project)
.js-project-permissions-form
= f.submit 'Save changes', class: "btn btn-save"
- %section.settings.merge-requests-feature{ class: ("hidden" if @project.project_feature.send(:merge_requests_access_level) == 0) }
+ %section.settings.merge-requests-feature.no-animate{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:merge_requests_access_level) == 0)] }
.settings-header
%h4
Merge request settings
@@ -83,14 +83,14 @@
= expanded ? 'Collapse' : 'Expand'
%p
Customize your merge request restrictions.
- .settings-content.no-animate{ class: ('expanded' if expanded) }
+ .settings-content
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "merge-request-settings-form" }, authenticity_token: true do |f|
= render 'merge_request_settings', form: f
= f.submit 'Save changes', class: "btn btn-save"
= render 'export', project: @project
- %section.settings.advanced-settings
+ %section.settings.advanced-settings.no-animate{ class: ('expanded' if expanded) }
.settings-header
%h4
Advanced settings
@@ -98,7 +98,7 @@
= expanded ? 'Collapse' : 'Expand'
%p
Perform advanced options such as housekeeping, archiving, renaming, transferring, or removing your project.
- .settings-content.no-animate{ class: ('expanded' if expanded) }
+ .settings-content
.sub-section
%h4 Housekeeping
%p
@@ -173,7 +173,10 @@
%p
This will remove the fork relationship to source project
= succeed "." do
- = link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project)
+ - if @project.fork_source
+ = link_to(fork_source_name(@project), project_path(@project.fork_source))
+ - else
+ = fork_source_name(@project)
= form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_project_path(@project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f|
%p
%strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source.
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index 70156c03e3c..cce16bc58b3 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -1,5 +1,5 @@
- @no_container = true
-- page_title "Contributors"
+- page_title _('Contributors')
- content_for :page_specific_javascripts do
= webpack_bundle_tag('common_d3')
= webpack_bundle_tag('graphs')
@@ -7,23 +7,23 @@
.js-graphs-show{ class: container_class, 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json) }
.sub-header-block
- .tree-ref-holder
+ .tree-ref-holder.inline.vertical-align-middle
= render 'shared/ref_switcher', destination: 'graphs'
- %ul.breadcrumb.repo-breadcrumb
- = commits_breadcrumbs
+ = link_to s_('Commits|History'), project_commits_path(@project, current_ref), class: 'btn'
.loading-graph
.center
%h3.page-title
%i.fa.fa-spinner.fa-spin
- Building repository graph.
- %p.slead Please wait a moment, this page will automatically refresh when ready.
+ = s_('ContributorsPage|Building repository graph.')
+ %p.slead
+ = s_('ContributorsPage|Please wait a moment, this page will automatically refresh when ready.')
.stat-graph.hide
.header.clearfix
%h3#date_header.page-title
%p.light
- Commits to #{@ref}, excluding merge commits. Limited to 6,000 commits.
+ = s_('ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits.') % { branch_name: @ref }
%input#brush_change{ :type => "hidden" }
.graphs.row
#contributors-master
diff --git a/app/views/projects/hook_logs/_index.html.haml b/app/views/projects/hook_logs/_index.html.haml
index 05b06cfc8b2..8096d9530c3 100644
--- a/app/views/projects/hook_logs/_index.html.haml
+++ b/app/views/projects/hook_logs/_index.html.haml
@@ -24,7 +24,7 @@
%td
= truncate(hook_log.url, length: 50)
%td.light
- #{number_with_precision(hook_log.execution_duration, precision: 2)} ms
+ #{number_with_precision(hook_log.execution_duration, precision: 2)} sec
%td.light
= time_ago_with_tooltip(hook_log.created_at)
%td
diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml
index 7da4ffd5e43..b5067367802 100644
--- a/app/views/projects/jobs/_sidebar.html.haml
+++ b/app/views/projects/jobs/_sidebar.html.haml
@@ -91,7 +91,7 @@
- builds.select{|build| build.status == build_status}.each do |build|
.build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } }
= link_to project_job_path(@project, build) do
- = icon('arrow-right')
+ = sprite_icon('arrow-right', size:16, css_class: 'icon-arrow-right')
%span{ class: "ci-status-icon-#{build.status}" }
= ci_icon_for_status(build.status)
%span
@@ -100,4 +100,5 @@
- else
= build.id
- if build.retried?
- %i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' }
+ %span.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' }
+ = sprite_icon('retry', size:16, css_class: 'icon-retry')
diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml
index cb723fe6a18..72d5c4961ec 100644
--- a/app/views/projects/merge_requests/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/_mr_title.html.haml
@@ -34,7 +34,7 @@
%li{ class: [merge_request_button_visibility(@merge_request, true), 'js-close-item'] }
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
%li{ class: merge_request_button_visibility(@merge_request, false) }
- = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
+ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: { state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
- if can_update_merge_request
= link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit"
diff --git a/app/views/projects/protected_branches/shared/_index.html.haml b/app/views/projects/protected_branches/shared/_index.html.haml
index 6a47cbdf724..ba7d98228c3 100644
--- a/app/views/projects/protected_branches/shared/_index.html.haml
+++ b/app/views/projects/protected_branches/shared/_index.html.haml
@@ -1,6 +1,6 @@
- expanded = Rails.env.test?
-%section.settings
+%section.settings.no-animate{ class: ('expanded' if expanded) }
.settings-header
%h4
Protected Branches
@@ -8,7 +8,7 @@
= expanded ? 'Collapse' : 'Expand'
%p
Keep stable branches secure and force developers to use merge requests.
- .settings-content.no-animate{ class: ('expanded' if expanded) }
+ .settings-content
%p
By default, protected branches are designed to:
%ul
diff --git a/app/views/projects/protected_tags/shared/_index.html.haml b/app/views/projects/protected_tags/shared/_index.html.haml
index c07bd454ff6..e764a37bbd7 100644
--- a/app/views/projects/protected_tags/shared/_index.html.haml
+++ b/app/views/projects/protected_tags/shared/_index.html.haml
@@ -1,6 +1,6 @@
- expanded = Rails.env.test?
-%section.settings
+%section.settings.no-animate{ class: ('expanded' if expanded) }
.settings-header
%h4
Protected Tags
@@ -8,7 +8,7 @@
= expanded ? 'Collapse' : 'Expand'
%p
Limit access to creating and updating tags.
- .settings-content.no-animate{ class: ('expanded' if expanded) }
+ .settings-content
%p
By default, protected tags are designed to:
%ul
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index 62455d0d40d..664a4554692 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -4,7 +4,7 @@
- expanded = Rails.env.test?
-%section.settings#js-general-pipeline-settings
+%section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if expanded) }
.settings-header
%h4
General pipelines settings
@@ -12,10 +12,10 @@
= expanded ? 'Collapse' : 'Expand'
%p
Update your CI/CD configuration, like job timeout or Auto DevOps.
- .settings-content.no-animate{ class: ('expanded' if expanded) }
+ .settings-content
= render 'projects/pipelines_settings/show'
-%section.settings
+%section.settings.no-animate{ class: ('expanded' if expanded) }
.settings-header
%h4
Runners settings
@@ -23,10 +23,10 @@
= expanded ? 'Collapse' : 'Expand'
%p
Register and see your runners for this project.
- .settings-content.no-animate{ class: ('expanded' if expanded) }
+ .settings-content
= render 'projects/runners/index'
-%section.settings
+%section.settings.no-animate{ class: ('expanded' if expanded) }
.settings-header
%h4
Secret variables
@@ -35,10 +35,10 @@
= expanded ? 'Collapse' : 'Expand'
%p
= render "ci/variables/content"
- .settings-content.no-animate{ class: ('expanded' if expanded) }
+ .settings-content
= render 'ci/variables/index'
-%section.settings
+%section.settings.no-animate{ class: ('expanded' if expanded) }
.settings-header
%h4
Pipeline triggers
@@ -48,5 +48,5 @@
Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will
impersonate their associated user including their access to projects and their project
permissions.
- .settings-content.no-animate{ class: ('expanded' if expanded) }
+ .settings-content
= render 'projects/triggers/index'
diff --git a/app/views/shared/_mini_pipeline_graph.html.haml b/app/views/shared/_mini_pipeline_graph.html.haml
index dff847159d3..901a177323b 100644
--- a/app/views/shared/_mini_pipeline_graph.html.haml
+++ b/app/views/shared/_mini_pipeline_graph.html.haml
@@ -7,7 +7,7 @@
.stage-container.dropdown{ class: klass }
%button.mini-pipeline-graph-dropdown-toggle.has-tooltip.js-builds-dropdown-button{ class: "ci-status-icon-#{detailed_status.group}", type: 'button', data: { toggle: 'dropdown', title: "#{stage.name}: #{detailed_status.label}", placement: 'top', "stage-endpoint" => stage_project_pipeline_path(pipeline.project, pipeline, stage: stage.name) } }
- = custom_icon(icon_status)
+ = sprite_icon(icon_status)
= icon('caret-down')
%ul.dropdown-menu.mini-pipeline-graph-dropdown-menu.js-builds-dropdown-container
diff --git a/app/views/shared/hook_logs/_content.html.haml b/app/views/shared/hook_logs/_content.html.haml
index af6a499fadb..c80b179d525 100644
--- a/app/views/shared/hook_logs/_content.html.haml
+++ b/app/views/shared/hook_logs/_content.html.haml
@@ -11,7 +11,7 @@
= hook_log.trigger.singularize.titleize
%p
%strong Elapsed time:
- #{number_with_precision(hook_log.execution_duration, precision: 2)} ms
+ #{number_with_precision(hook_log.execution_duration, precision: 2)} sec
%p
%strong Request time:
= time_ago_with_tooltip(hook_log.created_at)
diff --git a/changelogs/unreleased/3274-geo-route-whitelisting.yml b/changelogs/unreleased/3274-geo-route-whitelisting.yml
new file mode 100644
index 00000000000..43a5af80497
--- /dev/null
+++ b/changelogs/unreleased/3274-geo-route-whitelisting.yml
@@ -0,0 +1,5 @@
+---
+title: Tighten up whitelisting of certain Geo routes
+merge_request: 15082
+author:
+type: fixed
diff --git a/changelogs/unreleased/37473-expose-project-visibility-as-ci-variable.yml b/changelogs/unreleased/37473-expose-project-visibility-as-ci-variable.yml
new file mode 100644
index 00000000000..f6906a3b0e0
--- /dev/null
+++ b/changelogs/unreleased/37473-expose-project-visibility-as-ci-variable.yml
@@ -0,0 +1,5 @@
+---
+title: Expose project visibility as CI variable - CI_PROJECT_VISIBILITY
+merge_request: 15193
+author:
+type: added
diff --git a/changelogs/unreleased/37631-add-a-merge_request_diff_id-column-to-merge_requests.yml b/changelogs/unreleased/37631-add-a-merge_request_diff_id-column-to-merge_requests.yml
new file mode 100644
index 00000000000..a7127f49c16
--- /dev/null
+++ b/changelogs/unreleased/37631-add-a-merge_request_diff_id-column-to-merge_requests.yml
@@ -0,0 +1,5 @@
+---
+title: Add a latest_merge_request_diff_id column to merge_requests
+merge_request: 15035
+author:
+type: performance
diff --git a/changelogs/unreleased/39054-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml b/changelogs/unreleased/39054-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml
deleted file mode 100644
index 47bf30ecb5a..00000000000
--- a/changelogs/unreleased/39054-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Stop merge requests with thousands of commits from timing out
-merge_request: 15063
-author:
-type: performance
diff --git a/changelogs/unreleased/39188-change-default-disabled-merge-message.yml b/changelogs/unreleased/39188-change-default-disabled-merge-message.yml
deleted file mode 100644
index 7de65f5c3f6..00000000000
--- a/changelogs/unreleased/39188-change-default-disabled-merge-message.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update default disabled merge request widget message to reflect a general failure
-merge_request: 14960
-author:
-type: changed
diff --git a/changelogs/unreleased/39366-email-confirmation-fails.yml b/changelogs/unreleased/39366-email-confirmation-fails.yml
deleted file mode 100644
index a5568670c70..00000000000
--- a/changelogs/unreleased/39366-email-confirmation-fails.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Fix bug preventing secondary emails from being confirmed'
-merge_request: 15010
-author:
-type: fixed
diff --git a/changelogs/unreleased/39417-todos-spelled-correctly-on-todos-list-page.yml b/changelogs/unreleased/39417-todos-spelled-correctly-on-todos-list-page.yml
new file mode 100644
index 00000000000..edf142f0311
--- /dev/null
+++ b/changelogs/unreleased/39417-todos-spelled-correctly-on-todos-list-page.yml
@@ -0,0 +1,5 @@
+---
+title: Todos spelled correctly on Todos list page
+merge_request: 15015
+author:
+type: changed
diff --git a/changelogs/unreleased/39441-bring-edit-form-back.yml b/changelogs/unreleased/39441-bring-edit-form-back.yml
deleted file mode 100644
index 025417e4da9..00000000000
--- a/changelogs/unreleased/39441-bring-edit-form-back.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix editing issue description in mobile view
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/39495-fix-bitbucket-login.yml b/changelogs/unreleased/39495-fix-bitbucket-login.yml
deleted file mode 100644
index b48d557108b..00000000000
--- a/changelogs/unreleased/39495-fix-bitbucket-login.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix bitbucket login
-merge_request: 15051
-author:
-type: fixed
diff --git a/changelogs/unreleased/39639-clusters-poll.yml b/changelogs/unreleased/39639-clusters-poll.yml
deleted file mode 100644
index f0a82f58b19..00000000000
--- a/changelogs/unreleased/39639-clusters-poll.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds callback functions for initial request in clusters page
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/39704_fix_webhooks_log_time.yml b/changelogs/unreleased/39704_fix_webhooks_log_time.yml
new file mode 100644
index 00000000000..1234663e66b
--- /dev/null
+++ b/changelogs/unreleased/39704_fix_webhooks_log_time.yml
@@ -0,0 +1,5 @@
+---
+title: Fix webhooks recent deliveries
+merge_request: 15146
+author: Alexander Randa (@randaalex)
+type: fixed
diff --git a/changelogs/unreleased/39776-remove-responsive-table-bottom-border.yml b/changelogs/unreleased/39776-remove-responsive-table-bottom-border.yml
new file mode 100644
index 00000000000..52b6a267ced
--- /dev/null
+++ b/changelogs/unreleased/39776-remove-responsive-table-bottom-border.yml
@@ -0,0 +1,5 @@
+---
+title: Fix double border UI bug on pipelines/environments table and pagination
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/bvl-circuitbreaker-backoff.yml b/changelogs/unreleased/bvl-circuitbreaker-backoff.yml
deleted file mode 100644
index 5cb90e7c085..00000000000
--- a/changelogs/unreleased/bvl-circuitbreaker-backoff.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Make the circuitbreaker more robust by adding higher thresholds, and multiple
- access attempts.
-merge_request: 14933
-author:
-type: fixed
diff --git a/changelogs/unreleased/bvl-circuitbreaker-improvements.yml b/changelogs/unreleased/bvl-circuitbreaker-improvements.yml
deleted file mode 100644
index 15cbd5592e9..00000000000
--- a/changelogs/unreleased/bvl-circuitbreaker-improvements.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Store circuitbreaker settings in the database instead of config
-merge_request: 14842
-author:
-type: changed
diff --git a/changelogs/unreleased/bvl-do-not-use-redis-keys.yml b/changelogs/unreleased/bvl-do-not-use-redis-keys.yml
deleted file mode 100644
index f703aad2065..00000000000
--- a/changelogs/unreleased/bvl-do-not-use-redis-keys.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Forbid the usage of `Redis#keys`
-merge_request: 14889
-author:
-type: fixed
diff --git a/changelogs/unreleased/bvl-dont-rename-free-names.yml b/changelogs/unreleased/bvl-dont-rename-free-names.yml
deleted file mode 100644
index 60a4ec8afbe..00000000000
--- a/changelogs/unreleased/bvl-dont-rename-free-names.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Don't rename paths that were freed up when upgrading
-merge_request: 15029
-author:
-type: fixed
diff --git a/changelogs/unreleased/bvl-fix-push-event-service-for-forks.yml b/changelogs/unreleased/bvl-fix-push-event-service-for-forks.yml
deleted file mode 100644
index 2a7d80270ac..00000000000
--- a/changelogs/unreleased/bvl-fix-push-event-service-for-forks.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Only cache last push event for existing projects when pushing to a fork
-merge_request: 14989
-author:
-type: fixed
diff --git a/changelogs/unreleased/bvl-fix-system-hook-project-visibility.yml b/changelogs/unreleased/bvl-fix-system-hook-project-visibility.yml
deleted file mode 100644
index a17ed51c9b8..00000000000
--- a/changelogs/unreleased/bvl-fix-system-hook-project-visibility.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use the correct visibility attribute for projects in system hooks
-merge_request: 15065
-author:
-type: fixed
diff --git a/changelogs/unreleased/bvl-unlink-fixes.yml b/changelogs/unreleased/bvl-unlink-fixes.yml
new file mode 100644
index 00000000000..685d78f479d
--- /dev/null
+++ b/changelogs/unreleased/bvl-unlink-fixes.yml
@@ -0,0 +1,5 @@
+---
+title: Fix issues with forked projects of which the source was deleted
+merge_request: 15150
+author:
+type: fixed
diff --git a/changelogs/unreleased/dm-add-sudo-scope.yml b/changelogs/unreleased/dm-add-sudo-scope.yml
new file mode 100644
index 00000000000..a0c173ce781
--- /dev/null
+++ b/changelogs/unreleased/dm-add-sudo-scope.yml
@@ -0,0 +1,6 @@
+---
+title: Add sudo scope for OAuth and Personal Access Tokens to be used by admins to
+ impersonate other users on the API
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/dm-convert-private-tokens.yml b/changelogs/unreleased/dm-convert-private-tokens.yml
new file mode 100644
index 00000000000..8f5145c897b
--- /dev/null
+++ b/changelogs/unreleased/dm-convert-private-tokens.yml
@@ -0,0 +1,5 @@
+---
+title: Convert private tokens to Personal Access Tokens with sudo scope
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/dm-ldap-identity-normalize-dn.yml b/changelogs/unreleased/dm-ldap-identity-normalize-dn.yml
deleted file mode 100644
index 7ab25f79143..00000000000
--- a/changelogs/unreleased/dm-ldap-identity-normalize-dn.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Normalize LDAP DN when looking up identity
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/dm-remove-private-token-from-interface.yml b/changelogs/unreleased/dm-remove-private-token-from-interface.yml
new file mode 100644
index 00000000000..1b8996b08c3
--- /dev/null
+++ b/changelogs/unreleased/dm-remove-private-token-from-interface.yml
@@ -0,0 +1,5 @@
+---
+title: Remove private tokens from web interface and API
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/dm-remove-private-token.yml b/changelogs/unreleased/dm-remove-private-token.yml
new file mode 100644
index 00000000000..d721495721a
--- /dev/null
+++ b/changelogs/unreleased/dm-remove-private-token.yml
@@ -0,0 +1,5 @@
+---
+title: Remove Session API now that private tokens are removed from user API endpoints
+merge_request:
+author:
+type: removed
diff --git a/changelogs/unreleased/enable-scss-lint-mergeable-selector.yml b/changelogs/unreleased/enable-scss-lint-mergeable-selector.yml
new file mode 100644
index 00000000000..5f6e0cafe88
--- /dev/null
+++ b/changelogs/unreleased/enable-scss-lint-mergeable-selector.yml
@@ -0,0 +1,4 @@
+---
+title: Enable MergeableSelector in scss-lint
+merge_request: 12810
+author: Takuya Noguchi
diff --git a/changelogs/unreleased/fix-add-path-attr-to-wiki-file.yml b/changelogs/unreleased/fix-add-path-attr-to-wiki-file.yml
deleted file mode 100644
index 0847b5f6733..00000000000
--- a/changelogs/unreleased/fix-add-path-attr-to-wiki-file.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix broken wiki pages that link to a wiki file
-merge_request: 15019
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-import-issue-assignees.yml b/changelogs/unreleased/fix-import-issue-assignees.yml
deleted file mode 100644
index 063b6afaf08..00000000000
--- a/changelogs/unreleased/fix-import-issue-assignees.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix missing Import/Export issue assignees
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix_global_board_routes_39073.yml b/changelogs/unreleased/fix_global_board_routes_39073.yml
deleted file mode 100644
index cc9ae8592db..00000000000
--- a/changelogs/unreleased/fix_global_board_routes_39073.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow boards as top level route
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix_migration_that_adds_ff_merge_field.yml b/changelogs/unreleased/fix_migration_that_adds_ff_merge_field.yml
new file mode 100644
index 00000000000..a1685497331
--- /dev/null
+++ b/changelogs/unreleased/fix_migration_that_adds_ff_merge_field.yml
@@ -0,0 +1,5 @@
+---
+title: Fix a migration that adds merge_requests_ff_only_enabled column to MR table
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/issue_39176.yml b/changelogs/unreleased/issue_39176.yml
new file mode 100644
index 00000000000..6255b51c094
--- /dev/null
+++ b/changelogs/unreleased/issue_39176.yml
@@ -0,0 +1,5 @@
+---
+title: Render 404 when polling commit notes without having permissions
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/jivl-fix-cancel-button-file-upload-new-issue.yml b/changelogs/unreleased/jivl-fix-cancel-button-file-upload-new-issue.yml
new file mode 100644
index 00000000000..0205d9626b1
--- /dev/null
+++ b/changelogs/unreleased/jivl-fix-cancel-button-file-upload-new-issue.yml
@@ -0,0 +1,5 @@
+---
+title: Fix cancel button not working while uploading on the new issue page
+merge_request: 15137
+author:
+type: fixed
diff --git a/changelogs/unreleased/make-merge-jid-handling-less-stateful.yml b/changelogs/unreleased/make-merge-jid-handling-less-stateful.yml
deleted file mode 100644
index fe945e822fd..00000000000
--- a/changelogs/unreleased/make-merge-jid-handling-less-stateful.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix widget of locked merge requests not being presented
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/mr-14642.yml b/changelogs/unreleased/mr-14642.yml
deleted file mode 100644
index 048cc79e323..00000000000
--- a/changelogs/unreleased/mr-14642.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Auto Devops kubernetes default namespace is now correctly built out of gitlab
- project group-name
-merge_request: 14642
-author: Mircea Danila Dumitrescu
-type: fixed
diff --git a/changelogs/unreleased/pawel-disable_nfs_metrics_checks_39730.yml b/changelogs/unreleased/pawel-disable_nfs_metrics_checks_39730.yml
new file mode 100644
index 00000000000..556d7d069d3
--- /dev/null
+++ b/changelogs/unreleased/pawel-disable_nfs_metrics_checks_39730.yml
@@ -0,0 +1,5 @@
+---
+title: Remove Filesystem check metrics that use too much CPU to handle requests
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-fix-container-registry-destroy.yml b/changelogs/unreleased/sh-fix-container-registry-destroy.yml
deleted file mode 100644
index 21a463da62a..00000000000
--- a/changelogs/unreleased/sh-fix-container-registry-destroy.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix deletion of container registry or images returning an error
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-environment-slug-generation.yml b/changelogs/unreleased/sh-fix-environment-slug-generation.yml
new file mode 100644
index 00000000000..8a9c670c52c
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-environment-slug-generation.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid regenerating the ref path for the environment
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-environment-write-ref.yml b/changelogs/unreleased/sh-fix-environment-write-ref.yml
deleted file mode 100644
index 8f291843ebe..00000000000
--- a/changelogs/unreleased/sh-fix-environment-write-ref.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix the writing of invalid environment refs
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/winh-admin-projects-namespace-filter.yml b/changelogs/unreleased/winh-admin-projects-namespace-filter.yml
new file mode 100644
index 00000000000..7e906f446b0
--- /dev/null
+++ b/changelogs/unreleased/winh-admin-projects-namespace-filter.yml
@@ -0,0 +1,5 @@
+---
+title: Make NamespaceSelect change URL when filtering
+merge_request: 14888
+author:
+type: fixed
diff --git a/changelogs/unreleased/winh-i18n-contributors-page.yml b/changelogs/unreleased/winh-i18n-contributors-page.yml
new file mode 100644
index 00000000000..9b2611fc4fa
--- /dev/null
+++ b/changelogs/unreleased/winh-i18n-contributors-page.yml
@@ -0,0 +1,5 @@
+---
+title: Make contributors page translatable
+merge_request: 14915
+author:
+type: other
diff --git a/changelogs/unreleased/winh-namespace-rename-hooks.yml b/changelogs/unreleased/winh-namespace-rename-hooks.yml
new file mode 100644
index 00000000000..f5090b03b74
--- /dev/null
+++ b/changelogs/unreleased/winh-namespace-rename-hooks.yml
@@ -0,0 +1,5 @@
+---
+title: Add system hooks user_rename and group_rename
+merge_request: 15123
+author:
+type: changed
diff --git a/config/environments/test.rb b/config/environments/test.rb
index 1edb6fd39b8..d09e51e766a 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -1,6 +1,7 @@
Rails.application.configure do
# Make sure the middleware is inserted first in middleware chain
config.middleware.insert_before('ActionDispatch::Static', 'Gitlab::Testing::RequestBlockerMiddleware')
+ config.middleware.insert_before('ActionDispatch::Static', 'Gitlab::Testing::RequestInspectorMiddleware')
# Settings specified here will take precedence over those in config/application.rb
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 4bfa5be0136..7547ba4a8fa 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -501,7 +501,7 @@ production: &base
# Gitaly settings
gitaly:
# Path to the directory containing Gitaly client executables.
- client_path: /home/git/gitaly
+ client_path: /home/git/gitaly/bin
# Default Gitaly authentication token. Can be overriden per storage. Can
# be left blank when Gitaly is running locally on a Unix socket, which
# is the normal way to deploy Gitaly.
diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml
index 14d49885fb3..0da6b14c29e 100644
--- a/config/locales/doorkeeper.en.yml
+++ b/config/locales/doorkeeper.en.yml
@@ -58,9 +58,10 @@ en:
expired: "The access token expired"
unknown: "The access token is invalid"
scopes:
- api: Access your API
- read_user: Read user information
+ api: Access the authenticated user's API
+ read_user: Read the authenticated user's personal information
openid: Authenticate using OpenID Connect
+ sudo: Perform API actions as any user in the system (if the authenticated user is an admin)
flash:
applications:
diff --git a/config/routes/profile.rb b/config/routes/profile.rb
index ddc852f0132..bcfc17a5f66 100644
--- a/config/routes/profile.rb
+++ b/config/routes/profile.rb
@@ -6,7 +6,6 @@ resource :profile, only: [:show, :update] do
get :audit_log
get :applications, to: 'oauth/applications#index'
- put :reset_private_token
put :reset_incoming_email_token
put :reset_rss_token
put :update_username
diff --git a/db/migrate/20150827121444_add_fast_forward_option_to_project.rb b/db/migrate/20150827121444_add_fast_forward_option_to_project.rb
index 6f22641077d..35df121519e 100644
--- a/db/migrate/20150827121444_add_fast_forward_option_to_project.rb
+++ b/db/migrate/20150827121444_add_fast_forward_option_to_project.rb
@@ -8,7 +8,11 @@ class AddFastForwardOptionToProject < ActiveRecord::Migration
disable_ddl_transaction!
def up
- add_column_with_default(:projects, :merge_requests_ff_only_enabled, :boolean, default: false)
+ # We put condition here because of a mistake we made a couple of years ago
+ # see https://gitlab.com/gitlab-org/gitlab-ce/issues/39382#note_45716103
+ unless column_exists?(:projects, :merge_requests_ff_only_enabled)
+ add_column_with_default(:projects, :merge_requests_ff_only_enabled, :boolean, default: false)
+ end
end
def down
diff --git a/db/migrate/20171012125712_migrate_user_authentication_token_to_personal_access_token.rb b/db/migrate/20171012125712_migrate_user_authentication_token_to_personal_access_token.rb
new file mode 100644
index 00000000000..9a909644a44
--- /dev/null
+++ b/db/migrate/20171012125712_migrate_user_authentication_token_to_personal_access_token.rb
@@ -0,0 +1,78 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class MigrateUserAuthenticationTokenToPersonalAccessToken < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ # disable_ddl_transaction!
+
+ TOKEN_NAME = 'Private Token'.freeze
+
+ def up
+ execute <<~SQL
+ INSERT INTO personal_access_tokens (user_id, token, name, created_at, updated_at, scopes)
+ SELECT id, authentication_token, '#{TOKEN_NAME}', NOW(), NOW(), '#{%w[api].to_yaml}'
+ FROM users
+ WHERE authentication_token IS NOT NULL
+ AND admin = FALSE
+ AND NOT EXISTS (
+ SELECT true
+ FROM personal_access_tokens
+ WHERE user_id = users.id
+ AND token = users.authentication_token
+ )
+ SQL
+
+ # Admins also need the `sudo` scope
+ execute <<~SQL
+ INSERT INTO personal_access_tokens (user_id, token, name, created_at, updated_at, scopes)
+ SELECT id, authentication_token, '#{TOKEN_NAME}', NOW(), NOW(), '#{%w[api sudo].to_yaml}'
+ FROM users
+ WHERE authentication_token IS NOT NULL
+ AND admin = TRUE
+ AND NOT EXISTS (
+ SELECT true
+ FROM personal_access_tokens
+ WHERE user_id = users.id
+ AND token = users.authentication_token
+ )
+ SQL
+ end
+
+ def down
+ if Gitlab::Database.postgresql?
+ execute <<~SQL
+ UPDATE users
+ SET authentication_token = pats.token
+ FROM (
+ SELECT user_id, token
+ FROM personal_access_tokens
+ WHERE name = '#{TOKEN_NAME}'
+ ) AS pats
+ WHERE id = pats.user_id
+ SQL
+ else
+ execute <<~SQL
+ UPDATE users
+ INNER JOIN personal_access_tokens AS pats
+ ON users.id = pats.user_id
+ SET authentication_token = pats.token
+ WHERE pats.name = '#{TOKEN_NAME}'
+ SQL
+ end
+
+ execute <<~SQL
+ DELETE FROM personal_access_tokens
+ WHERE name = '#{TOKEN_NAME}'
+ AND EXISTS (
+ SELECT true
+ FROM users
+ WHERE id = personal_access_tokens.user_id
+ AND authentication_token = personal_access_tokens.token
+ )
+ SQL
+ end
+end
diff --git a/db/migrate/20171025110159_add_latest_merge_request_diff_id_to_merge_requests.rb b/db/migrate/20171025110159_add_latest_merge_request_diff_id_to_merge_requests.rb
new file mode 100644
index 00000000000..74a2badc130
--- /dev/null
+++ b/db/migrate/20171025110159_add_latest_merge_request_diff_id_to_merge_requests.rb
@@ -0,0 +1,26 @@
+class AddLatestMergeRequestDiffIdToMergeRequests < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column :merge_requests, :latest_merge_request_diff_id, :integer
+ add_concurrent_index :merge_requests, :latest_merge_request_diff_id
+
+ add_concurrent_foreign_key :merge_requests, :merge_request_diffs,
+ column: :latest_merge_request_diff_id,
+ on_delete: :nullify
+ end
+
+ def down
+ remove_foreign_key :merge_requests, column: :latest_merge_request_diff_id
+
+ if index_exists?(:merge_requests, :latest_merge_request_diff_id)
+ remove_concurrent_index :merge_requests, :latest_merge_request_diff_id
+ end
+
+ remove_column :merge_requests, :latest_merge_request_diff_id
+ end
+end
diff --git a/db/post_migrate/20171012150314_remove_user_authentication_token.rb b/db/post_migrate/20171012150314_remove_user_authentication_token.rb
new file mode 100644
index 00000000000..d0f3aa06e98
--- /dev/null
+++ b/db/post_migrate/20171012150314_remove_user_authentication_token.rb
@@ -0,0 +1,20 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveUserAuthenticationToken < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ remove_column :users, :authentication_token
+ end
+
+ def down
+ add_column :users, :authentication_token, :string
+
+ add_concurrent_index :users, :authentication_token, unique: true
+ end
+end
diff --git a/db/post_migrate/20171026082505_populate_merge_requests_latest_merge_request_diff_id.rb b/db/post_migrate/20171026082505_populate_merge_requests_latest_merge_request_diff_id.rb
new file mode 100644
index 00000000000..a7ebbbf34c0
--- /dev/null
+++ b/db/post_migrate/20171026082505_populate_merge_requests_latest_merge_request_diff_id.rb
@@ -0,0 +1,27 @@
+class PopulateMergeRequestsLatestMergeRequestDiffId < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 1_000
+
+ class MergeRequest < ActiveRecord::Base
+ self.table_name = 'merge_requests'
+
+ include ::EachBatch
+ end
+
+ disable_ddl_transaction!
+
+ def up
+ update = '
+ latest_merge_request_diff_id = (
+ SELECT MAX(id)
+ FROM merge_request_diffs
+ WHERE merge_requests.id = merge_request_diffs.merge_request_id
+ )'.squish
+
+ MergeRequest.where(latest_merge_request_diff_id: nil).each_batch(of: BATCH_SIZE) do |relation|
+ relation.update_all(update)
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 530f08022be..80d8ff92d6e 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: 20171017145932) do
+ActiveRecord::Schema.define(version: 20171026082505) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -973,6 +973,7 @@ ActiveRecord::Schema.define(version: 20171017145932) do
t.boolean "ref_fetched"
t.string "merge_jid"
t.boolean "discussion_locked"
+ t.integer "latest_merge_request_diff_id"
end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
@@ -981,6 +982,7 @@ ActiveRecord::Schema.define(version: 20171017145932) do
add_index "merge_requests", ["deleted_at"], name: "index_merge_requests_on_deleted_at", using: :btree
add_index "merge_requests", ["description"], name: "index_merge_requests_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
add_index "merge_requests", ["head_pipeline_id"], name: "index_merge_requests_on_head_pipeline_id", using: :btree
+ add_index "merge_requests", ["latest_merge_request_diff_id"], name: "index_merge_requests_on_latest_merge_request_diff_id", using: :btree
add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree
add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree
add_index "merge_requests", ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_id_and_source_branch", using: :btree
@@ -1670,7 +1672,6 @@ ActiveRecord::Schema.define(version: 20171017145932) do
t.string "skype", default: "", null: false
t.string "linkedin", default: "", null: false
t.string "twitter", default: "", null: false
- t.string "authentication_token"
t.string "bio"
t.integer "failed_attempts", default: 0
t.datetime "locked_at"
@@ -1720,7 +1721,6 @@ ActiveRecord::Schema.define(version: 20171017145932) do
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
- add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
add_index "users", ["created_at"], name: "index_users_on_created_at", using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
@@ -1846,6 +1846,7 @@ ActiveRecord::Schema.define(version: 20171017145932) do
add_foreign_key "merge_request_metrics", "ci_pipelines", column: "pipeline_id", on_delete: :cascade
add_foreign_key "merge_request_metrics", "merge_requests", on_delete: :cascade
add_foreign_key "merge_requests", "ci_pipelines", column: "head_pipeline_id", name: "fk_fd82eae0b9", on_delete: :nullify
+ add_foreign_key "merge_requests", "merge_request_diffs", column: "latest_merge_request_diff_id", name: "fk_06067f5644", on_delete: :nullify
add_foreign_key "merge_requests", "projects", column: "target_project_id", name: "fk_a6963e8447", on_delete: :cascade
add_foreign_key "merge_requests_closing_issues", "issues", on_delete: :cascade
add_foreign_key "merge_requests_closing_issues", "merge_requests", on_delete: :cascade
diff --git a/doc/administration/operations/sidekiq_memory_killer.md b/doc/administration/operations/sidekiq_memory_killer.md
index b5e78348989..cbffd883774 100644
--- a/doc/administration/operations/sidekiq_memory_killer.md
+++ b/doc/administration/operations/sidekiq_memory_killer.md
@@ -28,7 +28,7 @@ The MemoryKiller is controlled using environment variables.
delayed shutdown is triggered. The default value for Omnibus packages is set
[in the omnibus-gitlab
repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb).
-- `SIDEKIQ_MEMORY_KILLER_GRACE_TIME`: defaults 900 seconds (15 minutes). When
+- `SIDEKIQ_MEMORY_KILLER_GRACE_TIME`: defaults to 900 seconds (15 minutes). When
a shutdown is triggered, the Sidekiq process will keep working normally for
another 15 minutes.
- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT`: defaults to 30 seconds. When the grace
@@ -36,5 +36,3 @@ The MemoryKiller is controlled using environment variables.
Existing jobs get 30 seconds to finish. After that, the MemoryKiller tells
Sidekiq to shut down, and an external supervision mechanism (e.g. Runit) must
restart Sidekiq.
-- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_SIGNAL`: defaults to `SIGKILL`. The name of
- the final signal sent to the Sidekiq process when we want it to shut down.
diff --git a/doc/administration/troubleshooting/debug.md b/doc/administration/troubleshooting/debug.md
index 6f1356ddf8f..be538ea250a 100644
--- a/doc/administration/troubleshooting/debug.md
+++ b/doc/administration/troubleshooting/debug.md
@@ -141,7 +141,7 @@ separate Rails process to debug the issue:
1. Log in to your GitLab account.
1. Copy the URL that is causing problems (e.g. https://gitlab.com/ABC).
-1. Obtain the private token for your user (Profile Settings -> Account).
+1. Create a Personal Access Token for your user (Profile Settings -> Access Tokens).
1. Bring up the GitLab Rails console. For omnibus users, run:
```
diff --git a/doc/api/README.md b/doc/api/README.md
index 89ffe9d7868..f226716c3b5 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -50,7 +50,6 @@ following locations:
- [Repository Files](repository_files.md)
- [Runners](runners.md)
- [Services](services.md)
-- [Session](session.md)
- [Settings](settings.md)
- [Sidekiq metrics](sidekiq_metrics.md)
- [System Hooks](system_hooks.md)
@@ -86,27 +85,10 @@ API requests should be prefixed with `api` and the API version. The API version
is defined in [`lib/api.rb`][lib-api-url]. For example, the root of the v4 API
is at `/api/v4`.
-For endpoints that require [authentication](#authentication), you need to pass
-a `private_token` parameter via query string or header. If passed as a header,
-the header name must be `PRIVATE-TOKEN` (uppercase and with a dash instead of
-an underscore).
-
-Example of a valid API request:
-
-```
-GET /projects?private_token=9koXpg98eAheJpvBs5tK
-```
-
-Example of a valid API request using cURL and authentication via header:
+Example of a valid API request using cURL:
```shell
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects"
-```
-
-Example of a valid API request using cURL and authentication via a query string:
-
-```shell
-curl "https://gitlab.example.com/api/v4/projects?private_token=9koXpg98eAheJpvBs5tK"
+curl "https://gitlab.example.com/api/v4/projects"
```
The API uses JSON to serialize data. You don't need to specify `.json` at the
@@ -114,15 +96,20 @@ end of an API URL.
## Authentication
-Most API requests require authentication via a session cookie or token. For
+Most API requests require authentication, or will only return public data when
+authentication is not provided. For
those cases where it is not required, this will be mentioned in the documentation
for each individual endpoint. For example, the [`/projects/:id` endpoint](projects.md).
-There are three types of access tokens available:
+There are three ways to authenticate with the GitLab API:
1. [OAuth2 tokens](#oauth2-tokens)
-1. [Private tokens](#private-tokens)
1. [Personal access tokens](#personal-access-tokens)
+1. [Session cookie](#session-cookie)
+
+For admins who want to authenticate with the API as a specific user, or who want to build applications or scripts that do so, two options are available:
+1. [Impersonation tokens](#impersonation-tokens)
+2. [Sudo](#sudo)
If authentication information is invalid or omitted, an error message will be
returned with status code `401`:
@@ -133,74 +120,84 @@ returned with status code `401`:
}
```
-### Session cookie
+### OAuth2 tokens
-When signing in to GitLab as an ordinary user, a `_gitlab_session` cookie is
-set. The API will use this cookie for authentication if it is present, but using
-the API to generate a new session cookie is currently not supported.
+You can use an [OAuth2 token](oauth2.md) to authenticate with the API by passing it in either the
+`access_token` parameter or the `Authorization` header.
-### OAuth2 tokens
+Example of using the OAuth2 token in a parameter:
-You can use an OAuth 2 token to authenticate with the API by passing it either in the
-`access_token` parameter or in the `Authorization` header.
+```shell
+curl https://gitlab.example.com/api/v4/projects?access_token=OAUTH-TOKEN
+```
-Example of using the OAuth2 token in the header:
+Example of using the OAuth2 token in a header:
```shell
curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api/v4/projects
```
-Read more about [GitLab as an OAuth2 client](oauth2.md).
+Read more about [GitLab as an OAuth2 provider](oauth2.md).
-### Private tokens
+### Personal access tokens
-Private tokens provide full access to the GitLab API. Anyone with access to
-them can interact with GitLab as if they were you. You can find or reset your
-private token in your account page (`/profile/account`).
+You can use a [personal access token][pat] to authenticate with the API by passing it in either the
+`private_token` parameter or the `Private-Token` header.
-For examples of usage, [read the basic usage section](#basic-usage).
+Example of using the personal access token in a parameter:
-### Personal access tokens
+```shell
+curl https://gitlab.example.com/api/v4/projects?private_token=9koXpg98eAheJpvBs5tK
+```
+
+Example of using the personal access token in a header:
-Instead of using your private token which grants full access to your account,
-personal access tokens could be a better fit because of their granular
-permissions.
+```shell
+curl --header "Private-Token: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects
+```
-Once you have your token, pass it to the API using either the `private_token`
-parameter or the `PRIVATE-TOKEN` header. For examples of usage,
-[read the basic usage section](#basic-usage).
+Read more about [personal access tokens][pat].
+
+### Session cookie
+
+When signing in to the main GitLab application, a `_gitlab_session` cookie is
+set. The API will use this cookie for authentication if it is present, but using
+the API to generate a new session cookie is currently not supported.
-[Read more about personal access tokens.][pat]
+The primary user of this authentication method is the web frontend of GitLab itself,
+which can use the API as the authenticated user to get a list of their projects,
+for example, without needing to explicitly pass an access token.
### Impersonation tokens
> [Introduced][ce-9099] in GitLab 9.0. Needs admin permissions.
Impersonation tokens are a type of [personal access token][pat]
-that can only be created by an admin for a specific user.
+that can only be created by an admin for a specific user. They are a great fit
+if you want to build applications or scripts that authenticate with the API as a specific user.
-They are a better alternative to using the user's password/private token
-or using the [Sudo](#sudo) feature which also requires the admin's password
-or private token, since the password/token can change over time. Impersonation
-tokens are a great fit if you want to build applications or tools which
-authenticate with the API as a specific user.
+They are an alternative to directly using the user's password or one of their
+personal access tokens, and to using the [Sudo](#sudo) feature, since the user's (or admin's, in the case of Sudo)
+password/token may not be known or may change over time.
For more information, refer to the
[users API](users.md#retrieve-user-impersonation-tokens) docs.
-For examples of usage, [read the basic usage section](#basic-usage).
+Impersonation tokens are used exactly like regular personal access tokens, and can be passed in either the
+`private_token` parameter or the `Private-Token` header.
### Sudo
> Needs admin permissions.
All API requests support performing an API call as if you were another user,
-provided your private token is from an administrator account. You need to pass
-the `sudo` parameter either via query string or a header with an ID/username of
+provided you are authenticated as an administrator with an OAuth or Personal Access Token that has the `sudo` scope.
+
+You need to pass the `sudo` parameter either via query string or a header with an ID/username of
the user you want to perform the operation as. If passed as a header, the
-header name must be `SUDO` (uppercase).
+header name must be `Sudo`.
-If a non administrative `private_token` is provided, then an error message will
+If a non administrative access token is provided, an error message will
be returned with status code `403`:
```json
@@ -209,12 +206,23 @@ be returned with status code `403`:
}
```
+If an access token without the `sudo` scope is provided, an error message will
+be returned with status code `403`:
+
+```json
+{
+ "error": "insufficient_scope",
+ "error_description": "The request requires higher privileges than provided by the access token.",
+ "scope": "sudo"
+}
+```
+
If the sudo user ID or username cannot be found, an error message will be
returned with status code `404`:
```json
{
- "message": "404 Not Found: No user id or username for: <id/username>"
+ "message": "404 User with ID or username '123' Not Found"
}
```
@@ -228,7 +236,7 @@ GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=username
```
```shell
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: username" "https://gitlab.example.com/api/v4/projects"
+curl --header "Private-Token: 9koXpg98eAheJpvBs5tK" --header "Sudo: username" "https://gitlab.example.com/api/v4/projects"
```
Example of a valid API call and a request using cURL with sudo request,
@@ -239,7 +247,7 @@ GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23
```
```shell
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: 23" "https://gitlab.example.com/api/v4/projects"
+curl --header "Private-Token: 9koXpg98eAheJpvBs5tK" --header "Sudo: 23" "https://gitlab.example.com/api/v4/projects"
```
## Status codes
diff --git a/doc/api/session.md b/doc/api/session.md
deleted file mode 100644
index b97e26f34a2..00000000000
--- a/doc/api/session.md
+++ /dev/null
@@ -1,55 +0,0 @@
-# Session API
-
->**Deprecation notice:**
-Starting in GitLab 8.11, this feature has been **disabled** for users with
-[two-factor authentication][2fa] turned on. These users can access the API
-using [personal access tokens] instead.
-
-You can login with both GitLab and LDAP credentials in order to obtain the
-private token.
-
-```
-POST /session
-```
-
-| Attribute | Type | Required | Description |
-| ---------- | ------- | -------- | -------- |
-| `login` | string | yes | The username of the user|
-| `email` | string | yes if login is not provided | The email of the user |
-| `password` | string | yes | The password of the user |
-
-```bash
-curl --request POST "https://gitlab.example.com/api/v4/session?login=john_smith&password=strongpassw0rd"
-```
-
-Example response:
-
-```json
-{
- "name": "John Smith",
- "username": "john_smith",
- "id": 32,
- "state": "active",
- "avatar_url": null,
- "created_at": "2015-01-29T21:07:19.440Z",
- "is_admin": true,
- "bio": null,
- "skype": "",
- "linkedin": "",
- "twitter": "",
- "website_url": "",
- "email": "john@example.com",
- "theme_id": 1,
- "color_scheme_id": 1,
- "projects_limit": 10,
- "current_sign_in_at": "2015-07-07T07:10:58.392Z",
- "identities": [],
- "can_create_group": true,
- "can_create_project": true,
- "two_factor_enabled": false,
- "private_token": "9koXpg98eAheJpvBs5tK"
-}
-```
-
-[2fa]: ../user/profile/account/two_factor_authentication.md
-[personal access tokens]: ../user/profile/personal_access_tokens.md
diff --git a/doc/api/users.md b/doc/api/users.md
index 1643c584244..aa711090af1 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -410,8 +410,7 @@ GET /user
"can_create_group": true,
"can_create_project": true,
"two_factor_enabled": true,
- "external": false,
- "private_token": "dd34asd13as"
+ "external": false
}
```
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 4586caa457d..0a2419b7ed2 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -31,12 +31,12 @@ There are three methods to enable the use of `docker build` and `docker run` dur
The simplest approach is to install GitLab Runner in `shell` execution mode.
GitLab Runner then executes job scripts as the `gitlab-runner` user.
-1. Install [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/#installation).
+1. Install [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-runner/#installation).
1. During GitLab Runner installation select `shell` as method of executing job scripts or use command:
```bash
- sudo gitlab-ci-multi-runner register -n \
+ sudo gitlab-runner register -n \
--url https://gitlab.com/ \
--registration-token REGISTRATION_TOKEN \
--executor shell \
@@ -93,7 +93,7 @@ In order to do that, follow the steps:
mode:
```bash
- sudo gitlab-ci-multi-runner register -n \
+ sudo gitlab-runner register -n \
--url https://gitlab.com/ \
--registration-token REGISTRATION_TOKEN \
--executor docker \
@@ -178,7 +178,7 @@ In order to do that, follow the steps:
1. Register GitLab Runner from the command line to use `docker` and share `/var/run/docker.sock`:
```bash
- sudo gitlab-ci-multi-runner register -n \
+ sudo gitlab-runner register -n \
--url https://gitlab.com/ \
--registration-token REGISTRATION_TOKEN \
--executor docker \
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index f7493794b6a..ecb8f15c851 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -501,8 +501,8 @@ First start with creating a file named `build_script`:
```bash
cat <<EOF > build_script
-git clone https://gitlab.com/gitlab-org/gitlab-ci-multi-runner.git /builds/gitlab-org/gitlab-ci-multi-runner
-cd /builds/gitlab-org/gitlab-ci-multi-runner
+git clone https://gitlab.com/gitlab-org/gitlab-runner.git /builds/gitlab-org/gitlab-runner
+cd /builds/gitlab-org/gitlab-runner
make
EOF
```
diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md
index f2dd12b67d3..6768a2e012f 100644
--- a/doc/ci/examples/php.md
+++ b/doc/ci/examples/php.md
@@ -267,10 +267,10 @@ terminal execute:
```bash
# Check using docker executor
-gitlab-ci-multi-runner exec docker test:app
+gitlab-runner exec docker test:app
# Check using shell executor
-gitlab-ci-multi-runner exec shell test:app
+gitlab-runner exec shell test:app
```
## Example project
diff --git a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
index 0f7ed055e79..a6ed1c54e16 100644
--- a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
+++ b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md
@@ -64,7 +64,7 @@ To build this project you also need to have [GitLab Runner](https://docs.gitlab.
You can use public runners available on `gitlab.com`, but you can register your own:
```
-gitlab-ci-multi-runner register \
+gitlab-runner register \
--non-interactive \
--url "https://gitlab.com/" \
--registration-token "PROJECT_REGISTRATION_TOKEN" \
diff --git a/doc/ci/git_submodules.md b/doc/ci/git_submodules.md
index 36c6e153d95..c83d3f6f248 100644
--- a/doc/ci/git_submodules.md
+++ b/doc/ci/git_submodules.md
@@ -61,7 +61,7 @@ correctly with your CI jobs:
1. First, make sure you have used [relative URLs](#configuring-the-gitmodules-file)
for the submodules located in the same GitLab server.
-1. Next, if you are using `gitlab-ci-multi-runner` v1.10+, you can set the
+1. Next, if you are using `gitlab-runner` v1.10+, you can set the
`GIT_SUBMODULE_STRATEGY` variable to either `normal` or `recursive` to tell
the runner to fetch your submodules before the job:
```yaml
@@ -71,7 +71,7 @@ correctly with your CI jobs:
See the [`.gitlab-ci.yml` reference](yaml/README.md#git-submodule-strategy)
for more details about `GIT_SUBMODULE_STRATEGY`.
-1. If you are using an older version of `gitlab-ci-multi-runner`, then use
+1. If you are using an older version of `gitlab-runner`, then use
`git submodule sync/update` in `before_script`:
```yaml
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 535ed351366..a9e6bda9916 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -66,6 +66,7 @@ future GitLab releases.**
| **CI_PROJECT_PATH** | 8.10 | 0.5 | The namespace with project name |
| **CI_PROJECT_PATH_SLUG** | 9.3 | all | `$CI_PROJECT_PATH` lowercased and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
| **CI_PROJECT_URL** | 8.10 | 0.5 | The HTTP address to access project |
+| **CI_PROJECT_VISIBILITY** | 10.3 | all | The project visibility (internal, private, public) |
| **CI_REGISTRY** | 8.10 | 0.5 | If the Container Registry is enabled it returns the address of GitLab's Container Registry |
| **CI_REGISTRY_IMAGE** | 8.10 | 0.5 | If the Container Registry is enabled for the project it returns the address of the registry tied to the specific project |
| **CI_REGISTRY_PASSWORD** | 9.0 | all | The password to use to push containers to the GitLab Container Registry |
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index 798f40eef3d..0e4ffbd7910 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -459,11 +459,11 @@ Rendered example:
### cURL commands
- Use `https://gitlab.example.com/api/v4/` as an endpoint.
-- Wherever needed use this private token: `9koXpg98eAheJpvBs5tK`.
+- Wherever needed use this personal access token: `9koXpg98eAheJpvBs5tK`.
- Always put the request first. `GET` is the default so you don't have to
include it.
- Use double quotes to the URL when it includes additional parameters.
-- Prefer to use examples using the private token and don't pass data of
+- Prefer to use examples using the personal access token and don't pass data of
username and password.
| Methods | Description |
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index 7ddd02e6c73..8b7b015427f 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -60,6 +60,35 @@ writing one](testing_levels.md#consider-not-writing-a-system-test)!
- It's ok to look for DOM elements but don't abuse it since it makes the tests
more brittle
+#### Debugging Capybara
+
+Sometimes you may need to debug Capybara tests by observing browser behavior.
+
+You can pause Capybara and view the website on the browser by using the
+`live_debug` method in your spec. The current page will be automatically opened
+in your default browser.
+You may need to sign in first (the current user's credentials are displayed in
+the terminal).
+
+To resume the test run, press any key.
+
+For example:
+
+```
+$ bin/rspec spec/features/auto_deploy_spec.rb:34
+Running via Spring preloader in process 8999
+Run options: include {:locations=>{"./spec/features/auto_deploy_spec.rb"=>[34]}}
+
+Current example is paused for live debugging
+The current user credentials are: user2 / 12345678
+Press any key to resume the execution of the example!
+Back to the example!
+.
+
+Finished in 34.51 seconds (files took 0.76702 seconds to load)
+1 example, 0 failures
+```
+
### `let` variables
GitLab's RSpec suite has made extensive use of `let` variables to reduce
diff --git a/doc/development/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md
index 9b9ba0baa71..1cbd4350284 100644
--- a/doc/development/testing_guide/testing_levels.md
+++ b/doc/development/testing_guide/testing_levels.md
@@ -126,7 +126,7 @@ always in-sync with the codebase.
[GitLab Workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse
[Gitaly]: https://gitlab.com/gitlab-org/gitaly
[GitLab Pages]: https://gitlab.com/gitlab-org/gitlab-pages
-[GitLab Runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner
+[GitLab Runner]: https://gitlab.com/gitlab-org/gitlab-runner
[GitLab Omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab
[GitLab QA]: https://gitlab.com/gitlab-org/gitlab-qa
[part of GitLab Rails]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 7d9bbca4168..7bf126eec5d 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -184,7 +184,7 @@ Runner.
We recommend using a separate machine for each GitLab Runner, if you plan to
use the CI features.
-[security reasons]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/security/index.md
+[security reasons]: https://gitlab.com/gitlab-org/gitlab-runner/blob/master/docs/security/index.md
## Supported web browsers
diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md
index 3ae46019daf..5554a0c8b78 100644
--- a/doc/raketasks/user_management.md
+++ b/doc/raketasks/user_management.md
@@ -149,18 +149,3 @@ cp config/secrets.yml.bak config/secrets.yml
sudo /etc/init.d/gitlab start
```
-
-## Clear authentication tokens for all users. Important! Data loss!
-
-Clear authentication tokens for all users in the GitLab database. This
-task is useful if your users' authentication tokens might have been exposed in
-any way. All the existing tokens will become invalid, and new tokens are
-automatically generated upon sign-in or user modification.
-
-```
-# omnibus-gitlab
-sudo gitlab-rake gitlab:users:clear_all_authentication_tokens
-
-# installation from source
-bundle exec rake gitlab:users:clear_all_authentication_tokens RAILS_ENV=production
-```
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
index a45a4eb9e49..f2a9b1d769b 100644
--- a/doc/system_hooks/system_hooks.md
+++ b/doc/system_hooks/system_hooks.md
@@ -1,6 +1,24 @@
# System hooks
-Your GitLab instance can perform HTTP POST requests on the following events: `project_create`, `project_destroy`, `project_rename`, `project_transfer`, `project_update`, `user_add_to_team`, `user_remove_from_team`, `user_create`, `user_destroy`, `key_create`, `key_destroy`, `group_create`, `group_destroy`, `user_add_to_group` and `user_remove_from_group`.
+Your GitLab instance can perform HTTP POST requests on the following events:
+
+- `project_create`
+- `project_destroy`
+- `project_rename`
+- `project_transfer`
+- `project_update`
+- `user_add_to_team`
+- `user_remove_from_team`
+- `user_create`
+- `user_destroy`
+- `user_rename`
+- `key_create`
+- `key_destroy`
+- `group_create`
+- `group_destroy`
+- `group_rename`
+- `user_add_to_group`
+- `user_remove_from_group`
The triggers for most of these are self-explanatory, but `project_update` and `project_rename` deserve some clarification: `project_update` is fired any time an attribute of a project is changed (name, description, tags, etc.) *unless* the `path` attribute is also changed. In that case, a `project_rename` is triggered instead (so that, for instance, if all you care about is the repo URL, you can just listen for `project_rename`).
@@ -72,6 +90,9 @@ X-Gitlab-Event: System Hook
}
```
+Note that `project_rename` is not triggered if the namespace changes.
+Please refer to `group_rename` and `user_rename` for that case.
+
**Project transferred:**
```json
@@ -175,6 +196,21 @@ X-Gitlab-Event: System Hook
}
```
+**User renamed:**
+
+```json
+{
+ "event_name": "user_rename",
+ "created_at": "2017-11-01T11:21:04Z",
+ "updated_at": "2017-11-01T14:04:47Z",
+ "name": "new-name",
+ "email": "best-email@example.tld",
+ "user_id": 58,
+ "username": "new-exciting-name",
+ "old_username": "old-boring-name"
+}
+```
+
**Key added**
```json
@@ -209,13 +245,15 @@ X-Gitlab-Event: System Hook
"updated_at": "2012-07-21T07:38:22Z",
"event_name": "group_create",
"name": "StoreCloud",
- "owner_email": "johnsmith@gmail.com",
- "owner_name": "John Smith",
+ "owner_email": null,
+ "owner_name": null,
"path": "storecloud",
"group_id": 78
}
```
+`owner_name` and `owner_email` are always `null`. Please see https://gitlab.com/gitlab-org/gitlab-ce/issues/39675.
+
**Group removed:**
```json
@@ -224,13 +262,35 @@ X-Gitlab-Event: System Hook
"updated_at": "2012-07-21T07:38:22Z",
"event_name": "group_destroy",
"name": "StoreCloud",
- "owner_email": "johnsmith@gmail.com",
- "owner_name": "John Smith",
+ "owner_email": null,
+ "owner_name": null,
"path": "storecloud",
"group_id": 78
}
```
+`owner_name` and `owner_email` are always `null`. Please see https://gitlab.com/gitlab-org/gitlab-ce/issues/39675.
+
+**Group renamed:**
+
+```json
+{
+ "event_name": "group_rename",
+ "created_at": "2017-10-30T15:09:00Z",
+ "updated_at": "2017-11-01T10:23:52Z",
+ "name": "Better Name",
+ "path": "better-name",
+ "full_path": "parent-group/better-name",
+ "group_id": 64,
+ "owner_name": null,
+ "owner_email": null,
+ "old_path": "old-name",
+ "old_full_path": "parent-group/old-name"
+}
+```
+
+`owner_name` and `owner_email` are always `null`. Please see https://gitlab.com/gitlab-org/gitlab-ce/issues/39675.
+
**New Group Member:**
```json
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 5561784ed0b..1cfdabac248 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -144,6 +144,12 @@ has a `.gitlab-ci.yml` or not:
All you need to do is remove your existing `.gitlab-ci.yml`, and you can even
do that in a branch to test Auto DevOps before committing to `master`.
+NOTE: **Note:**
+If you are a GitLab Administrator, you can enable Auto DevOps instance wide
+in **Admin Area > Settings > Continuous Integration and Deployment**. Doing that,
+all the projects that haven't explicitly set an option will have Auto DevOps
+enabled by default.
+
## Stages of Auto DevOps
The following sections describe the stages of Auto DevOps. Read them carefully
@@ -517,7 +523,7 @@ Feature.get(:auto_devops_banner_disabled).enable
Or through the HTTP API with an admin access token:
```sh
-curl --data "value=true" --header "PRIVATE-TOKEN: private_token" https://gitlab.example.com/api/v4/features/auto_devops_banner_disabled
+curl --data "value=true" --header "PRIVATE-TOKEN: personal_access_token" https://gitlab.example.com/api/v4/features/auto_devops_banner_disabled
```
[ce-37115]: https://gitlab.com/gitlab-org/gitlab-ce/issues/37115
diff --git a/doc/university/README.md b/doc/university/README.md
index c96b9f38890..55865ac23e8 100644
--- a/doc/university/README.md
+++ b/doc/university/README.md
@@ -55,10 +55,10 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
#### 1.5. Migrating from other Source Control
-1. [Migrating from BitBucket/Stash](https://docs.gitlab.com/ee/workflow/importing/import_projects_from_bitbucket.html)
-1. [Migrating from GitHub](https://docs.gitlab.com/ee/workflow/importing/import_projects_from_github.html)
-1. [Migrating from SVN](https://docs.gitlab.com/ee/workflow/importing/migrating_from_svn.html)
-1. [Migrating from Fogbugz](https://docs.gitlab.com/ee/workflow/importing/import_projects_from_fogbugz.html)
+1. [Migrating from BitBucket/Stash](https://docs.gitlab.com/ee/user/project/import/bitbucket.html)
+1. [Migrating from GitHub](https://docs.gitlab.com/ee/user/project/import/github.html)
+1. [Migrating from SVN](https://docs.gitlab.com/ee/user/project/import/svn.html)
+1. [Migrating from Fogbugz](https://docs.gitlab.com/ee/user/project/import/fogbugz.html)
#### 1.6. GitLab Inc.
@@ -80,13 +80,13 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
- Being part of our Great Community and Contributing to GitLab
1. [Getting Started with the GitLab Development Kit (GDK)](https://about.gitlab.com/2016/06/08/getting-started-with-gitlab-development-kit/)
1. [Contributing Technical Articles to the GitLab Blog](https://about.gitlab.com/2016/01/26/call-for-writers/)
-1. [GitLab Training Workshops](https://about.gitlab.com/training)
+1. [GitLab Training Workshops](https://docs.gitlab.com/ce/university/training/end-user/)
+1. [GitLab Professional Services](https://about.gitlab.com/services/)
#### 1.8 GitLab Training Material
1. [Git and GitLab Terminology](glossary/README.md)
1. [Git and GitLab Workshop - Slides](https://docs.google.com/presentation/d/1JzTYD8ij9slejV2-TO-NzjCvlvj6mVn9BORePXNJoMI/edit?usp=drive_web)
-1. [Git and GitLab Revision](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/university/training/end-user)
---
diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md
index 02c0233d75a..c6a91c8d5c2 100644
--- a/doc/university/glossary/README.md
+++ b/doc/university/glossary/README.md
@@ -460,7 +460,7 @@ A route table contains rules (called routes) that determine where network traffi
### Runners
-Actual build machines/containers that [run and execute tests](https://gitlab.com/gitlab-org/gitlab-ci-multi-runner) you have specified to be run on GitLab CI.
+Actual build machines/containers that [run and execute tests](https://gitlab.com/gitlab-org/gitlab-runner) you have specified to be run on GitLab CI.
### Sidekiq
diff --git a/doc/university/training/topics/git_log.md b/doc/university/training/topics/git_log.md
index 21d81840ea7..f2709ae3890 100644
--- a/doc/university/training/topics/git_log.md
+++ b/doc/university/training/topics/git_log.md
@@ -53,8 +53,8 @@ git log --since=1.month.ago --until=3.weeks.ago
```
cd ~/workspace
-git clone git@gitlab.com:gitlab-org/gitlab-ci-multi-runner.git
-cd gitlab-ci-multi-runner
+git clone git@gitlab.com:gitlab-org/gitlab-runner.git
+cd gitlab-runner
git log --author="Travis"
git log --since=1.month.ago --until=3.weeks.ago
git log --since=1.month.ago --until=1.day.ago --author="Travis"
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index 5ebb88bf324..5fcc0501dc1 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -52,7 +52,7 @@ You can edit your account settings by navigating from the up-right corner menu b
From there, you can:
- Update your personal information
-- Manage [private tokens](../../api/README.md#private-tokens), email tokens, [2FA](account/two_factor_authentication.md)
+- Manage [2FA](account/two_factor_authentication.md)
- Change your username and [delete your account](account/delete_account.md)
- Manage applications that can
[use GitLab as an OAuth provider](../../integration/oauth_provider.md#introduction-to-oauth)
diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md
index f28c034e74c..9b4fdd65e2f 100644
--- a/doc/user/profile/personal_access_tokens.md
+++ b/doc/user/profile/personal_access_tokens.md
@@ -2,17 +2,15 @@
> [Introduced][ce-3749] in GitLab 8.8.
-Personal access tokens are useful if you need access to the [GitLab API][api].
-Instead of using your private token which grants full access to your account,
-personal access tokens could be a better fit because of their
-[granular permissions](#limiting-scopes-of-a-personal-access-token).
+Personal access tokens are the preferred way for third party applications and scripts to
+authenticate with the [GitLab API][api], if using [OAuth2](../../api/oauth2.md) is not practical.
You can also use them to authenticate against Git over HTTP. They are the only
accepted method of authentication when you have
[Two-Factor Authentication (2FA)][2fa] enabled.
Once you have your token, [pass it to the API][usage] using either the
-`private_token` parameter or the `PRIVATE-TOKEN` header.
+`private_token` parameter or the `Private-Token` header.
The expiration of personal access tokens happens on the date you define,
at midnight UTC.
@@ -49,12 +47,14 @@ the following table.
|`read_user` | Allows access to the read-only endpoints under `/users`. Essentially, any of the `GET` requests in the [Users API][users] are allowed ([introduced][ce-5951] in GitLab 8.15). |
| `api` | Grants complete access to the API (read/write) ([introduced][ce-5951] in GitLab 8.15). Required for accessing Git repositories over HTTP when 2FA is enabled. |
| `read_registry` | Allows to read [container registry] images if a project is private and authorization is required ([introduced][ce-11845] in GitLab 9.3). |
+| `sudo` | Allows performing API actions as any user in the system (if the authenticated user is an admin) ([introduced][ce-14838] in GitLab 10.2). |
[2fa]: ../account/two_factor_authentication.md
[api]: ../../api/README.md
[ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749
[ce-5951]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951
[ce-11845]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845
+[ce-14838]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14838
[container registry]: ../project/container_registry.md
[users]: ../../api/users.md
-[usage]: ../../api/README.md#basic-usage
+[usage]: ../../api/README.md#personal-access-tokens
diff --git a/doc/user/project/integrations/img/webhook_logs.png b/doc/user/project/integrations/img/webhook_logs.png
index 917068d9398..803678db6b6 100644
--- a/doc/user/project/integrations/img/webhook_logs.png
+++ b/doc/user/project/integrations/img/webhook_logs.png
Binary files differ
diff --git a/doc/user/project/pipelines/job_artifacts.md b/doc/user/project/pipelines/job_artifacts.md
index 9ef6f9185c9..f9a268fb789 100644
--- a/doc/user/project/pipelines/job_artifacts.md
+++ b/doc/user/project/pipelines/job_artifacts.md
@@ -52,7 +52,8 @@ directly in the job artifacts browser without the need to download them.
>**Note:**
With [GitLab 10.1][ce-14399], HTML files in a public project can be previewed
-directly in a new tab without the need to download them.
+directly in a new tab without the need to download them when
+[GitLab Pages](../../../administration/pages/index.md) is enabled
After a job finishes, if you visit the job's specific page, there are three
buttons. You can download the artifacts archive or browse its contents, whereas
@@ -69,7 +70,8 @@ browse inside them.
Below you can see how browsing looks like. In this case we have browsed inside
the archive and at this point there is one directory, a couple files, and
-one HTML file that you can view directly online (opens in a new tab).
+one HTML file that you can view directly online when
+[GitLab Pages](../../../administration/pages/index.md) is enabled (opens in a new tab).
![Job artifacts browser](img/job_artifacts_browser.png)
diff --git a/features/steps/profile/notifications.rb b/features/steps/profile/notifications.rb
index 7e339443b75..f8eb0f01de8 100644
--- a/features/steps/profile/notifications.rb
+++ b/features/steps/profile/notifications.rb
@@ -11,7 +11,7 @@ class Spinach::Features::ProfileNotifications < Spinach::FeatureSteps
end
step 'I select Mention setting from dropdown' do
- first(:link, "On mention").trigger('click')
+ first(:link, "On mention").click
end
step 'I should see Notification saved message' do
diff --git a/features/steps/project/commits/branches.rb b/features/steps/project/commits/branches.rb
index ccaf3237815..c3ae33d2aa9 100644
--- a/features/steps/project/commits/branches.rb
+++ b/features/steps/project/commits/branches.rb
@@ -40,6 +40,7 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
step 'I submit new branch form with invalid name' do
fill_in 'branch_name', with: '1.0 stable'
+ page.find("body").click # defocus the branch_name input
select_branch('master')
click_button 'Create branch'
end
@@ -70,17 +71,16 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
step "I click branch 'improve/awesome' delete link" do
page.within '.js-branch-improve\/awesome' do
- find('.btn-remove').click
- sleep 0.05
+ accept_alert { find('.btn-remove').click }
end
end
step "I should not see branch 'improve/awesome'" do
- expect(page.all(visible: true)).not_to have_content 'improve/awesome'
+ expect(page).to have_css('.js-branch-improve\\/awesome', visible: :hidden)
end
def select_branch(branch_name)
- click_button 'master'
+ find('.git-revision-dropdown-toggle').click
page.within '#new-branch-form .dropdown-menu' do
click_link branch_name
diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb
index dac18c537ac..196e0fff63a 100644
--- a/features/steps/project/issues/labels.rb
+++ b/features/steps/project/issues/labels.rb
@@ -16,7 +16,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
step 'I delete all labels' do
page.within '.labels' do
page.all('.remove-row').each do
- first('.remove-row').click
+ accept_confirm { first('.remove-row').click }
end
end
end
diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb
index 16a2d4a6f93..33a24e8913a 100644
--- a/features/steps/project/issues/milestones.rb
+++ b/features/steps/project/issues/milestones.rb
@@ -3,6 +3,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
include SharedProject
include SharedPaths
include SharedMarkdown
+ include CapybaraHelpers
step 'I should see milestone "v2.2"' do
milestone = @project.milestones.find_by(title: "v2.2")
@@ -65,7 +66,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
end
step 'I click link to remove milestone' do
- click_link 'Delete'
+ confirm_modal_if_present { click_link 'Delete' }
end
step 'I should see no milestones' do
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index c872bd6f861..aa32528a7ca 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -215,7 +215,7 @@ module SharedDiffNote
end
step 'I click side-by-side diff button' do
- find('#parallel-diff-btn').trigger('click')
+ find('#parallel-diff-btn').click
end
step 'I see side-by-side diff button' do
@@ -227,12 +227,11 @@ module SharedDiffNote
end
def click_diff_line(code)
- find(".line_holder[id='#{code}'] td:nth-of-type(1)").trigger 'mouseover'
- find(".line_holder[id='#{code}'] button").trigger 'click'
+ find(".line_holder[id='#{code}'] button").click
end
def click_parallel_diff_line(code, line_type)
- find(".line_holder.parallel td[id='#{code}']").find(:xpath, 'preceding-sibling::*[1][self::td]').trigger 'mouseover'
- find(".line_holder.parallel button[data-line-code='#{code}']").trigger 'click'
+ find(".line_holder.parallel td[id='#{code}']").find(:xpath, 'preceding-sibling::*[1][self::td]').hover
+ find(".line_holder.parallel button[data-line-code='#{code}']").click
end
end
diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb
index 0cd7b506a95..95f0cd2156e 100644
--- a/features/steps/shared/note.rb
+++ b/features/steps/shared/note.rb
@@ -14,7 +14,7 @@ module SharedNote
find('.more-actions').click
find('.more-actions .dropdown-menu li', match: :first)
- find(".js-note-delete").click
+ accept_confirm { find(".js-note-delete").click }
end
end
diff --git a/features/support/capybara.rb b/features/support/capybara.rb
index f4691647d4b..3c4db8b9601 100644
--- a/features/support/capybara.rb
+++ b/features/support/capybara.rb
@@ -1,22 +1,21 @@
-require 'capybara/poltergeist'
require 'capybara-screenshot/spinach'
# Give CI some extra time
timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 60 : 30
-Capybara.javascript_driver = :poltergeist
-Capybara.register_driver :poltergeist do |app|
- Capybara::Poltergeist::Driver.new(
- app,
- js_errors: true,
- timeout: timeout,
- window_size: [1366, 768],
- url_whitelist: %w[localhost 127.0.0.1],
- url_blacklist: %w[.mp4 .png .gif .avi .bmp .jpg .jpeg],
- phantomjs_options: [
- '--load-images=yes'
- ]
+Capybara.javascript_driver = :chrome
+Capybara.register_driver :chrome do |app|
+ extra_args = []
+ extra_args << 'headless' unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
+
+ capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
+ chromeOptions: {
+ 'args' => %w[no-sandbox disable-gpu --window-size=1240,1400] + extra_args
+ }
)
+
+ Capybara::Selenium::Driver
+ .new(app, browser: :chrome, desired_capabilities: capabilities)
end
Capybara.default_max_wait_time = timeout
@@ -24,6 +23,10 @@ Capybara.ignore_hidden_elements = false
# Keep only the screenshots generated from the last failing test suite
Capybara::Screenshot.prune_strategy = :keep_last_run
+# From https://github.com/mattheworiordan/capybara-screenshot/issues/84#issuecomment-41219326
+Capybara::Screenshot.register_driver(:chrome) do |driver, path|
+ driver.browser.save_screenshot(path)
+end
Spinach.hooks.before_run do
TestEnv.eager_load_driver_server
diff --git a/features/support/capybara_helpers.rb b/features/support/capybara_helpers.rb
new file mode 100644
index 00000000000..647f8d087c3
--- /dev/null
+++ b/features/support/capybara_helpers.rb
@@ -0,0 +1,10 @@
+module CapybaraHelpers
+ def confirm_modal_if_present
+ if Capybara.current_driver == Capybara.javascript_driver
+ accept_confirm { yield }
+ return
+ end
+
+ yield
+ end
+end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 7db18e25a5f..c37e596eb9d 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -142,7 +142,6 @@ module API
mount ::API::Runner
mount ::API::Runners
mount ::API::Services
- mount ::API::Session
mount ::API::Settings
mount ::API::SidekiqMetrics
mount ::API::Snippets
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index 87b9db66efd..b9c7d443f6c 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -42,72 +42,42 @@ module API
# Helper Methods for Grape Endpoint
module HelperMethods
- def find_current_user
- user =
- find_user_from_private_token ||
- find_user_from_oauth_token ||
- find_user_from_warden
+ def find_current_user!
+ user = find_user_from_access_token || find_user_from_warden
+ return unless user
- return nil unless user
-
- raise UnauthorizedError unless Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api)
+ forbidden!('User is blocked') unless Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api)
user
end
- def private_token
- params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]
- end
-
- private
-
- def find_user_from_private_token
- token_string = private_token.to_s
- return nil unless token_string.present?
+ def access_token
+ return @access_token if defined?(@access_token)
- user =
- find_user_by_authentication_token(token_string) ||
- find_user_by_personal_access_token(token_string)
-
- raise UnauthorizedError unless user
-
- user
+ @access_token = find_oauth_access_token || find_personal_access_token
end
- # Invokes the doorkeeper guard.
- #
- # If token is presented and valid, then it sets @current_user.
- #
- # If the token does not have sufficient scopes to cover the requred scopes,
- # then it raises InsufficientScopeError.
- #
- # If the token is expired, then it raises ExpiredError.
- #
- # If the token is revoked, then it raises RevokedError.
- #
- # If the token is not found (nil), then it returns nil
- #
- # Arguments:
- #
- # scopes: (optional) scopes required for this guard.
- # Defaults to empty array.
- #
- def find_user_from_oauth_token
- access_token = find_oauth_access_token
+ def validate_access_token!(scopes: [])
return unless access_token
- find_user_by_access_token(access_token)
+ case AccessTokenValidationService.new(access_token, request: request).validate(scopes: scopes)
+ when AccessTokenValidationService::INSUFFICIENT_SCOPE
+ raise InsufficientScopeError.new(scopes)
+ when AccessTokenValidationService::EXPIRED
+ raise ExpiredError
+ when AccessTokenValidationService::REVOKED
+ raise RevokedError
+ end
end
- def find_user_by_authentication_token(token_string)
- User.find_by_authentication_token(token_string)
- end
+ private
- def find_user_by_personal_access_token(token_string)
- access_token = PersonalAccessToken.find_by_token(token_string)
+ def find_user_from_access_token
return unless access_token
- find_user_by_access_token(access_token)
+ validate_access_token!
+
+ access_token.user || raise(UnauthorizedError)
end
# Check the Rails session for valid authentication details
@@ -125,34 +95,26 @@ module API
end
def find_oauth_access_token
- return @oauth_access_token if defined?(@oauth_access_token)
-
token = Doorkeeper::OAuth::Token.from_request(doorkeeper_request, *Doorkeeper.configuration.access_token_methods)
- return @oauth_access_token = nil unless token
+ return unless token
- @oauth_access_token = OauthAccessToken.by_token(token)
- raise UnauthorizedError unless @oauth_access_token
+ # Expiration, revocation and scopes are verified in `find_user_by_access_token`
+ access_token = OauthAccessToken.by_token(token)
+ raise UnauthorizedError unless access_token
- @oauth_access_token.revoke_previous_refresh_token!
- @oauth_access_token
+ access_token.revoke_previous_refresh_token!
+ access_token
end
- def find_user_by_access_token(access_token)
- scopes = scopes_registered_for_endpoint
+ def find_personal_access_token
+ token = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s
+ return unless token.present?
- case AccessTokenValidationService.new(access_token, request: request).validate(scopes: scopes)
- when AccessTokenValidationService::INSUFFICIENT_SCOPE
- raise InsufficientScopeError.new(scopes)
-
- when AccessTokenValidationService::EXPIRED
- raise ExpiredError
+ # Expiration, revocation and scopes are verified in `find_user_by_access_token`
+ access_token = PersonalAccessToken.find_by(token: token)
+ raise UnauthorizedError unless access_token
- when AccessTokenValidationService::REVOKED
- raise RevokedError
-
- when AccessTokenValidationService::VALID
- access_token.user
- end
+ access_token
end
def doorkeeper_request
@@ -236,7 +198,7 @@ module API
class InsufficientScopeError < StandardError
attr_reader :scopes
def initialize(scopes)
- @scopes = scopes
+ @scopes = scopes.map { |s| s.try(:name) || s }
end
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index efe874b2e6b..67cecb6a7ad 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -57,10 +57,6 @@ module API
expose :admin?, as: :is_admin
end
- class UserWithPrivateDetails < UserWithAdmin
- expose :private_token
- end
-
class Email < Grape::Entity
expose :id, :email
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 7a2ec865860..1c12166e434 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -41,6 +41,8 @@ module API
sudo!
+ validate_access_token!(scopes: scopes_registered_for_endpoint) unless sudo?
+
@current_user
end
@@ -385,7 +387,7 @@ module API
return @initial_current_user if defined?(@initial_current_user)
begin
- @initial_current_user = Gitlab::Auth::UniqueIpsLimiter.limit_user! { find_current_user }
+ @initial_current_user = Gitlab::Auth::UniqueIpsLimiter.limit_user! { find_current_user! }
rescue APIGuard::UnauthorizedError
unauthorized!
end
@@ -393,24 +395,23 @@ module API
def sudo!
return unless sudo_identifier
- return unless initial_current_user
+
+ unauthorized! unless initial_current_user
unless initial_current_user.admin?
forbidden!('Must be admin to use sudo')
end
- # Only private tokens should be used for the SUDO feature
- unless private_token == initial_current_user.private_token
- forbidden!('Private token must be specified in order to use sudo')
+ unless access_token
+ forbidden!('Must be authenticated using an OAuth or Personal Access Token to use sudo')
end
+ validate_access_token!(scopes: [:sudo])
+
sudoed_user = find_user(sudo_identifier)
+ not_found!("User with ID or username '#{sudo_identifier}'") unless sudoed_user
- if sudoed_user
- @current_user = sudoed_user
- else
- not_found!("No user id or username for: #{sudo_identifier}")
- end
+ @current_user = sudoed_user
end
def sudo_identifier
diff --git a/lib/api/session.rb b/lib/api/session.rb
deleted file mode 100644
index 016415c3023..00000000000
--- a/lib/api/session.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-module API
- class Session < Grape::API
- desc 'Login to get token' do
- success Entities::UserWithPrivateDetails
- end
- params do
- optional :login, type: String, desc: 'The username'
- optional :email, type: String, desc: 'The email of the user'
- requires :password, type: String, desc: 'The password of the user'
- at_least_one_of :login, :email
- end
- post "/session" do
- user = Gitlab::Auth.find_with_user_password(params[:email] || params[:login], params[:password])
-
- return unauthorized! unless user
- return render_api_error!('401 Unauthorized. You have 2FA enabled. Please use a personal access token to access the API', 401) if user.two_factor_enabled?
- present user, with: Entities::UserWithPrivateDetails
- end
- end
-end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index b6f97a1eac2..d80b364bd09 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -507,9 +507,7 @@ module API
end
get do
entity =
- if sudo?
- Entities::UserWithPrivateDetails
- elsif current_user.admin?
+ if current_user.admin?
Entities::UserWithAdmin
else
Entities::UserPublic
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index ef4578aabd6..a0f7e4e5ad5 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -95,7 +95,7 @@ module Banzai
end
def call
- return doc if project.nil?
+ return doc unless project || group
ref_pattern = object_class.reference_pattern
link_pattern = object_class.link_reference_pattern
@@ -288,10 +288,14 @@ module Banzai
end
def current_project_path
+ return unless project
+
@current_project_path ||= project.full_path
end
def current_project_namespace_path
+ return unless project
+
@current_project_namespace_path ||= project.namespace.full_path
end
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index a6f8650ed3d..c6ae28adf87 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -55,6 +55,10 @@ module Banzai
context[:project]
end
+ def group
+ context[:group]
+ end
+
def skip_project_check?
context[:skip_project_check]
end
diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb
index f3356d6c51e..afb6e25963c 100644
--- a/lib/banzai/filter/user_reference_filter.rb
+++ b/lib/banzai/filter/user_reference_filter.rb
@@ -24,7 +24,7 @@ module Banzai
end
def call
- return doc if project.nil? && !skip_project_check?
+ return doc if project.nil? && group.nil? && !skip_project_check?
ref_pattern = User.reference_pattern
ref_pattern_start = /\A#{ref_pattern}\z/
@@ -101,19 +101,12 @@ module Banzai
end
def link_to_all(link_content: nil)
- project = context[:project]
author = context[:author]
- if author && !project.team.member?(author)
+ if author && !team_member?(author)
link_content
else
- url = urls.project_url(project,
- only_path: context[:only_path])
-
- data = data_attribute(project: project.id, author: author.try(:id))
- content = link_content || User.reference_prefix + 'all'
-
- link_tag(url, data, content, 'All Project and Group Members')
+ parent_url(link_content, author)
end
end
@@ -144,6 +137,35 @@ module Banzai
def link_tag(url, data, link_content, title)
%(<a href="#{url}" #{data} class="#{link_class}" title="#{escape_once(title)}">#{link_content}</a>)
end
+
+ def parent
+ context[:project] || context[:group]
+ end
+
+ def parent_group?
+ parent.is_a?(Group)
+ end
+
+ def team_member?(user)
+ if parent_group?
+ parent.member?(user)
+ else
+ parent.team.member?(user)
+ end
+ end
+
+ def parent_url(link_content, author)
+ if parent_group?
+ url = urls.group_url(parent, only_path: context[:only_path])
+ data = data_attribute(group: group.id, author: author.try(:id))
+ else
+ url = urls.project_url(parent, only_path: context[:only_path])
+ data = data_attribute(project: project.id, author: author.try(:id))
+ end
+
+ content = link_content || User.reference_prefix + 'all'
+ link_tag(url, data, content, 'All Project and Group Members')
+ end
end
end
end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 87aeb76b66a..0ad9285c0ea 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -1,11 +1,11 @@
module Gitlab
module Auth
- MissingPersonalTokenError = Class.new(StandardError)
+ MissingPersonalAccessTokenError = Class.new(StandardError)
REGISTRY_SCOPES = [:read_registry].freeze
# Scopes used for GitLab API access
- API_SCOPES = [:api, :read_user].freeze
+ API_SCOPES = [:api, :read_user, :sudo].freeze
# Scopes used for OpenID Connect
OPENID_SCOPES = [:openid].freeze
@@ -38,7 +38,7 @@ module Gitlab
# If sign-in is disabled and LDAP is not configured, recommend a
# personal access token on failed auth attempts
- raise Gitlab::Auth::MissingPersonalTokenError
+ raise Gitlab::Auth::MissingPersonalAccessTokenError
end
def find_with_user_password(login, password)
@@ -106,7 +106,7 @@ module Gitlab
user = find_with_user_password(login, password)
return unless user
- raise Gitlab::Auth::MissingPersonalTokenError if user.two_factor_enabled?
+ raise Gitlab::Auth::MissingPersonalAccessTokenError if user.two_factor_enabled?
Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)
end
@@ -128,7 +128,7 @@ module Gitlab
token = PersonalAccessTokensFinder.new(state: 'active').find_by(token: password)
if token && valid_scoped_token?(token, available_scopes)
- Gitlab::Auth::Result.new(token.user, nil, :personal_token, abilities_for_scope(token.scopes))
+ Gitlab::Auth::Result.new(token.user, nil, :personal_access_token, abilities_for_scope(token.scopes))
end
end
@@ -226,8 +226,10 @@ module Gitlab
[]
end
- def available_scopes
- API_SCOPES + registry_scopes
+ def available_scopes(current_user = nil)
+ scopes = API_SCOPES + registry_scopes
+ scopes.delete(:sudo) if current_user && !current_user.admin?
+ scopes
end
# Other available scopes
diff --git a/lib/gitlab/ci/status/build/cancelable.rb b/lib/gitlab/ci/status/build/cancelable.rb
index 8ad3e57e59d..2d9166d6bdd 100644
--- a/lib/gitlab/ci/status/build/cancelable.rb
+++ b/lib/gitlab/ci/status/build/cancelable.rb
@@ -8,7 +8,7 @@ module Gitlab
end
def action_icon
- 'icon_action_cancel'
+ 'cancel'
end
def action_path
diff --git a/lib/gitlab/ci/status/build/failed_allowed.rb b/lib/gitlab/ci/status/build/failed_allowed.rb
index e42d3574357..d71e63e73eb 100644
--- a/lib/gitlab/ci/status/build/failed_allowed.rb
+++ b/lib/gitlab/ci/status/build/failed_allowed.rb
@@ -8,7 +8,7 @@ module Gitlab
end
def icon
- 'icon_status_warning'
+ 'warning'
end
def group
diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb
index c7726543599..b7b45466d3b 100644
--- a/lib/gitlab/ci/status/build/play.rb
+++ b/lib/gitlab/ci/status/build/play.rb
@@ -12,7 +12,7 @@ module Gitlab
end
def action_icon
- 'icon_action_play'
+ 'play'
end
def action_title
diff --git a/lib/gitlab/ci/status/build/retryable.rb b/lib/gitlab/ci/status/build/retryable.rb
index 8c8fdc56d75..44ffe783e50 100644
--- a/lib/gitlab/ci/status/build/retryable.rb
+++ b/lib/gitlab/ci/status/build/retryable.rb
@@ -8,7 +8,7 @@ module Gitlab
end
def action_icon
- 'icon_action_retry'
+ 'retry'
end
def action_title
diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb
index d464738deaf..46e730797e4 100644
--- a/lib/gitlab/ci/status/build/stop.rb
+++ b/lib/gitlab/ci/status/build/stop.rb
@@ -12,7 +12,7 @@ module Gitlab
end
def action_icon
- 'icon_action_stop'
+ 'stop'
end
def action_title
diff --git a/lib/gitlab/ci/status/canceled.rb b/lib/gitlab/ci/status/canceled.rb
index e5fdc1f8136..e6195a60d4f 100644
--- a/lib/gitlab/ci/status/canceled.rb
+++ b/lib/gitlab/ci/status/canceled.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def icon
- 'icon_status_canceled'
+ 'status_canceled'
end
def favicon
diff --git a/lib/gitlab/ci/status/created.rb b/lib/gitlab/ci/status/created.rb
index d188bd286a6..846f00b83dd 100644
--- a/lib/gitlab/ci/status/created.rb
+++ b/lib/gitlab/ci/status/created.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def icon
- 'icon_status_created'
+ 'status_created'
end
def favicon
diff --git a/lib/gitlab/ci/status/failed.rb b/lib/gitlab/ci/status/failed.rb
index 38e45714c22..27ce85bd3ed 100644
--- a/lib/gitlab/ci/status/failed.rb
+++ b/lib/gitlab/ci/status/failed.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def icon
- 'icon_status_failed'
+ 'status_failed'
end
def favicon
diff --git a/lib/gitlab/ci/status/manual.rb b/lib/gitlab/ci/status/manual.rb
index a4a7edadac9..fc387e2fd25 100644
--- a/lib/gitlab/ci/status/manual.rb
+++ b/lib/gitlab/ci/status/manual.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def icon
- 'icon_status_manual'
+ 'status_manual'
end
def favicon
diff --git a/lib/gitlab/ci/status/pending.rb b/lib/gitlab/ci/status/pending.rb
index 5164260b861..6780780db32 100644
--- a/lib/gitlab/ci/status/pending.rb
+++ b/lib/gitlab/ci/status/pending.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def icon
- 'icon_status_pending'
+ 'status_pending'
end
def favicon
diff --git a/lib/gitlab/ci/status/running.rb b/lib/gitlab/ci/status/running.rb
index 993937e98ca..ee13905e46d 100644
--- a/lib/gitlab/ci/status/running.rb
+++ b/lib/gitlab/ci/status/running.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def icon
- 'icon_status_running'
+ 'status_running'
end
def favicon
diff --git a/lib/gitlab/ci/status/skipped.rb b/lib/gitlab/ci/status/skipped.rb
index 0c942920b02..0dbdc4de426 100644
--- a/lib/gitlab/ci/status/skipped.rb
+++ b/lib/gitlab/ci/status/skipped.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def icon
- 'icon_status_skipped'
+ 'status_skipped'
end
def favicon
diff --git a/lib/gitlab/ci/status/success.rb b/lib/gitlab/ci/status/success.rb
index d7af98857b0..731013ec017 100644
--- a/lib/gitlab/ci/status/success.rb
+++ b/lib/gitlab/ci/status/success.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def icon
- 'icon_status_success'
+ 'status_success'
end
def favicon
diff --git a/lib/gitlab/ci/status/success_warning.rb b/lib/gitlab/ci/status/success_warning.rb
index 4d7d82e04cf..32b4cf43e48 100644
--- a/lib/gitlab/ci/status/success_warning.rb
+++ b/lib/gitlab/ci/status/success_warning.rb
@@ -15,7 +15,7 @@ module Gitlab
end
def icon
- 'icon_status_warning'
+ 'status_warning'
end
def group
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index 23ae37ff71e..d5518814483 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -73,7 +73,7 @@ module Gitlab
decorate(repo, commit) if commit
rescue Rugged::ReferenceError, Rugged::InvalidError, Rugged::ObjectError,
Gitlab::Git::CommandError, Gitlab::Git::Repository::NoRepository,
- Rugged::OdbError, Rugged::TreeError
+ Rugged::OdbError, Rugged::TreeError, ArgumentError
nil
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index a9e4e1130c3..4f9eac92d9a 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -758,13 +758,13 @@ module Gitlab
end
def ff_merge(user, source_sha, target_branch)
- OperationService.new(user, self).with_branch(target_branch) do |our_commit|
- raise ArgumentError, 'Invalid merge target' unless our_commit
-
- source_sha
+ gitaly_migrate(:operation_user_ff_branch) do |is_enabled|
+ if is_enabled
+ gitaly_ff_merge(user, source_sha, target_branch)
+ else
+ rugged_ff_merge(user, source_sha, target_branch)
+ end
end
- rescue Rugged::ReferenceError
- raise ArgumentError, 'Invalid merge source'
end
def revert(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:)
@@ -1177,10 +1177,10 @@ module Gitlab
Gitlab::GitalyClient.migrate(method, status: status, &block)
rescue GRPC::NotFound => e
raise NoRepository.new(e)
- rescue GRPC::BadStatus => e
- raise CommandError.new(e)
rescue GRPC::InvalidArgument => e
raise ArgumentError.new(e)
+ rescue GRPC::BadStatus => e
+ raise CommandError.new(e)
end
private
@@ -1622,6 +1622,22 @@ module Gitlab
run_git(args, env: env)
end
+
+ def gitaly_ff_merge(user, source_sha, target_branch)
+ gitaly_operations_client.user_ff_branch(user, source_sha, target_branch)
+ rescue GRPC::FailedPrecondition => e
+ raise CommitError, e
+ end
+
+ def rugged_ff_merge(user, source_sha, target_branch)
+ OperationService.new(user, self).with_branch(target_branch) do |our_commit|
+ raise ArgumentError, 'Invalid merge target' unless our_commit
+
+ source_sha
+ end
+ rescue Rugged::ReferenceError
+ raise ArgumentError, 'Invalid merge source'
+ end
end
end
end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 6868be26758..0b35a787e07 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -34,10 +34,11 @@ module Gitlab
private_constant :MUTEX
class << self
- attr_accessor :query_time
+ attr_accessor :query_time, :migrate_histogram
end
self.query_time = 0
+ self.migrate_histogram = Gitlab::Metrics.histogram(:gitaly_migrate_call_duration, "Gitaly migration call execution timings")
def self.stub(name, storage)
MUTEX.synchronize do
@@ -171,8 +172,11 @@ module Gitlab
feature_stack = Thread.current[:gitaly_feature_stack] ||= []
feature_stack.unshift(feature)
begin
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
yield is_enabled
ensure
+ total_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
+ migrate_histogram.observe({ gitaly_enabled: is_enabled, feature: feature }, total_time)
feature_stack.shift
Thread.current[:gitaly_feature_stack] = nil if feature_stack.empty?
end
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index a2b50f2507e..da5505cb2fe 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -18,7 +18,7 @@ module Gitlab
response = GitalyClient.call(@repository.storage, :commit_service, :list_files, request)
response.flat_map do |msg|
- msg.paths.map { |d| d.dup.force_encoding(Encoding::UTF_8) }
+ msg.paths.map { |d| EncodingHelper.encode!(d.dup) }
end
end
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index adaf255f24b..526d44a8b77 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -105,6 +105,23 @@ module Gitlab
ensure
request_enum.close
end
+
+ def user_ff_branch(user, source_sha, target_branch)
+ request = Gitaly::UserFFBranchRequest.new(
+ repository: @gitaly_repo,
+ user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
+ commit_id: source_sha,
+ branch: GitalyClient.encode(target_branch)
+ )
+
+ branch_update = GitalyClient.call(
+ @repository.storage,
+ :operation_service,
+ :user_ff_branch,
+ request
+ ).branch_update
+ Gitlab::Git::OperationService::BranchUpdate.from_gitaly(branch_update)
+ end
end
end
end
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index e68761066d8..561779182bc 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -114,6 +114,7 @@ excluded_attributes:
- :milestone_id
- :ref_fetched
- :merge_jid
+ - :latest_merge_request_diff_id
award_emoji:
- :awardable_id
statuses:
diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb
index aba3e0df382..c2cbd3c16a1 100644
--- a/lib/gitlab/metrics/system.rb
+++ b/lib/gitlab/metrics/system.rb
@@ -46,14 +46,14 @@ module Gitlab
# Returns the current real time in a given precision.
#
- # Returns the time as a Float.
+ # Returns the time as a Fixnum.
def self.real_time(precision = :millisecond)
Process.clock_gettime(Process::CLOCK_REALTIME, precision)
end
# Returns the current monotonic clock time in a given precision.
#
- # Returns the time as a Float.
+ # Returns the time as a Fixnum.
def self.monotonic_time(precision = :millisecond)
Process.clock_gettime(Process::CLOCK_MONOTONIC, precision)
end
diff --git a/lib/gitlab/middleware/read_only.rb b/lib/gitlab/middleware/read_only.rb
index 0de0cddcce4..8853dfa3d2d 100644
--- a/lib/gitlab/middleware/read_only.rb
+++ b/lib/gitlab/middleware/read_only.rb
@@ -12,6 +12,7 @@ module Gitlab
def call(env)
@env = env
+ @route_hash = nil
if disallowed_request? && Gitlab::Database.read_only?
Rails.logger.debug('GitLab ReadOnly: preventing possible non read-only operation')
@@ -77,11 +78,11 @@ module Gitlab
end
def grack_route
- request.path.end_with?('.git/git-upload-pack')
+ route_hash[:controller] == 'projects/git_http' && route_hash[:action] == 'git_upload_pack'
end
def lfs_route
- request.path.end_with?('/info/lfs/objects/batch')
+ route_hash[:controller] == 'projects/lfs_api' && route_hash[:action] == 'batch'
end
end
end
diff --git a/lib/gitlab/performance_bar/peek_query_tracker.rb b/lib/gitlab/performance_bar/peek_query_tracker.rb
index 69e117f1da9..f2825db59ae 100644
--- a/lib/gitlab/performance_bar/peek_query_tracker.rb
+++ b/lib/gitlab/performance_bar/peek_query_tracker.rb
@@ -36,7 +36,7 @@ module Gitlab
end
def track_query(raw_query, bindings, start, finish)
- duration = finish - start
+ duration = (finish - start) * 1000.0
query_info = { duration: duration.round(3), sql: raw_query }
PEEK_DB_CLIENT.query_details << query_info
diff --git a/lib/gitlab/sherlock/transaction.rb b/lib/gitlab/sherlock/transaction.rb
index 3489fb251b6..400a552bf99 100644
--- a/lib/gitlab/sherlock/transaction.rb
+++ b/lib/gitlab/sherlock/transaction.rb
@@ -89,7 +89,9 @@ module Gitlab
ActiveSupport::Notifications.subscribe('sql.active_record') do |_, start, finish, _, data|
next unless same_thread?
- track_query(data[:sql].strip, data[:binds], start, finish)
+ unless data.fetch(:cached, data[:name] == 'CACHE')
+ track_query(data[:sql].strip, data[:binds], start, finish)
+ end
end
end
diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb
index d7d24eeb37b..2bfb7caefd9 100644
--- a/lib/gitlab/sidekiq_middleware/memory_killer.rb
+++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb
@@ -7,7 +7,6 @@ module Gitlab
GRACE_TIME = (ENV['SIDEKIQ_MEMORY_KILLER_GRACE_TIME'] || 15 * 60).to_s.to_i
# Wait 30 seconds for running jobs to finish during graceful shutdown
SHUTDOWN_WAIT = (ENV['SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT'] || 30).to_s.to_i
- SHUTDOWN_SIGNAL = (ENV['SIDEKIQ_MEMORY_KILLER_SHUTDOWN_SIGNAL'] || 'SIGKILL').to_s
# Create a mutex used to ensure there will be only one thread waiting to
# shut Sidekiq down
@@ -15,6 +14,7 @@ module Gitlab
def call(worker, job, queue)
yield
+
current_rss = get_rss
return unless MAX_RSS > 0 && current_rss > MAX_RSS
@@ -23,32 +23,45 @@ module Gitlab
# Return if another thread is already waiting to shut Sidekiq down
return unless MUTEX.try_lock
- Sidekiq.logger.warn "current RSS #{current_rss} exceeds maximum RSS "\
- "#{MAX_RSS}"
- Sidekiq.logger.warn "this thread will shut down PID #{Process.pid} - Worker #{worker.class} - JID-#{job['jid']} "\
- "in #{GRACE_TIME} seconds"
- sleep(GRACE_TIME)
+ Sidekiq.logger.warn "Sidekiq worker PID-#{pid} current RSS #{current_rss}"\
+ " exceeds maximum RSS #{MAX_RSS} after finishing job #{worker.class} JID-#{job['jid']}"
+ Sidekiq.logger.warn "Sidekiq worker PID-#{pid} will stop fetching new jobs in #{GRACE_TIME} seconds, and will be shut down #{SHUTDOWN_WAIT} seconds later"
- Sidekiq.logger.warn "sending SIGTERM to PID #{Process.pid} - Worker #{worker.class} - JID-#{job['jid']}"
- Process.kill('SIGTERM', Process.pid)
+ # Wait `GRACE_TIME` to give the memory intensive job time to finish.
+ # Then, tell Sidekiq to stop fetching new jobs.
+ wait_and_signal(GRACE_TIME, 'SIGSTP', 'stop fetching new jobs')
- Sidekiq.logger.warn "waiting #{SHUTDOWN_WAIT} seconds before sending "\
- "#{SHUTDOWN_SIGNAL} to PID #{Process.pid} - Worker #{worker.class} - JID-#{job['jid']}"
- sleep(SHUTDOWN_WAIT)
+ # Wait `SHUTDOWN_WAIT` to give already fetched jobs time to finish.
+ # Then, tell Sidekiq to gracefully shut down by giving jobs a few more
+ # moments to finish, killing and requeuing them if they didn't, and
+ # then terminating itself.
+ wait_and_signal(SHUTDOWN_WAIT, 'SIGTERM', 'gracefully shut down')
- Sidekiq.logger.warn "sending #{SHUTDOWN_SIGNAL} to PID #{Process.pid} - Worker #{worker.class} - JID-#{job['jid']}"
- Process.kill(SHUTDOWN_SIGNAL, Process.pid)
+ # Wait for Sidekiq to shutdown gracefully, and kill it if it didn't.
+ wait_and_signal(Sidekiq.options[:timeout] + 2, 'SIGKILL', 'die')
end
end
private
def get_rss
- output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{Process.pid}))
+ output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{pid}))
return 0 unless status.zero?
output.to_i
end
+
+ def wait_and_signal(time, signal, explanation)
+ Sidekiq.logger.warn "waiting #{time} seconds before sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})"
+ sleep(time)
+
+ Sidekiq.logger.warn "sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})"
+ Process.kill(signal, pid)
+ end
+
+ def pid
+ Process.pid
+ end
end
end
end
diff --git a/lib/gitlab/testing/request_blocker_middleware.rb b/lib/gitlab/testing/request_blocker_middleware.rb
index aa67fa08577..4a8e3c2eee0 100644
--- a/lib/gitlab/testing/request_blocker_middleware.rb
+++ b/lib/gitlab/testing/request_blocker_middleware.rb
@@ -7,6 +7,7 @@ module Gitlab
class RequestBlockerMiddleware
@@num_active_requests = Concurrent::AtomicFixnum.new(0)
@@block_requests = Concurrent::AtomicBoolean.new(false)
+ @@slow_requests = Concurrent::AtomicBoolean.new(false)
# Returns the number of requests the server is currently processing.
def self.num_active_requests
@@ -19,9 +20,15 @@ module Gitlab
@@block_requests.value = true
end
+ # Slows down incoming requests (useful for race conditions).
+ def self.slow_requests!
+ @@slow_requests.value = true
+ end
+
# Allows the server to accept requests again.
def self.allow_requests!
@@block_requests.value = false
+ @@slow_requests.value = false
end
def initialize(app)
@@ -33,6 +40,7 @@ module Gitlab
if block_requests?
block_request(env)
else
+ sleep 0.2 if slow_requests?
@app.call(env)
end
ensure
@@ -45,6 +53,10 @@ module Gitlab
@@block_requests.true?
end
+ def slow_requests?
+ @@slow_requests.true?
+ end
+
def block_request(env)
[503, {}, []]
end
diff --git a/lib/gitlab/testing/request_inspector_middleware.rb b/lib/gitlab/testing/request_inspector_middleware.rb
new file mode 100644
index 00000000000..e387667480d
--- /dev/null
+++ b/lib/gitlab/testing/request_inspector_middleware.rb
@@ -0,0 +1,71 @@
+# rubocop:disable Style/ClassVars
+
+module Gitlab
+ module Testing
+ class RequestInspectorMiddleware
+ @@log_requests = Concurrent::AtomicBoolean.new(false)
+ @@logged_requests = Concurrent::Array.new
+ @@inject_headers = Concurrent::Hash.new
+
+ # Resets the current request log and starts logging requests
+ def self.log_requests!(headers = {})
+ @@inject_headers.replace(headers)
+ @@logged_requests.replace([])
+ @@log_requests.value = true
+ end
+
+ # Stops logging requests
+ def self.stop_logging!
+ @@log_requests.value = false
+ end
+
+ def self.requests
+ @@logged_requests
+ end
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ return @app.call(env) unless @@log_requests.true?
+
+ url = env['REQUEST_URI']
+ env.merge! http_headers_env(@@inject_headers) if @@inject_headers.any?
+ request_headers = env_http_headers(env)
+ status, headers, body = @app.call(env)
+
+ request = OpenStruct.new(
+ url: url,
+ status_code: status,
+ request_headers: request_headers,
+ response_headers: headers
+ )
+ log_request request
+
+ [status, headers, body]
+ end
+
+ private
+
+ def env_http_headers(env)
+ Hash[*env.select { |k, v| k.start_with? 'HTTP_' }
+ .collect { |k, v| [k.sub(/^HTTP_/, ''), v] }
+ .collect { |k, v| [k.split('_').collect(&:capitalize).join('-'), v] }
+ .sort
+ .flatten]
+ end
+
+ def http_headers_env(headers)
+ Hash[*headers
+ .collect { |k, v| [k.split('-').collect(&:upcase).join('_'), v] }
+ .collect { |k, v| [k.prepend('HTTP_'), v] }
+ .flatten]
+ end
+
+ def log_request(response)
+ @@logged_requests.push(response)
+ end
+ end
+ end
+end
diff --git a/lib/system_check/app/git_user_default_ssh_config_check.rb b/lib/system_check/app/git_user_default_ssh_config_check.rb
index 9af21078403..ad41760dff2 100644
--- a/lib/system_check/app/git_user_default_ssh_config_check.rb
+++ b/lib/system_check/app/git_user_default_ssh_config_check.rb
@@ -11,10 +11,10 @@ module SystemCheck
].freeze
set_name 'Git user has default SSH configuration?'
- set_skip_reason 'skipped (GitLab read-only, or git user is not present / configured)'
+ set_skip_reason 'skipped (git user is not present / configured)'
def skip?
- Gitlab::Database.read_only? || !home_dir || !File.directory?(home_dir)
+ !home_dir || !File.directory?(home_dir)
end
def check?
diff --git a/lib/tasks/gitlab/users.rake b/lib/tasks/gitlab/users.rake
deleted file mode 100644
index 3a16ace60bd..00000000000
--- a/lib/tasks/gitlab/users.rake
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace :gitlab do
- namespace :users do
- desc "GitLab | Clear the authentication token for all users"
- task clear_all_authentication_tokens: :environment do |t, args|
- # Do small batched updates because these updates will be slow and locking
- User.select(:id).find_in_batches(batch_size: 100) do |batch|
- User.where(id: batch.map(&:id)).update_all(authentication_token: nil)
- end
- end
- end
-end
diff --git a/lib/tasks/tokens.rake b/lib/tasks/tokens.rake
index ad1818ff1fa..693597afdf8 100644
--- a/lib/tasks/tokens.rake
+++ b/lib/tasks/tokens.rake
@@ -1,12 +1,7 @@
require_relative '../../app/models/concerns/token_authenticatable.rb'
namespace :tokens do
- desc "Reset all GitLab user auth tokens"
- task reset_all_auth: :environment do
- reset_all_users_token(:reset_authentication_token!)
- end
-
- desc "Reset all GitLab email tokens"
+ desc "Reset all GitLab incoming email tokens"
task reset_all_email: :environment do
reset_all_users_token(:reset_incoming_email_token!)
end
@@ -31,11 +26,6 @@ class TmpUser < ActiveRecord::Base
self.table_name = 'users'
- def reset_authentication_token!
- write_new_token(:authentication_token)
- save!(validate: false)
- end
-
def reset_incoming_email_token!
write_new_token(:incoming_email_token)
save!(validate: false)
diff --git a/package.json b/package.json
index 0a1f5c8d081..e607981143d 100644
--- a/package.json
+++ b/package.json
@@ -64,10 +64,10 @@
"underscore": "^1.8.3",
"url-loader": "^0.5.8",
"visibilityjs": "^1.2.4",
- "vue": "^2.2.6",
+ "vue": "^2.5.2",
"vue-loader": "^11.3.4",
"vue-resource": "^1.3.4",
- "vue-template-compiler": "^2.2.6",
+ "vue-template-compiler": "^2.5.2",
"vuex": "^3.0.0",
"webpack": "^3.5.5",
"webpack-bundle-analyzer": "^2.8.2",
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 6802b839eaa..b73ca0c2346 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -50,70 +50,36 @@ describe ApplicationController do
end
end
- describe "#authenticate_user_from_token!" do
- describe "authenticating a user from a private token" do
- controller(described_class) do
- def index
- render text: "authenticated"
- end
- end
-
- context "when the 'private_token' param is populated with the private token" do
- it "logs the user in" do
- get :index, private_token: user.private_token
- expect(response).to have_gitlab_http_status(200)
- expect(response.body).to eq("authenticated")
- end
- end
-
- context "when the 'PRIVATE-TOKEN' header is populated with the private token" do
- it "logs the user in" do
- @request.headers['PRIVATE-TOKEN'] = user.private_token
- get :index
- expect(response).to have_gitlab_http_status(200)
- expect(response.body).to eq("authenticated")
- end
- end
-
- it "doesn't log the user in otherwise" do
- @request.headers['PRIVATE-TOKEN'] = "token"
- get :index, private_token: "token", authenticity_token: "token"
- expect(response.status).not_to eq(200)
- expect(response.body).not_to eq("authenticated")
+ describe "#authenticate_user_from_personal_access_token!" do
+ controller(described_class) do
+ def index
+ render text: 'authenticated'
end
end
- describe "authenticating a user from a personal access token" do
- controller(described_class) do
- def index
- render text: 'authenticated'
- end
- end
-
- let(:personal_access_token) { create(:personal_access_token, user: user) }
+ let(:personal_access_token) { create(:personal_access_token, user: user) }
- context "when the 'personal_access_token' param is populated with the personal access token" do
- it "logs the user in" do
- get :index, private_token: personal_access_token.token
- expect(response).to have_gitlab_http_status(200)
- expect(response.body).to eq('authenticated')
- end
+ context "when the 'personal_access_token' param is populated with the personal access token" do
+ it "logs the user in" do
+ get :index, private_token: personal_access_token.token
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.body).to eq('authenticated')
end
+ end
- context "when the 'PERSONAL_ACCESS_TOKEN' header is populated with the personal access token" do
- it "logs the user in" do
- @request.headers["PRIVATE-TOKEN"] = personal_access_token.token
- get :index
- expect(response).to have_gitlab_http_status(200)
- expect(response.body).to eq('authenticated')
- end
+ context "when the 'PERSONAL_ACCESS_TOKEN' header is populated with the personal access token" do
+ it "logs the user in" do
+ @request.headers["PRIVATE-TOKEN"] = personal_access_token.token
+ get :index
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.body).to eq('authenticated')
end
+ end
- it "doesn't log the user in otherwise" do
- get :index, private_token: "token"
- expect(response.status).not_to eq(200)
- expect(response.body).not_to eq('authenticated')
- end
+ it "doesn't log the user in otherwise" do
+ get :index, private_token: "token"
+ expect(response.status).not_to eq(200)
+ expect(response.body).not_to eq('authenticated')
end
end
@@ -152,11 +118,15 @@ describe ApplicationController do
end
end
+ before do
+ sign_in user
+ end
+
context 'when format is handled' do
let(:requested_format) { :json }
it 'returns 200 response' do
- get :index, private_token: user.private_token, format: requested_format
+ get :index, format: requested_format
expect(response).to have_gitlab_http_status 200
end
@@ -164,7 +134,7 @@ describe ApplicationController do
context 'when format is not handled' do
it 'returns 404 response' do
- get :index, private_token: user.private_token
+ get :index
expect(response).to have_gitlab_http_status 404
end
diff --git a/spec/controllers/concerns/lfs_request_spec.rb b/spec/controllers/concerns/lfs_request_spec.rb
new file mode 100644
index 00000000000..33b23db302a
--- /dev/null
+++ b/spec/controllers/concerns/lfs_request_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+describe LfsRequest do
+ include ProjectForksHelper
+
+ controller(Projects::GitHttpClientController) do
+ # `described_class` is not available in this context
+ include LfsRequest # rubocop:disable RSpec/DescribedClass
+
+ def show
+ storage_project
+
+ render nothing: true
+ end
+
+ def project
+ @project ||= Project.find(params[:id])
+ end
+
+ def download_request?
+ true
+ end
+
+ def ci?
+ false
+ end
+ end
+
+ let(:project) { create(:project, :public) }
+
+ before do
+ stub_lfs_setting(enabled: true)
+ end
+
+ describe '#storage_project' do
+ it 'assigns the project as storage project' do
+ get :show, id: project.id
+
+ expect(assigns(:storage_project)).to eq(project)
+ end
+
+ it 'assigns the source of a forked project' do
+ forked_project = fork_project(project)
+
+ get :show, id: forked_project.id
+
+ expect(assigns(:storage_project)).to eq(project)
+ end
+ end
+end
diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb
index 7b0976e3e67..4aed2a25baa 100644
--- a/spec/controllers/metrics_controller_spec.rb
+++ b/spec/controllers/metrics_controller_spec.rb
@@ -59,17 +59,6 @@ describe MetricsController do
expect(response.body).to match(/^redis_shared_state_ping_latency_seconds [0-9\.]+$/)
end
- it 'returns file system check metrics' do
- get :index
-
- expect(response.body).to match(/^filesystem_access_latency_seconds{shard="default"} [0-9\.]+$/)
- expect(response.body).to match(/^filesystem_accessible{shard="default"} 1$/)
- expect(response.body).to match(/^filesystem_write_latency_seconds{shard="default"} [0-9\.]+$/)
- expect(response.body).to match(/^filesystem_writable{shard="default"} 1$/)
- expect(response.body).to match(/^filesystem_read_latency_seconds{shard="default"} [0-9\.]+$/)
- expect(response.body).to match(/^filesystem_readable{shard="default"} 1$/)
- end
-
context 'prometheus metrics are disabled' do
before do
allow(Gitlab::Metrics).to receive(:prometheus_metrics_enabled?).and_return(false)
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index aecdfb50759..8016176110e 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -248,6 +248,45 @@ describe Projects::IssuesController do
end
end
+ describe 'PUT #update' do
+ subject do
+ put :update,
+ namespace_id: project.namespace,
+ project_id: project,
+ id: issue.to_param,
+ issue: { title: 'New title' }, format: :json
+ end
+
+ before do
+ sign_in(user)
+ end
+
+ context 'when user has access to update issue' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'updates the issue' do
+ subject
+
+ expect(response).to have_http_status(:ok)
+ expect(issue.reload.title).to eq('New title')
+ end
+ end
+
+ context 'when user does not have access to update issue' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'responds with 404' do
+ subject
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+ end
+
describe 'Confidential Issues' do
let(:project) { create(:project_empty_repo, :public) }
let(:assignee) { create(:assignee) }
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 52ef8c6a589..14021b8ca50 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -186,17 +186,23 @@ describe Projects::MergeRequestsController do
end
describe 'PUT update' do
+ def update_merge_request(mr_params, additional_params = {})
+ params = {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: merge_request.iid,
+ merge_request: mr_params
+ }.merge(additional_params)
+
+ put :update, params
+ end
+
context 'changing the assignee' do
it 'limits the attributes exposed on the assignee' do
assignee = create(:user)
project.add_developer(assignee)
- put :update,
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: merge_request.iid,
- merge_request: { assignee_id: assignee.id },
- format: :json
+ update_merge_request({ assignee_id: assignee.id }, format: :json)
body = JSON.parse(response.body)
expect(body['assignee'].keys)
@@ -204,6 +210,20 @@ describe Projects::MergeRequestsController do
end
end
+ context 'when user does not have access to update issue' do
+ before do
+ reporter = create(:user)
+ project.add_reporter(reporter)
+ sign_in(reporter)
+ end
+
+ it 'responds with 404' do
+ update_merge_request(title: 'New title')
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
context 'there is no source project' do
let(:project) { create(:project, :repository) }
let(:forked_project) { fork_project_with_submodules(project) }
@@ -214,13 +234,7 @@ describe Projects::MergeRequestsController do
end
it 'closes MR without errors' do
- post :update,
- namespace_id: project.namespace,
- project_id: project,
- id: merge_request.iid,
- merge_request: {
- state_event: 'close'
- }
+ update_merge_request(state_event: 'close')
expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request])
expect(merge_request.reload.closed?).to be_truthy
@@ -229,13 +243,7 @@ describe Projects::MergeRequestsController do
it 'allows editing of a closed merge request' do
merge_request.close!
- put :update,
- namespace_id: project.namespace,
- project_id: project,
- id: merge_request.iid,
- merge_request: {
- title: 'New title'
- }
+ update_merge_request(title: 'New title')
expect(response).to redirect_to([merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request])
expect(merge_request.reload.title).to eq 'New title'
@@ -244,13 +252,7 @@ describe Projects::MergeRequestsController do
it 'does not allow to update target branch closed merge request' do
merge_request.close!
- put :update,
- namespace_id: project.namespace,
- project_id: project,
- id: merge_request.iid,
- merge_request: {
- target_branch: 'new_branch'
- }
+ update_merge_request(target_branch: 'new_branch')
expect { merge_request.reload.target_branch }.not_to change { merge_request.target_branch }
end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 1184c55e540..43afcc14957 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -105,6 +105,19 @@ describe Projects::NotesController do
expect(note_json[:discussion_html]).to be_nil
expect(note_json[:diff_discussion_html]).to be_nil
end
+
+ context 'when user cannot read commit' do
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(user, :download_code, project).and_return(false)
+ end
+
+ it 'renders 404' do
+ get :index, params
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
end
end
diff --git a/spec/features/admin/admin_disables_two_factor_spec.rb b/spec/features/admin/admin_disables_two_factor_spec.rb
index 6a97378391b..2abdd3c9ef2 100644
--- a/spec/features/admin/admin_disables_two_factor_spec.rb
+++ b/spec/features/admin/admin_disables_two_factor_spec.rb
@@ -7,7 +7,7 @@ feature 'Admin disables 2FA for a user' do
edit_user(user)
page.within('.two-factor-status') do
- click_link 'Disable'
+ accept_confirm { click_link 'Disable' }
end
page.within('.two-factor-status') do
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb
index 771fb5253da..a5f22848031 100644
--- a/spec/features/admin/admin_groups_spec.rb
+++ b/spec/features/admin/admin_groups_spec.rb
@@ -152,7 +152,7 @@ feature 'Admin Groups' do
expect(page).to have_content('Developer')
end
- find(:css, 'li', text: current_user.name).find(:css, 'a.btn-remove').click
+ accept_confirm { find(:css, 'li', text: current_user.name).find(:css, 'a.btn-remove').click }
visit group_group_members_path(group)
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index 2e65fcc5231..eec44549a03 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -62,14 +62,14 @@ describe 'Admin::Hooks', :js do
it 'from hooks list page' do
visit admin_hooks_path
- expect { click_link 'Remove' }.to change(SystemHook, :count).by(-1)
+ expect { accept_confirm { find(:link, 'Remove').send_keys(:return) } }.to change(SystemHook, :count).by(-1)
end
it 'from hook edit page' do
visit admin_hooks_path
click_link 'Edit'
- expect { click_link 'Remove' }.to change(SystemHook, :count).by(-1)
+ expect { accept_confirm { find(:link, 'Remove').send_keys(:return) } }.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 a5834056a1d..de406d7d966 100644
--- a/spec/features/admin/admin_labels_spec.rb
+++ b/spec/features/admin/admin_labels_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe 'admin issues labels' do
it 'deletes all labels', :js do
page.within '.labels' do
page.all('.btn-remove').each do |remove|
- remove.click
+ accept_confirm { remove.click }
wait_for_requests
end
end
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 85561511101..1218ea52227 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -73,7 +73,7 @@ feature 'Admin updates settings' do
context 'sign-in restrictions', :js do
it 'de-activates oauth sign-in source' do
- find('.btn', text: 'GitLab.com').click
+ find('input#application_setting_enabled_oauth_sign_in_sources_[value=gitlab]').send_keys(:return)
expect(find('.btn', text: 'GitLab.com')).not_to have_css('.active')
end
diff --git a/spec/features/admin/admin_users_impersonation_tokens_spec.rb b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
index 388d30828a7..e16eae219a4 100644
--- a/spec/features/admin/admin_users_impersonation_tokens_spec.rb
+++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
@@ -24,7 +24,7 @@ describe 'Admin > Users > Impersonation Tokens', :js do
fill_in "Name", with: name
# Set date to 1st of next month
- find_field("Expires at").trigger('focus')
+ find_field("Expires at").click
find(".pika-next").click
click_on "1"
@@ -60,7 +60,7 @@ describe 'Admin > Users > Impersonation Tokens', :js do
it "allows revocation of an active impersonation token" do
visit admin_user_impersonation_tokens_path(user_id: user.username)
- click_on "Revoke"
+ accept_confirm { click_on "Revoke" }
expect(page).to have_selector(".settings-message")
expect(no_personal_access_tokens_message).to have_text("This user has no active Impersonation Tokens.")
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index f9f4bd6f5b9..b47f9055d29 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -290,7 +290,7 @@ describe "Admin::Users" do
it 'allows group membership to be revoked', :js do
page.within(first('.group_member')) do
- find('.btn-remove').click
+ accept_confirm { find('.btn-remove').click }
end
wait_for_requests
@@ -319,7 +319,7 @@ describe "Admin::Users" do
expect(page).to have_content("Secondary email: #{secondary_email.email}")
- find("#remove_email_#{secondary_email.id}").click
+ accept_confirm { find("#remove_email_#{secondary_email.id}").click }
expect(page).not_to have_content(secondary_email.email)
end
diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb
index 42f5b5eb8dc..f1ac73ff819 100644
--- a/spec/features/admin/admin_uses_repository_checks_spec.rb
+++ b/spec/features/admin/admin_uses_repository_checks_spec.rb
@@ -37,7 +37,7 @@ feature 'Admin uses repository checks' do
expect(RepositoryCheck::ClearWorker).to receive(:perform_async)
- click_link 'Clear all repository checks'
+ accept_confirm { find(:link, 'Clear all repository checks').send_keys(:return) }
expect(page).to have_content('Started asynchronous removal of all repository check states.')
end
diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb
index 5aae2dbaf91..89c9d377003 100644
--- a/spec/features/atom/dashboard_issues_spec.rb
+++ b/spec/features/atom/dashboard_issues_spec.rb
@@ -13,8 +13,10 @@ describe "Dashboard Issues Feed" do
end
describe "atom feed" do
- it "renders atom feed via private token" do
- visit issues_dashboard_path(:atom, private_token: user.private_token)
+ it "renders atom feed via personal access token" do
+ personal_access_token = create(:personal_access_token, user: user)
+
+ visit issues_dashboard_path(:atom, private_token: personal_access_token.token)
expect(response_headers['Content-Type']).to have_content('application/atom+xml')
expect(body).to have_selector('title', text: "#{user.name} issues")
diff --git a/spec/features/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb
index 321c8a2a670..2c0c331b6db 100644
--- a/spec/features/atom/dashboard_spec.rb
+++ b/spec/features/atom/dashboard_spec.rb
@@ -4,9 +4,11 @@ describe "Dashboard Feed" do
describe "GET /" do
let!(:user) { create(:user, name: "Jonh") }
- context "projects atom feed via private token" do
+ context "projects atom feed via personal access token" do
it "renders projects atom feed" do
- visit dashboard_projects_path(:atom, private_token: user.private_token)
+ personal_access_token = create(:personal_access_token, user: user)
+
+ visit dashboard_projects_path(:atom, private_token: personal_access_token.token)
expect(body).to have_selector('feed title')
end
end
diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb
index 3eeb4d35131..4102ac0588a 100644
--- a/spec/features/atom/issues_spec.rb
+++ b/spec/features/atom/issues_spec.rb
@@ -28,10 +28,12 @@ describe 'Issues Feed' do
end
end
- context 'when authenticated via private token' do
+ context 'when authenticated via personal access token' do
it 'renders atom feed' do
+ personal_access_token = create(:personal_access_token, user: user)
+
visit project_issues_path(project, :atom,
- private_token: user.private_token)
+ private_token: personal_access_token.token)
expect(response_headers['Content-Type'])
.to have_content('application/atom+xml')
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index 9ce687afb31..2b934d81674 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -4,9 +4,11 @@ describe "User Feed" do
describe "GET /" do
let!(:user) { create(:user) }
- context 'user atom feed via private token' do
+ context 'user atom feed via personal access token' do
it "renders user atom feed" do
- visit user_path(user, :atom, private_token: user.private_token)
+ personal_access_token = create(:personal_access_token, user: user)
+
+ visit user_path(user, :atom, private_token: personal_access_token.token)
expect(body).to have_selector('feed title')
end
end
diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb
index c480b5b7e34..e4cfcea45a5 100644
--- a/spec/features/boards/add_issues_modal_spec.rb
+++ b/spec/features/boards/add_issues_modal_spec.rb
@@ -101,7 +101,7 @@ describe 'Issue Boards add issue modal', :js do
click_button 'Cancel'
end
- first('.board-delete').click
+ accept_confirm { first('.board-delete').click }
click_button('Add issues')
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index ebe6939df4c..e8d779f5772 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -2,6 +2,7 @@ require 'rails_helper'
describe 'Issue Boards', :js do
include DragTo
+ include MobileHelpers
let(:group) { create(:group, :nested) }
let(:project) { create(:project, :public, namespace: group) }
@@ -13,7 +14,7 @@ describe 'Issue Boards', :js do
project.team << [user, :master]
project.team << [user2, :master]
- page.driver.set_cookie('sidebar_collapsed', 'true')
+ set_cookie('sidebar_collapsed', 'true')
sign_in(user)
end
@@ -135,7 +136,7 @@ describe 'Issue Boards', :js do
it 'allows user to delete board' do
page.within(find('.board:nth-child(2)')) do
- find('.board-delete').click
+ accept_confirm { find('.board-delete').click }
end
wait_for_requests
@@ -150,7 +151,7 @@ describe 'Issue Boards', :js do
find('.dropdown-menu-close').click
page.within(find('.board:nth-child(2)')) do
- find('.board-delete').click
+ accept_confirm { find('.board-delete').click }
end
wait_for_requests
@@ -379,7 +380,7 @@ describe 'Issue Boards', :js do
end
it 'filters by milestone' do
- set_filter("milestone", "\"#{milestone.title}\"")
+ set_filter("milestone", "\"#{milestone.title}")
click_filter_link(milestone.title)
submit_filter
@@ -400,7 +401,7 @@ describe 'Issue Boards', :js do
end
it 'filters by label with space after reload' do
- set_filter("label", "\"#{accepting.title}\"")
+ set_filter("label", "\"#{accepting.title}")
click_filter_link(accepting.title)
submit_filter
@@ -521,7 +522,7 @@ describe 'Issue Boards', :js do
end
it 'allows user to use keyboard shortcuts' do
- find('.boards-list').native.send_keys('i')
+ find('body').native.send_keys('i')
expect(page).to have_content('New Issue')
end
end
@@ -563,6 +564,9 @@ describe 'Issue Boards', :js do
end
def drag(selector: '.board-list', list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0)
+ # ensure there is enough horizontal space for four boards
+ resize_window(2000, 800)
+
drag_to(selector: selector,
scrollable: '#board-app',
list_from_index: list_from_index,
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 4965f803883..9137ab82ff4 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -51,7 +51,7 @@ describe 'Issue Boards', :js do
expect(page).to have_selector('.issue-boards-sidebar')
- find('.gutter-toggle').trigger('click')
+ find('.gutter-toggle').click
expect(page).not_to have_selector('.issue-boards-sidebar')
end
@@ -171,7 +171,7 @@ describe 'Issue Boards', :js do
end
page.within(find('.board:nth-child(2)')) do
- find('.card:nth-child(2)').trigger('click')
+ find('.card:nth-child(2)').click
end
page.within('.assignee') do
diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb
index 4fc6956d111..a9530becb65 100644
--- a/spec/features/calendar_spec.rb
+++ b/spec/features/calendar_spec.rb
@@ -63,8 +63,8 @@ feature 'Contributions Calendar', :js do
Event.create(note_comment_params)
end
- def selected_day_activities
- find('.user-calendar-activities').text
+ def selected_day_activities(visible: true)
+ find('.user-calendar-activities', visible: visible).text
end
before do
@@ -112,7 +112,7 @@ feature 'Contributions Calendar', :js do
end
it 'hides calendar day activities' do
- expect(selected_day_activities).to be_empty
+ expect(selected_day_activities(visible: false)).to be_empty
end
end
end
diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb
index d5e9de20e59..bef2aa9e0e5 100644
--- a/spec/features/container_registry_spec.rb
+++ b/spec/features/container_registry_spec.rb
@@ -47,7 +47,7 @@ describe "Container Registry", :js do
scenario 'user removes a specific tag from container repository' do
visit_container_registry
- find('.js-toggle-repo').trigger('click')
+ find('.js-toggle-repo').click
wait_for_requests
expect_any_instance_of(ContainerRegistry::Tag)
diff --git a/spec/features/dashboard/group_spec.rb b/spec/features/dashboard/group_spec.rb
index 1213f8c32eb..1c7932e7964 100644
--- a/spec/features/dashboard/group_spec.rb
+++ b/spec/features/dashboard/group_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe 'Dashboard Group' do
it 'creates new group', :js do
visit dashboard_groups_path
- find('.btn-new').trigger('click')
+ find('.btn-new').click
new_path = 'Samurai'
new_description = 'Tokugawa Shogunate'
diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb
index c6873d1923c..d92c002b4e7 100644
--- a/spec/features/dashboard/groups_list_spec.rb
+++ b/spec/features/dashboard/groups_list_spec.rb
@@ -138,7 +138,7 @@ feature 'Dashboard Groups page', :js do
expect(page).not_to have_selector("#group-#{group.id}")
# Go to next page
- find(".gl-pagination .page:not(.active) a").trigger('click')
+ find(".gl-pagination .page:not(.active) a").click
wait_for_requests
diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb
index a8919976c31..5b4c00b3c7e 100644
--- a/spec/features/dashboard/issues_spec.rb
+++ b/spec/features/dashboard/issues_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe 'Dashboard Issues' do
end
it 'shows issues when current user is author', :js do
- find('#assignee_id', visible: false).set('')
+ execute_script("document.querySelector('#assignee_id').value=''")
find('.js-author-search', match: :first).click
expect(find('li[data-user-id="null"] a.is-active')).to be_visible
@@ -71,7 +71,7 @@ RSpec.describe 'Dashboard Issues' do
describe 'new issue dropdown' do
it 'shows projects only with issues feature enabled', :js do
- find('.new-project-item-select-button').trigger('click')
+ find('.new-project-item-select-button').click
page.within('.select2-results') do
expect(page).to have_content(project.name_with_namespace)
@@ -80,7 +80,7 @@ RSpec.describe 'Dashboard Issues' do
end
it 'shows the new issue page', :js do
- find('.new-project-item-select-button').trigger('click')
+ find('.new-project-item-select-button').click
wait_for_requests
@@ -93,7 +93,7 @@ RSpec.describe 'Dashboard Issues' do
find('#select2-drop-mask', visible: false)
execute_script("$('#select2-drop-mask').remove();")
- find('.new-project-item-link').trigger('click')
+ find('.new-project-item-link').click
expect(page).to have_current_path("#{project_path}/issues/new")
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index f01ba442e58..991d360ccaf 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -25,7 +25,7 @@ feature 'Dashboard Merge Requests' do
end
it 'shows projects only with merge requests feature enabled', :js do
- find('.new-project-item-select-button').trigger('click')
+ find('.new-project-item-select-button').click
page.within('.select2-results') do
expect(page).to have_content(project.name_with_namespace)
diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb
index 01aca443f4a..6f916078b1a 100644
--- a/spec/features/dashboard/todos/todos_spec.rb
+++ b/spec/features/dashboard/todos/todos_spec.rb
@@ -52,7 +52,7 @@ feature 'Dashboard Todos' do
end
it 'updates todo count' do
- expect(page).to have_content 'To do 0'
+ expect(page).to have_content 'Todos 0'
expect(page).to have_content 'Done 1'
end
@@ -81,7 +81,7 @@ feature 'Dashboard Todos' do
end
it 'updates todo count' do
- expect(page).to have_content 'To do 1'
+ expect(page).to have_content 'Todos 1'
expect(page).to have_content 'Done 0'
end
end
@@ -200,7 +200,7 @@ feature 'Dashboard Todos' do
end
it 'updates todo count' do
- expect(page).to have_content 'To do 1'
+ expect(page).to have_content 'Todos 1'
expect(page).to have_content 'Done 0'
end
end
@@ -252,11 +252,11 @@ feature 'Dashboard Todos' do
describe 'mark all as done', :js do
before do
visit dashboard_todos_path
- find('.js-todos-mark-all').trigger('click')
+ find('.js-todos-mark-all').click
end
it 'shows "All done" message!' do
- expect(page).to have_content 'To do 0'
+ expect(page).to have_content 'Todos 0'
expect(page).to have_content "You're all done!"
expect(page).not_to have_selector('.gl-pagination')
end
@@ -283,7 +283,7 @@ feature 'Dashboard Todos' do
it 'updates todo count' do
mark_all_and_undo
- expect(page).to have_content 'To do 2'
+ expect(page).to have_content 'Todos 2'
expect(page).to have_content 'Done 0'
end
@@ -309,9 +309,9 @@ feature 'Dashboard Todos' do
end
def mark_all_and_undo
- find('.js-todos-mark-all').trigger('click')
+ find('.js-todos-mark-all').click
wait_for_requests
- find('.js-todos-undo-all').trigger('click')
+ find('.js-todos-undo-all').click
wait_for_requests
end
end
diff --git a/spec/features/discussion_comments/commit_spec.rb b/spec/features/discussion_comments/commit_spec.rb
index 0375d0bf8ff..69d35cdbc72 100644
--- a/spec/features/discussion_comments/commit_spec.rb
+++ b/spec/features/discussion_comments/commit_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Discussion Comments Merge Request', :js do
+describe 'Discussion Comments Commit', :js do
include RepoHelpers
let(:user) { create(:user) }
diff --git a/spec/features/discussion_comments/snippets_spec.rb b/spec/features/discussion_comments/snippets_spec.rb
index 1e6389d9a13..4a236c4639b 100644
--- a/spec/features/discussion_comments/snippets_spec.rb
+++ b/spec/features/discussion_comments/snippets_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Discussion Comments Issue', :js do
+describe 'Discussion Comments Snippet', :js do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:snippet) { create(:project_snippet, :private, project: project, author: user) }
diff --git a/spec/features/explore/new_menu_spec.rb b/spec/features/explore/new_menu_spec.rb
index c5ec495a418..8d5233d0c0f 100644
--- a/spec/features/explore/new_menu_spec.rb
+++ b/spec/features/explore/new_menu_spec.rb
@@ -65,9 +65,9 @@ feature 'Top Plus Menu', :js do
visit project_path(project)
page.within '.header-content' do
- find('.header-new-dropdown-toggle').trigger('click')
+ find('.header-new-dropdown-toggle').click
expect(page).to have_selector('.header-new.dropdown.open', count: 1)
- find('.header-new-project-snippet a').trigger('click')
+ find('.header-new-project-snippet a').click
end
expect(page).to have_content('New Snippet')
@@ -87,9 +87,9 @@ feature 'Top Plus Menu', :js do
visit group_path(group)
page.within '.header-content' do
- find('.header-new-dropdown-toggle').trigger('click')
+ find('.header-new-dropdown-toggle').click
expect(page).to have_selector('.header-new.dropdown.open', count: 1)
- find('.header-new-group-project a').trigger('click')
+ find('.header-new-group-project a').click
end
expect(page).to have_content('Project path')
@@ -155,7 +155,7 @@ feature 'Top Plus Menu', :js do
def click_topmenuitem(item_name)
page.within '.header-content' do
- find('.header-new-dropdown-toggle').trigger('click')
+ find('.header-new-dropdown-toggle').click
expect(page).to have_selector('.header-new.dropdown.open', count: 1)
click_link item_name
end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index cc8906fa969..c1f3d94bc20 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -65,7 +65,7 @@ feature 'Group' do
end
it 'updates the team URL on graph path update', :js do
- out_span = find('span[data-bind-out="create_chat_team"]')
+ out_span = find('span[data-bind-out="create_chat_team"]', visible: false)
expect(out_span.text).to be_empty
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index 3223eb20b55..fa4d3a55c62 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -405,7 +405,7 @@ feature 'Issues > Labels bulk assignment' do
end
def update_issues
- find('.update-selected-issues').trigger('click')
+ find('.update-selected-issues').click
wait_for_requests
end
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index 1c4649d0ba9..2e4a25ee15d 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -43,15 +43,16 @@ describe 'Dropdown assignee', :js do
end
it 'should show loading indicator when opened' do
- filtered_search.set('assignee:')
+ slow_requests do
+ filtered_search.set('assignee:')
- expect(page).to have_css('#js-dropdown-assignee .filter-dropdown-loading', visible: true)
+ expect(page).to have_css('#js-dropdown-assignee .filter-dropdown-loading', visible: true)
+ end
end
it 'should hide loading indicator when loaded' do
filtered_search.set('assignee:')
- expect(find(js_dropdown_assignee)).to have_css('.filter-dropdown-loading')
expect(find(js_dropdown_assignee)).not_to have_css('.filter-dropdown-loading')
end
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
index 5e20fb48768..2fb5e7cdba4 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -51,9 +51,11 @@ describe 'Dropdown author', :js do
end
it 'should show loading indicator when opened' do
- filtered_search.set('author:')
+ slow_requests do
+ filtered_search.set('author:')
- expect(page).to have_css('#js-dropdown-author .filter-dropdown-loading', visible: true)
+ expect(page).to have_css('#js-dropdown-author .filter-dropdown-loading', visible: true)
+ end
end
it 'should hide loading indicator when loaded' do
diff --git a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb
index 3012c77f2b9..8db435634fd 100644
--- a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb
@@ -70,9 +70,11 @@ describe 'Dropdown emoji', :js do
end
it 'should show loading indicator when opened' do
- filtered_search.set('my-reaction:')
+ slow_requests do
+ filtered_search.set('my-reaction:')
- expect(page).to have_css('#js-dropdown-my-reaction .filter-dropdown-loading', visible: true)
+ expect(page).to have_css('#js-dropdown-my-reaction .filter-dropdown-loading', visible: true)
+ end
end
it 'should hide loading indicator when loaded' do
diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb
index cbc4f8d4c50..18cdb199c70 100644
--- a/spec/features/issues/filtered_search/dropdown_label_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb
@@ -66,9 +66,11 @@ describe 'Dropdown label', :js do
end
it 'shows loading indicator when opened and hides it when loaded' do
- filtered_search.set('label:')
+ slow_requests do
+ filtered_search.set('label:')
- expect(find(js_dropdown_label)).to have_css('.filter-dropdown-loading')
+ expect(page).to have_css("#{js_dropdown_label} .filter-dropdown-loading", visible: true)
+ end
expect(find(js_dropdown_label)).not_to have_css('.filter-dropdown-loading')
end
diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
index f6c2e952bea..031eb06723a 100644
--- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
@@ -50,15 +50,16 @@ describe 'Dropdown milestone', :js do
end
it 'should show loading indicator when opened' do
- filtered_search.set('milestone:')
+ slow_requests do
+ filtered_search.set('milestone:')
- expect(page).to have_css('#js-dropdown-milestone .filter-dropdown-loading', visible: true)
+ expect(page).to have_css('#js-dropdown-milestone .filter-dropdown-loading', visible: true)
+ end
end
it 'should hide loading indicator when loaded' do
filtered_search.set('milestone:')
- expect(find(js_dropdown_milestone)).to have_css('.filter-dropdown-loading')
expect(find(js_dropdown_milestone)).not_to have_css('.filter-dropdown-loading')
end
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index 2974016c6a7..b3c50964810 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -139,7 +139,7 @@ describe 'Filter issues', :js do
input_filtered_search('label:none')
expect_tokens([label_token('none', false)])
- expect_issues_list_count(8)
+ expect_issues_list_count(4)
expect_filtered_search_input_empty
end
diff --git a/spec/features/issues/filtered_search/recent_searches_spec.rb b/spec/features/issues/filtered_search/recent_searches_spec.rb
index eef7988e2bd..f355cec3ba9 100644
--- a/spec/features/issues/filtered_search/recent_searches_spec.rb
+++ b/spec/features/issues/filtered_search/recent_searches_spec.rb
@@ -27,9 +27,8 @@ describe 'Recent searches', :js do
input_filtered_search('foo', submit: true)
input_filtered_search('bar', submit: true)
- items = all('.filtered-search-history-dropdown-item', visible: false)
+ items = all('.filtered-search-history-dropdown-item', visible: false, count: 2)
- expect(items.count).to eq(2)
expect(items[0].text).to eq('bar')
expect(items[1].text).to eq('foo')
end
@@ -38,9 +37,8 @@ describe 'Recent searches', :js do
visit project_issues_path(project_1, label_name: 'foo', search: 'bar')
visit project_issues_path(project_1, label_name: 'qux', search: 'garply')
- items = all('.filtered-search-history-dropdown-item', visible: false)
+ items = all('.filtered-search-history-dropdown-item', visible: false, count: 2)
- expect(items.count).to eq(2)
expect(items[0].text).to eq('label:~qux garply')
expect(items[1].text).to eq('label:~foo bar')
end
@@ -50,9 +48,8 @@ describe 'Recent searches', :js do
visit project_issues_path(project_1, search: 'foo')
- items = all('.filtered-search-history-dropdown-item', visible: false)
+ items = all('.filtered-search-history-dropdown-item', visible: false, count: 3)
- expect(items.count).to eq(3)
expect(items[0].text).to eq('foo')
expect(items[1].text).to eq('saved1')
expect(items[2].text).to eq('saved2')
@@ -69,9 +66,8 @@ describe 'Recent searches', :js do
input_filtered_search('more', submit: true)
input_filtered_search('things', submit: true)
- items = all('.filtered-search-history-dropdown-item', visible: false)
+ items = all('.filtered-search-history-dropdown-item', visible: false, count: 2)
- expect(items.count).to eq(2)
expect(items[0].text).to eq('things')
expect(items[1].text).to eq('more')
end
@@ -80,7 +76,8 @@ describe 'Recent searches', :js do
set_recent_searches(project_1_local_storage_key, '["foo", "bar"]')
visit project_issues_path(project_1)
- all('.filtered-search-history-dropdown-item', visible: false)[0].trigger('click')
+ find('.filtered-search-history-dropdown-toggle-button').click
+ all('.filtered-search-history-dropdown-item', count: 2)[0].click
wait_for_filtered_search('foo')
expect(find('.filtered-search').value.strip).to eq('foo')
@@ -90,12 +87,11 @@ describe 'Recent searches', :js do
set_recent_searches(project_1_local_storage_key, '["foo"]')
visit project_issues_path(project_1)
- items_before = all('.filtered-search-history-dropdown-item', visible: false)
+ find('.filtered-search-history-dropdown-toggle-button').click
+ all('.filtered-search-history-dropdown-item', count: 1)
- expect(items_before.count).to eq(1)
-
- find('.filtered-search-history-clear-button', visible: false).trigger('click')
- items_after = all('.filtered-search-history-dropdown-item', visible: false)
+ find('.filtered-search-history-clear-button').click
+ items_after = all('.filtered-search-history-dropdown-item', count: 0)
expect(items_after.count).to eq(0)
end
diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb
index 920f5546eef..0ae70c855db 100644
--- a/spec/features/issues/filtered_search/visual_tokens_spec.rb
+++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb
@@ -2,7 +2,6 @@ require 'rails_helper'
describe 'Visual tokens', :js do
include FilteredSearchHelpers
- include WaitForRequests
let!(:project) { create(:project) }
let!(:user) { create(:user, name: 'administrator', username: 'root') }
@@ -28,7 +27,7 @@ describe 'Visual tokens', :js do
sign_in(user)
create(:issue, project: project)
- page.driver.set_cookie('sidebar_collapsed', 'true')
+ set_cookie('sidebar_collapsed', 'true')
visit project_issues_path(project)
end
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 15041ff04ea..b8a66245153 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -17,9 +17,9 @@ feature 'GFM autocomplete', :js do
it 'updates issue descripton with GFM reference' do
find('.issuable-edit').click
- find('#issue-description').native.send_keys("@#{user.name[0...3]}")
+ simulate_input('#issue-description', "@#{user.name[0...3]}")
- find('.atwho-view .cur').trigger('click')
+ find('.atwho-view .cur').click
click_button 'Save changes'
@@ -28,7 +28,6 @@ feature 'GFM autocomplete', :js do
it 'opens autocomplete menu when field starts with text' do
page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('')
find('#note-body').native.send_keys('@')
end
@@ -46,7 +45,6 @@ feature 'GFM autocomplete', :js do
it 'doesnt select the first item for non-assignee dropdowns' do
page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('')
find('#note-body').native.send_keys(':')
end
@@ -86,7 +84,6 @@ feature 'GFM autocomplete', :js do
it 'selects the first item for assignee dropdowns' do
page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('')
find('#note-body').native.send_keys('@')
end
@@ -100,7 +97,7 @@ feature 'GFM autocomplete', :js do
it 'includes items for assignee dropdowns with non-ASCII characters in name' do
page.within '.timeline-content-form' do
find('#note-body').native.send_keys('')
- find('#note-body').native.send_keys("@#{user.name[0...8]}")
+ simulate_input('#note-body', "@#{user.name[0...8]}")
end
expect(page).to have_selector('.atwho-container')
@@ -112,7 +109,6 @@ feature 'GFM autocomplete', :js do
it 'selects the first item for non-assignee dropdowns if a query is entered' do
page.within '.timeline-content-form' do
- find('#note-body').native.send_keys('')
find('#note-body').native.send_keys(':1')
end
@@ -127,9 +123,8 @@ feature 'GFM autocomplete', :js do
it 'wraps the result in double quotes' do
note = find('#note-body')
page.within '.timeline-content-form' do
- note.native.send_keys('')
- note.native.send_keys("~#{label.title[0]}")
- note.click
+ find('#note-body').native.send_keys('')
+ simulate_input('#note-body', "~#{label.title[0]}")
end
label_item = find('.atwho-view li', text: label.title)
@@ -152,16 +147,13 @@ feature 'GFM autocomplete', :js do
it "does not show dropdown when preceded with a special character" do
note = find('#note-body')
page.within '.timeline-content-form' do
- note.native.send_keys('')
note.native.send_keys("@")
- note.click
end
expect(page).to have_selector('.atwho-container')
page.within '.timeline-content-form' do
note.native.send_keys("@")
- note.click
end
expect(page).to have_selector('.atwho-container', visible: false)
@@ -170,9 +162,7 @@ feature 'GFM autocomplete', :js do
it "does not throw an error if no labels exist" do
note = find('#note-body')
page.within '.timeline-content-form' do
- note.native.send_keys('')
note.native.send_keys('~')
- note.click
end
expect(page).to have_selector('.atwho-container', visible: false)
@@ -181,9 +171,7 @@ feature 'GFM autocomplete', :js do
it 'doesn\'t wrap for assignee values' do
note = find('#note-body')
page.within '.timeline-content-form' do
- note.native.send_keys('')
note.native.send_keys("@#{user.username[0]}")
- note.click
end
user_item = find('.atwho-view li', text: user.username)
@@ -194,9 +182,7 @@ feature 'GFM autocomplete', :js do
it 'doesn\'t wrap for emoji values' do
note = find('#note-body')
page.within '.timeline-content-form' do
- note.native.send_keys('')
- note.native.send_keys(":cartwheel")
- note.click
+ note.native.send_keys(":cartwheel_")
end
emoji_item = find('.atwho-view li', text: 'cartwheel_tone1')
@@ -223,12 +209,11 @@ feature 'GFM autocomplete', :js do
it 'triggers autocomplete after selecting a quick action' do
note = find('#note-body')
page.within '.timeline-content-form' do
- note.native.send_keys('')
note.native.send_keys('/as')
- note.click
end
- find('.atwho-view li', text: '/assign').native.send_keys(:tab)
+ find('.atwho-view li', text: '/assign')
+ note.native.send_keys(:tab)
user_item = find('.atwho-view li', text: user.username)
expect(user_item).to have_content(user.username)
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index bc9c3d825c1..a9de52bd8d5 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -130,8 +130,8 @@ feature 'Issue Sidebar' do
it 'adds new label' do
page.within('.block.labels') do
fill_in 'new_label_name', with: 'wontfix'
- page.find('.suggest-colors a', match: :first).trigger('click')
- page.find('button', text: 'Create').trigger('click')
+ page.find('.suggest-colors a', match: :first).click
+ page.find('button', text: 'Create').click
page.within('.dropdown-page-one') do
expect(page).to have_content 'wontfix'
@@ -142,8 +142,8 @@ feature 'Issue Sidebar' do
it 'shows error message if label title is taken' do
page.within('.block.labels') do
fill_in 'new_label_name', with: label.title
- page.find('.suggest-colors a', match: :first).trigger('click')
- page.find('button', text: 'Create').trigger('click')
+ page.find('.suggest-colors a', match: :first).click
+ page.find('button', text: 'Create').click
page.within('.dropdown-page-two') do
expect(page).to have_content 'Title has already been taken'
@@ -170,7 +170,7 @@ feature 'Issue Sidebar' do
end
def open_issue_sidebar
- find('aside.right-sidebar.right-sidebar-collapsed .js-sidebar-toggle').trigger('click')
+ find('aside.right-sidebar.right-sidebar-collapsed .js-sidebar-toggle').click
find('aside.right-sidebar.right-sidebar-expanded')
end
end
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index 6d7b1b1cd8f..17035b5501c 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -38,7 +38,7 @@ feature 'issue move to another project' do
end
scenario 'moving issue to another project', :js do
- find('.js-move-issue').trigger('click')
+ find('.js-move-issue').click
wait_for_requests
all('.js-move-issue-dropdown-item')[0].click
find('.js-move-issue-confirmation-button').click
@@ -52,7 +52,7 @@ feature 'issue move to another project' do
scenario 'searching project dropdown', :js do
new_project_search.team << [user, :reporter]
- find('.js-move-issue').trigger('click')
+ find('.js-move-issue').click
wait_for_requests
page.within '.js-sidebar-move-issue-block' do
@@ -69,7 +69,7 @@ feature 'issue move to another project' do
background { another_project.team << [user, :guest] }
scenario 'browsing projects in projects select' do
- find('.js-move-issue').trigger('click')
+ find('.js-move-issue').click
wait_for_requests
page.within '.js-sidebar-move-issue-block' do
diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb
index 1f57c110c11..bcc6e9bab0f 100644
--- a/spec/features/issues/update_issues_spec.rb
+++ b/spec/features/issues/update_issues_spec.rb
@@ -118,7 +118,7 @@ feature 'Multiple issue updating from issues#index', :js do
end
def click_update_issues_button
- find('.update-selected-issues').trigger('click')
+ find('.update-selected-issues').click
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 9f5e25ff2cb..c4c06ed514b 100644
--- a/spec/features/issues/user_uses_slash_commands_spec.rb
+++ b/spec/features/issues/user_uses_slash_commands_spec.rb
@@ -226,7 +226,7 @@ feature 'Issues > User uses quick actions', :js do
end
it 'applies the commands to both issues and moves the issue' do
- write_note("/label ~#{bug.title} ~#{wontfix.title}\n/milestone %\"#{milestone.title}\"\n/move #{target_project.full_path}")
+ write_note("/label ~#{bug.title} ~#{wontfix.title}\n\n/milestone %\"#{milestone.title}\"\n\n/move #{target_project.full_path}")
expect(page).to have_content 'Commands applied'
expect(issue.reload).to be_closed
@@ -245,7 +245,7 @@ feature 'Issues > User uses quick actions', :js do
end
it 'moves the issue and applies the commands to both issues' do
- write_note("/move #{target_project.full_path}\n/label ~#{bug.title} ~#{wontfix.title}\n/milestone %\"#{milestone.title}\"")
+ write_note("/move #{target_project.full_path}\n\n/label ~#{bug.title} ~#{wontfix.title}\n\n/milestone %\"#{milestone.title}\"")
expect(page).to have_content 'Commands applied'
expect(issue.reload).to be_closed
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index d4fd3a50008..b9af77f918a 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -367,7 +367,7 @@ describe 'Issues' do
it 'changes incoming email address token', :js do
find('.issue-email-modal-btn').click
previous_token = find('input#issue_email').value
- find('.incoming-email-token-reset').trigger('click')
+ find('.incoming-email-token-reset').click
wait_for_requests
@@ -583,6 +583,18 @@ describe 'Issues' do
expect(page.find_field("issue_description").value).not_to match /\n\n$/
end
+
+ it "cancels a file upload correctly" do
+ slow_requests do
+ dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false)
+
+ click_button 'Cancel'
+ end
+
+ expect(page).to have_button('Attach a file')
+ expect(page).not_to have_button('Cancel')
+ expect(page).not_to have_selector('.uploading-progress-container', visible: true)
+ end
end
context 'form filled by URL parameters' do
diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb
index ba976bc7216..4e2963c116d 100644
--- a/spec/features/merge_requests/conflicts_spec.rb
+++ b/spec/features/merge_requests/conflicts_spec.rb
@@ -23,11 +23,11 @@ feature 'Merge request conflict resolution', :js do
within find('.files-wrapper .diff-file', text: 'files/ruby/regex.rb') do
all('button', text: 'Use ours').each do |button|
- button.trigger('click')
+ button.send_keys(:return)
end
end
- click_button 'Commit conflict resolution'
+ find_button('Commit conflict resolution').send_keys(:return)
expect(page).to have_content('All merge conflicts were resolved')
merge_request.reload_diff
@@ -71,7 +71,7 @@ feature 'Merge request conflict resolution', :js do
execute_script('ace.edit($(".files-wrapper .diff-file pre")[1]).setValue("Gregor Samsa woke from troubled dreams");')
end
- click_button 'Commit conflict resolution'
+ find_button('Commit conflict resolution').send_keys(:return)
expect(page).to have_content('All merge conflicts were resolved')
merge_request.reload_diff
diff --git a/spec/features/merge_requests/diff_notes_avatars_spec.rb b/spec/features/merge_requests/diff_notes_avatars_spec.rb
index 9aa0672feae..9e816cf041b 100644
--- a/spec/features/merge_requests/diff_notes_avatars_spec.rb
+++ b/spec/features/merge_requests/diff_notes_avatars_spec.rb
@@ -22,7 +22,7 @@ feature 'Diff note avatars', :js do
project.team << [user, :master]
sign_in user
- page.driver.set_cookie('sidebar_collapsed', 'true')
+ set_cookie('sidebar_collapsed', 'true')
end
context 'discussion tab' do
@@ -56,7 +56,7 @@ feature 'Diff note avatars', :js do
end
it 'does not render avatar after commenting' do
- first('.diff-line-num').trigger('mouseover')
+ first('.diff-line-num').click
find('.js-add-diff-note-button').click
page.within('.js-discussion-note-form') do
@@ -85,7 +85,7 @@ feature 'Diff note avatars', :js do
it 'shows note avatar' do
page.within find_line(position.line_code(project.repository)) do
- find('.diff-notes-collapse').click
+ find('.diff-notes-collapse').send_keys(:return)
expect(page).to have_selector('img.js-diff-comment-avatar', count: 1)
end
@@ -93,7 +93,7 @@ feature 'Diff note avatars', :js do
it 'shows comment on note avatar' do
page.within find_line(position.line_code(project.repository)) do
- find('.diff-notes-collapse').click
+ find('.diff-notes-collapse').send_keys(:return)
expect(first('img.js-diff-comment-avatar')["data-original-title"]).to eq("#{note.author.name}: #{note.note.truncate(17)}")
end
@@ -101,7 +101,7 @@ feature 'Diff note avatars', :js do
it 'toggles comments when clicking avatar' do
page.within find_line(position.line_code(project.repository)) do
- find('.diff-notes-collapse').click
+ find('.diff-notes-collapse').send_keys(:return)
end
expect(page).to have_selector('.notes_holder', visible: false)
@@ -117,7 +117,7 @@ feature 'Diff note avatars', :js do
open_more_actions_dropdown(note)
page.within find(".note-row-#{note.id}") do
- find('.js-note-delete').click
+ accept_confirm { find('.js-note-delete').click }
end
wait_for_requests
@@ -139,7 +139,7 @@ feature 'Diff note avatars', :js do
end
page.within find_line(position.line_code(project.repository)) do
- find('.diff-notes-collapse').trigger('click')
+ find('.diff-notes-collapse').send_keys(:return)
expect(page).to have_selector('img.js-diff-comment-avatar', count: 2)
end
@@ -152,14 +152,14 @@ feature 'Diff note avatars', :js do
page.within '.js-discussion-note-form' do
find('.js-note-text').native.send_keys('Test')
- find('.js-comment-button').trigger('click')
+ find('.js-comment-button').click
wait_for_requests
end
end
page.within find_line(position.line_code(project.repository)) do
- find('.diff-notes-collapse').trigger('click')
+ find('.diff-notes-collapse').send_keys(:return)
expect(page).to have_selector('img.js-diff-comment-avatar', count: 3)
expect(find('.diff-comments-more-count')).to have_content '+1'
@@ -177,7 +177,7 @@ feature 'Diff note avatars', :js do
it 'shows extra comment count' do
page.within find_line(position.line_code(project.repository)) do
- find('.diff-notes-collapse').click
+ find('.diff-notes-collapse').send_keys(:return)
expect(find('.diff-comments-more-count')).to have_content '+1'
end
diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb
index 3db0729cafb..15d380b1bf4 100644
--- a/spec/features/merge_requests/diff_notes_resolve_spec.rb
+++ b/spec/features/merge_requests/diff_notes_resolve_spec.rb
@@ -192,7 +192,7 @@ feature 'Diff notes resolve', :js do
page.find('.discussion-next-btn').click
end
- expect(page.evaluate_script("$('body').scrollTop()")).to be > 0
+ expect(page.evaluate_script("window.pageYOffset")).to be > 0
end
it 'hides jump to next button when all resolved' do
@@ -241,10 +241,8 @@ feature 'Diff notes resolve', :js do
end
it 'resolves discussion' do
- page.all('.note').each do |note|
- note.all('.line-resolve-btn').each do |button|
- button.click
- end
+ page.all('.note .line-resolve-btn').each do |button|
+ button.click
end
expect(page).to have_content('Resolved by')
@@ -305,10 +303,10 @@ feature 'Diff notes resolve', :js do
end
page.within '.line-resolve-all-container' do
- page.find('.discussion-next-btn').trigger('click')
+ page.find('.discussion-next-btn').click
end
- expect(page.evaluate_script("$('body').scrollTop()")).to be > 0
+ expect(page.evaluate_script("window.pageYOffset")).to be > 0
end
it 'updates updated text after resolving note' do
diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb
index 2adca58620f..1bf77296ae6 100644
--- a/spec/features/merge_requests/diffs_spec.rb
+++ b/spec/features/merge_requests/diffs_spec.rb
@@ -7,14 +7,12 @@ feature 'Diffs URL', :js do
let(:merge_request) { create(:merge_request, source_project: project) }
context 'when visit with */* as accept header' do
- before do
- page.driver.add_header('Accept', '*/*')
- end
-
it 'renders the notes' do
create :note_on_merge_request, project: project, noteable: merge_request, note: 'Rebasing with master'
- visit diffs_project_merge_request_path(project, merge_request)
+ inspect_requests(inject_headers: { 'Accept' => '*/*' }) do
+ visit diffs_project_merge_request_path(project, merge_request)
+ end
# Load notes and diff through AJAX
expect(page).to have_css('.note-text', visible: false, text: 'Rebasing with master')
@@ -90,7 +88,7 @@ feature 'Diffs URL', :js do
visit diffs_project_merge_request_path(project, merge_request)
# Throws `Capybara::Poltergeist::InvalidSelector` if we try to use `#hash` syntax
- find("[id=\"#{changelog_id}\"] .js-edit-blob").trigger('click')
+ find("[id=\"#{changelog_id}\"] .js-edit-blob").click
expect(page).to have_selector('.js-fork-suggestion-button', count: 1)
expect(page).to have_selector('.js-cancel-fork-suggestion-button', count: 1)
diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb
index 758fc9b139d..1dcc1e139a0 100644
--- a/spec/features/merge_requests/form_spec.rb
+++ b/spec/features/merge_requests/form_spec.rb
@@ -43,7 +43,7 @@ describe 'New/edit merge request', :js do
expect(page).to have_content user2.name
end
- find('a', text: 'Assign to me').trigger('click')
+ find('a', text: 'Assign to me').click
expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
page.within '.js-assignee-search' do
expect(page).to have_content user.name
diff --git a/spec/features/merge_requests/mini_pipeline_graph_spec.rb b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
index bf21a719901..bac56270362 100644
--- a/spec/features/merge_requests/mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
@@ -92,7 +92,7 @@ feature 'Mini Pipeline Graph', :js do
end
it 'should close when toggle is clicked again' do
- toggle.trigger('click')
+ toggle.click
expect(toggle.find(:xpath, '..')).not_to have_selector('.mini-pipeline-graph-dropdown-menu')
end
diff --git a/spec/features/merge_requests/update_merge_requests_spec.rb b/spec/features/merge_requests/update_merge_requests_spec.rb
index 1a41fd36a4f..c5498563b39 100644
--- a/spec/features/merge_requests/update_merge_requests_spec.rb
+++ b/spec/features/merge_requests/update_merge_requests_spec.rb
@@ -127,7 +127,7 @@ feature 'Multiple merge requests updating from merge_requests#index' do
end
def click_update_merge_requests_button
- find('.update-selected-issues').trigger('click')
+ find('.update-selected-issues').click
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 7a773fb2baa..d44eb23d7f4 100644
--- a/spec/features/merge_requests/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_requests/user_posts_diff_notes_spec.rb
@@ -8,7 +8,7 @@ feature 'Merge requests > User posts diff notes', :js do
let(:project) { merge_request.source_project }
before do
- page.driver.set_cookie('sidebar_collapsed', 'true')
+ set_cookie('sidebar_collapsed', 'true')
project.add_developer(user)
sign_in(user)
@@ -103,7 +103,10 @@ feature 'Merge requests > User posts diff notes', :js do
it 'allows commenting' do
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
- first('.js-note-delete', visible: false).trigger('click')
+ accept_confirm do
+ first('button.more-actions-toggle').click
+ first('.js-note-delete').click
+ end
should_allow_commenting(find('[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'))
end
@@ -236,7 +239,7 @@ feature 'Merge requests > User posts diff notes', :js do
def should_allow_dismissing_a_comment(line_holder, diff_side = nil)
write_comment_on_line(line_holder, diff_side)
- find('.js-close-discussion-note-form').trigger('click')
+ find('.js-close-discussion-note-form').click
assert_comment_dismissal(line_holder)
end
diff --git a/spec/features/merge_requests/user_posts_notes_spec.rb b/spec/features/merge_requests/user_posts_notes_spec.rb
index d7cda73ab40..f4c75a2f265 100644
--- a/spec/features/merge_requests/user_posts_notes_spec.rb
+++ b/spec/features/merge_requests/user_posts_notes_spec.rb
@@ -141,7 +141,7 @@ describe 'Merge requests > User posts notes', :js do
end
it 'removes the attachment div and resets the edit form' do
- find('.js-note-attachment-delete').click
+ accept_confirm { 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_requests
diff --git a/spec/features/merge_requests/versions_spec.rb b/spec/features/merge_requests/versions_spec.rb
index 50f7d721ff3..29f95039af8 100644
--- a/spec/features/merge_requests/versions_spec.rb
+++ b/spec/features/merge_requests/versions_spec.rb
@@ -67,8 +67,8 @@ feature 'Merge Request versions', :js do
line_code = '7445606fbf8f3683cd42bdc54b05d7a0bc2dfc44_2_2'
page.within(diff_file_selector) do
- find(".line_holder[id='#{line_code}'] td:nth-of-type(1)").trigger 'mouseover'
- find(".line_holder[id='#{line_code}'] button").trigger 'click'
+ find(".line_holder[id='#{line_code}'] td:nth-of-type(1)").hover
+ find(".line_holder[id='#{line_code}'] button").click
page.within("form[data-line-code='#{line_code}']") do
fill_in "note[note]", with: "Typo, please fix"
@@ -137,8 +137,8 @@ feature 'Merge Request versions', :js do
line_code = '7445606fbf8f3683cd42bdc54b05d7a0bc2dfc44_4_4'
page.within(diff_file_selector) do
- find(".line_holder[id='#{line_code}'] td:nth-of-type(1)").trigger 'mouseover'
- find(".line_holder[id='#{line_code}'] button").trigger 'click'
+ find(".line_holder[id='#{line_code}'] td:nth-of-type(1)").hover
+ find(".line_holder[id='#{line_code}'] button").click
page.within("form[data-line-code='#{line_code}']") do
fill_in "note[note]", with: "Typo, please fix"
diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb
index 5658c2c5122..72a52c979b3 100644
--- a/spec/features/merge_requests/widget_deployments_spec.rb
+++ b/spec/features/merge_requests/widget_deployments_spec.rb
@@ -42,7 +42,7 @@ feature 'Widget Deployments Header', :js do
end
scenario 'does start build when stop button clicked' do
- click_button('Stop environment')
+ accept_confirm { click_button('Stop environment') }
expect(page).to have_content('close_app')
end
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index 1cddd35fd8a..c60883911f7 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Profile account page' do
+describe 'Profile account page', :js do
let(:user) { create(:user) }
before do
@@ -56,47 +56,38 @@ describe 'Profile account page' do
end
end
- describe 'when I reset private token' do
- before do
- visit profile_account_path
- end
-
- it 'resets private token' do
- previous_token = find("#private-token").value
-
- click_link('Reset private token')
-
- expect(find('#private-token').value).not_to eq(previous_token)
- end
- end
-
describe 'when I reset RSS token' do
before do
- visit profile_account_path
+ visit profile_personal_access_tokens_path
end
it 'resets RSS token' do
- previous_token = find("#rss-token").value
+ within('.rss-token-reset') do
+ previous_token = find("#rss_token").value
- click_link('Reset RSS token')
+ accept_confirm { click_link('reset it') }
+
+ expect(find('#rss_token').value).not_to eq(previous_token)
+ end
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)
- visit profile_account_path
+ visit profile_personal_access_tokens_path
end
it 'resets incoming email token' do
- previous_token = find('#incoming-email-token').value
+ within('.incoming-email-token-reset') do
+ previous_token = find('#incoming_email_token').value
- click_link('Reset incoming email token')
+ accept_confirm { click_link('reset it') }
- expect(find('#incoming-email-token').value).not_to eq(previous_token)
+ expect(find('#incoming_email_token').value).not_to eq(previous_token)
+ end
end
end
diff --git a/spec/features/profiles/oauth_applications_spec.rb b/spec/features/profiles/oauth_applications_spec.rb
index 8cb240077eb..d1edeef8da4 100644
--- a/spec/features/profiles/oauth_applications_spec.rb
+++ b/spec/features/profiles/oauth_applications_spec.rb
@@ -14,7 +14,7 @@ describe 'Profile > Applications' do
page.within('.oauth-applications') do
expect(page).to have_content('Your applications (1)')
- click_button 'Destroy'
+ accept_confirm { click_button 'Destroy' }
end
expect(page).to have_content('The application was deleted successfully')
@@ -28,7 +28,7 @@ describe 'Profile > Applications' do
page.within('.oauth-authorized-applications') do
expect(page).to have_content('Authorized applications (1)')
- click_button 'Revoke'
+ accept_confirm { click_button 'Revoke' }
end
expect(page).to have_content('The application was revoked access.')
diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb
index a572160dae9..8461cd0027c 100644
--- a/spec/features/profiles/personal_access_tokens_spec.rb
+++ b/spec/features/profiles/personal_access_tokens_spec.rb
@@ -34,7 +34,7 @@ describe 'Profile > Personal Access Tokens', :js do
fill_in "Name", with: name
# Set date to 1st of next month
- find_field("Expires at").trigger('focus')
+ find_field("Expires at").click
find(".pika-next").click
click_on "1"
@@ -78,7 +78,7 @@ describe 'Profile > Personal Access Tokens', :js do
it "allows revocation of an active token" do
visit profile_personal_access_tokens_path
- click_on "Revoke"
+ accept_confirm { click_on "Revoke" }
expect(page).to have_selector(".settings-message")
expect(no_personal_access_tokens_message).to have_text("This user has no active Personal Access Tokens.")
@@ -100,7 +100,7 @@ describe 'Profile > Personal Access Tokens', :js do
errors = ActiveModel::Errors.new(PersonalAccessToken.new).tap { |e| e.add(:name, "cannot be nil") }
allow_any_instance_of(PersonalAccessToken).to receive(:errors).and_return(errors)
- click_on "Revoke"
+ accept_confirm { click_on "Revoke" }
expect(active_personal_access_tokens).to have_text(personal_access_token.name)
expect(page).to have_content("Could not revoke")
end
diff --git a/spec/features/profiles/user_visits_notifications_tab_spec.rb b/spec/features/profiles/user_visits_notifications_tab_spec.rb
index 923ca8b1c80..df89918f17a 100644
--- a/spec/features/profiles/user_visits_notifications_tab_spec.rb
+++ b/spec/features/profiles/user_visits_notifications_tab_spec.rb
@@ -13,7 +13,7 @@ feature 'User visits the notifications tab', :js do
it 'changes the project notifications setting' do
expect(page).to have_content('Notifications')
- first('#notifications-button').trigger('click')
+ first('#notifications-button').click
click_link('On mention')
expect(page).to have_content('On mention')
diff --git a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
index 924ee0e4174..90d6841af0e 100644
--- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
+++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
@@ -53,7 +53,7 @@ describe 'User visits the profile preferences page' do
expect(page).to have_content("You don't have starred projects yet")
expect(page.current_path).to eq starred_dashboard_projects_path
- find('.shortcuts-activity').trigger('click')
+ find('.shortcuts-activity').click
expect(page).not_to have_content("You don't have starred projects yet")
expect(page.current_path).to eq dashboard_projects_path
diff --git a/spec/features/projects/artifacts/download_spec.rb b/spec/features/projects/artifacts/download_spec.rb
index f1bdb2812c6..6f76c14910b 100644
--- a/spec/features/projects/artifacts/download_spec.rb
+++ b/spec/features/projects/artifacts/download_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Download artifact', :js do
+feature 'Download artifact' do
let(:project) { create(:project, :public) }
let(:pipeline) { create(:ci_empty_pipeline, status: :success, project: project) }
let(:job) { create(:ci_build, :artifacts, :success, pipeline: pipeline) }
diff --git a/spec/features/projects/artifacts/file_spec.rb b/spec/features/projects/artifacts/file_spec.rb
index b2be10a7e0c..df1d17bdcb7 100644
--- a/spec/features/projects/artifacts/file_spec.rb
+++ b/spec/features/projects/artifacts/file_spec.rb
@@ -39,7 +39,6 @@ feature 'Artifact file', :js do
context 'JPG file' do
before do
- page.driver.browser.url_blacklist = []
visit_file('rails_sample.jpg')
wait_for_requests
diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb
index 941d34dd660..7a77df83034 100644
--- a/spec/features/projects/branches_spec.rb
+++ b/spec/features/projects/branches_spec.rb
@@ -67,7 +67,7 @@ describe 'Branches' do
expect(page).to have_content('fix')
expect(find('.all-branches')).to have_selector('li', count: 1)
- find('.js-branch-fix .btn-remove').trigger(:click)
+ accept_confirm { find('.js-branch-fix .btn-remove').click }
expect(page).not_to have_content('fix')
expect(find('.all-branches')).to have_selector('li', count: 0)
diff --git a/spec/features/projects/commit/diff_notes_spec.rb b/spec/features/projects/commit/diff_notes_spec.rb
index f0fe4e00acc..4dbfc6f6edf 100644
--- a/spec/features/projects/commit/diff_notes_spec.rb
+++ b/spec/features/projects/commit/diff_notes_spec.rb
@@ -20,8 +20,8 @@ feature 'Commit diff', :js do
it "adds comment to diff" do
diff_line_num = first('.diff-line-num.new')
- diff_line_num.trigger('mouseover')
- diff_line_num.find('.js-add-diff-note-button').trigger('click')
+ diff_line_num.hover
+ diff_line_num.find('.js-add-diff-note-button').click
page.within(first('.diff-viewer')) do
find('.js-note-text').set 'test comment'
diff --git a/spec/features/projects/deploy_keys_spec.rb b/spec/features/projects/deploy_keys_spec.rb
index 2d1a9b931b5..e445758cb5e 100644
--- a/spec/features/projects/deploy_keys_spec.rb
+++ b/spec/features/projects/deploy_keys_spec.rb
@@ -20,7 +20,7 @@ describe 'Project deploy keys', :js do
page.within(find('.deploy-keys')) do
expect(page).to have_selector('.deploy-keys li', count: 1)
- click_on 'Remove'
+ accept_confirm { find(:button, text: 'Remove').send_keys(:return) }
expect(page).not_to have_selector('.fa-spinner', count: 0)
expect(page).to have_selector('.deploy-keys li', count: 0)
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index 0fe1eb4c293..5fc3ba54f65 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -193,12 +193,14 @@ feature 'Environment' do
create(:environment, project: project,
name: 'staging-1.0/review',
state: :available)
-
- visit folder_project_environments_path(project, id: 'staging-1.0')
end
it 'renders a correct environment folder' do
- expect(page).to have_gitlab_http_status(:ok)
+ reqs = inspect_requests do
+ visit folder_project_environments_path(project, id: 'staging-1.0')
+ end
+
+ expect(reqs.first.status_code).to eq(200)
expect(page).to have_content('Environments / staging-1.0')
end
end
diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index 610f566c0cf..b4eb5795470 100644
--- a/spec/features/projects/environments/environments_spec.rb
+++ b/spec/features/projects/environments/environments_spec.rb
@@ -151,7 +151,7 @@ feature 'Environments page', :js do
find('.js-dropdown-play-icon-container').click
expect(page).to have_content(action.name.humanize)
- expect { find('.js-manual-action-link').trigger('click') }
+ expect { find('.js-manual-action-link').click }
.not_to change { Ci::Pipeline.count }
end
diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb
index e5282b42a4f..951456763dc 100644
--- a/spec/features/projects/features_visibility_spec.rb
+++ b/spec/features/projects/features_visibility_spec.rb
@@ -22,7 +22,7 @@ describe 'Edit Project Settings' do
# disable by clicking toggle
toggle_feature_off("project[project_feature_attributes][#{tool_name}_access_level]")
page.within('.sharing-permissions') do
- click_button 'Save changes'
+ find('input[value="Save changes"]').click
end
wait_for_requests
expect(page).not_to have_selector(".shortcuts-#{shortcut_name}")
@@ -30,7 +30,7 @@ describe 'Edit Project Settings' do
# re-enable by clicking toggle again
toggle_feature_on("project[project_feature_attributes][#{tool_name}_access_level]")
page.within('.sharing-permissions') do
- click_button 'Save changes'
+ find('input[value="Save changes"]').click
end
wait_for_requests
expect(page).to have_selector(".shortcuts-#{shortcut_name}")
diff --git a/spec/features/projects/files/edit_file_soft_wrap_spec.rb b/spec/features/projects/files/edit_file_soft_wrap_spec.rb
index 25f7e18ac5c..3ab43b3c656 100644
--- a/spec/features/projects/files/edit_file_soft_wrap_spec.rb
+++ b/spec/features/projects/files/edit_file_soft_wrap_spec.rb
@@ -7,18 +7,18 @@ feature 'User uses soft wrap whilst editing file', :js do
project.team << [user, :master]
sign_in user
visit project_new_blob_path(project, 'master', file_name: 'test_file-name')
- editor = find('.file-editor.code')
- editor.click
- editor.send_keys 'Touch water with paw then recoil in horror chase dog then
- run away chase the pig around the house eat owner\'s food, and knock
- dish off table head butt cant eat out of my own dish. Cat is love, cat
- is life rub face on everything poop on grasses so meow. Playing with
- balls of wool flee in terror at cucumber discovered on floor run in
- circles tuxedo cats always looking dapper, but attack dog, run away
- and pretend to be victim so all of a sudden cat goes crazy, yet chase
- laser. Make muffins sit in window and stare ooo, a bird! yum lick yarn
- hanging out of own butt jump off balcony, onto stranger\'s head yet
- chase laser. Purr for no reason stare at ceiling hola te quiero.'.squish
+ page.within('.file-editor.code') do
+ find('.ace_text-input', visible: false).send_keys 'Touch water with paw then recoil in horror chase dog then
+ run away chase the pig around the house eat owner\'s food, and knock
+ dish off table head butt cant eat out of my own dish. Cat is love, cat
+ is life rub face on everything poop on grasses so meow. Playing with
+ balls of wool flee in terror at cucumber discovered on floor run in
+ circles tuxedo cats always looking dapper, but attack dog, run away
+ and pretend to be victim so all of a sudden cat goes crazy, yet chase
+ laser. Make muffins sit in window and stare ooo, a bird! yum lick yarn
+ hanging out of own butt jump off balcony, onto stranger\'s head yet
+ chase laser. Purr for no reason stare at ceiling hola te quiero.'.squish
+ end
end
let(:toggle_button) { find('.soft-wrap-toggle') }
@@ -36,6 +36,6 @@ feature 'User uses soft wrap whilst editing file', :js do
end
def get_content_width
- find('.ace_content')[:style].slice!(/width: \d+/).slice!(/\d+/)
+ find('.ace_content')[:style].slice!(/width: \d+/).slice!(/\d+/).to_i
end
end
diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb
index 05776c50f9d..461aa39d0ad 100644
--- a/spec/features/projects/import_export/export_file_spec.rb
+++ b/spec/features/projects/import_export/export_file_spec.rb
@@ -41,7 +41,7 @@ feature 'Import/Export - project export integration test', :js do
expect(page).to have_content('Export project')
- click_link 'Export project'
+ find(:link, 'Export project').send_keys(:return)
visit edit_project_path(project)
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index 026aa03f7cf..af125e1b9d3 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -94,6 +94,6 @@ feature 'Import/Export - project import integration test', :js do
end
def click_import_project_tab
- find('#import-project-tab').trigger('click')
+ find('#import-project-tab').click
end
end
diff --git a/spec/features/projects/import_export/namespace_export_file_spec.rb b/spec/features/projects/import_export/namespace_export_file_spec.rb
index b6a7c3cdcdb..e76bc6f1220 100644
--- a/spec/features/projects/import_export/namespace_export_file_spec.rb
+++ b/spec/features/projects/import_export/namespace_export_file_spec.rb
@@ -52,7 +52,7 @@ feature 'Import/Export - Namespace export file cleanup', :js do
expect(page).to have_content('Export project')
- click_link 'Export project'
+ find(:link, 'Export project').send_keys(:return)
visit edit_project_path(project)
diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb
index 21c9acc7ac0..5d9208ebadd 100644
--- a/spec/features/projects/jobs/user_browses_job_spec.rb
+++ b/spec/features/projects/jobs/user_browses_job_spec.rb
@@ -21,12 +21,12 @@ describe 'User browses a job', :js do
expect(page).to have_content("Job ##{build.id}")
expect(page).to have_css('#build-trace')
- click_link('Erase')
+ accept_confirm { click_link('Erase') }
+ expect(page).to have_no_css('.artifacts')
expect(build).not_to have_trace
expect(build.artifacts_file.exists?).to be_falsy
expect(build.artifacts_metadata.exists?).to be_falsy
- expect(page).to have_no_css('.artifacts')
page.within('.erased') do
expect(page).to have_content('Job has been erased')
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index b095c3e6f7b..c2a0d2395a9 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -380,7 +380,6 @@ feature 'Jobs' do
end
it 'loads the page and shows all needed controls' do
- expect(page.status_code).to eq(200)
expect(page).to have_content 'Retry'
end
end
@@ -392,11 +391,10 @@ feature 'Jobs' do
job.run!
visit project_job_path(project, job)
find('.js-cancel-job').click()
- find('.js-retry-button').trigger('click')
+ find('.js-retry-button').click
end
it 'shows the right status and buttons', :js do
- expect(page).to have_gitlab_http_status(200)
page.within('aside.right-sidebar') do
expect(page).to have_content 'Cancel'
end
@@ -443,28 +441,30 @@ feature 'Jobs' do
context 'access source' do
context 'job from project' do
before do
- Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' }
job.run!
- visit project_job_path(project, job)
- find('.js-raw-link-controller').click()
end
it 'sends the right headers' do
- expect(page.status_code).to eq(200)
- expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8')
- expect(page.response_headers['X-Sendfile']).to eq(job.trace.send(:current_path))
+ requests = inspect_requests(inject_headers: { 'X-Sendfile-Type' => 'X-Sendfile' }) do
+ visit raw_project_job_path(project, job)
+ end
+
+ expect(requests.first.status_code).to eq(200)
+ expect(requests.first.response_headers['Content-Type']).to eq('text/plain; charset=utf-8')
+ expect(requests.first.response_headers['X-Sendfile']).to eq(job.trace.send(:current_path))
end
end
context 'job from other project' do
before do
- Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' }
job2.run!
- visit raw_project_job_path(project, job2)
end
it 'sends the right headers' do
- expect(page.status_code).to eq(404)
+ requests = inspect_requests(inject_headers: { 'X-Sendfile-Type' => 'X-Sendfile' }) do
+ visit raw_project_job_path(project, job2)
+ end
+ expect(requests.first.status_code).to eq(404)
end
end
end
@@ -473,8 +473,6 @@ feature 'Jobs' do
let(:existing_file) { Tempfile.new('existing-trace-file').path }
before do
- Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' }
-
job.run!
end
@@ -483,16 +481,14 @@ feature 'Jobs' do
allow_any_instance_of(Gitlab::Ci::Trace)
.to receive(:paths)
.and_return([existing_file])
-
- visit project_job_path(project, job)
-
- find('.js-raw-link-controller').click
end
it 'sends the right headers' do
- expect(page.status_code).to eq(200)
- expect(page.response_headers['Content-Type']).to eq('text/plain; charset=utf-8')
- expect(page.response_headers['X-Sendfile']).to eq(existing_file)
+ requests = inspect_requests(inject_headers: { 'X-Sendfile-Type' => 'X-Sendfile' }) do
+ visit raw_project_job_path(project, job)
+ end
+ expect(requests.first.response_headers['Content-Type']).to eq('text/plain; charset=utf-8')
+ expect(requests.first.response_headers['X-Sendfile']).to eq(existing_file)
end
end
diff --git a/spec/features/projects/members/groups_with_access_list_spec.rb b/spec/features/projects/members/groups_with_access_list_spec.rb
index b1053982eee..7f067aadec6 100644
--- a/spec/features/projects/members/groups_with_access_list_spec.rb
+++ b/spec/features/projects/members/groups_with_access_list_spec.rb
@@ -31,6 +31,7 @@ feature 'Projects > Members > Groups with access list', :js do
tomorrow = Date.today + 3
fill_in "member_expires_at_#{group.id}", with: tomorrow.strftime("%F")
+ find('body').click
wait_for_requests
page.within(find('li.group_member')) do
@@ -40,7 +41,7 @@ feature 'Projects > Members > Groups with access list', :js do
scenario 'deletes group link' do
page.within(first('.group_member')) do
- find('.btn-remove').click
+ accept_confirm { find('.btn-remove').click }
end
wait_for_requests
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 5f7b4ee0e77..0f88f4cb1e8 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
@@ -20,7 +20,7 @@ feature 'Projects > Members > Master adds member with expiration date', :js do
page.within '.users-project-form' do
select2(new_member.id, from: '#user_ids', multiple: true)
- fill_in 'expires_at', with: date.to_s(:medium)
+ fill_in 'expires_at', with: date.to_s(:medium) + "\n"
click_on 'Add to project'
end
@@ -37,7 +37,7 @@ feature 'Projects > Members > Master adds member with expiration date', :js do
visit project_project_members_path(project)
page.within "#project_member_#{new_member.project_members.first.id}" do
- find('.js-access-expiration-date').set date.to_s(:medium)
+ find('.js-access-expiration-date').set date.to_s(:medium) + "\n"
wait_for_requests
expect(page).to have_content('Expires in 3 days')
end
diff --git a/spec/features/projects/members/share_with_group_spec.rb b/spec/features/projects/members/share_with_group_spec.rb
index 63b5df5a8f5..3198798306c 100644
--- a/spec/features/projects/members/share_with_group_spec.rb
+++ b/spec/features/projects/members/share_with_group_spec.rb
@@ -41,7 +41,7 @@ feature 'Project > Members > Share with Group', :js do
select2 group_to_share_with.id, from: '#link_group_id'
page.find('body').click
- find('.btn-create').trigger('click')
+ find('.btn-create').click
page.within('.project-members-groups') do
expect(page).to have_content(group_to_share_with.name)
@@ -123,7 +123,7 @@ feature 'Project > Members > Share with Group', :js do
fill_in 'expires_at_groups', with: (Time.now + 4.5.days).strftime('%Y-%m-%d')
page.find('body').click
- find('.btn-create').trigger('click')
+ find('.btn-create').click
end
scenario 'the group link shows the expiration time with a warning class' do
diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb
index 0fbe1ddb2a5..4eb36156812 100644
--- a/spec/features/projects/members/user_requests_access_spec.rb
+++ b/spec/features/projects/members/user_requests_access_spec.rb
@@ -60,7 +60,7 @@ feature 'Projects > Members > User requests access', :js do
expect(project.requesters.exists?(user_id: user)).to be_truthy
- click_link 'Withdraw Access Request'
+ accept_confirm { click_link 'Withdraw Access Request' }
expect(project.requesters.exists?(user_id: user)).to be_falsey
expect(page).to have_content 'Your access request to the project has been withdrawn.'
diff --git a/spec/features/projects/merge_requests/user_comments_on_diff_spec.rb b/spec/features/projects/merge_requests/user_comments_on_diff_spec.rb
index f34302f25f8..e3f90a78cb5 100644
--- a/spec/features/projects/merge_requests/user_comments_on_diff_spec.rb
+++ b/spec/features/projects/merge_requests/user_comments_on_diff_spec.rb
@@ -31,7 +31,7 @@ describe 'User comments on a diff', :js do
page.within('.files > div:nth-child(3)') do
expect(page).to have_content('Line is wrong')
- find('.js-toggle-diff-comments').trigger('click')
+ find('.js-toggle-diff-comments').click
expect(page).not_to have_content('Line is wrong')
end
@@ -64,7 +64,7 @@ describe 'User comments on a diff', :js do
# Hide the comment.
page.within('.files > div:nth-child(3)') do
- find('.js-toggle-diff-comments').trigger('click')
+ find('.js-toggle-diff-comments').click
expect(page).not_to have_content('Line is wrong')
end
@@ -77,7 +77,7 @@ describe 'User comments on a diff', :js do
# Show the comment.
page.within('.files > div:nth-child(3)') do
- find('.js-toggle-diff-comments').trigger('click')
+ find('.js-toggle-diff-comments').click
end
# Now both the comments should be shown.
@@ -90,6 +90,7 @@ describe 'User comments on a diff', :js do
end
# Check the same comments in the side-by-side view.
+ execute_script("window.scrollTo(0,0);")
click_link('Side-by-side')
wait_for_requests
@@ -153,11 +154,11 @@ describe 'User comments on a diff', :js do
find('.more-actions').click
find('.more-actions .dropdown-menu li', match: :first)
- find('.js-note-delete').click
+ accept_confirm { find('.js-note-delete').click }
end
page.within('.merge-request-tabs') do
- find('.notes-tab').trigger('click')
+ find('.notes-tab').click
end
wait_for_requests
diff --git a/spec/features/projects/merge_requests/user_edits_merge_request_spec.rb b/spec/features/projects/merge_requests/user_edits_merge_request_spec.rb
index f6e3997383f..3d19a2923b9 100644
--- a/spec/features/projects/merge_requests/user_edits_merge_request_spec.rb
+++ b/spec/features/projects/merge_requests/user_edits_merge_request_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe 'User edits a merge request', :js do
+ include Select2Helper
+
let(:project) { create(:project, :repository) }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:user) { create(:user) }
@@ -15,8 +17,7 @@ describe 'User edits a merge request', :js do
it 'changes the target branch' do
expect(page).to have_content('Target branch')
- first('.target_branch').click
- select('merge-test', from: 'merge_request_target_branch', visible: false)
+ select2('merge-test', from: '#merge_request_target_branch')
click_button('Save changes')
expect(page).to have_content("Request to merge #{merge_request.source_branch} into merge-test")
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index 8e11cb94350..6f097ad16c7 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -15,7 +15,7 @@ feature 'New project' do
expect(page).to have_content('Project path')
expect(page).to have_content('Project name')
- find('#import-project-tab').trigger('click')
+ find('#import-project-tab').click
expect(page).to have_link('GitHub')
expect(page).to have_link('Bitbucket')
@@ -137,7 +137,7 @@ feature 'New project' do
context 'Import project options', :js do
before do
visit new_project_path
- find('#import-project-tab').trigger('click')
+ find('#import-project-tab').click
end
context 'from git repository url' do
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index 24b335a7068..fa2f7a1fd78 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -54,7 +54,7 @@ feature 'Pipeline Schedules', :js do
end
it 'deletes the pipeline' do
- click_link 'Delete'
+ accept_confirm { click_link 'Delete' }
expect(page).not_to have_css(".pipeline-schedule-table-row")
end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index acbc5b046e6..b8fa1a54c24 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -67,13 +67,13 @@ describe 'Pipeline', :js do
it 'shows a running icon and a cancel action for the running build' do
page.within('#ci-badge-deploy') do
expect(page).to have_selector('.js-ci-status-icon-running')
- expect(page).to have_selector('.js-icon-action-cancel')
+ expect(page).to have_selector('.js-icon-cancel')
expect(page).to have_content('deploy')
end
end
it 'should be possible to cancel the running build' do
- find('#ci-badge-deploy .ci-action-icon-container').trigger('click')
+ find('#ci-badge-deploy .ci-action-icon-container').click
expect(page).not_to have_content('Cancel running')
end
@@ -86,13 +86,13 @@ describe 'Pipeline', :js do
expect(page).to have_content('build')
end
- page.within('#ci-badge-build .ci-action-icon-container') do
- expect(page).to have_selector('.js-icon-action-retry')
+ page.within('#ci-badge-build .ci-action-icon-container.js-icon-retry') do
+ expect(page).to have_selector('svg')
end
end
it 'should be possible to retry the success job' do
- find('#ci-badge-build .ci-action-icon-container').trigger('click')
+ find('#ci-badge-build .ci-action-icon-container').click
expect(page).not_to have_content('Retry job')
end
@@ -105,13 +105,13 @@ describe 'Pipeline', :js do
expect(page).to have_content('test')
end
- page.within('#ci-badge-test .ci-action-icon-container') do
- expect(page).to have_selector('.js-icon-action-retry')
+ page.within('#ci-badge-test .ci-action-icon-container.js-icon-retry') do
+ expect(page).to have_selector('svg')
end
end
it 'should be possible to retry the failed build' do
- find('#ci-badge-test .ci-action-icon-container').trigger('click')
+ find('#ci-badge-test .ci-action-icon-container').click
expect(page).not_to have_content('Retry job')
end
@@ -124,13 +124,13 @@ describe 'Pipeline', :js do
expect(page).to have_content('manual')
end
- page.within('#ci-badge-manual-build .ci-action-icon-container') do
- expect(page).to have_selector('.js-icon-action-play')
+ page.within('#ci-badge-manual-build .ci-action-icon-container.js-icon-play') do
+ expect(page).to have_selector('svg')
end
end
it 'should be possible to play the manual job' do
- find('#ci-badge-manual-build .ci-action-icon-container').trigger('click')
+ find('#ci-badge-manual-build .ci-action-icon-container').click
expect(page).not_to have_content('Play job')
end
@@ -165,7 +165,7 @@ describe 'Pipeline', :js do
context 'when retrying' do
before do
- find('.js-retry-button').trigger('click')
+ find('.js-retry-button').click
end
it { expect(page).not_to have_content('Retry') }
@@ -231,7 +231,7 @@ describe 'Pipeline', :js do
context 'when retrying' do
before do
- find('.js-retry-button').trigger('click')
+ find('.js-retry-button').click
end
it { expect(page).not_to have_content('Retry') }
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index ae888fd4343..fc689bbb486 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -103,7 +103,7 @@ describe 'Pipelines', :js do
context 'when canceling' do
before do
- find('.js-pipelines-cancel-button').click
+ accept_confirm { find('.js-pipelines-cancel-button').click }
wait_for_requests
end
@@ -232,7 +232,7 @@ describe 'Pipelines', :js do
context 'when canceling' do
before do
- find('.js-pipelines-cancel-button').trigger('click')
+ accept_alert { find('.js-pipelines-cancel-button').click }
end
it 'indicates that pipeline was canceled' do
@@ -345,14 +345,14 @@ describe 'Pipelines', :js do
context 'when clicking a stage badge' do
it 'should open a dropdown' do
- find('.js-builds-dropdown-button').trigger('click')
+ find('.js-builds-dropdown-button').click
expect(page).to have_link build.name
end
it 'should be possible to cancel pending build' do
- find('.js-builds-dropdown-button').trigger('click')
- find('a.js-ci-action-icon').trigger('click')
+ find('.js-builds-dropdown-button').click
+ find('a.js-ci-action-icon').click
expect(page).to have_content('canceled')
expect(build.reload).to be_canceled
@@ -361,11 +361,16 @@ describe 'Pipelines', :js do
context 'dropdown jobs list' do
it 'should keep the dropdown open when the user ctr/cmd + clicks in the job name' do
- find('.js-builds-dropdown-button').trigger('click')
-
- execute_script('var e = $.Event("keydown", { keyCode: 64 }); $("body").trigger(e);')
-
- find('.mini-pipeline-graph-dropdown-item').trigger('click')
+ find('.js-builds-dropdown-button').click
+ dropdown_item = find('.mini-pipeline-graph-dropdown-item').native
+
+ %i(alt control).each do |meta_key|
+ page.driver.browser.action
+ .key_down(meta_key)
+ .click(dropdown_item)
+ .key_up(meta_key)
+ .perform
+ end
expect(page).to have_selector('.js-ci-action-icon')
end
@@ -525,7 +530,6 @@ describe 'Pipelines', :js do
let(:project) { create(:project, :public, :repository) }
it { expect(page).to have_content 'Build with confidence' }
- it { expect(page).to have_gitlab_http_status(:success) }
end
context 'when project is private' do
diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb
index 50c0bfd580d..33ccbc1a29f 100644
--- a/spec/features/projects/ref_switcher_spec.rb
+++ b/spec/features/projects/ref_switcher_spec.rb
@@ -6,7 +6,7 @@ feature 'Ref switcher', :js do
before do
project.team << [user, :master]
- page.driver.set_cookie('new_repo', 'true')
+ set_cookie('new_repo', 'true')
sign_in(user)
visit project_tree_path(project, 'master')
end
diff --git a/spec/features/projects/services/user_activates_jira_spec.rb b/spec/features/projects/services/user_activates_jira_spec.rb
index 0a86292ae6c..ac78b1dfb1c 100644
--- a/spec/features/projects/services/user_activates_jira_spec.rb
+++ b/spec/features/projects/services/user_activates_jira_spec.rb
@@ -65,7 +65,7 @@ describe 'User activates Jira', :js do
expect(find('.flash-container-page')).to have_content 'Test failed. message'
expect(find('.flash-container-page')).to have_content 'Save anyway'
- find('.flash-alert .flash-action').trigger('click')
+ find('.flash-alert .flash-action').click
wait_for_requests
expect(page).to have_content('JIRA activated.')
diff --git a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb
index 95d5e8b14b9..6f057137867 100644
--- a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb
+++ b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb
@@ -76,7 +76,7 @@ feature 'Setup Mattermost slash commands', :js do
select_element = find('#mattermost_team_id')
selected_option = select_element.find('option[selected]')
- expect(select_element['disabled']).to be(true)
+ expect(select_element['disabled']).to eq("true")
expect(selected_option).to have_content(team_name.to_s)
end
@@ -104,7 +104,7 @@ feature 'Setup Mattermost slash commands', :js do
select_element = find('#mattermost_team_id')
- expect(select_element['disabled']).to be(false)
+ expect(select_element['disabled']).to be_falsey
expect(select_element.all('option').count).to eq(3)
end
@@ -122,7 +122,7 @@ feature 'Setup Mattermost slash commands', :js do
click_link 'Add to Mattermost'
- expect(find('input[type="submit"]')['disabled']).not_to be(true)
+ expect(find('input[type="submit"]')['disabled']).not_to eq("true")
end
it 'disables the submit button if the required fields are not provided', :js do
@@ -132,7 +132,7 @@ feature 'Setup Mattermost slash commands', :js do
fill_in('mattermost_trigger', with: '')
- expect(find('input[type="submit"]')['disabled']).to be(true)
+ expect(find('input[type="submit"]')['disabled']).to eq("true")
end
def stub_teams(count: 0)
diff --git a/spec/features/projects/settings/forked_project_settings_spec.rb b/spec/features/projects/settings/forked_project_settings_spec.rb
new file mode 100644
index 00000000000..28954a4fb40
--- /dev/null
+++ b/spec/features/projects/settings/forked_project_settings_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+feature 'Settings for a forked project', :js do
+ include ProjectForksHelper
+ let(:user) { create(:user) }
+ let(:original_project) { create(:project) }
+ let(:forked_project) { fork_project(original_project, user) }
+
+ before do
+ original_project.add_master(user)
+ forked_project.add_master(user)
+ sign_in(user)
+ end
+
+ shared_examples 'project settings for a forked projects' do
+ it 'allows deleting the link to the forked project' do
+ visit edit_project_path(forked_project)
+
+ click_button 'Remove fork relationship'
+
+ wait_for_requests
+
+ fill_in('confirm_name_input', with: forked_project.name)
+ click_button('Confirm')
+
+ expect(page).to have_content('The fork relationship has been removed.')
+ expect(forked_project.reload.forked?).to be_falsy
+ end
+ end
+
+ it_behaves_like 'project settings for a forked projects'
+
+ context 'when the original project is deleted' do
+ before do
+ original_project.destroy!
+ end
+
+ it_behaves_like 'project settings for a forked projects'
+ end
+end
diff --git a/spec/features/projects/settings/merge_requests_settings_spec.rb b/spec/features/projects/settings/merge_requests_settings_spec.rb
index b1ec556bf16..ac76c30cc7c 100644
--- a/spec/features/projects/settings/merge_requests_settings_spec.rb
+++ b/spec/features/projects/settings/merge_requests_settings_spec.rb
@@ -21,7 +21,7 @@ feature 'Project settings > Merge Requests', :js do
within('.sharing-permissions-form') do
find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .project-feature-toggle').click
- click_on('Save changes')
+ find('input[value="Save changes"]').send_keys(:return)
end
expect(page).not_to have_content('Only allow merge requests to be merged if the pipeline succeeds')
@@ -41,7 +41,7 @@ feature 'Project settings > Merge Requests', :js do
within('.sharing-permissions-form') do
find('.project-feature-controls[data-for="project[project_feature_attributes][builds_access_level]"] .project-feature-toggle').click
- click_on('Save changes')
+ find('input[value="Save changes"]').send_keys(:return)
end
expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds')
@@ -62,7 +62,7 @@ feature 'Project settings > Merge Requests', :js do
within('.sharing-permissions-form') do
find('.project-feature-controls[data-for="project[project_feature_attributes][merge_requests_access_level]"] .project-feature-toggle').click
- click_on('Save changes')
+ find('input[value="Save changes"]').send_keys(:return)
end
expect(page).to have_content('Only allow merge requests to be merged if the pipeline succeeds')
diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb
index de8fbb15b9c..ea8f997409d 100644
--- a/spec/features/projects/settings/pipelines_settings_spec.rb
+++ b/spec/features/projects/settings/pipelines_settings_spec.rb
@@ -22,7 +22,7 @@ feature "Pipelines settings" do
context 'for master' do
given(:role) { :master }
- scenario 'be allowed to change', :js do
+ scenario 'be allowed to change' do
fill_in('Test coverage parsing', with: 'coverage_regex')
click_on 'Save changes'
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index a4fefb0d0e7..e2a5619c22b 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -34,7 +34,6 @@ feature 'Repository settings' do
visit project_settings_repository_path(project)
- expect(page.status_code).to eq(200)
expect(page).to have_content('private_deploy_key')
expect(page).to have_content('public_deploy_key')
end
@@ -86,7 +85,7 @@ feature 'Repository settings' do
project.deploy_keys << private_deploy_key
visit project_settings_repository_path(project)
- find('li', text: private_deploy_key.title).click_button('Remove')
+ accept_confirm { find('li', text: private_deploy_key.title).click_button('Remove') }
expect(page).not_to have_content(private_deploy_key.title)
end
diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb
index 3e79dba3f19..e4215291f99 100644
--- a/spec/features/projects/snippets/create_snippet_spec.rb
+++ b/spec/features/projects/snippets/create_snippet_spec.rb
@@ -10,7 +10,7 @@ feature 'Create Snippet', :js do
fill_in 'project_snippet_title', with: 'My Snippet Title'
fill_in 'project_snippet_description', with: 'My Snippet **Description**'
page.within('.file-editor') do
- find('.ace_editor').native.send_keys('Hello World!')
+ find('.ace_text-input', visible: false).send_keys('Hello World!')
end
end
@@ -59,7 +59,7 @@ feature 'Create Snippet', :js do
fill_form
dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
- click_button('Create snippet')
+ find("input[value='Create snippet']").send_keys(:return)
wait_for_requests
expect(page).to have_content('My Snippet Title')
diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb
index 4c1fa5a666e..8ee7b9cf015 100644
--- a/spec/features/projects/tree/create_directory_spec.rb
+++ b/spec/features/projects/tree/create_directory_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
feature 'Multi-file editor new directory', :js do
- include WaitForRequests
-
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
@@ -10,7 +8,7 @@ feature 'Multi-file editor new directory', :js do
project.add_master(user)
sign_in(user)
- page.driver.set_cookie('new_repo', 'true')
+ set_cookie('new_repo', 'true')
visit project_tree_path(project, :master)
@@ -32,7 +30,6 @@ feature 'Multi-file editor new directory', :js do
click_button('Commit 1 file')
- expect(page).to have_content('Your changes have been committed')
expect(page).to have_selector('td', text: 'commit message')
click_link('foldername')
diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb
index ed3b52a5790..1e2de0711b8 100644
--- a/spec/features/projects/tree/create_file_spec.rb
+++ b/spec/features/projects/tree/create_file_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
feature 'Multi-file editor new file', :js do
- include WaitForRequests
-
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
@@ -10,7 +8,7 @@ feature 'Multi-file editor new file', :js do
project.add_master(user)
sign_in(user)
- page.driver.set_cookie('new_repo', 'true')
+ set_cookie('new_repo', 'true')
visit project_tree_path(project, :master)
@@ -32,7 +30,6 @@ feature 'Multi-file editor new file', :js do
click_button('Commit 1 file')
- expect(page).to have_content('Your changes have been committed')
expect(page).to have_selector('td', text: 'commit message')
end
end
diff --git a/spec/features/projects/tree/upload_file_spec.rb b/spec/features/projects/tree/upload_file_spec.rb
index 7dbe4fd0aa5..8439bb5a69e 100644
--- a/spec/features/projects/tree/upload_file_spec.rb
+++ b/spec/features/projects/tree/upload_file_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
feature 'Multi-file editor upload file', :js do
- include WaitForRequests
-
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:txt_file) { File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt') }
@@ -12,7 +10,7 @@ feature 'Multi-file editor upload file', :js do
project.add_master(user)
sign_in(user)
- page.driver.set_cookie('new_repo', 'true')
+ set_cookie('new_repo', 'true')
visit project_tree_path(project, :master)
@@ -29,7 +27,7 @@ feature 'Multi-file editor upload file', :js do
find('.add-to-tree').click
expect(page).to have_selector('.repo-tab', text: 'doc_sample.txt')
- expect(page).to have_content(File.open(txt_file, &:readline))
+ expect(find('.blob-editor-container .lines-content')['innerText']).to have_content(File.open(txt_file, &:readline))
end
it 'uploads image file' do
diff --git a/spec/features/projects/user_browses_files_spec.rb b/spec/features/projects/user_browses_files_spec.rb
index f43b11c9485..f5e4d7f5130 100644
--- a/spec/features/projects/user_browses_files_spec.rb
+++ b/spec/features/projects/user_browses_files_spec.rb
@@ -175,10 +175,11 @@ describe 'User browses files' do
page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message')
+ fill_in(:branch_name, with: 'new_branch_name', visible: true)
+ click_button('Upload file')
end
- fill_in(:branch_name, with: 'new_branch_name', visible: true)
- click_button('Upload file')
+ wait_for_all_requests
visit(project_blob_path(project, 'new_branch_name/logo_sample.svg'))
diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb
index d63cbe578d8..337baaf4dcd 100644
--- a/spec/features/projects/wiki/markdown_preview_spec.rb
+++ b/spec/features/projects/wiki/markdown_preview_spec.rb
@@ -18,13 +18,13 @@ feature 'Projects > Wiki > User previews markdown changes', :js do
sign_in(user)
visit project_path(project)
- find('.shortcuts-wiki').trigger('click')
+ find('.shortcuts-wiki').click
end
context "while creating a new wiki page" do
context "when there are no spaces or hyphens in the page name" do
it "rewrites relative links as expected" do
- find('.add-new-wiki').trigger('click')
+ find('.add-new-wiki').click
page.within '#modal-new-wiki' do
fill_in :new_wiki_path, with: 'a/b/c/d'
click_button 'Create page'
@@ -91,7 +91,7 @@ feature 'Projects > Wiki > User previews markdown changes', :js do
context "while editing a wiki page" do
def create_wiki_page(path)
- find('.add-new-wiki').trigger('click')
+ find('.add-new-wiki').click
page.within '#modal-new-wiki' do
fill_in :new_wiki_path, with: path
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 e72b7dc0dd5..4a9d1cb87e1 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -77,14 +77,14 @@ describe 'User creates wiki page' do
[stem]
++++
- \sqrt{4} = 2
+ \\sqrt{4} = 2
++++
another part
[latexmath]
++++
- \beta_x \gamma
+ \\beta_x \\gamma
++++
stem:[2+2] is 4
diff --git a/spec/features/projects/wiki/user_views_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
index 89f6901eb01..ff325aeadd3 100644
--- a/spec/features/projects/wiki/user_views_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
@@ -138,7 +138,7 @@ describe 'User views a wiki page' do
it 'opens a default wiki page', :js do
visit(project_path(project))
- find('.shortcuts-wiki').trigger('click')
+ find('.shortcuts-wiki').click
expect(page).to have_content('Home · Create Page')
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 3b01ed442bf..63e6051b571 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -13,8 +13,8 @@ feature 'Project' do
end
it "allows creation from templates", :js do
- find('#create-from-template-tab').trigger('click')
- find("##{template.name}").trigger('click')
+ find('#create-from-template-tab').click
+ find("label[for=#{template.name}]").click
fill_in("project_path", with: template.name)
page.within '#content-body' do
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index 2ab1eda90f1..a4084818284 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -48,7 +48,7 @@ feature 'Protected Branches', :js do
expect(page).to have_content('fix')
expect(find('.all-branches')).to have_selector('li', count: 1)
- page.find('[data-target="#modal-delete-branch"]').trigger(:click)
+ page.find('[data-target="#modal-delete-branch"]').click
expect(page).to have_css('.js-delete-branch[disabled]')
fill_in 'delete_branch_input', with: 'fix'
@@ -67,9 +67,9 @@ feature 'Protected Branches', :js do
form = '.js-new-protected-branch'
within form do
- find(".js-allowed-to-merge").trigger('click')
+ find(".js-allowed-to-merge").click
click_link 'No one'
- find(".js-allowed-to-push").trigger('click')
+ find(".js-allowed-to-push").click
click_link 'Developers + Masters'
end
@@ -171,7 +171,7 @@ feature 'Protected Branches', :js do
end
def set_protected_branch_name(branch_name)
- find(".js-protected-branch-select").trigger('click')
+ find(".js-protected-branch-select").click
find(".dropdown-input-field").set(branch_name)
click_on("Create wildcard #{branch_name}")
end
diff --git a/spec/features/raven_js_spec.rb b/spec/features/raven_js_spec.rb
index b1f51959d54..74890c86047 100644
--- a/spec/features/raven_js_spec.rb
+++ b/spec/features/raven_js_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'RavenJS', :js do
+feature 'RavenJS' do
let(:raven_path) { '/raven.bundle.js' }
it 'should not load raven if sentry is disabled' do
@@ -18,6 +18,8 @@ feature 'RavenJS', :js do
end
def has_requested_raven
- page.driver.network_traffic.one? {|request| request.url.end_with?(raven_path)}
+ page.all('script', visible: false).one? do |elm|
+ elm[:src] =~ /#{raven_path}$/
+ end
end
end
diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb
index 0ed797a62ea..77212fb105b 100644
--- a/spec/features/search/user_searches_for_code_spec.rb
+++ b/spec/features/search/user_searches_for_code_spec.rb
@@ -32,14 +32,14 @@ describe 'User searches for code' do
include_examples 'top right search form'
it 'finds code' do
- find('.js-search-project-dropdown').trigger('click')
+ find('.js-search-project-dropdown').click
page.within('.project-filter') do
click_link(project.name_with_namespace)
end
fill_in('dashboard_search', with: 'rspec')
- find('.btn-search').trigger('click')
+ find('.btn-search').click
page.within('.results') do
expect(find(:css, '.search-results')).to have_content('Update capybara, rspec-rails, poltergeist to recent versions')
diff --git a/spec/features/search/user_searches_for_issues_spec.rb b/spec/features/search/user_searches_for_issues_spec.rb
index 630a81b1c5e..ef9553f2a91 100644
--- a/spec/features/search/user_searches_for_issues_spec.rb
+++ b/spec/features/search/user_searches_for_issues_spec.rb
@@ -18,7 +18,7 @@ describe 'User searches for issues', :js do
it 'finds an issue' do
fill_in('dashboard_search', with: issue1.title)
- find('.btn-search').trigger('click')
+ find('.btn-search').click
page.within('.search-filter') do
click_link('Issues')
@@ -31,14 +31,14 @@ describe 'User searches for issues', :js do
context 'when on a project page' do
it 'finds an issue' do
- find('.js-search-project-dropdown').trigger('click')
+ find('.js-search-project-dropdown').click
page.within('.project-filter') do
click_link(project.name_with_namespace)
end
fill_in('dashboard_search', with: issue1.title)
- find('.btn-search').trigger('click')
+ find('.btn-search').click
page.within('.search-filter') do
click_link('Issues')
@@ -62,7 +62,7 @@ describe 'User searches for issues', :js do
it 'finds an issue' do
fill_in('dashboard_search', with: issue1.title)
- find('.btn-search').trigger('click')
+ find('.btn-search').click
page.within('.search-filter') do
click_link('Issues')
diff --git a/spec/features/search/user_searches_for_merge_requests_spec.rb b/spec/features/search/user_searches_for_merge_requests_spec.rb
index 116256682f4..3b6739aecbd 100644
--- a/spec/features/search/user_searches_for_merge_requests_spec.rb
+++ b/spec/features/search/user_searches_for_merge_requests_spec.rb
@@ -17,7 +17,7 @@ describe 'User searches for merge requests', :js do
it 'finds a merge request' do
fill_in('dashboard_search', with: merge_request1.title)
- find('.btn-search').trigger('click')
+ find('.btn-search').click
page.within('.search-filter') do
click_link('Merge requests')
@@ -30,14 +30,14 @@ describe 'User searches for merge requests', :js do
context 'when on a project page' do
it 'finds a merge request' do
- find('.js-search-project-dropdown').trigger('click')
+ find('.js-search-project-dropdown').click
page.within('.project-filter') do
click_link(project.name_with_namespace)
end
fill_in('dashboard_search', with: merge_request1.title)
- find('.btn-search').trigger('click')
+ find('.btn-search').click
page.within('.search-filter') do
click_link('Merge requests')
diff --git a/spec/features/search/user_searches_for_milestones_spec.rb b/spec/features/search/user_searches_for_milestones_spec.rb
index 4fa9fe9ce8c..6e197aee498 100644
--- a/spec/features/search/user_searches_for_milestones_spec.rb
+++ b/spec/features/search/user_searches_for_milestones_spec.rb
@@ -17,7 +17,7 @@ describe 'User searches for milestones', :js do
it 'finds a milestone' do
fill_in('dashboard_search', with: milestone1.title)
- find('.btn-search').trigger('click')
+ find('.btn-search').click
page.within('.search-filter') do
click_link('Milestones')
@@ -30,14 +30,14 @@ describe 'User searches for milestones', :js do
context 'when on a project page' do
it 'finds a milestone' do
- find('.js-search-project-dropdown').trigger('click')
+ find('.js-search-project-dropdown').click
page.within('.project-filter') do
click_link(project.name_with_namespace)
end
fill_in('dashboard_search', with: milestone1.title)
- find('.btn-search').trigger('click')
+ find('.btn-search').click
page.within('.search-filter') do
click_link('Milestones')
diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb
index 1ea56479ecc..00af625dc86 100644
--- a/spec/features/search/user_searches_for_wiki_pages_spec.rb
+++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb
@@ -15,14 +15,14 @@ describe 'User searches for wiki pages', :js do
include_examples 'top right search form'
it 'finds a page' do
- find('.js-search-project-dropdown').trigger('click')
+ find('.js-search-project-dropdown').click
page.within('.project-filter') do
click_link(project.name_with_namespace)
end
fill_in('dashboard_search', with: 'content')
- find('.btn-search').trigger('click')
+ find('.btn-search').click
page.within('.search-filter') do
click_link('Wiki')
diff --git a/spec/features/search/user_uses_search_filters_spec.rb b/spec/features/search/user_uses_search_filters_spec.rb
index 95f3eb5e805..aa883c964d2 100644
--- a/spec/features/search/user_uses_search_filters_spec.rb
+++ b/spec/features/search/user_uses_search_filters_spec.rb
@@ -16,7 +16,7 @@ describe 'User uses search filters', :js do
context' when filtering by group' do
it 'shows group projects' do
- find('.js-search-group-dropdown').trigger('click')
+ find('.js-search-group-dropdown').click
wait_for_requests
@@ -27,7 +27,7 @@ describe 'User uses search filters', :js do
expect(find('.js-search-group-dropdown')).to have_content(group.name)
page.within('.project-filter') do
- find('.js-search-project-dropdown').trigger('click')
+ find('.js-search-project-dropdown').click
wait_for_requests
@@ -39,7 +39,7 @@ describe 'User uses search filters', :js do
context' when filtering by project' do
it 'shows a project' do
page.within('.project-filter') do
- find('.js-search-project-dropdown').trigger('click')
+ find('.js-search-project-dropdown').click
wait_for_requests
diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb
index bf79974b8c6..269351e55c9 100644
--- a/spec/features/snippets/notes_on_personal_snippets_spec.rb
+++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb
@@ -74,24 +74,21 @@ describe 'Comments on personal snippets', :js do
it 'should not have autocomplete' do
wait_for_requests
- request_count_before = page.driver.network_traffic.count
find('#note_note').native.send_keys('')
fill_in 'note[note]', with: '@'
wait_for_requests
- request_count_after = page.driver.network_traffic.count
# This selector probably won't be in place even if autocomplete was enabled
# but we want to make sure
expect(page).not_to have_selector('.atwho-view')
- expect(request_count_before).to eq(request_count_after)
end
end
context 'when editing a note' do
it 'changes the text' do
- find('.js-note-edit').trigger('click')
+ find('.js-note-edit').click
page.within('.current-note-edit-form') do
fill_in 'note[note]', with: 'new content'
@@ -113,7 +110,7 @@ describe 'Comments on personal snippets', :js do
open_more_actions_dropdown(snippet_notes[0])
page.within("#notes-list li#note_#{snippet_notes[0].id}") do
- click_on 'Delete comment'
+ accept_confirm { click_on 'Delete comment' }
end
wait_for_requests
diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb
index d732383a1e1..941765b7578 100644
--- a/spec/features/snippets/user_creates_snippet_spec.rb
+++ b/spec/features/snippets/user_creates_snippet_spec.rb
@@ -14,7 +14,7 @@ feature 'User creates snippet', :js do
fill_in 'personal_snippet_title', with: 'My Snippet Title'
fill_in 'personal_snippet_description', with: 'My Snippet **Description**'
page.within('.file-editor') do
- find('.ace_editor').native.send_keys 'Hello World!'
+ find('.ace_text-input', visible: false).send_keys 'Hello World!'
end
end
@@ -43,8 +43,8 @@ feature 'User creates snippet', :js do
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
expect(link).to match(%r{/uploads/-/system/temp/\h{32}/banana_sample\.gif\z})
- visit(link)
- expect(page.status_code).to eq(200)
+ reqs = inspect_requests { visit(link) }
+ expect(reqs.first.status_code).to eq(200)
end
end
@@ -61,8 +61,8 @@ feature 'User creates snippet', :js do
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
expect(link).to match(%r{/uploads/-/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z})
- visit(link)
- expect(page.status_code).to eq(200)
+ reqs = inspect_requests { visit(link) }
+ expect(reqs.first.status_code).to eq(200)
end
scenario 'validation fails for the first time' do
@@ -86,15 +86,15 @@ feature 'User creates snippet', :js do
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
expect(link).to match(%r{/uploads/-/system/personal_snippet/#{Snippet.last.id}/\h{32}/banana_sample\.gif\z})
- visit(link)
- expect(page.status_code).to eq(200)
+ reqs = inspect_requests { visit(link) }
+ expect(reqs.first.status_code).to eq(200)
end
scenario 'Authenticated user creates a snippet with + in filename' do
fill_in 'personal_snippet_title', with: 'My Snippet Title'
page.within('.file-editor') do
find(:xpath, "//input[@id='personal_snippet_file_name']").set 'snippet+file+name'
- find('.ace_editor').native.send_keys 'Hello World!'
+ find('.ace_text-input', visible: false).send_keys 'Hello World!'
end
click_button 'Create snippet'
diff --git a/spec/features/tags/master_creates_tag_spec.rb b/spec/features/tags/master_creates_tag_spec.rb
index 1455345bd56..1f8bd8d681e 100644
--- a/spec/features/tags/master_creates_tag_spec.rb
+++ b/spec/features/tags/master_creates_tag_spec.rb
@@ -63,7 +63,7 @@ feature 'Master creates tag' do
expect(ref_input.value).to eq 'master'
expect(find('.dropdown-toggle-text')).to have_content 'master'
- find('.js-branch-select').trigger('click')
+ find('.js-branch-select').click
expect(find('.dropdown-menu')).to have_content 'empty-branch'
end
diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb
index f5b3774122b..dfda664d673 100644
--- a/spec/features/tags/master_deletes_tag_spec.rb
+++ b/spec/features/tags/master_deletes_tag_spec.rb
@@ -64,7 +64,7 @@ feature 'Master deletes tag' do
def delete_first_tag
page.within('.content') do
- first('.btn-remove').click
+ accept_confirm { first('.btn-remove').click }
end
end
end
diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb
index 548d8372a07..bc472e74997 100644
--- a/spec/features/triggers_spec.rb
+++ b/spec/features/triggers_spec.rb
@@ -45,7 +45,7 @@ feature 'Triggers', :js do
visit project_settings_ci_cd_path(@project)
# See if edit page has correct descrption
- find('a[title="Edit"]').click
+ find('a[title="Edit"]').send_keys(:return)
expect(page.find('#trigger_description').value).to have_content 'trigger desc'
end
@@ -54,7 +54,7 @@ feature 'Triggers', :js do
visit project_settings_ci_cd_path(@project)
# See if edit page opens, then fill in new description and save
- find('a[title="Edit"]').click
+ find('a[title="Edit"]').send_keys(:return)
fill_in 'trigger_description', with: new_trigger_title
click_button 'Save trigger'
@@ -70,7 +70,7 @@ feature 'Triggers', :js do
visit project_settings_ci_cd_path(@project)
# See if the trigger can be edited and description is blank
- find('a[title="Edit"]').click
+ find('a[title="Edit"]').send_keys(:return)
expect(page.find('#trigger_description').value).to have_content ''
# See if trigger can be updated with description and saved successfully
@@ -94,12 +94,13 @@ feature 'Triggers', :js do
scenario 'take trigger ownership' do
# See if "Take ownership" on trigger works post trigger creation
- find('a.btn-trigger-take-ownership').click
page.accept_confirm do
- expect(page.find('.flash-notice')).to have_content 'Trigger was re-assigned.'
- expect(page.find('.triggers-list')).to have_content trigger_title
- expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
+ first(:link, "Take ownership").send_keys(:return)
end
+
+ expect(page.find('.flash-notice')).to have_content 'Trigger was re-assigned.'
+ expect(page.find('.triggers-list')).to have_content trigger_title
+ expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
end
end
@@ -116,11 +117,12 @@ feature 'Triggers', :js do
scenario 'revoke trigger' do
# See if "Revoke" on trigger works post trigger creation
- find('a.btn-trigger-revoke').click
page.accept_confirm do
- expect(page.find('.flash-notice')).to have_content 'Trigger removed'
- expect(page).to have_selector('p.settings-message.text-center.append-bottom-default')
+ find('a.btn-trigger-revoke').send_keys(:return)
end
+
+ expect(page.find('.flash-notice')).to have_content 'Trigger removed'
+ expect(page).to have_selector('p.settings-message.text-center.append-bottom-default')
end
end
diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb
index f3662cb184f..c9afef2a8de 100644
--- a/spec/features/u2f_spec.rb
+++ b/spec/features/u2f_spec.rb
@@ -79,7 +79,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
first_u2f_device = register_u2f_device
second_u2f_device = register_u2f_device(name: 'My other device')
- click_on "Delete", match: :first
+ accept_confirm { click_on "Delete", match: :first }
expect(page).to have_content('Successfully deleted')
expect(page.body).not_to match(first_u2f_device.name)
@@ -162,7 +162,6 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
@u2f_device.respond_to_u2f_authentication
- expect(page).to have_content('We heard back from your U2F device')
expect(page).to have_css('.sign-out-link', visible: false)
end
end
@@ -174,23 +173,10 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
@u2f_device.respond_to_u2f_authentication
- expect(page).to have_content('We heard back from your U2F device')
expect(page).to have_css('.sign-out-link', visible: false)
end
end
- it 'persists remember_me value via hidden field' do
- gitlab_sign_in(user, remember: true)
-
- @u2f_device.respond_to_u2f_authentication
- expect(page).to have_content('We heard back from your U2F device')
-
- within 'div#js-authenticate-u2f' do
- field = first('input#user_remember_me', visible: false)
- expect(field.value).to eq '1'
- end
- end
-
describe "when a given U2F device has already been registered by another user" do
describe "but not the current user" do
it "does not allow logging in with that particular device" do
@@ -205,7 +191,6 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
# Try authenticating user with the old U2F device
gitlab_sign_in(current_user)
@u2f_device.respond_to_u2f_authentication
- expect(page).to have_content('We heard back from your U2F device')
expect(page).to have_content('Authentication via U2F device failed')
end
end
@@ -223,7 +208,6 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
# Try authenticating user with the same U2F device
gitlab_sign_in(current_user)
@u2f_device.respond_to_u2f_authentication
- expect(page).to have_content('We heard back from your U2F device')
expect(page).to have_css('.sign-out-link', visible: false)
end
@@ -235,7 +219,6 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
unregistered_device = FakeU2fDevice.new(page, 'My device')
gitlab_sign_in(user)
unregistered_device.respond_to_u2f_authentication
- expect(page).to have_content('We heard back from your U2F device')
expect(page).to have_content('Authentication via U2F device failed')
end
@@ -260,7 +243,6 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
[first_device, second_device].each do |device|
gitlab_sign_in(user)
device.respond_to_u2f_authentication
- expect(page).to have_content('We heard back from your U2F device')
expect(page).to have_css('.sign-out-link', visible: false)
@@ -283,7 +265,9 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
it "deletes u2f registrations" do
visit profile_account_path
- expect { click_on "Disable" }.to change { U2fRegistration.count }.by(-1)
+ expect do
+ accept_confirm { click_on "Disable" }
+ end.to change { U2fRegistration.count }.by(-1)
end
end
end
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 1261ffdc2ee..972c10aaf23 100644
--- a/spec/features/uploads/user_uploads_file_to_note_spec.rb
+++ b/spec/features/uploads/user_uploads_file_to_note_spec.rb
@@ -21,16 +21,12 @@ feature 'User uploads file to note' do
end
context 'uploading is in progress' do
- it 'shows "Cancel" button on uploading', :js do
- dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false)
-
- expect(page).to have_button('Cancel')
- end
-
it 'cancels uploading on clicking to "Cancel" button', :js do
- dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false)
+ slow_requests do
+ dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false)
- click_button 'Cancel'
+ click_button 'Cancel'
+ end
expect(page).to have_button('Attach a file')
expect(page).not_to have_button('Cancel')
@@ -38,16 +34,20 @@ feature 'User uploads file to note' do
end
it 'shows "Attaching a file" message on uploading 1 file', :js do
- dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false)
+ slow_requests do
+ dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false)
- expect(page).to have_selector('.attaching-file-message', visible: true, text: 'Attaching a file -')
+ expect(page).to have_selector('.attaching-file-message', visible: true, text: 'Attaching a file -')
+ end
end
it 'shows "Attaching 2 files" message on uploading 2 file', :js do
- dropzone_file([Rails.root.join('spec', 'fixtures', 'video_sample.mp4'),
- Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false)
+ slow_requests do
+ dropzone_file([Rails.root.join('spec', 'fixtures', 'video_sample.mp4'),
+ Rails.root.join('spec', 'fixtures', 'dk.png')], 0, false)
- expect(page).to have_selector('.attaching-file-message', visible: true, text: 'Attaching 2 files -')
+ expect(page).to have_selector('.attaching-file-message', visible: true, text: 'Attaching 2 files -')
+ end
end
it 'shows error message, "retry" and "attach a new file" link a if file is too big', :js do
diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb
index 0252c957c95..a9973cdf214 100644
--- a/spec/features/users_spec.rb
+++ b/spec/features/users_spec.rb
@@ -24,6 +24,7 @@ feature 'Users', :js do
user.reload
expect(user.reset_password_token).not_to be_nil
+ find('a[href="#login-pane"]').click
gitlab_sign_in(user)
expect(current_path).to eq root_path
diff --git a/spec/features/variables_spec.rb b/spec/features/variables_spec.rb
index 5d8e818f7bf..c78f7d0d9be 100644
--- a/spec/features/variables_spec.rb
+++ b/spec/features/variables_spec.rb
@@ -82,7 +82,7 @@ describe 'Project variables', :js do
it 'deletes variable' do
page.within('.variables-table') do
- click_on 'Remove'
+ accept_confirm { click_on 'Remove' }
end
expect(page).not_to have_selector('variables-table')
diff --git a/spec/fixtures/api/schemas/public_api/v4/user/login.json b/spec/fixtures/api/schemas/public_api/v4/user/login.json
index e6c1d9c9d84..aa066883c47 100644
--- a/spec/fixtures/api/schemas/public_api/v4/user/login.json
+++ b/spec/fixtures/api/schemas/public_api/v4/user/login.json
@@ -27,11 +27,9 @@
"can_create_group",
"can_create_project",
"two_factor_enabled",
- "external",
- "private_token"
+ "external"
],
"properties": {
- "$ref": "full.json",
- "private_token": { "type": "string" }
+ "$ref": "full.json"
}
}
diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb
index 6a3945c0ebc..bc2422aba90 100644
--- a/spec/helpers/ci_status_helper_spec.rb
+++ b/spec/helpers/ci_status_helper_spec.rb
@@ -8,17 +8,13 @@ describe CiStatusHelper do
describe '#ci_icon_for_status' do
it 'renders to correct svg on success' do
- expect(helper).to receive(:render)
- .with('shared/icons/icon_status_success.svg', anything)
-
- helper.ci_icon_for_status(success_commit.status)
+ expect(helper.ci_icon_for_status('success').to_s)
+ .to include 'status_success'
end
it 'renders the correct svg on failure' do
- expect(helper).to receive(:render)
- .with('shared/icons/icon_status_failed.svg', anything)
-
- helper.ci_icon_for_status(failed_commit.status)
+ expect(helper.ci_icon_for_status('failed').to_s)
+ .to include 'status_failed'
end
end
diff --git a/spec/helpers/gitlab_routing_helper_spec.rb b/spec/helpers/gitlab_routing_helper_spec.rb
index a44b200c5da..6c4f7050ee0 100644
--- a/spec/helpers/gitlab_routing_helper_spec.rb
+++ b/spec/helpers/gitlab_routing_helper_spec.rb
@@ -63,4 +63,30 @@ describe GitlabRoutingHelper do
it { expect(resend_invite_group_member_path(group_member)).to eq resend_invite_group_group_member_path(group_member.source, group_member) }
end
end
+
+ describe '#preview_markdown_path' do
+ let(:project) { create(:project) }
+
+ it 'returns group preview markdown path for a group parent' do
+ group = create(:group)
+
+ expect(preview_markdown_path(group)).to eq("/groups/#{group.path}/preview_markdown")
+ end
+
+ it 'returns project preview markdown path for a project parent' do
+ expect(preview_markdown_path(project)).to eq("/#{project.full_path}/preview_markdown")
+ end
+
+ it 'returns snippet preview markdown path for a personal snippet' do
+ @snippet = create(:personal_snippet)
+
+ expect(preview_markdown_path(nil)).to eq("/snippets/preview_markdown")
+ end
+
+ it 'returns project preview markdown path for a project snippet' do
+ @snippet = create(:project_snippet, project: project)
+
+ expect(preview_markdown_path(project)).to eq("/#{project.full_path}/preview_markdown")
+ end
+ end
end
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index ead3e28438e..cb851d828f2 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -159,4 +159,36 @@ describe IssuablesHelper do
end
end
end
+
+ describe '#issuable_initial_data' do
+ let(:user) { create(:user) }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+ allow(helper).to receive(:can?).and_return(true)
+ end
+
+ it 'returns the correct json for an issue' do
+ issue = create(:issue, author: user, description: 'issue text')
+ @project = issue.project
+
+ expected_data = {
+ 'endpoint' => "/#{@project.full_path}/issues/#{issue.iid}",
+ 'canUpdate' => true,
+ 'canDestroy' => true,
+ 'issuableRef' => "##{issue.iid}",
+ 'markdownPreviewPath' => "/#{@project.full_path}/preview_markdown",
+ 'markdownDocsPath' => '/help/user/markdown',
+ 'issuableTemplates' => [],
+ 'projectPath' => @project.path,
+ 'projectNamespace' => @project.namespace.path,
+ 'initialTitleHtml' => issue.title,
+ 'initialTitleText' => issue.title,
+ 'initialDescriptionHtml' => '<p dir="auto">issue text</p>',
+ 'initialDescriptionText' => 'issue text',
+ 'initialTaskStatus' => '0 of 0 tasks completed'
+ }
+ expect(JSON.parse(helper.issuable_initial_data(issue))).to eq(expected_data)
+ end
+ end
end
diff --git a/spec/javascripts/groups/components/app_spec.js b/spec/javascripts/groups/components/app_spec.js
index cd19a0fae1e..59d4f7c45c6 100644
--- a/spec/javascripts/groups/components/app_spec.js
+++ b/spec/javascripts/groups/components/app_spec.js
@@ -431,9 +431,9 @@ describe('AppComponent', () => {
});
it('should render groups tree', (done) => {
- vm.groups = [mockParentGroupItem];
+ vm.store.state.groups = [mockParentGroupItem];
vm.isLoading = false;
- vm.pageInfo = mockPageInfo;
+ vm.store.state.pageInfo = mockPageInfo;
Vue.nextTick(() => {
expect(vm.$el.querySelector('.groups-list-tree-container')).toBeDefined();
done();
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index 60a452f2223..3636aac79a0 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -1,6 +1,5 @@
/* eslint-disable space-before-function-paren, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */
import Issue from '~/issue';
-import CloseReopenReportToggle from '~/close_reopen_report_toggle';
import '~/lib/utils/text_utility';
describe('Issue', function() {
@@ -189,37 +188,4 @@ describe('Issue', function() {
});
});
});
-
- describe('units', () => {
- describe('class constructor', () => {
- it('calls .initCloseReopenReport', () => {
- spyOn(Issue.prototype, 'initCloseReopenReport');
-
- new Issue(); // eslint-disable-line no-new
-
- expect(Issue.prototype.initCloseReopenReport).toHaveBeenCalled();
- });
- });
-
- describe('initCloseReopenReport', () => {
- it('calls .initDroplab', () => {
- const container = jasmine.createSpyObj('container', ['querySelector']);
- const dropdownTrigger = {};
- const dropdownList = {};
- const button = {};
-
- spyOn(document, 'querySelector').and.returnValue(container);
- spyOn(CloseReopenReportToggle.prototype, 'initDroplab');
- container.querySelector.and.returnValues(dropdownTrigger, dropdownList, button);
-
- Issue.prototype.initCloseReopenReport();
-
- expect(document.querySelector).toHaveBeenCalledWith('.js-issuable-close-dropdown');
- expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-toggle');
- expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-menu');
- expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-button');
- expect(CloseReopenReportToggle.prototype.initDroplab).toHaveBeenCalled();
- });
- });
- });
});
diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js
index 17e4ef26b2c..43532275121 100644
--- a/spec/javascripts/jobs/mock_data.js
+++ b/spec/javascripts/jobs/mock_data.js
@@ -22,7 +22,7 @@ export default {
details_path: '/root/ci-mock/-/jobs/4757',
favicon: '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico',
action: {
- icon: 'icon_action_retry',
+ icon: 'retry',
title: 'Retry',
path: '/root/ci-mock/-/jobs/4757/retry',
method: 'post',
diff --git a/spec/javascripts/namespace_select_spec.js b/spec/javascripts/namespace_select_spec.js
new file mode 100644
index 00000000000..9d7625ca269
--- /dev/null
+++ b/spec/javascripts/namespace_select_spec.js
@@ -0,0 +1,65 @@
+import NamespaceSelect from '~/namespace_select';
+
+describe('NamespaceSelect', () => {
+ beforeEach(() => {
+ spyOn($.fn, 'glDropdown');
+ });
+
+ it('initializes glDropdown', () => {
+ const dropdown = document.createElement('div');
+
+ // eslint-disable-next-line no-new
+ new NamespaceSelect({ dropdown });
+
+ expect($.fn.glDropdown).toHaveBeenCalled();
+ });
+
+ describe('as input', () => {
+ let glDropdownOptions;
+
+ beforeEach(() => {
+ const dropdown = document.createElement('div');
+ // eslint-disable-next-line no-new
+ new NamespaceSelect({ dropdown });
+ glDropdownOptions = $.fn.glDropdown.calls.argsFor(0)[0];
+ });
+
+ it('prevents click events', () => {
+ const dummyEvent = new Event('dummy');
+ spyOn(dummyEvent, 'preventDefault');
+
+ glDropdownOptions.clicked({ e: dummyEvent });
+
+ expect(dummyEvent.preventDefault).toHaveBeenCalled();
+ });
+ });
+
+ describe('as filter', () => {
+ let glDropdownOptions;
+
+ beforeEach(() => {
+ const dropdown = document.createElement('div');
+ dropdown.dataset.isFilter = 'true';
+ // eslint-disable-next-line no-new
+ new NamespaceSelect({ dropdown });
+ glDropdownOptions = $.fn.glDropdown.calls.argsFor(0)[0];
+ });
+
+ it('does not prevent click events', () => {
+ const dummyEvent = new Event('dummy');
+ spyOn(dummyEvent, 'preventDefault');
+
+ glDropdownOptions.clicked({ e: dummyEvent });
+
+ expect(dummyEvent.preventDefault).not.toHaveBeenCalled();
+ });
+
+ it('sets URL of dropdown items', () => {
+ const dummyNamespace = { id: 'eal' };
+
+ const itemUrl = glDropdownOptions.url(dummyNamespace);
+
+ expect(itemUrl).toContain(`namespace_id=${dummyNamespace.id}`);
+ });
+ });
+});
diff --git a/spec/javascripts/pipelines/graph/action_component_spec.js b/spec/javascripts/pipelines/graph/action_component_spec.js
index 85bd87318db..e8fcd4b1a36 100644
--- a/spec/javascripts/pipelines/graph/action_component_spec.js
+++ b/spec/javascripts/pipelines/graph/action_component_spec.js
@@ -11,7 +11,7 @@ describe('pipeline graph action component', () => {
tooltipText: 'bar',
link: 'foo',
actionMethod: 'post',
- actionIcon: 'icon_action_cancel',
+ actionIcon: 'cancel',
},
}).$mount();
diff --git a/spec/javascripts/pipelines/graph/dropdown_action_component_spec.js b/spec/javascripts/pipelines/graph/dropdown_action_component_spec.js
index 25fd18b197e..ba721bc53c6 100644
--- a/spec/javascripts/pipelines/graph/dropdown_action_component_spec.js
+++ b/spec/javascripts/pipelines/graph/dropdown_action_component_spec.js
@@ -11,7 +11,7 @@ describe('action component', () => {
tooltipText: 'bar',
link: 'foo',
actionMethod: 'post',
- actionIcon: 'icon_action_cancel',
+ actionIcon: 'cancel',
},
}).$mount();
diff --git a/spec/javascripts/pipelines/graph/job_component_spec.js b/spec/javascripts/pipelines/graph/job_component_spec.js
index e90593e0f40..342ee6c1242 100644
--- a/spec/javascripts/pipelines/graph/job_component_spec.js
+++ b/spec/javascripts/pipelines/graph/job_component_spec.js
@@ -14,7 +14,7 @@ describe('pipeline graph job component', () => {
group: 'success',
details_path: '/root/ci-mock/builds/4256',
action: {
- icon: 'icon_action_retry',
+ icon: 'retry',
title: 'Retry',
path: '/root/ci-mock/builds/4256/retry',
method: 'post',
diff --git a/spec/javascripts/pipelines/graph/mock_data.js b/spec/javascripts/pipelines/graph/mock_data.js
index 56c522b7f77..b9494f86d74 100644
--- a/spec/javascripts/pipelines/graph/mock_data.js
+++ b/spec/javascripts/pipelines/graph/mock_data.js
@@ -39,7 +39,7 @@ export default {
"details_path": "/root/ci-mock/builds/4153",
"favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
"action": {
- "icon": "icon_action_retry",
+ "icon": "retry",
"title": "Retry",
"path": "/root/ci-mock/builds/4153/retry",
"method": "post"
@@ -62,7 +62,7 @@ export default {
"details_path": "/root/ci-mock/builds/4153",
"favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
"action": {
- "icon": "icon_action_retry",
+ "icon": "retry",
"title": "Retry",
"path": "/root/ci-mock/builds/4153/retry",
"method": "post"
@@ -96,7 +96,7 @@ export default {
"details_path": "/root/ci-mock/builds/4166",
"favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
"action": {
- "icon": "icon_action_retry",
+ "icon": "retry",
"title": "Retry",
"path": "/root/ci-mock/builds/4166/retry",
"method": "post"
@@ -119,7 +119,7 @@ export default {
"details_path": "/root/ci-mock/builds/4166",
"favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
"action": {
- "icon": "icon_action_retry",
+ "icon": "retry",
"title": "Retry",
"path": "/root/ci-mock/builds/4166/retry",
"method": "post"
@@ -138,7 +138,7 @@ export default {
"details_path": "/root/ci-mock/builds/4159",
"favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
"action": {
- "icon": "icon_action_retry",
+ "icon": "retry",
"title": "Retry",
"path": "/root/ci-mock/builds/4159/retry",
"method": "post"
@@ -161,7 +161,7 @@ export default {
"details_path": "/root/ci-mock/builds/4159",
"favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico",
"action": {
- "icon": "icon_action_retry",
+ "icon": "retry",
"title": "Retry",
"path": "/root/ci-mock/builds/4159/retry",
"method": "post"
diff --git a/spec/javascripts/pipelines/graph/stage_column_component_spec.js b/spec/javascripts/pipelines/graph/stage_column_component_spec.js
index aa4d6eedaf4..063ab53681b 100644
--- a/spec/javascripts/pipelines/graph/stage_column_component_spec.js
+++ b/spec/javascripts/pipelines/graph/stage_column_component_spec.js
@@ -13,7 +13,7 @@ describe('stage column component', () => {
group: 'success',
details_path: '/root/ci-mock/builds/4256',
action: {
- icon: 'icon_action_retry',
+ icon: 'retry',
title: 'Retry',
path: '/root/ci-mock/builds/4256/retry',
method: 'post',
diff --git a/spec/javascripts/repo/components/repo_editor_spec.js b/spec/javascripts/repo/components/repo_editor_spec.js
index 82f914b7c9d..979d2185076 100644
--- a/spec/javascripts/repo/components/repo_editor_spec.js
+++ b/spec/javascripts/repo/components/repo_editor_spec.js
@@ -17,6 +17,7 @@ describe('RepoEditor', () => {
f.active = true;
f.tempFile = true;
vm.$store.state.openFiles.push(f);
+ vm.$store.getters.activeFile.html = 'testing';
vm.monaco = true;
vm.$mount();
@@ -31,18 +32,25 @@ describe('RepoEditor', () => {
it('renders an ide container', (done) => {
Vue.nextTick(() => {
expect(vm.shouldHideEditor).toBeFalsy();
+ expect(vm.$el.textContent.trim()).toBe('');
+
done();
});
});
describe('when open file is binary and not raw', () => {
- it('does not render the IDE', (done) => {
+ beforeEach((done) => {
vm.$store.getters.activeFile.binary = true;
- Vue.nextTick(() => {
- expect(vm.shouldHideEditor).toBeTruthy();
- done();
- });
+ Vue.nextTick(done);
+ });
+
+ it('does not render the IDE', () => {
+ expect(vm.shouldHideEditor).toBeTruthy();
+ });
+
+ it('shows activeFile html', () => {
+ expect(vm.$el.textContent.trim()).toBe('testing');
});
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
index 690665ae12c..33ed0cb4342 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js
@@ -1,5 +1,4 @@
import Vue from 'vue';
-import { statusIconEntityMap } from '~/vue_shared/ci_status_icons';
import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline';
import mockData from '../mock_data';
@@ -29,14 +28,6 @@ describe('MRWidgetPipeline', () => {
});
describe('computed', () => {
- describe('svg', () => {
- it('should have the proper SVG icon', () => {
- const vm = createComponent({ pipeline: mockData.pipeline });
-
- expect(vm.svg).toEqual(statusIconEntityMap.icon_status_failed);
- });
- });
-
describe('hasPipeline', () => {
it('should return true when there is a pipeline', () => {
expect(Object.keys(mockData.pipeline).length).toBeGreaterThan(0);
@@ -142,6 +133,7 @@ describe('MRWidgetPipeline', () => {
Vue.nextTick(() => {
expect(el.querySelectorAll('.js-ci-error').length).toEqual(1);
expect(el.innerText).toContain('Could not connect to the CI server');
+ expect(el.querySelector('.ci-status-icon svg use').getAttribute('xlink:href')).toContain('status_failed');
done();
});
});
diff --git a/spec/javascripts/vue_shared/ci_action_icons_spec.js b/spec/javascripts/vue_shared/ci_action_icons_spec.js
deleted file mode 100644
index 3d53a5ab24d..00000000000
--- a/spec/javascripts/vue_shared/ci_action_icons_spec.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import getActionIcon from '~/vue_shared/ci_action_icons';
-import cancelSVG from 'icons/_icon_action_cancel.svg';
-import retrySVG from 'icons/_icon_action_retry.svg';
-import playSVG from 'icons/_icon_action_play.svg';
-import stopSVG from 'icons/_icon_action_stop.svg';
-
-describe('getActionIcon', () => {
- it('should return an empty string', () => {
- expect(getActionIcon()).toEqual('');
- });
-
- it('should return cancel svg', () => {
- expect(getActionIcon('icon_action_cancel')).toEqual(cancelSVG);
- });
-
- it('should return retry svg', () => {
- expect(getActionIcon('icon_action_retry')).toEqual(retrySVG);
- });
-
- it('should return play svg', () => {
- expect(getActionIcon('icon_action_play')).toEqual(playSVG);
- });
-
- it('should render stop svg', () => {
- expect(getActionIcon('icon_action_stop')).toEqual(stopSVG);
- });
-});
diff --git a/spec/javascripts/vue_shared/ci_status_icon_spec.js b/spec/javascripts/vue_shared/ci_status_icon_spec.js
deleted file mode 100644
index b6621d6054d..00000000000
--- a/spec/javascripts/vue_shared/ci_status_icon_spec.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import { borderlessStatusIconEntityMap, statusIconEntityMap } from '~/vue_shared/ci_status_icons';
-
-describe('CI status icons', () => {
- const statuses = [
- 'icon_status_canceled',
- 'icon_status_created',
- 'icon_status_failed',
- 'icon_status_manual',
- 'icon_status_pending',
- 'icon_status_running',
- 'icon_status_skipped',
- 'icon_status_success',
- 'icon_status_warning',
- ];
-
- it('should have a dictionary for borderless icons', () => {
- statuses.forEach((status) => {
- expect(borderlessStatusIconEntityMap[status]).toBeDefined();
- });
- });
-
- it('should have a dictionary for icons', () => {
- statuses.forEach((status) => {
- expect(statusIconEntityMap[status]).toBeDefined();
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/ci_badge_link_spec.js b/spec/javascripts/vue_shared/components/ci_badge_link_spec.js
index ba303738f71..8762ce9903b 100644
--- a/spec/javascripts/vue_shared/components/ci_badge_link_spec.js
+++ b/spec/javascripts/vue_shared/components/ci_badge_link_spec.js
@@ -11,63 +11,63 @@ describe('CI Badge Link Component', () => {
text: 'canceled',
label: 'canceled',
group: 'canceled',
- icon: 'icon_status_canceled',
+ icon: 'status_canceled',
details_path: 'status/canceled',
},
created: {
text: 'created',
label: 'created',
group: 'created',
- icon: 'icon_status_created',
+ icon: 'status_created',
details_path: 'status/created',
},
failed: {
text: 'failed',
label: 'failed',
group: 'failed',
- icon: 'icon_status_failed',
+ icon: 'status_failed',
details_path: 'status/failed',
},
manual: {
text: 'manual',
label: 'manual action',
group: 'manual',
- icon: 'icon_status_manual',
+ icon: 'status_manual',
details_path: 'status/manual',
},
pending: {
text: 'pending',
label: 'pending',
group: 'pending',
- icon: 'icon_status_pending',
+ icon: 'status_pending',
details_path: 'status/pending',
},
running: {
text: 'running',
label: 'running',
group: 'running',
- icon: 'icon_status_running',
+ icon: 'status_running',
details_path: 'status/running',
},
skipped: {
text: 'skipped',
label: 'skipped',
group: 'skipped',
- icon: 'icon_status_skipped',
+ icon: 'status_skipped',
details_path: 'status/skipped',
},
success_warining: {
text: 'passed',
label: 'passed',
group: 'success_with_warnings',
- icon: 'icon_status_warning',
+ icon: 'status_warning',
details_path: 'status/warning',
},
success: {
text: 'passed',
label: 'passed',
group: 'passed',
- icon: 'icon_status_success',
+ icon: 'status_success',
details_path: 'status/passed',
},
};
diff --git a/spec/javascripts/vue_shared/components/icon_spec.js b/spec/javascripts/vue_shared/components/icon_spec.js
new file mode 100644
index 00000000000..104da4473ce
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/icon_spec.js
@@ -0,0 +1,48 @@
+import Vue from 'vue';
+import Icon from '~/vue_shared/components/icon.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('Sprite Icon Component', function () {
+ describe('Initialization', function () {
+ let icon;
+
+ beforeEach(function () {
+ const IconComponent = Vue.extend(Icon);
+
+ icon = mountComponent(IconComponent, {
+ name: 'test',
+ size: 99,
+ cssClasses: 'extraclasses',
+ });
+ });
+
+ afterEach(() => {
+ icon.$destroy();
+ });
+
+ it('should return a defined Vue component', function () {
+ expect(icon).toBeDefined();
+ });
+
+ it('should have <svg> as a child element', function () {
+ expect(icon.$el.tagName).toBe('svg');
+ });
+
+ it('should have <use> as a child element with the correct href', function () {
+ expect(icon.$el.firstChild.tagName).toBe('use');
+ expect(icon.$el.firstChild.getAttribute('xlink:href')).toBe(`${gon.sprite_icons}#test`);
+ });
+
+ it('should properly compute iconSizeClass', function () {
+ expect(icon.iconSizeClass).toBe('s99');
+ });
+
+ it('should properly render img css', function () {
+ const classList = icon.$el.classList;
+ const containsSizeClass = classList.contains('s99');
+ const containsCustomClass = classList.contains('extraclasses');
+ expect(containsSizeClass).toBe(true);
+ expect(containsCustomClass).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/markdown/field_spec.js b/spec/javascripts/vue_shared/components/markdown/field_spec.js
index 60a5c2ae74e..65c49b9f30b 100644
--- a/spec/javascripts/vue_shared/components/markdown/field_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/field_spec.js
@@ -42,12 +42,14 @@ describe('Markdown field component', () => {
beforeEach(() => {
spyOn(Vue.http, 'post').and.callFake(() => new Promise((resolve) => {
- resolve({
- json() {
- return {
- body: '<p>markdown preview</p>',
- };
- },
+ setTimeout(() => {
+ resolve({
+ json() {
+ return {
+ body: '<p>markdown preview</p>',
+ };
+ },
+ });
});
}));
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index 9c74c9b8c99..3c98b18f99b 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -317,6 +317,68 @@ describe Banzai::Filter::IssueReferenceFilter do
end
end
+ context 'group context' do
+ let(:group) { create(:group) }
+ let(:context) { { project: nil, group: group } }
+
+ it 'ignores shorthanded issue reference' do
+ reference = "##{issue.iid}"
+ text = "Fixed #{reference}"
+
+ expect(reference_filter(text, context).to_html).to eq(text)
+ end
+
+ it 'ignores valid references when cross-reference project uses external tracker' do
+ expect_any_instance_of(described_class).to receive(:find_object)
+ .with(project, issue.iid)
+ .and_return(nil)
+
+ reference = "#{project.full_path}##{issue.iid}"
+ text = "Issue #{reference}"
+
+ expect(reference_filter(text, context).to_html).to eq(text)
+ end
+
+ it 'links to a valid reference for complete cross-reference' do
+ reference = "#{project.full_path}##{issue.iid}"
+ doc = reference_filter("See #{reference}", context)
+
+ expect(doc.css('a').first.attr('href')).to eq helper.url_for_issue(issue.iid, project)
+ end
+
+ it 'ignores reference for shorthand cross-reference' do
+ reference = "#{project.path}##{issue.iid}"
+ text = "See #{reference}"
+
+ expect(reference_filter(text, context).to_html).to eq(text)
+ end
+
+ it 'links to a valid reference for url cross-reference' do
+ reference = helper.url_for_issue(issue.iid, project) + "#note_123"
+
+ doc = reference_filter("See #{reference}", context)
+
+ expect(doc.css('a').first.attr('href')).to eq(helper.url_for_issue(issue.iid, project) + "#note_123")
+ end
+
+ it 'links to a valid reference for cross-reference in link href' do
+ reference = "#{helper.url_for_issue(issue.iid, project) + "#note_123"}"
+ reference_link = %{<a href="#{reference}">Reference</a>}
+
+ doc = reference_filter("See #{reference_link}", context)
+
+ expect(doc.css('a').first.attr('href')).to eq helper.url_for_issue(issue.iid, project) + "#note_123"
+ end
+
+ it 'links to a valid reference for issue reference in the link href' do
+ reference = issue.to_reference(group)
+ reference_link = %{<a href="#{reference}">Reference</a>}
+ doc = reference_filter("See #{reference_link}", context)
+
+ expect(doc.css('a').first.attr('href')).to eq helper.url_for_issue(issue.iid, project)
+ end
+ end
+
describe '#issues_per_project' do
context 'using an internal issue tracker' do
it 'returns a Hash containing the issues per project' do
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index 2cd30a5e302..862b1fe3fd3 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -594,4 +594,16 @@ describe Banzai::Filter::LabelReferenceFilter do
expect(reference_filter(act).to_html).to eq exp
end
end
+
+ describe 'group context' do
+ it 'points to referenced project issues page' do
+ project = create(:project)
+ label = create(:label, project: project)
+ reference = "#{project.full_path}~#{label.name}"
+
+ result = reference_filter("See #{reference}", { project: nil, group: create(:group) } )
+
+ expect(result.css('a').first.attr('href')).to eq(urls.project_issues_url(project, label_name: label.name))
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
index ed2788f8a33..158844e25ae 100644
--- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
@@ -214,4 +214,14 @@ describe Banzai::Filter::MergeRequestReferenceFilter do
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)<\/a>\.\)/)
end
end
+
+ context 'group context' do
+ it 'links to a valid reference' do
+ reference = "#{project.full_path}!#{merge.iid}"
+
+ result = reference_filter("See #{reference}", { project: nil, group: create(:group) } )
+
+ expect(result.css('a').first.attr('href')).to eq(urls.project_merge_request_url(project, merge))
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index fe7a8c84c9e..84578668133 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -343,4 +343,15 @@ describe Banzai::Filter::MilestoneReferenceFilter do
expect(doc.css('a')).to be_empty
end
end
+
+ context 'group context' do
+ it 'links to a valid reference' do
+ milestone = create(:milestone, project: project)
+ reference = "#{project.full_path}%#{milestone.iid}"
+
+ result = reference_filter("See #{reference}", { project: nil, group: create(:group) } )
+
+ expect(result.css('a').first.attr('href')).to eq(urls.milestone_url(milestone))
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
index 90ac4c7b238..3a07a6dc179 100644
--- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
@@ -201,4 +201,14 @@ describe Banzai::Filter::SnippetReferenceFilter do
expect(reference_filter(act).to_html).to match(/<a.+>#{Regexp.escape(invalidate_reference(reference))}<\/a>/)
end
end
+
+ context 'group context' do
+ it 'links to a valid reference' do
+ reference = "#{project.full_path}$#{snippet.id}"
+
+ result = reference_filter("See #{reference}", { project: nil, group: create(:group) } )
+
+ expect(result.css('a').first.attr('href')).to eq(urls.project_snippet_url(project, snippet))
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 34dac1db69a..fc03741976e 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -208,6 +208,39 @@ describe Banzai::Filter::UserReferenceFilter do
end
end
+ context 'in group context' do
+ let(:group) { create(:group) }
+ let(:group_member) { create(:user) }
+
+ before do
+ group.add_developer(group_member)
+ end
+
+ let(:context) { { author: group_member, project: nil, group: group } }
+
+ it 'supports a special @all mention' do
+ reference = User.reference_prefix + 'all'
+ doc = reference_filter("Hey #{reference}", context)
+
+ expect(doc.css('a').length).to eq(1)
+ expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
+ end
+
+ it 'supports mentioning a single user' do
+ reference = group_member.to_reference
+ doc = reference_filter("Hey #{reference}", context)
+
+ expect(doc.css('a').first.attr('href')).to eq urls.user_url(group_member)
+ end
+
+ it 'supports mentioning a group' do
+ reference = group.to_reference
+ doc = reference_filter("Hey #{reference}", context)
+
+ expect(doc.css('a').first.attr('href')).to eq urls.user_url(group)
+ end
+ end
+
describe '#namespaces' do
it 'returns a Hash containing all Namespaces' do
document = Nokogiri::HTML.fragment("<p>#{user.to_reference}</p>")
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index af1db2c3455..54a853c9ce3 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::Auth do
describe 'constants' do
it 'API_SCOPES contains all scopes for API access' do
- expect(subject::API_SCOPES).to eq [:api, :read_user]
+ expect(subject::API_SCOPES).to eq %i[api read_user sudo]
end
it 'OPENID_SCOPES contains all scopes for OpenID Connect' do
@@ -19,7 +19,7 @@ describe Gitlab::Auth do
it 'optional_scopes contains all non-default scopes' do
stub_container_registry_config(enabled: true)
- expect(subject.optional_scopes).to eq %i[read_user read_registry openid]
+ expect(subject.optional_scopes).to eq %i[read_user sudo read_registry openid]
end
context 'registry_scopes' do
@@ -164,7 +164,7 @@ describe Gitlab::Auth do
personal_access_token = create(:personal_access_token, scopes: ['api'])
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
- expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_token, full_authentication_abilities))
+ expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_access_token, full_authentication_abilities))
end
context 'when registry is enabled' do
@@ -176,7 +176,7 @@ describe Gitlab::Auth do
personal_access_token = create(:personal_access_token, scopes: ['read_registry'])
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
- expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_token, [:read_container_image]))
+ expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_access_token, [:read_container_image]))
end
end
@@ -184,14 +184,14 @@ describe Gitlab::Auth do
impersonation_token = create(:personal_access_token, :impersonation, scopes: ['api'])
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
- expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(impersonation_token.user, nil, :personal_token, full_authentication_abilities))
+ expect(gl_auth.find_for_git_client('', impersonation_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(impersonation_token.user, nil, :personal_access_token, full_authentication_abilities))
end
it 'limits abilities based on scope' do
personal_access_token = create(:personal_access_token, scopes: ['read_user'])
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
- expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_token, []))
+ expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_access_token, []))
end
it 'fails if password is nil' do
@@ -234,7 +234,7 @@ describe Gitlab::Auth do
it 'throws an error suggesting user create a PAT when internal auth is disabled' do
allow_any_instance_of(ApplicationSetting).to receive(:password_authentication_enabled?) { false }
- expect { gl_auth.find_for_git_client('foo', 'bar', project: nil, ip: 'ip') }.to raise_error(Gitlab::Auth::MissingPersonalTokenError)
+ expect { gl_auth.find_for_git_client('foo', 'bar', project: nil, ip: 'ip') }.to raise_error(Gitlab::Auth::MissingPersonalAccessTokenError)
end
end
diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb
index 809fda11879..2a3f7807fdb 100644
--- a/spec/lib/gitlab/ci/cron_parser_spec.rb
+++ b/spec/lib/gitlab/ci/cron_parser_spec.rb
@@ -77,8 +77,20 @@ describe Gitlab::Ci::CronParser do
it_behaves_like "returns time in the future"
- it 'converts time in server time zone' do
- expect(subject.hour).to eq(hour_in_utc)
+ context 'when PST (Pacific Standard Time)' do
+ it 'converts time in server time zone' do
+ Timecop.freeze(Time.utc(2017, 1, 1)) do
+ expect(subject.hour).to eq(hour_in_utc)
+ end
+ end
+ end
+
+ context 'when PDT (Pacific Daylight Time)' do
+ it 'converts time in server time zone' do
+ Timecop.freeze(Time.utc(2017, 6, 1)) do
+ expect(subject.hour).to eq(hour_in_utc)
+ end
+ end
end
end
end
@@ -100,8 +112,20 @@ describe Gitlab::Ci::CronParser do
it_behaves_like "returns time in the future"
- it 'converts time in server time zone' do
- expect(subject.hour).to eq(hour_in_utc)
+ context 'when CET (Central European Time)' do
+ it 'converts time in server time zone' do
+ Timecop.freeze(Time.utc(2017, 1, 1)) do
+ expect(subject.hour).to eq(hour_in_utc)
+ end
+ end
+ end
+
+ context 'when CEST (Central European Summer Time)' do
+ it 'converts time in server time zone' do
+ Timecop.freeze(Time.utc(2017, 6, 1)) do
+ expect(subject.hour).to eq(hour_in_utc)
+ end
+ end
end
end
@@ -111,8 +135,20 @@ describe Gitlab::Ci::CronParser do
it_behaves_like "returns time in the future"
- it 'converts time in server time zone' do
- expect(subject.hour).to eq(hour_in_utc)
+ context 'when EST (Eastern Standard Time)' do
+ it 'converts time in server time zone' do
+ Timecop.freeze(Time.utc(2017, 1, 1)) do
+ expect(subject.hour).to eq(hour_in_utc)
+ end
+ end
+ end
+
+ context 'when EDT (Eastern Daylight Time)' do
+ it 'converts time in server time zone' do
+ Timecop.freeze(Time.utc(2017, 6, 1)) do
+ expect(subject.hour).to eq(hour_in_utc)
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
index 5a7a42d84c0..9cdebaa5cf2 100644
--- a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
@@ -66,7 +66,7 @@ describe Gitlab::Ci::Status::Build::Cancelable do
end
describe '#action_icon' do
- it { expect(subject.action_icon).to eq 'icon_action_cancel' }
+ it { expect(subject.action_icon).to eq 'cancel' }
end
describe '#action_title' do
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index 8768302eda1..2b32e47e9ba 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -30,7 +30,7 @@ describe Gitlab::Ci::Status::Build::Factory do
it 'fabricates status with correct details' do
expect(status.text).to eq 'passed'
- expect(status.icon).to eq 'icon_status_success'
+ expect(status.icon).to eq 'status_success'
expect(status.favicon).to eq 'favicon_status_success'
expect(status.label).to eq 'passed'
expect(status).to have_details
@@ -57,7 +57,7 @@ describe Gitlab::Ci::Status::Build::Factory do
it 'fabricates status with correct details' do
expect(status.text).to eq 'failed'
- expect(status.icon).to eq 'icon_status_failed'
+ expect(status.icon).to eq 'status_failed'
expect(status.favicon).to eq 'favicon_status_failed'
expect(status.label).to eq 'failed'
expect(status).to have_details
@@ -84,7 +84,7 @@ describe Gitlab::Ci::Status::Build::Factory do
it 'fabricates status with correct details' do
expect(status.text).to eq 'failed'
- expect(status.icon).to eq 'icon_status_warning'
+ expect(status.icon).to eq 'warning'
expect(status.favicon).to eq 'favicon_status_failed'
expect(status.label).to eq 'failed (allowed to fail)'
expect(status).to have_details
@@ -113,7 +113,7 @@ describe Gitlab::Ci::Status::Build::Factory do
it 'fabricates status with correct details' do
expect(status.text).to eq 'canceled'
- expect(status.icon).to eq 'icon_status_canceled'
+ expect(status.icon).to eq 'status_canceled'
expect(status.favicon).to eq 'favicon_status_canceled'
expect(status.label).to eq 'canceled'
expect(status).to have_details
@@ -139,7 +139,7 @@ describe Gitlab::Ci::Status::Build::Factory do
it 'fabricates status with correct details' do
expect(status.text).to eq 'running'
- expect(status.icon).to eq 'icon_status_running'
+ expect(status.icon).to eq 'status_running'
expect(status.favicon).to eq 'favicon_status_running'
expect(status.label).to eq 'running'
expect(status).to have_details
@@ -165,7 +165,7 @@ describe Gitlab::Ci::Status::Build::Factory do
it 'fabricates status with correct details' do
expect(status.text).to eq 'pending'
- expect(status.icon).to eq 'icon_status_pending'
+ expect(status.icon).to eq 'status_pending'
expect(status.favicon).to eq 'favicon_status_pending'
expect(status.label).to eq 'pending'
expect(status).to have_details
@@ -190,7 +190,7 @@ describe Gitlab::Ci::Status::Build::Factory do
it 'fabricates status with correct details' do
expect(status.text).to eq 'skipped'
- expect(status.icon).to eq 'icon_status_skipped'
+ expect(status.icon).to eq 'status_skipped'
expect(status.favicon).to eq 'favicon_status_skipped'
expect(status.label).to eq 'skipped'
expect(status).to have_details
@@ -219,7 +219,7 @@ describe Gitlab::Ci::Status::Build::Factory do
it 'fabricates status with correct details' do
expect(status.text).to eq 'manual'
expect(status.group).to eq 'manual'
- expect(status.icon).to eq 'icon_status_manual'
+ expect(status.icon).to eq 'status_manual'
expect(status.favicon).to eq 'favicon_status_manual'
expect(status.label).to include 'manual play action'
expect(status).to have_details
@@ -274,7 +274,7 @@ describe Gitlab::Ci::Status::Build::Factory do
it 'fabricates status with correct details' do
expect(status.text).to eq 'manual'
expect(status.group).to eq 'manual'
- expect(status.icon).to eq 'icon_status_manual'
+ expect(status.icon).to eq 'status_manual'
expect(status.favicon).to eq 'favicon_status_manual'
expect(status.label).to eq 'manual stop action (not allowed)'
expect(status).to have_details
diff --git a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
index 20f71459738..79a65fc67e8 100644
--- a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
@@ -18,7 +18,7 @@ describe Gitlab::Ci::Status::Build::FailedAllowed do
describe '#icon' do
it 'returns a warning icon' do
- expect(subject.icon).to eq 'icon_status_warning'
+ expect(subject.icon).to eq 'warning'
end
end
diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb
index 32b2e62e4e0..81d5f553fd1 100644
--- a/spec/lib/gitlab/ci/status/build/play_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/play_spec.rb
@@ -46,7 +46,7 @@ describe Gitlab::Ci::Status::Build::Play do
end
describe '#action_icon' do
- it { expect(subject.action_icon).to eq 'icon_action_play' }
+ it { expect(subject.action_icon).to eq 'play' }
end
describe '#action_title' do
diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
index 21026f2c968..14d42e0d70f 100644
--- a/spec/lib/gitlab/ci/status/build/retryable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
@@ -66,7 +66,7 @@ describe Gitlab::Ci::Status::Build::Retryable do
end
describe '#action_icon' do
- it { expect(subject.action_icon).to eq 'icon_action_retry' }
+ it { expect(subject.action_icon).to eq 'retry' }
end
describe '#action_title' do
diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb
index e0425103f41..18e250772f0 100644
--- a/spec/lib/gitlab/ci/status/build/stop_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb
@@ -38,7 +38,7 @@ describe Gitlab::Ci::Status::Build::Stop do
end
describe '#action_icon' do
- it { expect(subject.action_icon).to eq 'icon_action_stop' }
+ it { expect(subject.action_icon).to eq 'stop' }
end
describe '#action_title' do
diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb
index 530639a5897..dc74d7e28c5 100644
--- a/spec/lib/gitlab/ci/status/canceled_spec.rb
+++ b/spec/lib/gitlab/ci/status/canceled_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::Canceled do
end
describe '#icon' do
- it { expect(subject.icon).to eq 'icon_status_canceled' }
+ it { expect(subject.icon).to eq 'status_canceled' }
end
describe '#favicon' do
diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb
index aef982e17f1..ce4333f2aca 100644
--- a/spec/lib/gitlab/ci/status/created_spec.rb
+++ b/spec/lib/gitlab/ci/status/created_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::Created do
end
describe '#icon' do
- it { expect(subject.icon).to eq 'icon_status_created' }
+ it { expect(subject.icon).to eq 'status_created' }
end
describe '#favicon' do
diff --git a/spec/lib/gitlab/ci/status/failed_spec.rb b/spec/lib/gitlab/ci/status/failed_spec.rb
index 9a25743885c..a4a92117c7f 100644
--- a/spec/lib/gitlab/ci/status/failed_spec.rb
+++ b/spec/lib/gitlab/ci/status/failed_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::Failed do
end
describe '#icon' do
- it { expect(subject.icon).to eq 'icon_status_failed' }
+ it { expect(subject.icon).to eq 'status_failed' }
end
describe '#favicon' do
diff --git a/spec/lib/gitlab/ci/status/manual_spec.rb b/spec/lib/gitlab/ci/status/manual_spec.rb
index 6fdc3801d71..0463f2e1aff 100644
--- a/spec/lib/gitlab/ci/status/manual_spec.rb
+++ b/spec/lib/gitlab/ci/status/manual_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::Manual do
end
describe '#icon' do
- it { expect(subject.icon).to eq 'icon_status_manual' }
+ it { expect(subject.icon).to eq 'status_manual' }
end
describe '#favicon' do
diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb
index ffc53f0506b..0e25358dd8a 100644
--- a/spec/lib/gitlab/ci/status/pending_spec.rb
+++ b/spec/lib/gitlab/ci/status/pending_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::Pending do
end
describe '#icon' do
- it { expect(subject.icon).to eq 'icon_status_pending' }
+ it { expect(subject.icon).to eq 'status_pending' }
end
describe '#favicon' do
diff --git a/spec/lib/gitlab/ci/status/running_spec.rb b/spec/lib/gitlab/ci/status/running_spec.rb
index 0babf1fb54e..9c9d431bb5d 100644
--- a/spec/lib/gitlab/ci/status/running_spec.rb
+++ b/spec/lib/gitlab/ci/status/running_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::Running do
end
describe '#icon' do
- it { expect(subject.icon).to eq 'icon_status_running' }
+ it { expect(subject.icon).to eq 'status_running' }
end
describe '#favicon' do
diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb
index 670747c9f0b..63694ca0ea6 100644
--- a/spec/lib/gitlab/ci/status/skipped_spec.rb
+++ b/spec/lib/gitlab/ci/status/skipped_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::Skipped do
end
describe '#icon' do
- it { expect(subject.icon).to eq 'icon_status_skipped' }
+ it { expect(subject.icon).to eq 'status_skipped' }
end
describe '#favicon' do
diff --git a/spec/lib/gitlab/ci/status/success_spec.rb b/spec/lib/gitlab/ci/status/success_spec.rb
index ff65b074808..2f67df71c4f 100644
--- a/spec/lib/gitlab/ci/status/success_spec.rb
+++ b/spec/lib/gitlab/ci/status/success_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::Success do
end
describe '#icon' do
- it { expect(subject.icon).to eq 'icon_status_success' }
+ it { expect(subject.icon).to eq 'status_success' }
end
describe '#favicon' do
diff --git a/spec/lib/gitlab/ci/status/success_warning_spec.rb b/spec/lib/gitlab/ci/status/success_warning_spec.rb
index 7e2269397c6..4582354e739 100644
--- a/spec/lib/gitlab/ci/status/success_warning_spec.rb
+++ b/spec/lib/gitlab/ci/status/success_warning_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Ci::Status::SuccessWarning do
end
describe '#icon' do
- it { expect(subject.icon).to eq 'icon_status_warning' }
+ it { expect(subject.icon).to eq 'status_warning' }
end
describe '#group' do
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 0fa88d17f5d..bf6d199ebe2 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1163,6 +1163,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
describe "#ls_files" do
let(:master_file_paths) { repository.ls_files("master") }
+ let(:utf8_file_paths) { repository.ls_files("ls-files-utf8") }
let(:not_existed_branch) { repository.ls_files("not_existed_branch") }
it "read every file paths of master branch" do
@@ -1184,6 +1185,10 @@ describe Gitlab::Git::Repository, seed_helper: true do
it "returns empty array when not existed branch" do
expect(not_existed_branch.length).to equal(0)
end
+
+ it "returns valid utf-8 data" do
+ expect(utf8_file_paths.map { |file| file.force_encoding('utf-8') }).to all(be_valid_encoding)
+ end
end
describe "#copy_gitattributes" do
@@ -1627,38 +1632,56 @@ describe Gitlab::Git::Repository, seed_helper: true do
subject { repository.ff_merge(user, source_sha, target_branch) }
- it 'performs a ff_merge' do
- expect(subject.newrev).to eq(source_sha)
- expect(subject.repo_created).to be(false)
- expect(subject.branch_created).to be(false)
+ shared_examples '#ff_merge' do
+ it 'performs a ff_merge' do
+ expect(subject.newrev).to eq(source_sha)
+ expect(subject.repo_created).to be(false)
+ expect(subject.branch_created).to be(false)
- expect(repository.commit(target_branch).id).to eq(source_sha)
- end
+ expect(repository.commit(target_branch).id).to eq(source_sha)
+ end
- context 'with a non-existing target branch' do
- subject { repository.ff_merge(user, source_sha, 'this-isnt-real') }
+ context 'with a non-existing target branch' do
+ subject { repository.ff_merge(user, source_sha, 'this-isnt-real') }
- it 'throws an ArgumentError' do
- expect { subject }.to raise_error(ArgumentError)
+ it 'throws an ArgumentError' do
+ expect { subject }.to raise_error(ArgumentError)
+ end
end
- end
- context 'with a non-existing source commit' do
- let(:source_sha) { 'f001' }
+ context 'with a non-existing source commit' do
+ let(:source_sha) { 'f001' }
- it 'throws an ArgumentError' do
- expect { subject }.to raise_error(ArgumentError)
+ it 'throws an ArgumentError' do
+ expect { subject }.to raise_error(ArgumentError)
+ end
end
- end
- context 'when the source sha is not a descendant of the branch head' do
- let(:source_sha) { '1a0b36b3cdad1d2ee32457c102a8c0b7056fa863' }
+ context 'when the source sha is not a descendant of the branch head' do
+ let(:source_sha) { '1a0b36b3cdad1d2ee32457c102a8c0b7056fa863' }
+
+ it "doesn't perform the ff_merge" do
+ expect { subject }.to raise_error(Gitlab::Git::CommitError)
- it "doesn't perform the ff_merge" do
- expect { subject }.to raise_error(Gitlab::Git::CommitError)
+ expect(repository.commit(target_branch).id).to eq(branch_head)
+ end
+ end
+ end
+
+ context 'with gitaly' do
+ it "calls Gitaly's OperationService" do
+ expect_any_instance_of(Gitlab::GitalyClient::OperationService)
+ .to receive(:user_ff_branch).with(user, source_sha, target_branch)
+ .and_return(nil)
- expect(repository.commit(target_branch).id).to eq(branch_head)
+ subject
end
+
+ it_behaves_like '#ff_merge'
+ end
+
+ context 'without gitaly', :skip_gitaly_mock do
+ it_behaves_like '#ff_merge'
end
end
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index e144e28b5d8..d9ec28ab02e 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -89,4 +89,38 @@ describe Gitlab::GitalyClient::OperationService do
end
end
end
+
+ describe '#user_ff_branch' do
+ let(:target_branch) { 'my-branch' }
+ let(:source_sha) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
+ let(:request) do
+ Gitaly::UserFFBranchRequest.new(
+ repository: repository.gitaly_repository,
+ branch: target_branch,
+ commit_id: source_sha,
+ user: gitaly_user
+ )
+ end
+ let(:branch_update) do
+ Gitaly::OperationBranchUpdate.new(
+ commit_id: source_sha,
+ repo_created: false,
+ branch_created: false
+ )
+ end
+ let(:response) { Gitaly::UserFFBranchResponse.new(branch_update: branch_update) }
+
+ subject { client.user_ff_branch(user, source_sha, target_branch) }
+
+ it 'sends a user_ff_branch message and returns a BranchUpdate object' do
+ expect_any_instance_of(Gitaly::OperationService::Stub)
+ .to receive(:user_ff_branch).with(request, kind_of(Hash))
+ .and_return(response)
+
+ expect(subject).to be_a(Gitlab::Git::OperationService::BranchUpdate)
+ expect(subject.newrev).to eq(source_sha)
+ expect(subject.repo_created).to be(false)
+ expect(subject.branch_created).to be(false)
+ end
+ end
end
diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb
index 742a792a1af..86be06ff595 100644
--- a/spec/lib/gitlab/middleware/read_only_spec.rb
+++ b/spec/lib/gitlab/middleware/read_only_spec.rb
@@ -83,6 +83,13 @@ describe Gitlab::Middleware::ReadOnly do
expect(subject).to disallow_request
end
+ it 'expects POST of new file that looks like an LFS batch url to be disallowed' do
+ response = request.post('/root/gitlab-ce/new/master/app/info/lfs/objects/batch')
+
+ expect(response).to be_a_redirect
+ expect(subject).to disallow_request
+ end
+
context 'whitelisted requests' do
it 'expects DELETE request to logout to be allowed' do
response = request.delete('/users/sign_out')
@@ -104,6 +111,25 @@ describe Gitlab::Middleware::ReadOnly do
expect(response).not_to be_a_redirect
expect(subject).not_to disallow_request
end
+
+ it 'expects a POST request to git-upload-pack URL to be allowed' do
+ response = request.post('/root/rouge.git/git-upload-pack')
+
+ expect(response).not_to be_a_redirect
+ expect(subject).not_to disallow_request
+ end
+
+ it 'expects requests to sidekiq admin to be allowed' do
+ response = request.post('/admin/sidekiq')
+
+ expect(response).not_to be_a_redirect
+ expect(subject).not_to disallow_request
+
+ response = request.get('/admin/sidekiq')
+
+ expect(response).not_to be_a_redirect
+ expect(subject).not_to disallow_request
+ end
end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
new file mode 100644
index 00000000000..8fdbbacd04d
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
@@ -0,0 +1,63 @@
+require 'spec_helper'
+
+describe Gitlab::SidekiqMiddleware::MemoryKiller do
+ subject { described_class.new }
+ let(:pid) { 999 }
+
+ let(:worker) { double(:worker, class: 'TestWorker') }
+ let(:job) { { 'jid' => 123 } }
+ let(:queue) { 'test_queue' }
+
+ def run
+ thread = subject.call(worker, job, queue) { nil }
+ thread&.join
+ end
+
+ before do
+ allow(subject).to receive(:get_rss).and_return(10.kilobytes)
+ allow(subject).to receive(:pid).and_return(pid)
+ end
+
+ context 'when MAX_RSS is set to 0' do
+ before do
+ stub_const("#{described_class}::MAX_RSS", 0)
+ end
+
+ it 'does nothing' do
+ expect(subject).not_to receive(:sleep)
+
+ run
+ end
+ end
+
+ context 'when MAX_RSS is exceeded' do
+ before do
+ stub_const("#{described_class}::MAX_RSS", 5.kilobytes)
+ end
+
+ it 'sends the STP, TERM and KILL signals at expected times' do
+ expect(subject).to receive(:sleep).with(15 * 60).ordered
+ expect(Process).to receive(:kill).with('SIGSTP', pid).ordered
+
+ expect(subject).to receive(:sleep).with(30).ordered
+ expect(Process).to receive(:kill).with('SIGTERM', pid).ordered
+
+ expect(subject).to receive(:sleep).with(10).ordered
+ expect(Process).to receive(:kill).with('SIGKILL', pid).ordered
+
+ run
+ end
+ end
+
+ context 'when MAX_RSS is not exceeded' do
+ before do
+ stub_const("#{described_class}::MAX_RSS", 15.kilobytes)
+ end
+
+ it 'does nothing' do
+ expect(subject).not_to receive(:sleep)
+
+ run
+ end
+ end
+end
diff --git a/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb b/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
index b4b83b70d1c..a0fb86345f3 100644
--- a/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
+++ b/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
@@ -39,14 +39,6 @@ describe SystemCheck::App::GitUserDefaultSSHConfigCheck do
it { is_expected.to eq(expected_result) }
end
-
- it 'skips GitLab read-only instances' do
- stub_user
- stub_home_dir
- allow(Gitlab::Database).to receive(:read_only?).and_return(true)
-
- is_expected.to be_truthy
- end
end
describe '#check?' do
diff --git a/spec/migrations/migrate_user_authentication_token_to_personal_access_token_spec.rb b/spec/migrations/migrate_user_authentication_token_to_personal_access_token_spec.rb
new file mode 100644
index 00000000000..b4834705011
--- /dev/null
+++ b/spec/migrations/migrate_user_authentication_token_to_personal_access_token_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20171012125712_migrate_user_authentication_token_to_personal_access_token.rb')
+
+describe MigrateUserAuthenticationTokenToPersonalAccessToken, :migration do
+ let(:users) { table(:users) }
+ let(:personal_access_tokens) { table(:personal_access_tokens) }
+
+ let!(:user) { users.create!(id: 1, email: 'user@example.com', authentication_token: 'user-token', admin: false) }
+ let!(:admin) { users.create!(id: 2, email: 'admin@example.com', authentication_token: 'admin-token', admin: true) }
+
+ it 'migrates private tokens to Personal Access Tokens' do
+ migrate!
+
+ expect(personal_access_tokens.count).to eq(2)
+
+ user_token = personal_access_tokens.find_by(user_id: user.id)
+ admin_token = personal_access_tokens.find_by(user_id: admin.id)
+
+ expect(user_token.token).to eq('user-token')
+ expect(admin_token.token).to eq('admin-token')
+
+ expect(user_token.scopes).to eq(%w[api].to_yaml)
+ expect(admin_token.scopes).to eq(%w[api sudo].to_yaml)
+ end
+end
diff --git a/spec/migrations/populate_merge_requests_latest_merge_request_diff_id_spec.rb b/spec/migrations/populate_merge_requests_latest_merge_request_diff_id_spec.rb
new file mode 100644
index 00000000000..4ea7f441f7c
--- /dev/null
+++ b/spec/migrations/populate_merge_requests_latest_merge_request_diff_id_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20171026082505_populate_merge_requests_latest_merge_request_diff_id')
+
+describe PopulateMergeRequestsLatestMergeRequestDiffId, :migration do
+ let(:projects_table) { table(:projects) }
+ let(:merge_requests_table) { table(:merge_requests) }
+ let(:merge_request_diffs_table) { table(:merge_request_diffs) }
+
+ let(:project) { projects_table.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce') }
+
+ def create_mr!(name, diffs: 0)
+ merge_request =
+ merge_requests_table.create!(target_project_id: project.id,
+ target_branch: 'master',
+ source_project_id: project.id,
+ source_branch: name,
+ title: name)
+
+ diffs.times do
+ merge_request_diffs_table.create!(merge_request_id: merge_request.id)
+ end
+
+ merge_request
+ end
+
+ def diffs_for(merge_request)
+ merge_request_diffs_table.where(merge_request_id: merge_request.id)
+ end
+
+ describe '#up' do
+ it 'ignores MRs without diffs' do
+ merge_request_without_diff = create_mr!('without_diff')
+
+ expect(merge_request_without_diff.latest_merge_request_diff_id).to be_nil
+
+ expect { migrate! }
+ .not_to change { merge_request_without_diff.reload.latest_merge_request_diff_id }
+ end
+
+ it 'ignores MRs that have a diff ID already set' do
+ merge_request_with_multiple_diffs = create_mr!('with_multiple_diffs', diffs: 3)
+ diff_id = diffs_for(merge_request_with_multiple_diffs).minimum(:id)
+
+ merge_request_with_multiple_diffs.update!(latest_merge_request_diff_id: diff_id)
+
+ expect { migrate! }
+ .not_to change { merge_request_with_multiple_diffs.reload.latest_merge_request_diff_id }
+ end
+
+ it 'migrates multiple MR diffs to the correct values' do
+ merge_requests = Array.new(3).map.with_index { |_, i| create_mr!(i, diffs: 3) }
+
+ migrate!
+
+ merge_requests.each do |merge_request|
+ expect(merge_request.reload.latest_merge_request_diff_id)
+ .to eq(diffs_for(merge_request).maximum(:id))
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 41ecdb604f1..5ed2e1ca99a 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1271,6 +1271,7 @@ describe Ci::Build do
{ key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true },
{ key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true },
{ key: 'CI_PROJECT_URL', value: project.web_url, public: true },
+ { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true },
{ key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true },
{ key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true },
{ key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true },
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index ab8773b7ede..3106207811a 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -134,6 +134,7 @@ describe Group, 'Routable' do
context 'with RequestStore active', :request_store do
it 'does not load the route table more than once' do
+ group.expires_full_path_cache
expect(group).to receive(:uncached_full_path).once.and_call_original
3.times { group.full_path }
diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb
index 882afeccfc6..dfb83578fce 100644
--- a/spec/models/concerns/token_authenticatable_spec.rb
+++ b/spec/models/concerns/token_authenticatable_spec.rb
@@ -12,7 +12,7 @@ shared_examples 'TokenAuthenticatable' do
end
describe User, 'TokenAuthenticatable' do
- let(:token_field) { :authentication_token }
+ let(:token_field) { :rss_token }
it_behaves_like 'TokenAuthenticatable'
describe 'ensures authentication token' do
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index e1be23541e8..1ce1d595c60 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -18,7 +18,6 @@ describe Environment do
it { is_expected.to validate_length_of(:slug).is_at_most(24) }
it { is_expected.to validate_length_of(:external_url).is_at_most(255) }
- it { is_expected.to validate_uniqueness_of(:external_url).scoped_to(:project_id) }
describe '.order_by_last_deployed_at' do
let(:project) { create(:project, :repository) }
@@ -547,6 +546,15 @@ describe Environment do
expect(environment.slug).to eq(original_slug)
end
+
+ it "regenerates the slug if nil" do
+ environment = build(:environment, slug: nil)
+
+ new_slug = environment.slug
+
+ expect(new_slug).not_to be_nil
+ expect(environment.slug).to eq(new_slug)
+ end
end
describe '#generate_slug' do
@@ -583,6 +591,12 @@ describe Environment do
it 'returns a path that uses the slug and does not have spaces' do
expect(environment.ref_path).to start_with('refs/environments/staging-review-1-')
end
+
+ it "doesn't change when the slug is nil initially" do
+ environment.slug = nil
+
+ expect(environment.ref_path).to eq(environment.ref_path)
+ end
end
describe '#external_url_for' do
diff --git a/spec/models/fork_network_spec.rb b/spec/models/fork_network_spec.rb
index 605ccd6db06..a43baf1820a 100644
--- a/spec/models/fork_network_spec.rb
+++ b/spec/models/fork_network_spec.rb
@@ -24,6 +24,16 @@ describe ForkNetwork do
end
end
+ describe '#merge_requests' do
+ it 'finds merge requests within the fork network' do
+ project = create(:project)
+ forked_project = fork_project(project)
+ merge_request = create(:merge_request, source_project: forked_project, target_project: project)
+
+ expect(project.fork_network.merge_requests).to include(merge_request)
+ end
+ end
+
context 'for a deleted project' do
it 'keeps the fork network' do
project = create(:project, :public)
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index f36d6eeb327..0e1a7fdce0b 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -488,6 +488,47 @@ describe Group do
end
end
+ describe '#path_changed_hook' do
+ let(:system_hook_service) { SystemHooksService.new }
+
+ context 'for a new group' do
+ let(:group) { build(:group) }
+
+ before do
+ expect(group).to receive(:system_hook_service).and_return(system_hook_service)
+ end
+
+ it 'does not trigger system hook' do
+ expect(system_hook_service).to receive(:execute_hooks_for).with(group, :create)
+
+ group.save!
+ end
+ end
+
+ context 'for an existing group' do
+ let(:group) { create(:group, path: 'old-path') }
+
+ context 'when the path is changed' do
+ let(:new_path) { 'very-new-path' }
+
+ it 'triggers the rename system hook' do
+ expect(group).to receive(:system_hook_service).and_return(system_hook_service)
+ expect(system_hook_service).to receive(:execute_hooks_for).with(group, :rename)
+
+ group.update_attributes!(path: new_path)
+ end
+ end
+
+ context 'when the path is not changed' do
+ it 'does not trigger system hook' do
+ expect(group).not_to receive(:system_hook_service)
+
+ group.update_attributes!(name: 'new name')
+ end
+ end
+ end
+ end
+
describe '#secret_variables_for' do
let(:project) { create(:project, group: group) }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index ed6e42d476e..e8588975118 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1923,6 +1923,20 @@ describe Project do
expect(forked_project.in_fork_network_of?(other_project)).to be_falsy
end
end
+
+ describe '#fork_source' do
+ let!(:second_fork) { fork_project(forked_project) }
+
+ it 'returns the direct source if it exists' do
+ expect(second_fork.fork_source).to eq(forked_project)
+ end
+
+ it 'returns the root of the fork network when the directs source was deleted' do
+ forked_project.destroy
+
+ expect(second_fork.fork_source).to eq(project)
+ end
+ end
end
describe '#pushes_since_gc' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 1c3c9068f12..e0896d64c8f 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -346,7 +346,6 @@ describe User do
describe "Respond to" do
it { is_expected.to respond_to(:admin?) }
it { is_expected.to respond_to(:name) }
- it { is_expected.to respond_to(:private_token) }
it { is_expected.to respond_to(:external?) }
end
@@ -526,14 +525,6 @@ describe User do
end
end
- describe 'authentication token' do
- it "has authentication token" do
- user = create(:user)
-
- expect(user.authentication_token).not_to be_blank
- end
- end
-
describe 'ensure incoming email token' do
it 'has incoming email token' do
user = create(:user)
@@ -2226,6 +2217,42 @@ describe User do
end
end
+ describe '#username_changed_hook' do
+ context 'for a new user' do
+ let(:user) { build(:user) }
+
+ it 'does not trigger system hook' do
+ expect(user).not_to receive(:system_hook_service)
+
+ user.save!
+ end
+ end
+
+ context 'for an existing user' do
+ let(:user) { create(:user, username: 'old-username') }
+
+ context 'when the username is changed' do
+ let(:new_username) { 'very-new-name' }
+
+ it 'triggers the rename system hook' do
+ system_hook_service = SystemHooksService.new
+ expect(system_hook_service).to receive(:execute_hooks_for).with(user, :rename)
+ expect(user).to receive(:system_hook_service).and_return(system_hook_service)
+
+ user.update_attributes!(username: new_username)
+ end
+ end
+
+ context 'when the username is not changed' do
+ it 'does not trigger system hook' do
+ expect(user).not_to receive(:system_hook_service)
+
+ user.update_attributes!(email: 'asdf@asdf.com')
+ end
+ end
+ end
+ end
+
describe '#sync_attribute?' do
let(:user) { described_class.new }
diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb
index de7ce848a31..308134eba72 100644
--- a/spec/requests/api/doorkeeper_access_spec.rb
+++ b/spec/requests/api/doorkeeper_access_spec.rb
@@ -25,7 +25,7 @@ describe 'doorkeeper access' do
end
end
- describe "authorization by private token" do
+ describe "authorization by OAuth token" do
it "returns authentication success" do
get api("/user", user)
expect(response).to have_gitlab_http_status(200)
@@ -39,20 +39,20 @@ describe 'doorkeeper access' do
end
describe "when user is blocked" do
- it "returns authentication error" do
+ it "returns authorization error" do
user.block
get api("/user"), access_token: token.token
- expect(response).to have_gitlab_http_status(401)
+ expect(response).to have_gitlab_http_status(403)
end
end
describe "when user is ldap_blocked" do
- it "returns authentication error" do
+ it "returns authorization error" do
user.ldap_block
get api("/user"), access_token: token.token
- expect(response).to have_gitlab_http_status(401)
+ expect(response).to have_gitlab_http_status(403)
end
end
end
diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb
index 9f3b5a809d7..6c0996c543d 100644
--- a/spec/requests/api/helpers_spec.rb
+++ b/spec/requests/api/helpers_spec.rb
@@ -28,39 +28,11 @@ describe API::Helpers do
allow_any_instance_of(self.class).to receive(:options).and_return({})
end
- def set_env(user_or_token, identifier)
- clear_env
- clear_param
- env[API::APIGuard::PRIVATE_TOKEN_HEADER] = user_or_token.respond_to?(:private_token) ? user_or_token.private_token : user_or_token
- env[API::Helpers::SUDO_HEADER] = identifier.to_s
- end
-
- def set_param(user_or_token, identifier)
- clear_env
- clear_param
- params[API::APIGuard::PRIVATE_TOKEN_PARAM] = user_or_token.respond_to?(:private_token) ? user_or_token.private_token : user_or_token
- params[API::Helpers::SUDO_PARAM] = identifier.to_s
- end
-
- def clear_env
- env.delete(API::APIGuard::PRIVATE_TOKEN_HEADER)
- env.delete(API::Helpers::SUDO_HEADER)
- end
-
- def clear_param
- params.delete(API::APIGuard::PRIVATE_TOKEN_PARAM)
- params.delete(API::Helpers::SUDO_PARAM)
- end
-
def warden_authenticate_returns(value)
warden = double("warden", authenticate: value)
env['warden'] = warden
end
- def doorkeeper_guard_returns(value)
- allow_any_instance_of(self.class).to receive(:doorkeeper_guard) { value }
- end
-
def error!(message, status, header)
raise Exception.new("#{status} - #{message}")
end
@@ -69,10 +41,6 @@ describe API::Helpers do
subject { current_user }
describe "Warden authentication", :allow_forgery_protection do
- before do
- doorkeeper_guard_returns false
- end
-
context "with invalid credentials" do
context "GET request" do
before do
@@ -160,75 +128,32 @@ describe API::Helpers do
end
end
- describe "when authenticating using a user's private token" do
- it "returns a 401 response for an invalid token" do
- env[API::APIGuard::PRIVATE_TOKEN_HEADER] = 'invalid token'
- allow_any_instance_of(self.class).to receive(:doorkeeper_guard) { false }
-
- expect { current_user }.to raise_error /401/
- end
-
- it "returns a 401 response for a user without access" do
- env[API::APIGuard::PRIVATE_TOKEN_HEADER] = user.private_token
- allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false)
-
- expect { current_user }.to raise_error /401/
- end
-
- it 'returns a 401 response for a user who is blocked' do
- user.block!
- env[API::APIGuard::PRIVATE_TOKEN_HEADER] = user.private_token
-
- expect { current_user }.to raise_error /401/
- end
-
- it "leaves user as is when sudo not specified" do
- env[API::APIGuard::PRIVATE_TOKEN_HEADER] = user.private_token
-
- expect(current_user).to eq(user)
-
- clear_env
-
- params[API::APIGuard::PRIVATE_TOKEN_PARAM] = user.private_token
-
- expect(current_user).to eq(user)
- end
- end
-
describe "when authenticating using a user's personal access tokens" do
let(:personal_access_token) { create(:personal_access_token, user: user) }
- before do
- allow_any_instance_of(self.class).to receive(:doorkeeper_guard) { false }
- end
-
it "returns a 401 response for an invalid token" do
env[API::APIGuard::PRIVATE_TOKEN_HEADER] = 'invalid token'
expect { current_user }.to raise_error /401/
end
- it "returns a 401 response for a user without access" do
+ it "returns a 403 response for a user without access" do
env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token
allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false)
- expect { current_user }.to raise_error /401/
+ expect { current_user }.to raise_error /403/
end
- it 'returns a 401 response for a user who is blocked' do
+ it 'returns a 403 response for a user who is blocked' do
user.block!
env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token
- expect { current_user }.to raise_error /401/
+ expect { current_user }.to raise_error /403/
end
- it "leaves user as is when sudo not specified" do
+ it "sets current_user" do
env[API::APIGuard::PRIVATE_TOKEN_HEADER] = personal_access_token.token
expect(current_user).to eq(user)
- clear_env
- params[API::APIGuard::PRIVATE_TOKEN_PARAM] = personal_access_token.token
-
- expect(current_user).to eq(user)
end
it "does not allow tokens without the appropriate scope" do
@@ -252,210 +177,6 @@ describe API::Helpers do
expect { current_user }.to raise_error API::APIGuard::ExpiredError
end
end
-
- context 'sudo usage' do
- context 'with admin' do
- context 'with header' do
- context 'with id' do
- it 'changes current_user to sudo' do
- set_env(admin, user.id)
-
- expect(current_user).to eq(user)
- end
-
- it 'memoize the current_user: sudo permissions are not run against the sudoed user' do
- set_env(admin, user.id)
-
- expect(current_user).to eq(user)
- expect(current_user).to eq(user)
- end
-
- it 'handles sudo to oneself' do
- set_env(admin, admin.id)
-
- expect(current_user).to eq(admin)
- end
-
- it 'throws an error when user cannot be found' do
- id = user.id + admin.id
- expect(user.id).not_to eq(id)
- expect(admin.id).not_to eq(id)
-
- set_env(admin, id)
-
- expect { current_user }.to raise_error(Exception)
- end
- end
-
- context 'with username' do
- it 'changes current_user to sudo' do
- set_env(admin, user.username)
-
- expect(current_user).to eq(user)
- end
-
- it 'handles sudo to oneself' do
- set_env(admin, admin.username)
-
- expect(current_user).to eq(admin)
- end
-
- it "throws an error when the user cannot be found for a given username" do
- username = "#{user.username}#{admin.username}"
- expect(user.username).not_to eq(username)
- expect(admin.username).not_to eq(username)
-
- set_env(admin, username)
-
- expect { current_user }.to raise_error(Exception)
- end
- end
- end
-
- context 'with param' do
- context 'with id' do
- it 'changes current_user to sudo' do
- set_param(admin, user.id)
-
- expect(current_user).to eq(user)
- end
-
- it 'handles sudo to oneself' do
- set_param(admin, admin.id)
-
- expect(current_user).to eq(admin)
- end
-
- it 'handles sudo to oneself using string' do
- set_env(admin, user.id.to_s)
-
- expect(current_user).to eq(user)
- end
-
- it 'throws an error when user cannot be found' do
- id = user.id + admin.id
- expect(user.id).not_to eq(id)
- expect(admin.id).not_to eq(id)
-
- set_param(admin, id)
-
- expect { current_user }.to raise_error(Exception)
- end
- end
-
- context 'with username' do
- it 'changes current_user to sudo' do
- set_param(admin, user.username)
-
- expect(current_user).to eq(user)
- end
-
- it 'handles sudo to oneself' do
- set_param(admin, admin.username)
-
- expect(current_user).to eq(admin)
- end
-
- it "throws an error when the user cannot be found for a given username" do
- username = "#{user.username}#{admin.username}"
- expect(user.username).not_to eq(username)
- expect(admin.username).not_to eq(username)
-
- set_param(admin, username)
-
- expect { current_user }.to raise_error(Exception)
- end
- end
- end
-
- context 'when user is blocked' do
- before do
- user.block!
- end
-
- it 'changes current_user to sudo' do
- set_env(admin, user.id)
-
- expect(current_user).to eq(user)
- end
- end
- end
-
- context 'with regular user' do
- context 'with env' do
- it 'changes current_user to sudo when admin and user id' do
- set_env(user, admin.id)
-
- expect { current_user }.to raise_error(Exception)
- end
-
- it 'changes current_user to sudo when admin and user username' do
- set_env(user, admin.username)
-
- expect { current_user }.to raise_error(Exception)
- end
- end
-
- context 'with params' do
- it 'changes current_user to sudo when admin and user id' do
- set_param(user, admin.id)
-
- expect { current_user }.to raise_error(Exception)
- end
-
- it 'changes current_user to sudo when admin and user username' do
- set_param(user, admin.username)
-
- expect { current_user }.to raise_error(Exception)
- end
- end
- end
- end
- end
-
- describe '.sudo?' do
- context 'when no sudo env or param is passed' do
- before do
- doorkeeper_guard_returns(nil)
- end
-
- it 'returns false' do
- expect(sudo?).to be_falsy
- end
- end
-
- context 'when sudo env or param is passed', 'user is not an admin' do
- before do
- set_env(user, '123')
- end
-
- it 'returns an 403 Forbidden' do
- expect { sudo? }.to raise_error '403 - {"message"=>"403 Forbidden - Must be admin to use sudo"}'
- end
- end
-
- context 'when sudo env or param is passed', 'user is admin' do
- context 'personal access token is used' do
- before do
- personal_access_token = create(:personal_access_token, user: admin)
- set_env(personal_access_token.token, user.id)
- end
-
- it 'returns an 403 Forbidden' do
- expect { sudo? }.to raise_error '403 - {"message"=>"403 Forbidden - Private token must be specified in order to use sudo"}'
- end
- end
-
- context 'private access token is used' do
- before do
- set_env(admin.private_token, user.id)
- end
-
- it 'returns true' do
- expect(sudo?).to be_truthy
- end
- end
- end
end
describe '.handle_api_exception' do
@@ -582,4 +303,147 @@ describe API::Helpers do
end
end
end
+
+ context 'sudo' do
+ shared_examples 'successful sudo' do
+ it 'sets current_user' do
+ expect(current_user).to eq(user)
+ end
+
+ it 'sets sudo?' do
+ expect(sudo?).to be_truthy
+ end
+ end
+
+ shared_examples 'sudo' do
+ context 'when admin' do
+ before do
+ token.user = admin
+ token.save!
+ end
+
+ context 'when token has sudo scope' do
+ before do
+ token.scopes = %w[sudo]
+ token.save!
+ end
+
+ context 'when user exists' do
+ context 'when using header' do
+ context 'when providing username' do
+ before do
+ env[API::Helpers::SUDO_HEADER] = user.username
+ end
+
+ it_behaves_like 'successful sudo'
+ end
+
+ context 'when providing user ID' do
+ before do
+ env[API::Helpers::SUDO_HEADER] = user.id.to_s
+ end
+
+ it_behaves_like 'successful sudo'
+ end
+ end
+
+ context 'when using param' do
+ context 'when providing username' do
+ before do
+ params[API::Helpers::SUDO_PARAM] = user.username
+ end
+
+ it_behaves_like 'successful sudo'
+ end
+
+ context 'when providing user ID' do
+ before do
+ params[API::Helpers::SUDO_PARAM] = user.id.to_s
+ end
+
+ it_behaves_like 'successful sudo'
+ end
+ end
+ end
+
+ context 'when user does not exist' do
+ before do
+ params[API::Helpers::SUDO_PARAM] = 'nonexistent'
+ end
+
+ it 'raises an error' do
+ expect { current_user }.to raise_error /User with ID or username 'nonexistent' Not Found/
+ end
+ end
+ end
+
+ context 'when token does not have sudo scope' do
+ before do
+ token.scopes = %w[api]
+ token.save!
+
+ params[API::Helpers::SUDO_PARAM] = user.id.to_s
+ end
+
+ it 'raises an error' do
+ expect { current_user }.to raise_error API::APIGuard::InsufficientScopeError
+ end
+ end
+ end
+
+ context 'when not admin' do
+ before do
+ token.user = user
+ token.save!
+
+ params[API::Helpers::SUDO_PARAM] = user.id.to_s
+ end
+
+ it 'raises an error' do
+ expect { current_user }.to raise_error /Must be admin to use sudo/
+ end
+ end
+ end
+
+ context 'using an OAuth token' do
+ let(:token) { create(:oauth_access_token) }
+
+ before do
+ env['HTTP_AUTHORIZATION'] = "Bearer #{token.token}"
+ end
+
+ it_behaves_like 'sudo'
+ end
+
+ context 'using a personal access token' do
+ let(:token) { create(:personal_access_token) }
+
+ context 'passed as param' do
+ before do
+ params[API::APIGuard::PRIVATE_TOKEN_PARAM] = token.token
+ end
+
+ it_behaves_like 'sudo'
+ end
+
+ context 'passed as header' do
+ before do
+ env[API::APIGuard::PRIVATE_TOKEN_HEADER] = token.token
+ end
+
+ it_behaves_like 'sudo'
+ end
+ end
+
+ context 'using warden authentication' do
+ before do
+ warden_authenticate_returns admin
+ env[API::Helpers::SUDO_HEADER] = user.username
+ end
+
+ it 'raises an error' do
+ expect { current_user }.to raise_error /Must be authenticated using an OAuth or Personal Access Token to use sudo/
+ end
+ end
+ end
end
diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb
deleted file mode 100644
index 83d09878813..00000000000
--- a/spec/requests/api/session_spec.rb
+++ /dev/null
@@ -1,107 +0,0 @@
-require 'spec_helper'
-
-describe API::Session do
- let(:user) { create(:user) }
-
- describe "POST /session" do
- context "when valid password" do
- it "returns private token" do
- post api("/session"), email: user.email, password: '12345678'
- expect(response).to have_gitlab_http_status(201)
-
- expect(json_response['email']).to eq(user.email)
- expect(json_response['private_token']).to eq(user.private_token)
- expect(json_response['is_admin']).to eq(user.admin?)
- expect(json_response['can_create_project']).to eq(user.can_create_project?)
- expect(json_response['can_create_group']).to eq(user.can_create_group?)
- end
-
- context 'with 2FA enabled' do
- it 'rejects sign in attempts' do
- user = create(:user, :two_factor)
-
- post api('/session'), email: user.email, password: user.password
-
- expect(response).to have_gitlab_http_status(401)
- expect(response.body).to include('You have 2FA enabled.')
- end
- end
- end
-
- context 'when email has case-typo and password is valid' do
- it 'returns private token' do
- post api('/session'), email: user.email.upcase, password: '12345678'
- expect(response.status).to eq 201
-
- expect(json_response['email']).to eq user.email
- expect(json_response['private_token']).to eq user.private_token
- expect(json_response['is_admin']).to eq user.admin?
- expect(json_response['can_create_project']).to eq user.can_create_project?
- expect(json_response['can_create_group']).to eq user.can_create_group?
- end
- end
-
- context 'when login has case-typo and password is valid' do
- it 'returns private token' do
- post api('/session'), login: user.username.upcase, password: '12345678'
- expect(response.status).to eq 201
-
- expect(json_response['email']).to eq user.email
- expect(json_response['private_token']).to eq user.private_token
- expect(json_response['is_admin']).to eq user.admin?
- expect(json_response['can_create_project']).to eq user.can_create_project?
- expect(json_response['can_create_group']).to eq user.can_create_group?
- end
- end
-
- context "when invalid password" do
- it "returns authentication error" do
- post api("/session"), email: user.email, password: '123'
- expect(response).to have_gitlab_http_status(401)
-
- expect(json_response['email']).to be_nil
- expect(json_response['private_token']).to be_nil
- end
- end
-
- context "when empty password" do
- it "returns authentication error with email" do
- post api("/session"), email: user.email
-
- expect(response).to have_gitlab_http_status(400)
- end
-
- it "returns authentication error with username" do
- post api("/session"), email: user.username
-
- expect(response).to have_gitlab_http_status(400)
- end
- end
-
- context "when empty name" do
- it "returns authentication error" do
- post api("/session"), password: user.password
-
- expect(response).to have_gitlab_http_status(400)
- end
- end
-
- context "when user is blocked" do
- it "returns authentication error" do
- user.block
- post api("/session"), email: user.username, password: user.password
-
- expect(response).to have_gitlab_http_status(401)
- end
- end
-
- context "when user is ldap_blocked" do
- it "returns authentication error" do
- user.ldap_block
- post api("/session"), email: user.username, password: user.password
-
- expect(response).to have_gitlab_http_status(401)
- end
- end
- end
-end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 4737f034f21..634c8dae0ba 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -127,8 +127,8 @@ describe API::Users do
context "when admin" do
context 'when sudo is defined' do
it 'does not return 500' do
- admin_personal_access_token = create(:personal_access_token, user: admin).token
- get api("/users?private_token=#{admin_personal_access_token}&sudo=#{user.id}", admin)
+ admin_personal_access_token = create(:personal_access_token, user: admin, scopes: [:sudo])
+ get api("/users?sudo=#{user.id}", admin, personal_access_token: admin_personal_access_token)
expect(response).to have_gitlab_http_status(:success)
end
@@ -1097,14 +1097,6 @@ describe API::Users do
end
end
- context 'with private token' do
- it 'returns 403 without private token when sudo defined' do
- get api("/user?private_token=#{user.private_token}&sudo=123")
-
- expect(response).to have_gitlab_http_status(403)
- end
- end
-
it 'returns current user without private token when sudo not defined' do
get api("/user", user)
@@ -1139,24 +1131,6 @@ describe API::Users do
expect(json_response['id']).to eq(admin.id)
end
end
-
- context 'with private token' do
- it 'returns sudoed user with private token when sudo defined' do
- get api("/user?private_token=#{admin.private_token}&sudo=#{user.id}")
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to match_response_schema('public_api/v4/user/login')
- expect(json_response['id']).to eq(user.id)
- end
-
- it 'returns initial current user without private token but with is_admin when sudo not defined' do
- get api("/user?private_token=#{admin.private_token}")
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to match_response_schema('public_api/v4/user/admin')
- expect(json_response['id']).to eq(admin.id)
- end
- end
end
context 'with unauthenticated user' do
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 407d19c3b2a..609481603af 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -135,7 +135,6 @@ end
# profile_history GET /profile/history(.:format) profile#history
# profile_password PUT /profile/password(.:format) profile#password_update
# profile_token GET /profile/token(.:format) profile#token
-# profile_reset_private_token PUT /profile/reset_private_token(.:format) profile#reset_private_token
# profile GET /profile(.:format) profile#show
# profile_update PUT /profile/update(.:format) profile#update
describe ProfilesController, "routing" do
@@ -147,10 +146,6 @@ describe ProfilesController, "routing" do
expect(get("/profile/audit_log")).to route_to('profiles#audit_log')
end
- it "to #reset_private_token" 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
diff --git a/spec/serializers/issue_entity_spec.rb b/spec/serializers/issue_entity_spec.rb
new file mode 100644
index 00000000000..caa3e41402b
--- /dev/null
+++ b/spec/serializers/issue_entity_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe IssueEntity do
+ let(:project) { create(:project) }
+ let(:resource) { create(:issue, project: project) }
+ let(:user) { create(:user) }
+
+ let(:request) { double('request', current_user: user) }
+
+ subject { described_class.new(resource, request: request).as_json }
+
+ it 'has Issuable attributes' do
+ expect(subject).to include(:id, :iid, :author_id, :description, :lock_version, :milestone_id,
+ :title, :updated_by_id, :created_at, :updated_at, :milestone, :labels)
+ end
+
+ it 'has time estimation attributes' do
+ expect(subject).to include(:time_estimate, :total_time_spent, :human_time_estimate, :human_total_time_spent)
+ end
+end
diff --git a/spec/serializers/merge_request_entity_spec.rb b/spec/serializers/merge_request_entity_spec.rb
index 87832b3dca1..f9285049c0d 100644
--- a/spec/serializers/merge_request_entity_spec.rb
+++ b/spec/serializers/merge_request_entity_spec.rb
@@ -30,8 +30,17 @@ describe MergeRequestEntity do
:assign_to_closing)
end
+ it 'has Issuable attributes' do
+ expect(subject).to include(:id, :iid, :author_id, :description, :lock_version, :milestone_id,
+ :title, :updated_by_id, :created_at, :updated_at, :milestone, :labels)
+ end
+
+ it 'has time estimation attributes' do
+ expect(subject).to include(:time_estimate, :total_time_spent, :human_time_estimate, :human_total_time_spent)
+ end
+
it 'has important MergeRequest attributes' do
- expect(subject).to include(:diff_head_sha, :merge_commit_message,
+ expect(subject).to include(:state, :deleted_at, :diff_head_sha, :merge_commit_message,
:has_conflicts, :has_ci, :merge_path,
:conflict_resolution_path,
:cancel_merge_when_pipeline_succeeds_path,
diff --git a/spec/services/applications/create_service_spec.rb b/spec/services/applications/create_service_spec.rb
new file mode 100644
index 00000000000..47a2a9d6403
--- /dev/null
+++ b/spec/services/applications/create_service_spec.rb
@@ -0,0 +1,13 @@
+require 'spec_helper'
+
+describe ::Applications::CreateService do
+ let(:user) { create(:user) }
+ let(:params) { attributes_for(:application) }
+ let(:request) { ActionController::TestRequest.new(remote_ip: '127.0.0.1') }
+
+ subject { described_class.new(user, params) }
+
+ it 'creates an application' do
+ expect { subject.execute(request) }.to change { Doorkeeper::Application.count }.by(1)
+ end
+end
diff --git a/spec/services/issuable/common_system_notes_service_spec.rb b/spec/services/issuable/common_system_notes_service_spec.rb
new file mode 100644
index 00000000000..9f92b662be1
--- /dev/null
+++ b/spec/services/issuable/common_system_notes_service_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe Issuable::CommonSystemNotesService do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:issuable) { create(:issue) }
+
+ shared_examples 'system note creation' do |update_params, note_text|
+ subject { described_class.new(project, user).execute(issuable, [])}
+
+ before do
+ issuable.assign_attributes(update_params)
+ issuable.save
+ end
+
+ it 'creates 1 system note with the correct content' do
+ expect { subject }.to change { Note.count }.from(0).to(1)
+
+ note = Note.last
+ expect(note.note).to match(note_text)
+ expect(note.noteable_type).to eq('Issue')
+ end
+ end
+
+ describe '#execute' do
+ it_behaves_like 'system note creation', { title: 'New title' }, 'changed title'
+ it_behaves_like 'system note creation', { description: 'New description' }, 'changed the description'
+ it_behaves_like 'system note creation', { discussion_locked: true }, 'locked this issue'
+ it_behaves_like 'system note creation', { time_estimate: 5 }, 'changed time estimate'
+
+ context 'when new label is added' do
+ before do
+ label = create(:label, project: project)
+ issuable.labels << label
+ end
+
+ it_behaves_like 'system note creation', {}, /added ~\w+ label/
+ end
+
+ context 'when new milestone is assigned' do
+ before do
+ milestone = create(:milestone, project: project)
+ issuable.milestone_id = milestone.id
+ end
+
+ it_behaves_like 'system note creation', {}, 'changed milestone'
+ end
+ end
+end
diff --git a/spec/services/projects/unlink_fork_service_spec.rb b/spec/services/projects/unlink_fork_service_spec.rb
index 50d3a4ec982..2bba71fef4f 100644
--- a/spec/services/projects/unlink_fork_service_spec.rb
+++ b/spec/services/projects/unlink_fork_service_spec.rb
@@ -12,6 +12,9 @@ describe Projects::UnlinkForkService do
context 'with opened merge request on the source project' do
let(:merge_request) { create(:merge_request, source_project: forked_project, target_project: fork_link.forked_from_project) }
+ let(:merge_request2) { create(:merge_request, source_project: forked_project, target_project: fork_project(project)) }
+ let(:merge_request_in_fork) { create(:merge_request, source_project: forked_project, target_project: forked_project) }
+
let(:mr_close_service) { MergeRequests::CloseService.new(forked_project, user) }
before do
@@ -22,9 +25,14 @@ describe Projects::UnlinkForkService do
it 'close all pending merge requests' do
expect(mr_close_service).to receive(:execute).with(merge_request)
+ expect(mr_close_service).to receive(:execute).with(merge_request2)
subject.execute
end
+
+ it 'does not close merge requests for the project being unlinked' do
+ expect(mr_close_service).not_to receive(:execute).with(merge_request_in_fork)
+ end
end
it 'remove fork relation' do
@@ -53,4 +61,14 @@ describe Projects::UnlinkForkService do
expect(source.forks_count).to be_zero
end
+
+ context 'when the original project was deleted' do
+ it 'does not fail when the original project is deleted' do
+ source = forked_project.forked_from_project
+ source.destroy
+ forked_project.reload
+
+ expect { subject.execute }.not_to raise_error
+ end
+ end
end
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
index 8f7aea533dc..46cd10cdc12 100644
--- a/spec/services/system_hooks_service_spec.rb
+++ b/spec/services/system_hooks_service_spec.rb
@@ -69,11 +69,48 @@ describe SystemHooksService do
expect(data[:project_visibility]).to eq('private')
end
+
+ context 'group_rename' do
+ it 'contains old and new path' do
+ allow(group).to receive(:path_was).and_return('old-path')
+
+ data = event_data(group, :rename)
+
+ expect(data).to include(:event_name, :name, :created_at, :updated_at, :full_path, :path, :group_id, :old_path, :old_full_path)
+ expect(data[:path]).to eq(group.path)
+ expect(data[:full_path]).to eq(group.path)
+ expect(data[:old_path]).to eq(group.path_was)
+ expect(data[:old_full_path]).to eq(group.path_was)
+ end
+
+ it 'contains old and new full_path for subgroup' do
+ subgroup = create(:group, parent: group)
+ allow(subgroup).to receive(:path_was).and_return('old-path')
+
+ data = event_data(subgroup, :rename)
+
+ expect(data[:full_path]).to eq(subgroup.full_path)
+ expect(data[:old_path]).to eq('old-path')
+ end
+ end
+
+ context 'user_rename' do
+ it 'contains old and new username' do
+ allow(user).to receive(:username_was).and_return('old-username')
+
+ data = event_data(user, :rename)
+
+ expect(data).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id, :username, :old_username)
+ expect(data[:username]).to eq(user.username)
+ expect(data[:old_username]).to eq(user.username_was)
+ end
+ end
end
context 'event names' do
it { expect(event_name(user, :create)).to eq "user_create" }
it { expect(event_name(user, :destroy)).to eq "user_destroy" }
+ it { expect(event_name(user, :rename)).to eq 'user_rename' }
it { expect(event_name(project, :create)).to eq "project_create" }
it { expect(event_name(project, :destroy)).to eq "project_destroy" }
it { expect(event_name(project, :rename)).to eq "project_rename" }
@@ -85,6 +122,7 @@ describe SystemHooksService do
it { expect(event_name(key, :destroy)).to eq 'key_destroy' }
it { expect(event_name(group, :create)).to eq 'group_create' }
it { expect(event_name(group, :destroy)).to eq 'group_destroy' }
+ it { expect(event_name(group, :rename)).to eq 'group_rename' }
it { expect(event_name(group_member, :create)).to eq 'user_add_to_group' }
it { expect(event_name(group_member, :destroy)).to eq 'user_remove_from_group' }
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 48cacba6a8a..7c8331f6c60 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -48,7 +48,11 @@ RSpec.configure do |config|
config.include Warden::Test::Helpers, type: :request
config.include LoginHelpers, type: :feature
config.include SearchHelpers, type: :feature
+ config.include CookieHelper, :js
+ config.include InputHelper, :js
+ config.include InspectRequests, :js
config.include WaitForRequests, :js
+ config.include LiveDebugger, :js
config.include StubConfiguration
config.include EmailHelpers, :mailer, type: :mailer
config.include TestEnv
diff --git a/spec/support/api_helpers.rb b/spec/support/api_helpers.rb
index 01aca74274c..ac0c7a9b493 100644
--- a/spec/support/api_helpers.rb
+++ b/spec/support/api_helpers.rb
@@ -18,21 +18,23 @@ module ApiHelpers
#
# Returns the relative path to the requested API resource
def api(path, user = nil, version: API::API.version, personal_access_token: nil, oauth_access_token: nil)
- "/api/#{version}#{path}" +
+ full_path = "/api/#{version}#{path}"
- # Normalize query string
- (path.index('?') ? '' : '?') +
+ if oauth_access_token
+ query_string = "access_token=#{oauth_access_token.token}"
+ elsif personal_access_token
+ query_string = "private_token=#{personal_access_token.token}"
+ elsif user
+ personal_access_token = create(:personal_access_token, user: user)
+ query_string = "private_token=#{personal_access_token.token}"
+ end
- if personal_access_token.present?
- "&private_token=#{personal_access_token.token}"
- elsif oauth_access_token.present?
- "&access_token=#{oauth_access_token.token}"
- # Append private_token if given a User object
- elsif user.respond_to?(:private_token)
- "&private_token=#{user.private_token}"
- else
- ''
- end
+ if query_string
+ full_path << (path.index('?') ? '&' : '?')
+ full_path << query_string
+ end
+
+ full_path
end
# Temporary helper method for simplifying V3 exclusive API specs
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index c45c4a4310d..9f672bc92fc 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -1,25 +1,25 @@
# rubocop:disable Style/GlobalVars
require 'capybara/rails'
require 'capybara/rspec'
-require 'capybara/poltergeist'
require 'capybara-screenshot/rspec'
+require 'selenium-webdriver'
# Give CI some extra time
timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 60 : 30
-Capybara.javascript_driver = :poltergeist
-Capybara.register_driver :poltergeist do |app|
- Capybara::Poltergeist::Driver.new(
- app,
- js_errors: true,
- timeout: timeout,
- window_size: [1366, 768],
- url_whitelist: %w[localhost 127.0.0.1],
- url_blacklist: %w[.mp4 .png .gif .avi .bmp .jpg .jpeg],
- phantomjs_options: [
- '--load-images=yes'
- ]
+Capybara.javascript_driver = :chrome
+Capybara.register_driver :chrome do |app|
+ extra_args = []
+ extra_args << 'headless' unless ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i
+
+ capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
+ chromeOptions: {
+ 'args' => %w[no-sandbox disable-gpu --window-size=1240,1400] + extra_args
+ }
)
+
+ Capybara::Selenium::Driver
+ .new(app, browser: :chrome, desired_capabilities: capabilities)
end
Capybara.default_max_wait_time = timeout
@@ -27,6 +27,10 @@ Capybara.ignore_hidden_elements = true
# Keep only the screenshots generated from the last failing test suite
Capybara::Screenshot.prune_strategy = :keep_last_run
+# From https://github.com/mattheworiordan/capybara-screenshot/issues/84#issuecomment-41219326
+Capybara::Screenshot.register_driver(:chrome) do |driver, path|
+ driver.browser.save_screenshot(path)
+end
RSpec.configure do |config|
config.before(:context, :js) do
@@ -37,13 +41,23 @@ RSpec.configure do |config|
end
config.before(:example, :js) do
+ session = Capybara.current_session
+
allow(Gitlab::Application.routes).to receive(:default_url_options).and_return(
- host: Capybara.current_session.server.host,
- port: Capybara.current_session.server.port,
+ host: session.server.host,
+ port: session.server.port,
protocol: 'http')
+
+ # reset window size between tests
+ unless session.current_window.size == [1240, 1400]
+ session.current_window.resize_to(1240, 1400) rescue nil
+ end
end
config.after(:example, :js) do |example|
+ # prevent localstorage from introducing side effects based on test order
+ execute_script("localStorage.clear();")
+
# capybara/rspec already calls Capybara.reset_sessions! in an `after` hook,
# but `block_and_wait_for_requests_complete` is called before it so by
# calling it explicitely here, we prevent any new requests from being fired
diff --git a/spec/support/capybara_helpers.rb b/spec/support/capybara_helpers.rb
index 3eb7bea3227..868233416bf 100644
--- a/spec/support/capybara_helpers.rb
+++ b/spec/support/capybara_helpers.rb
@@ -38,7 +38,7 @@ module CapybaraHelpers
# Simulate a browser restart by clearing the session cookie.
def clear_browser_session
- page.driver.remove_cookie('_gitlab_session')
+ page.driver.browser.manage.delete_cookie('_gitlab_session')
end
end
diff --git a/spec/support/cookie_helper.rb b/spec/support/cookie_helper.rb
new file mode 100644
index 00000000000..224619c899c
--- /dev/null
+++ b/spec/support/cookie_helper.rb
@@ -0,0 +1,17 @@
+# Helper for setting cookies in Selenium/WebDriver
+#
+module CookieHelper
+ def set_cookie(name, value, options = {})
+ # Selenium driver will not set cookies for a given domain when the browser is at `about:blank`.
+ # It also doesn't appear to allow overriding the cookie path. loading `/` is the most inclusive.
+ visit options.fetch(:path, '/') unless on_a_page?
+ page.driver.browser.manage.add_cookie(name: name, value: value, **options)
+ end
+
+ private
+
+ def on_a_page?
+ current_url = Capybara.current_session.driver.browser.current_url
+ current_url && current_url != '' && current_url != 'about:blank' && current_url != 'data:,'
+ end
+end
diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb
index 7132b9cd221..aabc64d972b 100644
--- a/spec/support/features/discussion_comments_shared_example.rb
+++ b/spec/support/features/discussion_comments_shared_example.rb
@@ -71,26 +71,28 @@ shared_examples 'discussion comments' do |resource_name|
expect(page).not_to have_selector menu_selector
find(toggle_selector).click
- find('body').trigger 'click'
+ find('body').click
expect(page).not_to have_selector menu_selector
end
it 'clicking the ul padding or divider should not change the text' do
- find(menu_selector).trigger 'click'
+ execute_script("document.querySelector('#{menu_selector}').click()")
+ # on issues page, the menu closes when clicking anywhere, on other pages it will
+ # remain open if clicking divider or menu padding, but should not change button action
if resource_name == 'issue'
expect(find(dropdown_selector)).to have_content 'Comment'
find(toggle_selector).click
- find("#{menu_selector} .divider").trigger 'click'
+ execute_script("document.querySelector('#{menu_selector} .divider').click()")
else
- find(menu_selector).trigger 'click'
+ execute_script("document.querySelector('#{menu_selector}').click()")
expect(page).to have_selector menu_selector
expect(find(dropdown_selector)).to have_content 'Comment'
- find("#{menu_selector} .divider").trigger 'click'
+ execute_script("document.querySelector('#{menu_selector} .divider').click()")
expect(page).to have_selector menu_selector
end
@@ -105,7 +107,12 @@ shared_examples 'discussion comments' do |resource_name|
end
it 'updates the submit button text and closes the dropdown' do
- expect(find(dropdown_selector)).to have_content 'Start discussion'
+ # on issues page, the submit input is a <button>, on other pages it is <input>
+ if resource_name == 'issue'
+ expect(find(submit_selector)).to have_content 'Start discussion'
+ else
+ expect(find(submit_selector).value).to eq 'Start discussion'
+ end
expect(page).not_to have_selector menu_selector
end
@@ -187,7 +194,12 @@ shared_examples 'discussion comments' do |resource_name|
end
it 'updates the submit button text and closes the dropdown' do
- expect(find(dropdown_selector)).to have_content 'Comment'
+ # on issues page, the submit input is a <button>, on other pages it is <input>
+ if resource_name == 'issue'
+ expect(find(submit_selector)).to have_content 'Comment'
+ else
+ expect(find(submit_selector).value).to eq 'Comment'
+ end
expect(page).not_to have_selector menu_selector
end
@@ -226,6 +238,7 @@ shared_examples 'discussion comments' do |resource_name|
describe "on a closed #{resource_name}" do
before do
find("#{form_selector} .js-note-target-close").click
+ wait_for_requests
find("#{form_selector} .note-textarea").send_keys('a')
end
diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb
index 061e0d35590..08e21ee2537 100644
--- a/spec/support/features/issuable_slash_commands_shared_examples.rb
+++ b/spec/support/features/issuable_slash_commands_shared_examples.rb
@@ -61,7 +61,7 @@ shared_examples 'issuable record that supports quick actions in its description
context 'with a note containing commands' do
it 'creates a note without the commands and interpret the commands accordingly' do
assignee = create(:user, username: 'bob')
- write_note("Awesome!\n/assign @bob\n/label ~bug\n/milestone %\"ASAP\"")
+ write_note("Awesome!\n\n/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"")
expect(page).to have_content 'Awesome!'
expect(page).not_to have_content '/assign @bob'
@@ -82,7 +82,7 @@ shared_examples 'issuable record that supports quick actions in its description
context 'with a note containing only commands' do
it 'does not create a note but interpret the commands accordingly' do
assignee = create(:user, username: 'bob')
- write_note("/assign @bob\n/label ~bug\n/milestone %\"ASAP\"")
+ write_note("/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"")
expect(page).not_to have_content '/assign @bob'
expect(page).not_to have_content '/label ~bug'
diff --git a/spec/support/features/reportable_note_shared_examples.rb b/spec/support/features/reportable_note_shared_examples.rb
index 192a2fed0a8..836e5e7be23 100644
--- a/spec/support/features/reportable_note_shared_examples.rb
+++ b/spec/support/features/reportable_note_shared_examples.rb
@@ -39,7 +39,7 @@ shared_examples 'reportable note' do |type|
end
def open_dropdown(dropdown)
- dropdown.find('.more-actions-toggle').trigger('click')
+ dropdown.find('.more-actions-toggle').click
dropdown.find('.dropdown-menu li', match: :first)
end
end
diff --git a/spec/support/gitlab-git-test.git/objects/88/3e379fcaa5f818fca81cdbabd7a497794d6535 b/spec/support/gitlab-git-test.git/objects/88/3e379fcaa5f818fca81cdbabd7a497794d6535
new file mode 100644
index 00000000000..1c47f34b9a5
--- /dev/null
+++ b/spec/support/gitlab-git-test.git/objects/88/3e379fcaa5f818fca81cdbabd7a497794d6535
Binary files differ
diff --git a/spec/support/gitlab-git-test.git/objects/c8/b1ab16c858c67b680eea4644cf652485f555cf b/spec/support/gitlab-git-test.git/objects/c8/b1ab16c858c67b680eea4644cf652485f555cf
new file mode 100644
index 00000000000..ca13c8df66a
--- /dev/null
+++ b/spec/support/gitlab-git-test.git/objects/c8/b1ab16c858c67b680eea4644cf652485f555cf
Binary files differ
diff --git a/spec/support/gitlab-git-test.git/objects/e3/7697aea12699f0b44544332a7c0f41ace5fb16 b/spec/support/gitlab-git-test.git/objects/e3/7697aea12699f0b44544332a7c0f41ace5fb16
new file mode 100644
index 00000000000..3be244dbda4
--- /dev/null
+++ b/spec/support/gitlab-git-test.git/objects/e3/7697aea12699f0b44544332a7c0f41ace5fb16
@@ -0,0 +1,2 @@
+xK
+0EgNI|ADt*^ mZ qGčY8ZK7"Fc%oHD9rZLsMJ2=ACmeFgVxI9H2XJrp6;N8z??>+zWƏBÞ f}bN@K\SY iSC \ No newline at end of file
diff --git a/spec/support/gitlab-git-test.git/objects/eb/a0c153ed20d927bab00507f356043b6b4be31e b/spec/support/gitlab-git-test.git/objects/eb/a0c153ed20d927bab00507f356043b6b4be31e
new file mode 100644
index 00000000000..2bf27fe5048
--- /dev/null
+++ b/spec/support/gitlab-git-test.git/objects/eb/a0c153ed20d927bab00507f356043b6b4be31e
Binary files differ
diff --git a/spec/support/gitlab-git-test.git/objects/f6/5ad228d96e2a2ae7088e8557fe8906f6dd2b3f b/spec/support/gitlab-git-test.git/objects/f6/5ad228d96e2a2ae7088e8557fe8906f6dd2b3f
new file mode 100644
index 00000000000..8ab8606c6be
--- /dev/null
+++ b/spec/support/gitlab-git-test.git/objects/f6/5ad228d96e2a2ae7088e8557fe8906f6dd2b3f
Binary files differ
diff --git a/spec/support/gitlab_stubs/session.json b/spec/support/gitlab_stubs/session.json
index 688175369ae..658ff5871b0 100644
--- a/spec/support/gitlab_stubs/session.json
+++ b/spec/support/gitlab_stubs/session.json
@@ -14,7 +14,5 @@
"provider":null,
"is_admin":false,
"can_create_group":false,
- "can_create_project":false,
- "private_token":"Wvjy2Krpb7y8xi93owUz",
- "access_token":"Wvjy2Krpb7y8xi93owUz"
+ "can_create_project":false
}
diff --git a/spec/support/gitlab_stubs/user.json b/spec/support/gitlab_stubs/user.json
index ce8dfe5ae75..658ff5871b0 100644
--- a/spec/support/gitlab_stubs/user.json
+++ b/spec/support/gitlab_stubs/user.json
@@ -14,7 +14,5 @@
"provider":null,
"is_admin":false,
"can_create_group":false,
- "can_create_project":false,
- "private_token":"Wvjy2Krpb7y8xi93owUz",
- "access_token":"Wvjy2Krpb7y8xi93owUz"
-} \ No newline at end of file
+ "can_create_project":false
+}
diff --git a/spec/support/helpers/merge_request_diff_helpers.rb b/spec/support/helpers/merge_request_diff_helpers.rb
index fd22e384b1b..c98aa503ed1 100644
--- a/spec/support/helpers/merge_request_diff_helpers.rb
+++ b/spec/support/helpers/merge_request_diff_helpers.rb
@@ -2,7 +2,7 @@ module MergeRequestDiffHelpers
def click_diff_line(line_holder, diff_side = nil)
line = get_line_components(line_holder, diff_side)
line[:content].hover
- line[:num].find('.add-diff-note').trigger('click')
+ line[:num].find('.add-diff-note', visible: false).send_keys(:return)
end
def get_line_components(line_holder, diff_side = nil)
diff --git a/spec/support/helpers/note_interaction_helpers.rb b/spec/support/helpers/note_interaction_helpers.rb
index 86008698692..79a0aa174b1 100644
--- a/spec/support/helpers/note_interaction_helpers.rb
+++ b/spec/support/helpers/note_interaction_helpers.rb
@@ -2,7 +2,7 @@ module NoteInteractionHelpers
def open_more_actions_dropdown(note)
note_element = find("#note_#{note.id}")
- note_element.find('.more-actions-toggle').trigger('click')
+ note_element.find('.more-actions-toggle').click
note_element.find('.more-actions .dropdown-menu li', match: :first)
end
end
diff --git a/spec/support/input_helper.rb b/spec/support/input_helper.rb
new file mode 100644
index 00000000000..acbb42274ec
--- /dev/null
+++ b/spec/support/input_helper.rb
@@ -0,0 +1,7 @@
+# see app/assets/javascripts/test_utils/simulate_input.js
+
+module InputHelper
+ def simulate_input(selector, input = '')
+ evaluate_script("window.simulateInput(#{selector.to_json}, #{input.to_json});")
+ end
+end
diff --git a/spec/support/inspect_requests.rb b/spec/support/inspect_requests.rb
new file mode 100644
index 00000000000..88ddc5c7f6c
--- /dev/null
+++ b/spec/support/inspect_requests.rb
@@ -0,0 +1,17 @@
+require_relative './wait_for_requests'
+
+module InspectRequests
+ extend self
+ include WaitForRequests
+
+ def inspect_requests(inject_headers: {})
+ Gitlab::Testing::RequestInspectorMiddleware.log_requests!(inject_headers)
+
+ yield
+
+ wait_for_all_requests
+ Gitlab::Testing::RequestInspectorMiddleware.requests
+ ensure
+ Gitlab::Testing::RequestInspectorMiddleware.stop_logging!
+ end
+end
diff --git a/spec/support/live_debugger.rb b/spec/support/live_debugger.rb
new file mode 100644
index 00000000000..911eb48a8ca
--- /dev/null
+++ b/spec/support/live_debugger.rb
@@ -0,0 +1,17 @@
+require 'io/console'
+
+module LiveDebugger
+ def live_debug
+ puts
+ puts "Current example is paused for live debugging."
+ puts "Opening #{current_url} in your default browser..."
+ puts "The current user credentials are: #{@current_user.username} / #{@current_user.password}" if @current_user
+ puts "Press any key to resume the execution of the example!!"
+
+ `open #{current_url}`
+
+ loop until $stdin.getch
+
+ puts "Back to the example!"
+ end
+end
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index 4aed40bf22d..50702a0ac88 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -3,6 +3,21 @@ require_relative 'devise_helpers'
module LoginHelpers
include DeviseHelpers
+ # Overriding Devise::Test::IntegrationHelpers#sign_in to store @current_user
+ # since we may need it in LiveDebugger#live_debug.
+ def sign_in(resource, scope: nil)
+ super
+
+ @current_user = resource
+ end
+
+ # Overriding Devise::Test::IntegrationHelpers#sign_out to clear @current_user.
+ def sign_out(resource_or_scope)
+ super
+
+ @current_user = nil
+ end
+
# Internal: Log in as a specific user or a new user of a specific role
#
# user_or_role - User object, or a role to create (e.g., :admin, :user)
@@ -28,7 +43,7 @@ module LoginHelpers
gitlab_sign_in_with(user, **kwargs)
- user
+ @current_user = user
end
def gitlab_sign_in_via(provider, user, uid)
@@ -41,6 +56,7 @@ module LoginHelpers
def gitlab_sign_out
find(".header-user-dropdown-toggle").click
click_link "Sign out"
+ @current_user = nil
expect(page).to have_button('Sign in')
end
diff --git a/spec/support/mobile_helpers.rb b/spec/support/mobile_helpers.rb
index 431f20a2a5c..3b9eb84e824 100644
--- a/spec/support/mobile_helpers.rb
+++ b/spec/support/mobile_helpers.rb
@@ -12,6 +12,6 @@ module MobileHelpers
end
def resize_window(width, height)
- page.driver.resize_window width, height
+ Capybara.current_session.current_window.resize_to(width, height)
end
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 421a51fc336..2770cdcbefc 100644
--- a/spec/support/protected_tags/access_control_ce_shared_examples.rb
+++ b/spec/support/protected_tags/access_control_ce_shared_examples.rb
@@ -9,7 +9,7 @@ RSpec.shared_examples "protected tags > access control > CE" do
allowed_to_create_button = find(".js-allowed-to-create")
unless allowed_to_create_button.text == access_type_name
- allowed_to_create_button.trigger('click')
+ allowed_to_create_button.click
find('.create_access_levels-container .dropdown-menu li', match: :first)
within('.create_access_levels-container .dropdown-menu') { click_on access_type_name }
end
diff --git a/spec/support/quick_actions_helpers.rb b/spec/support/quick_actions_helpers.rb
index d2aaae7518f..361190aa352 100644
--- a/spec/support/quick_actions_helpers.rb
+++ b/spec/support/quick_actions_helpers.rb
@@ -3,7 +3,7 @@ module QuickActionsHelpers
Sidekiq::Testing.fake! do
page.within('.js-main-target-form') do
fill_in 'note[note]', with: text
- find('.js-comment-submit-button').trigger('click')
+ find('.js-comment-submit-button').click
end
end
end
diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
index d5bc12f3bc5..5fde91512da 100644
--- a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
+++ b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
@@ -9,7 +9,7 @@ shared_examples "protected branches > access control > CE" do
allowed_to_push_button = find(".js-allowed-to-push")
unless allowed_to_push_button.text == access_type_name
- allowed_to_push_button.trigger('click')
+ allowed_to_push_button.click
within(".dropdown.open .dropdown-menu") { click_on access_type_name }
end
end
@@ -34,7 +34,7 @@ shared_examples "protected branches > access control > CE" do
within('.js-allowed-to-push-container') do
expect(first("li")).to have_content("Roles")
- click_on access_type_name
+ find(:link, access_type_name).click
end
end
@@ -79,7 +79,7 @@ shared_examples "protected branches > access control > CE" do
within('.js-allowed-to-merge-container') do
expect(first("li")).to have_content("Roles")
- click_on access_type_name
+ find(:link, access_type_name).click
end
end
diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb
index 4d448a55978..4ead78529c3 100644
--- a/spec/support/stub_configuration.rb
+++ b/spec/support/stub_configuration.rb
@@ -38,6 +38,10 @@ module StubConfiguration
allow(Gitlab.config.backup).to receive_messages(to_settings(messages))
end
+ def stub_lfs_setting(messages)
+ allow(Gitlab.config.lfs).to receive_messages(to_settings(messages))
+ end
+
def stub_storage_settings(messages)
# Default storage is always required
messages['default'] ||= Gitlab.config.repositories.storages.default
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index a27bfdee3d2..fff120fcb88 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -182,6 +182,8 @@ module TestEnv
return unless @gitaly_pid
Process.kill('KILL', @gitaly_pid)
+ rescue Errno::ESRCH
+ # The process can already be gone if the test run was INTerrupted.
end
def setup_factory_repo
diff --git a/spec/support/time_tracking_shared_examples.rb b/spec/support/time_tracking_shared_examples.rb
index 0fa74f911f6..909d4e2ee8d 100644
--- a/spec/support/time_tracking_shared_examples.rb
+++ b/spec/support/time_tracking_shared_examples.rb
@@ -80,6 +80,6 @@ end
def submit_time(quick_action)
fill_in 'note[note]', with: quick_action
- find('.js-comment-submit-button').trigger('click')
+ find('.js-comment-submit-button').click
wait_for_requests
end
diff --git a/spec/support/wait_for_requests.rb b/spec/support/wait_for_requests.rb
index b5c3c0f55b8..f4130d68271 100644
--- a/spec/support/wait_for_requests.rb
+++ b/spec/support/wait_for_requests.rb
@@ -1,25 +1,47 @@
-require_relative './wait_for_requests'
-
module WaitForRequests
extend self
# This is inspired by http://www.salsify.com/blog/engineering/tearing-capybara-ajax-tests
def block_and_wait_for_requests_complete
+ block_requests { wait_for_all_requests }
+ end
+
+ # Block all requests inside block with 503 response
+ def block_requests
Gitlab::Testing::RequestBlockerMiddleware.block_requests!
- wait_for('pending requests complete') do
- Gitlab::Testing::RequestBlockerMiddleware.num_active_requests.zero? && finished_all_requests?
- end
+ yield
ensure
Gitlab::Testing::RequestBlockerMiddleware.allow_requests!
end
+ # Slow down requests inside block by injecting `sleep 0.2` before each response
+ def slow_requests
+ Gitlab::Testing::RequestBlockerMiddleware.slow_requests!
+ yield
+ ensure
+ Gitlab::Testing::RequestBlockerMiddleware.allow_requests!
+ end
+
+ # Wait for client-side AJAX requests
def wait_for_requests
- wait_for('JS requests') { finished_all_requests? }
+ wait_for('JS requests complete') { finished_all_js_requests? }
+ end
+
+ # Wait for active Rack requests and client-side AJAX requests
+ def wait_for_all_requests
+ wait_for('pending requests complete') do
+ finished_all_rack_reqiests? &&
+ finished_all_js_requests?
+ end
end
private
- def finished_all_requests?
+ def finished_all_rack_reqiests?
+ Gitlab::Testing::RequestBlockerMiddleware.num_active_requests.zero?
+ end
+
+ def finished_all_js_requests?
return true unless javascript_test?
finished_all_ajax_requests? &&
diff --git a/spec/tasks/gitlab/users_rake_spec.rb b/spec/tasks/gitlab/users_rake_spec.rb
deleted file mode 100644
index 972670e7f91..00000000000
--- a/spec/tasks/gitlab/users_rake_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-require 'spec_helper'
-require 'rake'
-
-describe 'gitlab:users namespace rake task' do
- let(:enable_registry) { true }
-
- before :all do
- Rake.application.rake_require 'tasks/gitlab/helpers'
- Rake.application.rake_require 'tasks/gitlab/users'
-
- # empty task as env is already loaded
- Rake::Task.define_task :environment
- end
-
- def run_rake_task(task_name)
- Rake::Task[task_name].reenable
- Rake.application.invoke_task task_name
- end
-
- describe 'clear_all_authentication_tokens' do
- before do
- # avoid writing task output to spec progress
- allow($stdout).to receive :write
- end
-
- context 'gitlab version' do
- it 'clears the authentication token for all users' do
- create_list(:user, 2)
-
- expect(User.pluck(:authentication_token)).to all(be_present)
-
- run_rake_task('gitlab:users:clear_all_authentication_tokens')
-
- expect(User.pluck(:authentication_token)).to all(be_nil)
- end
- end
- end
-end
diff --git a/spec/tasks/tokens_spec.rb b/spec/tasks/tokens_spec.rb
index b84137eb365..51f7a536cbb 100644
--- a/spec/tasks/tokens_spec.rb
+++ b/spec/tasks/tokens_spec.rb
@@ -7,12 +7,6 @@ describe 'tokens rake tasks' do
Rake.application.rake_require 'tasks/tokens'
end
- describe 'reset_all task' do
- it 'invokes create_hooks task' do
- expect { run_rake_task('tokens:reset_all_auth') }.to change { user.reload.authentication_token }
- end
- end
-
describe 'reset_all_email task' do
it 'invokes create_hooks task' do
expect { run_rake_task('tokens:reset_all_email') }.to change { user.reload.incoming_email_token }
diff --git a/vendor/assets/javascripts/peek.js b/vendor/assets/javascripts/peek.js
index f7e77de34ff..6a341a3f0fe 100644
--- a/vendor/assets/javascripts/peek.js
+++ b/vendor/assets/javascripts/peek.js
@@ -1,5 +1,14 @@
+/*
+ * This is a modified version of https://github.com/peek/peek/blob/master/app/assets/javascripts/peek.js
+ *
+ * - Removed the dependency on jquery.tipsy
+ * - Removed the initializeTipsy and toggleBar functions
+ * - Customized updatePerformanceBar to handle SQL queries report specificities
+ * - Changed /peek/results to /-/peek/results
+ * - Removed the keypress, pjax:end, page:change, and turbolinks:load handlers
+ */
(function($) {
- var fetchRequestResults, getRequestId, peekEnabled, toggleBar, updatePerformanceBar;
+ var fetchRequestResults, getRequestId, peekEnabled, updatePerformanceBar;
getRequestId = function() {
return $('#peek').data('request-id');
};
@@ -41,22 +50,6 @@
});
return $(document).trigger('peek:render', [getRequestId(), results]);
};
- toggleBar = function(event) {
- var wrapper;
- if ($(event.target).is(':input')) {
- return;
- }
- if (event.which === 96 && !event.metaKey) {
- wrapper = $('#peek');
- if (wrapper.hasClass('disabled')) {
- wrapper.removeClass('disabled');
- return document.cookie = "peek=true; path=/";
- } else {
- wrapper.addClass('disabled');
- return document.cookie = "peek=false; path=/";
- }
- }
- };
fetchRequestResults = function() {
return $.ajax('/-/peek/results', {
data: {
@@ -68,7 +61,6 @@
error: function(xhr, textStatus, error) {}
});
};
- $(document).on('keypress', toggleBar);
$(document).on('peek:update', fetchRequestResults);
return $(function() {
if (peekEnabled()) {
diff --git a/yarn.lock b/yarn.lock
index 818878fe36c..ee00c1f4f3e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6398,9 +6398,9 @@ vue-style-loader@^2.0.0:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
-vue-template-compiler@^2.2.6:
- version "2.2.6"
- resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.2.6.tgz#2e2928daf0cd0feca9dfc35a9729adeae173ec68"
+vue-template-compiler@^2.5.2:
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.2.tgz#6f198ebc677b8f804315cd33b91e849315ae7177"
dependencies:
de-indent "^1.0.2"
he "^1.1.0"
@@ -6409,9 +6409,9 @@ vue-template-es2015-compiler@^1.2.2:
version "1.5.1"
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.5.1.tgz#0c36cc57aa3a9ec13e846342cb14a72fcac8bd93"
-vue@^2.2.6:
- version "2.2.6"
- resolved "https://registry.yarnpkg.com/vue/-/vue-2.2.6.tgz#451714b394dd6d4eae7b773c40c2034a59621aed"
+vue@^2.5.2:
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.2.tgz#fd367a87bae7535e47f9dc5c9ec3b496e5feb5a4"
vuex@^3.0.0:
version "3.0.0"